4 * Code to read and set symbolic links in WIM files.
8 * Copyright (C) 2012, 2013 Eric Biggers
10 * This file is part of wimlib, a library for working with WIM files.
12 * wimlib is free software; you can redistribute it and/or modify it under the
13 * terms of the GNU General Public License as published by the Free
14 * Software Foundation; either version 3 of the License, or (at your option)
17 * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
18 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19 * A PARTICULAR PURPOSE. See the GNU General Public License for more
22 * You should have received a copy of the GNU General Public License
23 * along with wimlib; if not, see http://www.gnu.org/licenses/.
27 #include "buffer_io.h"
28 #include "lookup_table.h"
32 /* UNIX version of getting and setting the data in reparse points */
33 #if !defined(__WIN32__)
38 * Find the symlink target of a symbolic link or junction point in the WIM.
40 * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
41 * description of the format of the so-called "reparse point data buffers".
43 * But, in the WIM format, the first 8 bytes of the reparse point data buffer
44 * are omitted, presumably because we already know the reparse tag from the
45 * dentry, and we already know the reparse tag length from the lookup table
46 * entry resource length.
49 get_symlink_name(const void *resource, size_t resource_len, char *buf,
50 size_t buf_len, u32 reparse_tag)
52 const void *p = resource;
53 u16 substitute_name_offset;
54 u16 substitute_name_len;
55 u16 print_name_offset;
58 char *translated_target;
59 size_t link_target_len;
62 bool translate_slashes;
64 if (resource_len < 12)
66 p = get_u16(p, &substitute_name_offset);
67 p = get_u16(p, &substitute_name_len);
68 p = get_u16(p, &print_name_offset);
69 p = get_u16(p, &print_name_len);
71 wimlib_assert(reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
72 reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
74 if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
80 if (header_size + substitute_name_offset + substitute_name_len > resource_len)
83 ret = utf16le_to_tstr((const utf16lechar*)(p + substitute_name_offset),
85 &link_target, &link_target_len);
89 if (link_target_len + 1 > buf_len) {
94 DEBUG("Interpeting substitute name \"%s\" (ReparseTag=0x%x)",
95 link_target, reparse_tag);
96 translate_slashes = true;
97 translated_target = link_target;
98 if (link_target_len >= 7 &&
99 translated_target[0] == '\\' &&
100 translated_target[1] == '?' &&
101 translated_target[2] == '?' &&
102 translated_target[3] == '\\' &&
103 translated_target[4] != '\0' &&
104 translated_target[5] == ':' &&
105 translated_target[6] == '\\')
107 /* "Full" symlink or junction (\??\x:\ prefixed path) */
108 translated_target += 6;
109 link_target_len -= 6;
110 } else if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
111 link_target_len >= 12 &&
112 memcmp(translated_target, "\\\\?\\Volume{", 11) == 0 &&
113 translated_target[link_target_len - 1] == '\\')
115 /* Volume junction. Can't really do anything with it. */
116 translate_slashes = false;
117 } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
118 link_target_len >= 3 &&
119 translated_target[0] != '\0' &&
120 translated_target[1] == ':' &&
121 translated_target[2] == '/')
123 /* "Absolute" symlink, with drive letter */
124 translated_target += 2;
125 link_target_len -= 2;
126 } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
127 link_target_len >= 1)
129 if (translated_target[0] == '/')
130 /* "Absolute" symlink, without drive letter */
133 /* "Relative" symlink, without drive letter */
136 ERROR("Invalid reparse point: \"%s\"", translated_target);
141 if (translate_slashes)
142 for (size_t i = 0; i < link_target_len; i++)
143 if (translated_target[i] == '\\')
144 translated_target[i] = '/';
145 memcpy(buf, translated_target, link_target_len + 1);
146 ret = link_target_len;
153 make_symlink_reparse_data_buf(const char *symlink_target,
154 size_t *len_ret, void **buf_ret)
156 utf16lechar *name_utf16le;
157 size_t name_utf16le_nbytes;
160 ret = tstr_to_utf16le(symlink_target, strlen(symlink_target),
161 &name_utf16le, &name_utf16le_nbytes);
165 for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
166 if (name_utf16le[i] == cpu_to_le16('/'))
167 name_utf16le[i] = cpu_to_le16('\\');
169 size_t len = 12 + (name_utf16le_nbytes + 2) * 2;
170 void *buf = MALLOC(len);
173 p = put_u16(p, 0); /* Substitute name offset */
174 p = put_u16(p, name_utf16le_nbytes); /* Substitute name length */
175 p = put_u16(p, name_utf16le_nbytes + 2); /* Print name offset */
176 p = put_u16(p, name_utf16le_nbytes); /* Print name length */
177 p = put_u32(p, 1); /* flags: 0 if relative link, otherwise 1 */
178 p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
180 p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
186 ret = WIMLIB_ERR_NOMEM;
192 /* Get the symlink target from a WIM inode.
194 * The inode may be either a "real" symlink (reparse tag
195 * WIM_IO_REPARSE_TAG_SYMLINK), or it may be a junction point (reparse tag
196 * WIM_IO_REPARSE_TAG_MOUNT_POINT).
199 inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
200 const WIMStruct *w, bool threadsafe)
202 const struct wim_lookup_table_entry *lte;
205 wimlib_assert(inode_is_symlink(inode));
207 lte = inode_unnamed_lte(inode, w->lookup_table);
211 if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE)
214 u8 res_buf[wim_resource_size(lte)];
215 ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
218 return get_symlink_name(res_buf, wim_resource_size(lte), buf,
219 buf_len, inode->i_reparse_tag);
223 * Sets @inode to be a symbolic link pointing to @target.
225 * A lookup table entry for the symbolic link data buffer is created and
226 * inserted into @lookup_table, unless there is an existing lookup table entry
227 * for the exact same data, in which its reference count is incremented.
229 * The lookup table entry is returned in @lte_ret.
231 * On failure @dentry and @lookup_table are not modified.
234 inode_set_symlink(struct wim_inode *inode,
236 struct wim_lookup_table *lookup_table,
237 struct wim_lookup_table_entry **lte_ret)
241 size_t symlink_buf_len;
242 struct wim_lookup_table_entry *lte = NULL, *existing_lte;
243 u8 symlink_buf_hash[SHA1_HASH_SIZE];
246 ret = make_symlink_reparse_data_buf(target, &symlink_buf_len,
251 DEBUG("Made symlink reparse data buf (len = %zu, name len = %zu)",
252 symlink_buf_len, symlink_buf_len);
254 sha1_buffer(symlink_buf, symlink_buf_len, symlink_buf_hash);
256 existing_lte = __lookup_resource(lookup_table, symlink_buf_hash);
263 DEBUG("Creating new lookup table entry for symlink buf");
264 lte = new_lookup_table_entry();
266 ret = WIMLIB_ERR_NOMEM;
267 goto out_free_symlink_buf;
269 lte->resource_location = RESOURCE_IN_ATTACHED_BUFFER;
270 lte->attached_buffer = symlink_buf;
271 lte->resource_entry.original_size = symlink_buf_len;
272 copy_hash(lte->hash, symlink_buf_hash);
276 inode->i_resolved = 1;
278 DEBUG("Loaded symlink buf");
283 lookup_table_insert(lookup_table, lte);
287 out_free_symlink_buf:
293 unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
296 if (stat(path, &stbuf)) {
297 WARNING_WITH_ERRNO("Failed to stat \"%s\"", path);
298 /* Treat as a link pointing outside the capture root (it
299 * most likely is). */
300 return WIMLIB_ERR_STAT;
302 *ino_ret = stbuf.st_ino;
303 *dev_ret = stbuf.st_dev;
308 #endif /* !defined(__WIN32__) */
312 # define RP_PATH_SEPARATOR L'\\'
313 # define os_get_ino_and_dev win32_get_file_and_vol_ids
315 # define RP_PATH_SEPARATOR '/'
316 # define os_get_ino_and_dev unix_get_ino_and_dev
319 /* Fix up reparse points--- mostly shared between UNIX and Windows */
321 fixup_symlink(tchar *dest, u64 capture_root_ino, u64 capture_root_dev)
326 /* Skip over drive letter */
327 if (*p != RP_PATH_SEPARATOR)
331 DEBUG("Fixing symlink or junction \"%"TS"\"", dest);
338 while (*p == RP_PATH_SEPARATOR)
343 ret = os_get_ino_and_dev(dest, &ino, &dev);
346 if (ino == capture_root_ino && dev == capture_root_dev) {
347 /* Link points inside capture root. Return abbreviated
350 *(p - 1) = RP_PATH_SEPARATOR;
351 while (p - 1 >= dest && *(p - 1) == RP_PATH_SEPARATOR)
354 /* Add back drive letter */
355 if (*dest != RP_PATH_SEPARATOR) {
360 wimlib_assert(p >= dest);
365 /* Link points outside capture root. */
371 } while (*p != RP_PATH_SEPARATOR && *p != T('\0'));