]> wimlib.net Git - wimlib/blob - src/reparse.c
reparse.c: Read/write from structure
[wimlib] / src / reparse.c
1 /*
2  * reparse.c - Handle reparse data.
3  */
4
5 /*
6  * Copyright (C) 2012, 2013 Eric Biggers
7  *
8  * This file is part of wimlib, a library for working with WIM files.
9  *
10  * wimlib is free software; you can redistribute it and/or modify it under the
11  * terms of the GNU General Public License as published by the Free
12  * Software Foundation; either version 3 of the License, or (at your option)
13  * any later version.
14  *
15  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17  * A PARTICULAR PURPOSE. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with wimlib; if not, see http://www.gnu.org/licenses/.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #include "wimlib/assert.h"
29 #include "wimlib/compiler.h"
30 #include "wimlib/endianness.h"
31 #include "wimlib/dentry.h"
32 #include "wimlib/encoding.h"
33 #include "wimlib/error.h"
34 #include "wimlib/lookup_table.h"
35 #include "wimlib/reparse.h"
36 #include "wimlib/resource.h"
37
38 #ifdef __WIN32__
39 #  include "wimlib/win32.h" /* for win32_get_file_and_vol_ids() */
40 #endif
41
42 #ifdef HAVE_ALLOCA_H
43 #  include <alloca.h>
44 #endif
45 #include <errno.h>
46 #include <stdlib.h>
47
48 static const utf16lechar volume_junction_prefix[11] = {
49         cpu_to_le16('\\'),
50         cpu_to_le16('\\'),
51         cpu_to_le16('?'),
52         cpu_to_le16('\\'),
53         cpu_to_le16('V'),
54         cpu_to_le16('o'),
55         cpu_to_le16('l'),
56         cpu_to_le16('u'),
57         cpu_to_le16('m'),
58         cpu_to_le16('e'),
59         cpu_to_le16('{'),
60 };
61
62 /* Parse the "substitute name" (link target) from a symbolic link or junction
63  * reparse point.
64  *
65  * Return value is:
66  *
67  * Non-negative integer:
68  *      The name is an absolute symbolic link in one of several formats,
69  *      and the return value is the number of UTF-16LE characters that need to
70  *      be advanced to reach a simple "absolute" path starting with a backslash
71  *      (i.e. skip over \??\ and/or drive letter)
72  * Negative integer:
73  *      SUBST_NAME_IS_VOLUME_JUNCTION:
74  *              The name is a volume junction.
75  *      SUBST_NAME_IS_RELATIVE_LINK:
76  *              The name is a relative symbolic link.
77  *      SUBST_NAME_IS_UNKNOWN:
78  *              The name does not appear to be a valid symbolic link, junction,
79  *              or mount point.
80  */
81 int
82 parse_substitute_name(const utf16lechar *substitute_name,
83                       u16 substitute_name_nbytes, u32 rptag)
84 {
85         u16 substitute_name_nchars = substitute_name_nbytes / 2;
86
87         if (substitute_name_nchars >= 7 &&
88             substitute_name[0] == cpu_to_le16('\\') &&
89             substitute_name[1] == cpu_to_le16('?') &&
90             substitute_name[2] == cpu_to_le16('?') &&
91             substitute_name[3] == cpu_to_le16('\\') &&
92             substitute_name[4] != cpu_to_le16('\0') &&
93             substitute_name[5] == cpu_to_le16(':') &&
94             substitute_name[6] == cpu_to_le16('\\'))
95         {
96                 /* "Full" symlink or junction (\??\x:\ prefixed path) */
97                 return 6;
98         } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
99                    substitute_name_nchars >= 12 &&
100                    memcmp(substitute_name, volume_junction_prefix,
101                           sizeof(volume_junction_prefix)) == 0 &&
102                    substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\'))
103         {
104                 /* Volume junction.  Can't really do anything with it. */
105                 return SUBST_NAME_IS_VOLUME_JUNCTION;
106         } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
107                    substitute_name_nchars >= 3 &&
108                    substitute_name[0] != cpu_to_le16('\0') &&
109                    substitute_name[1] == cpu_to_le16(':') &&
110                    substitute_name[2] == cpu_to_le16('\\'))
111         {
112                 /* "Absolute" symlink, with drive letter */
113                 return 2;
114         } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
115                    substitute_name_nchars >= 1)
116         {
117                 if (substitute_name[0] == cpu_to_le16('\\'))
118                         /* "Absolute" symlink, without drive letter */
119                         return 0;
120                 else
121                         /* "Relative" symlink, without drive letter */
122                         return SUBST_NAME_IS_RELATIVE_LINK;
123         } else {
124                 return SUBST_NAME_IS_UNKNOWN;
125         }
126 }
127
128 struct reparse_buffer_disk {
129         le32 rptag;
130         le16 rpdatalen;
131         le16 rpreserved;
132         le16 substitute_name_offset;
133         le16 substitute_name_nbytes;
134         le16 print_name_offset;
135         le16 print_name_nbytes;
136         union {
137                 struct {
138                         le32 rpflags;
139                         u8 data[REPARSE_POINT_MAX_SIZE - 20];
140                 } _packed_attribute symlink;
141                 struct {
142                         u8 data[REPARSE_POINT_MAX_SIZE - 16];
143                 } _packed_attribute junction;
144         };
145 } _packed_attribute;
146
147 /*
148  * Read the data from a symbolic link, junction, or mount point reparse point
149  * buffer into a `struct reparse_data'.
150  *
151  * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
152  * description of the format of the reparse point buffers.
153  */
154 int
155 parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen,
156                    struct reparse_data * restrict rpdata)
157 {
158         u16 substitute_name_offset;
159         u16 print_name_offset;
160         const struct reparse_buffer_disk *rpbuf_disk =
161                 (const struct reparse_buffer_disk*)rpbuf;
162         const u8 *data;
163
164         memset(rpdata, 0, sizeof(*rpdata));
165         if (rpbuflen < 16)
166                 goto out_invalid;
167         rpdata->rptag = le32_to_cpu(rpbuf_disk->rptag);
168         wimlib_assert(rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK ||
169                       rpdata->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
170         rpdata->rpdatalen = le16_to_cpu(rpbuf_disk->rpdatalen);
171         rpdata->rpreserved = le16_to_cpu(rpbuf_disk->rpreserved);
172         substitute_name_offset = le16_to_cpu(rpbuf_disk->substitute_name_offset);
173         rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->substitute_name_nbytes);
174         print_name_offset = le16_to_cpu(rpbuf_disk->print_name_offset);
175         rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->print_name_nbytes);
176
177         if ((substitute_name_offset & 1) | (print_name_offset & 1) |
178             (rpdata->substitute_name_nbytes & 1) | (rpdata->print_name_nbytes & 1))
179         {
180                 /* Names would be unaligned... */
181                 goto out_invalid;
182         }
183
184         if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
185                 if (rpbuflen < 20)
186                         goto out_invalid;
187                 rpdata->rpflags = le16_to_cpu(rpbuf_disk->symlink.rpflags);
188                 data = rpbuf_disk->symlink.data;
189         } else {
190                 data = rpbuf_disk->junction.data;
191         }
192         if ((size_t)substitute_name_offset + rpdata->substitute_name_nbytes +
193             (data - rpbuf) > rpbuflen)
194                 goto out_invalid;
195         if ((size_t)print_name_offset + rpdata->print_name_nbytes +
196             (data - rpbuf) > rpbuflen)
197                 goto out_invalid;
198         rpdata->substitute_name = (utf16lechar*)&data[substitute_name_offset];
199         rpdata->print_name = (utf16lechar*)&data[print_name_offset];
200         return 0;
201 out_invalid:
202         ERROR("Invalid reparse data");
203         return WIMLIB_ERR_INVALID_REPARSE_DATA;
204 }
205
206 /*
207  * Create a reparse point data buffer.
208  *
209  * @rpdata:  Structure that contains the data we need.
210  *
211  * @rpbuf:     Buffer into which to write the reparse point data buffer.  Must be
212  *              at least REPARSE_POINT_MAX_SIZE bytes long.
213  */
214 int
215 make_reparse_buffer(const struct reparse_data * restrict rpdata,
216                     u8 * restrict rpbuf)
217 {
218         struct reparse_buffer_disk *rpbuf_disk =
219                 (struct reparse_buffer_disk*)rpbuf;
220         u8 *data;
221
222         rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag);
223         rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved);
224         rpbuf_disk->substitute_name_offset = cpu_to_le16(0);
225         rpbuf_disk->substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes);
226         rpbuf_disk->print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2);
227         rpbuf_disk->print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes);
228
229         if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
230                 rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags);
231                 data = rpbuf_disk->symlink.data;
232         } else {
233                 data = rpbuf_disk->junction.data;
234         }
235
236         /* We null-terminate the substitute and print names, although this may
237          * not be strictly necessary.  Note that the byte counts should not
238          * include the null terminators. */
239         if (data + rpdata->substitute_name_nbytes +
240             rpdata->print_name_nbytes +
241             2 * sizeof(utf16lechar) - rpbuf > REPARSE_POINT_MAX_SIZE)
242         {
243                 ERROR("Reparse data is too long!");
244                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
245         }
246         data = mempcpy(data, rpdata->substitute_name, rpdata->substitute_name_nbytes);
247         *(utf16lechar*)data = cpu_to_le16(0);
248         data += 2;
249         data = mempcpy(data, rpdata->print_name, rpdata->print_name_nbytes);
250         *(utf16lechar*)data = cpu_to_le16(0);
251         data += 2;
252         rpbuf_disk->rpdatalen = cpu_to_le16(data - rpbuf - 8);
253         return 0;
254 }
255
256 /*
257  * Read the reparse data from a WIM inode that is a reparse point.
258  *
259  * @rpbuf points to a buffer at least REPARSE_POINT_MAX_SIZE bytes into which
260  * the reparse point data buffer will be reconstructed.
261  *
262  * Note: in the WIM format, the first 8 bytes of the reparse point data buffer
263  * are omitted, presumably because we already know the reparse tag from the
264  * dentry, and we already know the reparse tag length from the lookup table
265  * entry resource length.  However, we reconstruct the first 8 bytes in the
266  * buffer returned by this function.
267  */
268 int
269 wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
270                            u8 * restrict rpbuf)
271 {
272         struct wim_lookup_table_entry *lte;
273         int ret;
274         struct reparse_buffer_disk *rpbuf_disk;
275
276         wimlib_assert(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT);
277
278         lte = inode_unnamed_lte_resolved(inode);
279         if (!lte) {
280                 ERROR("Reparse point has no reparse data!");
281                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
282         }
283         if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE - 8) {
284                 ERROR("Reparse data is too long!");
285                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
286         }
287
288         /* Read the data from the WIM file */
289         ret = read_full_resource_into_buf(lte, rpbuf + 8);
290         if (ret)
291                 return ret;
292
293         /* Reconstruct the first 8 bytes of the reparse point buffer */
294         rpbuf_disk = (struct reparse_buffer_disk*)rpbuf;
295
296         /* ReparseTag */
297         rpbuf_disk->rptag = cpu_to_le32(inode->i_reparse_tag);
298
299         /* ReparseDataLength */
300         rpbuf_disk->rpdatalen = cpu_to_le16(wim_resource_size(lte));
301
302         /* ReparseReserved
303          * XXX this could be one of the unknown fields in the WIM dentry. */
304         rpbuf_disk->rpreserved = cpu_to_le16(0);
305         return 0;
306 }
307
308 /* UNIX version of getting and setting the data in reparse points */
309 #if !defined(__WIN32__)
310
311 /* Get the UNIX symlink target from a WIM inode.  The inode may be either a
312  * "real" symlink (reparse tag WIM_IO_REPARSE_TAG_SYMLINK), or it may be a
313  * junction point (reparse tag WIM_IO_REPARSE_TAG_MOUNT_POINT).
314  *
315  * This has similar semantics to the UNIX readlink() function, except the path
316  * argument is swapped out with the `struct wim_inode' for a reparse point, and
317  * on failure a negated error code is returned rather than -1 with errno set.  */
318 ssize_t
319 wim_inode_readlink(const struct wim_inode * restrict inode,
320                    char * restrict buf, size_t bufsize)
321 {
322         int ret;
323         struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
324         u16 rpdatalen;
325         struct reparse_data rpdata;
326         char *link_target;
327         char *translated_target;
328         size_t link_target_len;
329
330         wimlib_assert(inode_is_symlink(inode));
331
332         if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk))
333                 return -EIO;
334
335         if (parse_reparse_data((const u8*)&rpbuf_disk,
336                                le16_to_cpu(rpbuf_disk.rpdatalen) + 8, &rpdata))
337                 return -EIO;
338
339         ret = utf16le_to_tstr(rpdata.substitute_name,
340                               rpdata.substitute_name_nbytes,
341                               &link_target, &link_target_len);
342         if (ret)
343                 return -errno;
344
345         translated_target = link_target;
346         ret = parse_substitute_name(rpdata.substitute_name,
347                                     rpdata.substitute_name_nbytes,
348                                     rpdata.rptag);
349         switch (ret) {
350         case SUBST_NAME_IS_RELATIVE_LINK:
351                 goto out_translate_slashes;
352         case SUBST_NAME_IS_VOLUME_JUNCTION:
353                 goto out_have_link;
354         case SUBST_NAME_IS_UNKNOWN:
355                 ERROR("Can't understand reparse point "
356                       "substitute name \"%s\"", link_target);
357                 return -EIO;
358         default:
359                 translated_target += ret;
360                 link_target_len -= ret;
361                 break;
362         }
363
364 out_translate_slashes:
365         for (size_t i = 0; i < link_target_len; i++)
366                 if (translated_target[i] == '\\')
367                         translated_target[i] = '/';
368 out_have_link:
369         if (link_target_len > bufsize) {
370                 link_target_len = bufsize;
371                 ret = -ENAMETOOLONG;
372         } else {
373                 ret = link_target_len;
374         }
375         memcpy(buf, translated_target, link_target_len);
376         FREE(link_target);
377         return ret;
378 }
379
380 int
381 wim_inode_set_symlink(struct wim_inode *inode,
382                       const char *target,
383                       struct wim_lookup_table *lookup_table)
384
385 {
386         struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
387         struct reparse_data rpdata;
388         static const char abs_subst_name_prefix[12] = "\\\0?\0?\0\\\0C\0:\0";
389         static const char abs_print_name_prefix[4] = "C\0:\0";
390         utf16lechar *name_utf16le;
391         size_t name_utf16le_nbytes;
392         int ret;
393
394         DEBUG("Creating reparse point data buffer for UNIX "
395               "symlink target \"%s\"", target);
396         memset(&rpdata, 0, sizeof(rpdata));
397         ret = tstr_to_utf16le(target, strlen(target),
398                               &name_utf16le, &name_utf16le_nbytes);
399         if (ret)
400                 return ret;
401
402         for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
403                 if (name_utf16le[i] == cpu_to_le16('/'))
404                         name_utf16le[i] = cpu_to_le16('\\');
405
406         /* Compatability notes:
407          *
408          * On UNIX, an absolute symbolic link begins with '/'; everything else
409          * is a relative symbolic link.  (Quite simple compared to the various
410          * ways to provide Windows paths.)
411          *
412          * To change a UNIX relative symbolic link to Windows format, we only
413          * need to translate it to UTF-16LE and replace backslashes with forward
414          * slashes.  We do not make any attempt to handle filename character
415          * problems, such as a link target that itself contains backslashes on
416          * UNIX.  Then, for these relative links, we set the reparse header
417          * @flags field to SYMBOLIC_LINK_RELATIVE.
418          *
419          * For UNIX absolute symbolic links, we must set the @flags field to 0.
420          * Then, there are multiple options as to actually represent the
421          * absolute link targets:
422          *
423          * (1) An absolute path beginning with one backslash character. similar
424          * to UNIX-style, just with a different path separator.  Print name same
425          * as substitute name.
426          *
427          * (2) Absolute path beginning with drive letter followed by a
428          * backslash.  Print name same as substitute name.
429          *
430          * (3) Absolute path beginning with drive letter followed by a
431          * backslash; substitute name prefixed with \??\, otherwise same as
432          * print name.
433          *
434          * We choose option (3) here, and we just assume C: for the drive
435          * letter.  The reasoning for this is:
436          *
437          * (1) Microsoft imagex.exe has a bug where it does not attempt to do
438          * reparse point fixups for these links, even though they are valid
439          * absolute links.  (Note: in this case prefixing the substitute name
440          * with \??\ does not work; it just makes the data unable to be restored
441          * at all.)
442          * (2) Microsoft imagex.exe will fail when doing reparse point fixups
443          * for these.  It apparently contains a bug that causes it to create an
444          * invalid reparse point, which then cannot be restored.
445          * (3) This is the only option I tested for which reparse point fixups
446          * worked properly in Microsoft imagex.exe.
447          *
448          * So option (3) it is.
449          */
450
451         rpdata.rptag = inode->i_reparse_tag;
452         if (target[0] == '/') {
453                 rpdata.substitute_name_nbytes = name_utf16le_nbytes +
454                                                 sizeof(abs_subst_name_prefix);
455                 rpdata.print_name_nbytes = name_utf16le_nbytes +
456                                            sizeof(abs_print_name_prefix);
457                 rpdata.substitute_name = alloca(rpdata.substitute_name_nbytes);
458                 rpdata.print_name = alloca(rpdata.print_name_nbytes);
459                 memcpy(rpdata.substitute_name, abs_subst_name_prefix,
460                        sizeof(abs_subst_name_prefix));
461                 memcpy(rpdata.print_name, abs_print_name_prefix,
462                        sizeof(abs_print_name_prefix));
463                 memcpy((void*)rpdata.substitute_name + sizeof(abs_subst_name_prefix),
464                        name_utf16le, name_utf16le_nbytes);
465                 memcpy((void*)rpdata.print_name + sizeof(abs_print_name_prefix),
466                        name_utf16le, name_utf16le_nbytes);
467         } else {
468                 rpdata.substitute_name_nbytes = name_utf16le_nbytes;
469                 rpdata.print_name_nbytes = name_utf16le_nbytes;
470                 rpdata.substitute_name = name_utf16le;
471                 rpdata.print_name = name_utf16le;
472                 rpdata.rpflags = SYMBOLIC_LINK_RELATIVE;
473         }
474
475         ret = make_reparse_buffer(&rpdata, (u8*)&rpbuf_disk);
476         if (ret == 0) {
477                 ret = inode_set_unnamed_stream(inode,
478                                                (u8*)&rpbuf_disk + 8,
479                                                le16_to_cpu(rpbuf_disk.rpdatalen),
480                                                lookup_table);
481         }
482         FREE(name_utf16le);
483         return ret;
484 }
485
486 #include <sys/stat.h>
487
488 static int
489 unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
490 {
491         struct stat stbuf;
492         if (stat(path, &stbuf)) {
493                 if (errno != ENOENT)
494                         WARNING_WITH_ERRNO("Failed to stat \"%s\"", path);
495                 /* Treat as a link pointing outside the capture root (it
496                  * most likely is). */
497                 return WIMLIB_ERR_STAT;
498         } else {
499                 *ino_ret = stbuf.st_ino;
500                 *dev_ret = stbuf.st_dev;
501                 return 0;
502         }
503 }
504
505 #endif /* !defined(__WIN32__) */
506
507 #ifdef __WIN32__
508 #  define RP_PATH_SEPARATOR L'\\'
509 #  define is_rp_path_separator(c) ((c) == L'\\' || (c) == L'/')
510 #  define os_get_ino_and_dev win32_get_file_and_vol_ids
511 #else
512 #  define RP_PATH_SEPARATOR '/'
513 #  define is_rp_path_separator(c) ((c) == '/')
514 #  define os_get_ino_and_dev unix_get_ino_and_dev
515 #endif
516
517 /* Fix up absolute symbolic link targets--- mostly shared between UNIX and
518  * Windows */
519 tchar *
520 capture_fixup_absolute_symlink(tchar *dest,
521                                u64 capture_root_ino, u64 capture_root_dev)
522 {
523         tchar *p = dest;
524
525 #ifdef __WIN32__
526         /* Skip drive letter */
527         if (!is_rp_path_separator(*dest))
528                 p += 2;
529 #endif
530
531         DEBUG("Fixing symlink or junction \"%"TS"\"", dest);
532         for (;;) {
533                 tchar save;
534                 int ret;
535                 u64 ino;
536                 u64 dev;
537
538                 while (is_rp_path_separator(*p))
539                         p++;
540
541                 save = *p;
542                 *p = T('\0');
543                 ret = os_get_ino_and_dev(dest, &ino, &dev);
544                 *p = save;
545
546                 if (ret) /* stat() failed before we got to the capture root---
547                             assume the link points outside it. */
548                         return NULL;
549
550                 if (ino == capture_root_ino && dev == capture_root_dev) {
551                         /* Link points inside capture root.  Return abbreviated
552                          * path. */
553                         if (*p == T('\0'))
554                                 *(p - 1) = RP_PATH_SEPARATOR;
555                         while (p - 1 >= dest && is_rp_path_separator(*(p - 1)))
556                                 p--;
557                 #ifdef __WIN32__
558                         if (!is_rp_path_separator(dest[0])) {
559                                 *--p = dest[1];
560                                 *--p = dest[0];
561                         }
562                 #endif
563                         wimlib_assert(p >= dest);
564                         return p;
565                 }
566
567                 if (*p == T('\0')) {
568                         /* Link points outside capture root. */
569                         return NULL;
570                 }
571
572                 do {
573                         p++;
574                 } while (!is_rp_path_separator(*p) && *p != T('\0'));
575         }
576 }