]> wimlib.net Git - wimlib/blob - src/symlink.c
587b71cb8f07f5c31b802b53df1cdda71c398ff7
[wimlib] / src / symlink.c
1 /*
2  * symlink.c
3  *
4  * Code to read and set symbolic links in WIM files.
5  */
6
7 /*
8  * Copyright (C) 2012, 2013 Eric Biggers
9  *
10  * This file is part of wimlib, a library for working with WIM files.
11  *
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)
15  * any later version.
16  *
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
20  * details.
21  *
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/.
24  */
25
26 #include "dentry.h"
27 #include "buffer_io.h"
28 #include "lookup_table.h"
29 #include "sha1.h"
30 #include <errno.h>
31
32 /* UNIX version of getting and setting the data in reparse points */
33 #if !defined(__WIN32__)
34
35 #include <sys/stat.h>
36
37 /*
38  * Find the symlink target of a symbolic link or junction point in the WIM.
39  *
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".
42  *
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.
47  */
48 static ssize_t
49 get_symlink_name(const void *resource, size_t resource_len, char *buf,
50                  size_t buf_len, u32 reparse_tag)
51 {
52         const void *p = resource;
53         u16 substitute_name_offset;
54         u16 substitute_name_len;
55         u16 print_name_offset;
56         u16 print_name_len;
57         char *link_target;
58         char *translated_target;
59         size_t link_target_len;
60         ssize_t ret;
61         unsigned header_size;
62         bool translate_slashes;
63
64         if (resource_len < 12)
65                 return -EIO;
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);
70
71         wimlib_assert(reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
72                       reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
73
74         if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
75                 header_size = 8;
76         else {
77                 header_size = 12;
78                 p += 4;
79         }
80         if (header_size + substitute_name_offset + substitute_name_len > resource_len)
81                 return -EIO;
82
83         ret = utf16le_to_tstr((const utf16lechar*)(p + substitute_name_offset),
84                               substitute_name_len,
85                               &link_target, &link_target_len);
86         if (ret)
87                 return -errno;
88
89         if (link_target_len + 1 > buf_len) {
90                 ret = -ENAMETOOLONG;
91                 goto out;
92         }
93
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] == '\\')
106         {
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] == '\\')
114         {
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] == '\\')
122         {
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)
128         {
129                 if (translated_target[0] == '\\')
130                         /* "Absolute" symlink, without drive letter */
131                         ;
132                 else
133                         /* "Relative" symlink, without drive letter */
134                         ;
135         } else {
136                 ERROR("Invalid reparse point: \"%s\"", translated_target);
137                 ret = -EIO;
138                 goto out;
139         }
140
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;
147 out:
148         FREE(link_target);
149         return ret;
150 }
151
152 #define SYMBOLIC_LINK_RELATIVE 0x00000001
153
154 /* Given a UNIX symlink target, prepare the corresponding symbolic link reparse
155  * data buffer. */
156 static int
157 make_symlink_reparse_data_buf(const char *symlink_target,
158                               size_t *len_ret, void **buf_ret)
159 {
160         int ret;
161         utf16lechar *name_utf16le;
162         size_t name_utf16le_nbytes;
163         size_t substitute_name_nbytes;
164         size_t print_name_nbytes;
165         static const char abs_subst_name_prefix[12] = "\\\0?\0?\0\\\0C\0:\0";
166         static const char abs_print_name_prefix[4] = "C\0:\0";
167         u32 flags;
168         size_t len;
169         void *buf;
170         void *p;
171
172         ret = tstr_to_utf16le(symlink_target, strlen(symlink_target),
173                               &name_utf16le, &name_utf16le_nbytes);
174         if (ret)
175                 return ret;
176
177         for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
178                 if (name_utf16le[i] == cpu_to_le16('/'))
179                         name_utf16le[i] = cpu_to_le16('\\');
180
181         /* Compatability notes:
182          *
183          * On UNIX, an absolute symbolic link begins with '/'; everything else
184          * is a relative symbolic link.  (Quite simple compared to the various
185          * ways to provide Windows paths.)
186          *
187          * To change a UNIX relative symbolic link to Windows format, we only
188          * need to translate it to UTF-16LE and replace backslashes with forward
189          * slashes.  We do not make any attempt to handle filename character
190          * problems, such as a link target that itself contains backslashes on
191          * UNIX.  Then, for these relative links, we set the reparse header
192          * @flags field to SYMBOLIC_LINK_RELATIVE.
193          *
194          * For UNIX absolute symbolic links, we must set the @flags field to 0.
195          * Then, there are multiple options as to actually represent the
196          * absolute link targets:
197          *
198          * (1) An absolute path beginning with one backslash character. similar
199          * to UNIX-style, just with a different path separator.  Print name same
200          * as substitute name.
201          *
202          * (2) Absolute path beginning with drive letter followed by a
203          * backslash.  Print name same as substitute name.
204          *
205          * (3) Absolute path beginning with drive letter followed by a
206          * backslash; substitute name prefixed with \??\, otherwise same as
207          * print name.
208          *
209          * We choose option (3) here, and we just assume C: for the drive
210          * letter.  The reasoning for this is:
211          *
212          * (1) Microsoft imagex.exe has a bug where it does not attempt to do
213          * reparse point fixups for these links, even though they are valid
214          * absolute links.  (Note: in this case prefixing the substitute name
215          * with \??\ does not work; it just makes the data unable to be restored
216          * at all.)
217          * (2) Microsoft imagex.exe will fail when doing reparse point fixups
218          * for these.  It apparently contains a bug that causes it to create an
219          * invalid reparse point, which then cannot be restored.
220          * (3) This is the only option I tested for which reparse point fixups
221          * worked properly in Microsoft imagex.exe.
222          *
223          * So option (3) it is.
224          */
225
226         substitute_name_nbytes = name_utf16le_nbytes;
227         print_name_nbytes = name_utf16le_nbytes;
228         if (symlink_target[0] == '/') {
229                 substitute_name_nbytes += sizeof(abs_subst_name_prefix);
230                 print_name_nbytes += sizeof(abs_print_name_prefix);
231         }
232
233         len = 12 + substitute_name_nbytes + print_name_nbytes +
234                         2 * sizeof(utf16lechar);
235         buf = MALLOC(len);
236
237         if (!buf) {
238                 ret = WIMLIB_ERR_NOMEM;
239                 goto out_free_name_utf16le;
240         }
241
242         p = buf;
243
244         /* Substitute name offset */
245         p = put_u16(p, 0);
246
247         /* Substitute name length */
248         p = put_u16(p, substitute_name_nbytes);
249
250         /* Print name offset */
251         p = put_u16(p, substitute_name_nbytes + sizeof(utf16lechar));
252
253         /* Print name length */
254         p = put_u16(p, print_name_nbytes);
255
256         /* Flags */
257         flags = 0;
258         if (symlink_target[0] != '/')
259                 flags |= SYMBOLIC_LINK_RELATIVE;
260         p = put_u32(p, flags);
261
262         /* Substitute name */
263         if (symlink_target[0] == '/')
264                 p = put_bytes(p, sizeof(abs_subst_name_prefix), abs_subst_name_prefix);
265         p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
266         p = put_u16(p, 0);
267
268         /* Print name */
269         if (symlink_target[0] == '/')
270                 p = put_bytes(p, sizeof(abs_print_name_prefix), abs_print_name_prefix);
271         p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
272         p = put_u16(p, 0);
273
274         *len_ret = len;
275         *buf_ret = buf;
276         ret = 0;
277 out_free_name_utf16le:
278         FREE(name_utf16le);
279         return ret;
280 }
281
282 /* Get the symlink target from a WIM inode.
283  *
284  * The inode may be either a "real" symlink (reparse tag
285  * WIM_IO_REPARSE_TAG_SYMLINK), or it may be a junction point (reparse tag
286  * WIM_IO_REPARSE_TAG_MOUNT_POINT).
287  */
288 ssize_t
289 inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
290                const WIMStruct *w, bool threadsafe)
291 {
292         const struct wim_lookup_table_entry *lte;
293         int ret;
294
295         wimlib_assert(inode_is_symlink(inode));
296
297         lte = inode_unnamed_lte(inode, w->lookup_table);
298         if (!lte)
299                 return -EIO;
300
301         if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE)
302                 return -EIO;
303
304         u8 res_buf[wim_resource_size(lte)];
305         ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
306         if (ret)
307                 return -EIO;
308         return get_symlink_name(res_buf, wim_resource_size(lte), buf,
309                                 buf_len, inode->i_reparse_tag);
310 }
311
312 /*
313  * Sets @inode to be a symbolic link pointing to @target.
314  *
315  * A lookup table entry for the symbolic link data buffer is created and
316  * inserted into @lookup_table, unless there is an existing lookup table entry
317  * for the exact same data, in which its reference count is incremented.
318  *
319  * The lookup table entry is returned in @lte_ret.
320  *
321  * On failure @dentry and @lookup_table are not modified.
322  */
323 int
324 inode_set_symlink(struct wim_inode *inode,
325                   const char *target,
326                   struct wim_lookup_table *lookup_table,
327                   struct wim_lookup_table_entry **lte_ret)
328
329 {
330         int ret;
331         size_t symlink_buf_len;
332         struct wim_lookup_table_entry *lte = NULL, *existing_lte;
333         u8 symlink_buf_hash[SHA1_HASH_SIZE];
334         void *symlink_buf;
335
336         ret = make_symlink_reparse_data_buf(target, &symlink_buf_len,
337                                             &symlink_buf);
338         if (ret)
339                 return ret;
340
341         DEBUG("Made symlink reparse data buf (len = %zu, name len = %zu)",
342                         symlink_buf_len, symlink_buf_len);
343
344         sha1_buffer(symlink_buf, symlink_buf_len, symlink_buf_hash);
345
346         existing_lte = __lookup_resource(lookup_table, symlink_buf_hash);
347
348         if (existing_lte) {
349                 lte = existing_lte;
350                 FREE(symlink_buf);
351                 symlink_buf = NULL;
352         } else {
353                 DEBUG("Creating new lookup table entry for symlink buf");
354                 lte = new_lookup_table_entry();
355                 if (!lte) {
356                         ret = WIMLIB_ERR_NOMEM;
357                         goto out_free_symlink_buf;
358                 }
359                 lte->resource_location            = RESOURCE_IN_ATTACHED_BUFFER;
360                 lte->attached_buffer              = symlink_buf;
361                 lte->resource_entry.original_size = symlink_buf_len;
362                 copy_hash(lte->hash, symlink_buf_hash);
363         }
364
365         inode->i_lte = lte;
366         inode->i_resolved = 1;
367
368         DEBUG("Loaded symlink buf");
369
370         if (existing_lte)
371                 lte->refcnt++;
372         else
373                 lookup_table_insert(lookup_table, lte);
374         if (lte_ret)
375                 *lte_ret = lte;
376         return 0;
377 out_free_symlink_buf:
378         FREE(symlink_buf);
379         return ret;
380 }
381
382 static int
383 unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
384 {
385         struct stat stbuf;
386         if (stat(path, &stbuf)) {
387                 WARNING_WITH_ERRNO("Failed to stat \"%s\"", path);
388                 /* Treat as a link pointing outside the capture root (it
389                  * most likely is). */
390                 return WIMLIB_ERR_STAT;
391         } else {
392                 *ino_ret = stbuf.st_ino;
393                 *dev_ret = stbuf.st_dev;
394                 return 0;
395         }
396 }
397
398 #endif /* !defined(__WIN32__) */
399
400 #ifdef __WIN32__
401 #  include "win32.h"
402 #  define RP_PATH_SEPARATOR L'\\'
403 #  define os_get_ino_and_dev win32_get_file_and_vol_ids
404 #else
405 #  define RP_PATH_SEPARATOR '/'
406 #  define os_get_ino_and_dev unix_get_ino_and_dev
407 #endif
408
409 /* Fix up reparse points--- mostly shared between UNIX and Windows */
410 tchar *
411 fixup_symlink(tchar *dest, u64 capture_root_ino, u64 capture_root_dev)
412 {
413         tchar *p = dest;
414
415 #ifdef __WIN32__
416         /* Skip over drive letter */
417         if (*p != RP_PATH_SEPARATOR)
418                 p += 2;
419 #endif
420
421         DEBUG("Fixing symlink or junction \"%"TS"\"", dest);
422         for (;;) {
423                 tchar save;
424                 int ret;
425                 u64 ino;
426                 u64 dev;
427
428                 while (*p == RP_PATH_SEPARATOR)
429                         p++;
430
431                 save = *p;
432                 *p = T('\0');
433                 ret = os_get_ino_and_dev(dest, &ino, &dev);
434                 *p = save;
435
436                 if (ino == capture_root_ino && dev == capture_root_dev) {
437                         /* Link points inside capture root.  Return abbreviated
438                          * path. */
439                         if (*p == T('\0'))
440                                 *(p - 1) = RP_PATH_SEPARATOR;
441                         while (p - 1 >= dest && *(p - 1) == RP_PATH_SEPARATOR)
442                                 p--;
443                 #ifdef __WIN32__
444                         /* Add back drive letter */
445                         if (*dest != RP_PATH_SEPARATOR) {
446                                 *--p = *(dest + 1);
447                                 *--p = *dest;
448                         }
449                 #endif
450                         wimlib_assert(p >= dest);
451                         return p;
452                 }
453
454                 if (*p == T('\0')) {
455                         /* Link points outside capture root. */
456                         return NULL;
457                 }
458
459                 do {
460                         p++;
461                 } while (*p != RP_PATH_SEPARATOR && *p != T('\0'));
462         }
463 }
464