]> wimlib.net Git - wimlib/blob - src/reparse.c
28976927c0abdbeeda87f954cef5f35e748cd877
[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/encoding.h"
32 #include "wimlib/error.h"
33 #include "wimlib/inode.h"
34 #include "wimlib/lookup_table.h"
35 #include "wimlib/reparse.h"
36 #include "wimlib/resource.h"
37
38 #ifdef HAVE_ALLOCA_H
39 #  include <alloca.h>
40 #endif
41 #include <errno.h>
42 #include <stdlib.h>
43
44 /* On-disk format of a symbolic link (WIM_IO_REPARSE_TAG_SYMLINK) or junction
45  * point (WIM_IO_REPARSE_TAG_MOUNT_POINT) reparse data buffer.  */
46 struct reparse_buffer_disk {
47         le32 rptag;
48         le16 rpdatalen;
49         le16 rpreserved;
50         le16 substitute_name_offset;
51         le16 substitute_name_nbytes;
52         le16 print_name_offset;
53         le16 print_name_nbytes;
54         union {
55                 struct {
56                         le32 rpflags;
57                         u8 data[REPARSE_POINT_MAX_SIZE - 20];
58                 } _packed_attribute symlink;
59                 struct {
60                         u8 data[REPARSE_POINT_MAX_SIZE - 16];
61                 } _packed_attribute junction;
62         };
63 } _packed_attribute;
64
65 static const utf16lechar volume_junction_prefix[11] = {
66         cpu_to_le16('\\'),
67         cpu_to_le16('?'),
68         cpu_to_le16('?'),
69         cpu_to_le16('\\'),
70         cpu_to_le16('V'),
71         cpu_to_le16('o'),
72         cpu_to_le16('l'),
73         cpu_to_le16('u'),
74         cpu_to_le16('m'),
75         cpu_to_le16('e'),
76         cpu_to_le16('{'),
77 };
78
79 /* Parse the "substitute name" (link target) from a symbolic link or junction
80  * reparse point.
81  *
82  * Return value is:
83  *
84  * Non-negative integer:
85  *      The name is an absolute symbolic link in one of several formats,
86  *      and the return value is the number of UTF-16LE characters that need to
87  *      be advanced to reach a simple "absolute" path starting with a backslash
88  *      (i.e. skip over \??\ and/or drive letter)
89  * Negative integer:
90  *      SUBST_NAME_IS_VOLUME_JUNCTION:
91  *              The name is a volume junction.
92  *      SUBST_NAME_IS_RELATIVE_LINK:
93  *              The name is a relative symbolic link.
94  *      SUBST_NAME_IS_UNKNOWN:
95  *              The name does not appear to be a valid symbolic link, junction,
96  *              or mount point.
97  */
98 int
99 parse_substitute_name(const utf16lechar *substitute_name,
100                       u16 substitute_name_nbytes, u32 rptag)
101 {
102         u16 substitute_name_nchars = substitute_name_nbytes / 2;
103
104         if (substitute_name_nchars >= 7 &&
105             substitute_name[0] == cpu_to_le16('\\') &&
106             substitute_name[1] == cpu_to_le16('?') &&
107             substitute_name[2] == cpu_to_le16('?') &&
108             substitute_name[3] == cpu_to_le16('\\') &&
109             substitute_name[4] != cpu_to_le16('\0') &&
110             substitute_name[5] == cpu_to_le16(':') &&
111             substitute_name[6] == cpu_to_le16('\\'))
112         {
113                 /* "Full" symlink or junction (\??\x:\ prefixed path) */
114                 return 6;
115         } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
116                    substitute_name_nchars >= 12 &&
117                    memcmp(substitute_name, volume_junction_prefix,
118                           sizeof(volume_junction_prefix)) == 0 &&
119                    substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\'))
120         {
121                 /* Volume junction.  Can't really do anything with it. */
122                 return SUBST_NAME_IS_VOLUME_JUNCTION;
123         } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
124                    substitute_name_nchars >= 3 &&
125                    substitute_name[0] != cpu_to_le16('\0') &&
126                    substitute_name[1] == cpu_to_le16(':') &&
127                    substitute_name[2] == cpu_to_le16('\\'))
128         {
129                 /* "Absolute" symlink, with drive letter */
130                 return 2;
131         } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
132                    substitute_name_nchars >= 1)
133         {
134                 if (substitute_name[0] == cpu_to_le16('\\'))
135                         /* "Absolute" symlink, without drive letter */
136                         return 0;
137                 else
138                         /* "Relative" symlink, without drive letter */
139                         return SUBST_NAME_IS_RELATIVE_LINK;
140         } else {
141                 return SUBST_NAME_IS_UNKNOWN;
142         }
143 }
144
145 /*
146  * Read the data from a symbolic link, junction, or mount point reparse point
147  * buffer into a `struct reparse_data'.
148  *
149  * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
150  * description of the format of the reparse point buffers.
151  */
152 int
153 parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen,
154                    struct reparse_data * restrict rpdata)
155 {
156         u16 substitute_name_offset;
157         u16 print_name_offset;
158         const struct reparse_buffer_disk *rpbuf_disk =
159                 (const struct reparse_buffer_disk*)rpbuf;
160         const u8 *data;
161
162         memset(rpdata, 0, sizeof(*rpdata));
163         if (rpbuflen < 16)
164                 goto out_invalid;
165         rpdata->rptag = le32_to_cpu(rpbuf_disk->rptag);
166         wimlib_assert(rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK ||
167                       rpdata->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
168         rpdata->rpdatalen = le16_to_cpu(rpbuf_disk->rpdatalen);
169         rpdata->rpreserved = le16_to_cpu(rpbuf_disk->rpreserved);
170         substitute_name_offset = le16_to_cpu(rpbuf_disk->substitute_name_offset);
171         rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->substitute_name_nbytes);
172         print_name_offset = le16_to_cpu(rpbuf_disk->print_name_offset);
173         rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->print_name_nbytes);
174
175         if ((substitute_name_offset & 1) | (print_name_offset & 1) |
176             (rpdata->substitute_name_nbytes & 1) | (rpdata->print_name_nbytes & 1))
177         {
178                 /* Names would be unaligned... */
179                 goto out_invalid;
180         }
181
182         if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
183                 if (rpbuflen < 20)
184                         goto out_invalid;
185                 rpdata->rpflags = le32_to_cpu(rpbuf_disk->symlink.rpflags);
186                 data = rpbuf_disk->symlink.data;
187         } else {
188                 data = rpbuf_disk->junction.data;
189         }
190         if ((size_t)substitute_name_offset + rpdata->substitute_name_nbytes +
191             (data - rpbuf) > rpbuflen)
192                 goto out_invalid;
193         if ((size_t)print_name_offset + rpdata->print_name_nbytes +
194             (data - rpbuf) > rpbuflen)
195                 goto out_invalid;
196         rpdata->substitute_name = (utf16lechar*)&data[substitute_name_offset];
197         rpdata->print_name = (utf16lechar*)&data[print_name_offset];
198         return 0;
199 out_invalid:
200         ERROR("Invalid reparse data");
201         return WIMLIB_ERR_INVALID_REPARSE_DATA;
202 }
203
204 /*
205  * Create a reparse point data buffer.
206  *
207  * @rpdata:  Structure that contains the data we need.
208  *
209  * @rpbuf:     Buffer into which to write the reparse point data buffer.  Must be
210  *              at least REPARSE_POINT_MAX_SIZE bytes long.
211  */
212 int
213 make_reparse_buffer(const struct reparse_data * restrict rpdata,
214                     u8 * restrict rpbuf,
215                     u16 * restrict rpbuflen_ret)
216 {
217         struct reparse_buffer_disk *rpbuf_disk =
218                 (struct reparse_buffer_disk*)rpbuf;
219         u8 *data;
220
221         rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag);
222         rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved);
223         rpbuf_disk->substitute_name_offset = cpu_to_le16(0);
224         rpbuf_disk->substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes);
225         rpbuf_disk->print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2);
226         rpbuf_disk->print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes);
227
228         if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
229                 rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags);
230                 data = rpbuf_disk->symlink.data;
231         } else {
232                 data = rpbuf_disk->junction.data;
233         }
234
235         /* We null-terminate the substitute and print names, although this may
236          * not be strictly necessary.  Note that the byte counts should not
237          * include the null terminators. */
238         if (data + rpdata->substitute_name_nbytes +
239             rpdata->print_name_nbytes +
240             2 * sizeof(utf16lechar) - rpbuf > REPARSE_POINT_MAX_SIZE)
241         {
242                 ERROR("Reparse data is too long!");
243                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
244         }
245         data = mempcpy(data, rpdata->substitute_name, rpdata->substitute_name_nbytes);
246         *(utf16lechar*)data = cpu_to_le16(0);
247         data += 2;
248         data = mempcpy(data, rpdata->print_name, rpdata->print_name_nbytes);
249         *(utf16lechar*)data = cpu_to_le16(0);
250         data += 2;
251         rpbuf_disk->rpdatalen = cpu_to_le16(data - rpbuf - 8);
252         *rpbuflen_ret = data - rpbuf;
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                            u16 * restrict rpbuflen_ret,
272                            struct wim_lookup_table_entry *lte_override)
273 {
274         struct wim_lookup_table_entry *lte;
275         int ret;
276         struct reparse_buffer_disk *rpbuf_disk;
277         u16 rpdatalen;
278
279         wimlib_assert(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT);
280
281         if (!lte_override) {
282                 lte = inode_unnamed_lte_resolved(inode);
283                 if (!lte) {
284                         ERROR("Reparse point has no reparse data!");
285                         return WIMLIB_ERR_INVALID_REPARSE_DATA;
286                 }
287         } else {
288                 lte = lte_override;
289         }
290
291         if (lte->size > REPARSE_POINT_MAX_SIZE - 8) {
292                 ERROR("Reparse data is too long!");
293                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
294         }
295         rpdatalen = lte->size;
296
297         /* Read the data from the WIM file */
298         ret = read_full_stream_into_buf(lte, rpbuf + 8);
299         if (ret)
300                 return ret;
301
302         /* Reconstruct the first 8 bytes of the reparse point buffer */
303         rpbuf_disk = (struct reparse_buffer_disk*)rpbuf;
304
305         /* ReparseTag */
306         rpbuf_disk->rptag = cpu_to_le32(inode->i_reparse_tag);
307
308         /* ReparseDataLength */
309         rpbuf_disk->rpdatalen = cpu_to_le16(rpdatalen);
310
311         /* ReparseReserved
312          * XXX this could be one of the unknown fields in the WIM dentry. */
313         rpbuf_disk->rpreserved = cpu_to_le16(0);
314
315         *rpbuflen_ret = rpdatalen + 8;
316         return 0;
317 }
318
319 /* UNIX version of getting and setting the data in reparse points */
320 #ifndef __WIN32__
321
322 /*
323  * Get the UNIX-style symlink target from the WIM inode for a reparse point.
324  * Specifically, this translates the target from UTF-16 to the current multibyte
325  * encoding, strips the drive prefix if present, and replaces backslashes with
326  * forward slashes.
327  *
328  * @inode
329  *      The inode to read the symlink from.  It must be a reparse point with
330  *      tag WIM_IO_REPARSE_TAG_SYMLINK (a real symlink) or
331  *      WIM_IO_REPARSE_TAG_MOUNT_POINT (a mount point or junction point).
332  *
333  * @buf
334  *      Buffer into which to place the link target.
335  *
336  * @bufsize
337  *      Available space in @buf, in bytes.
338  *
339  * @lte_override
340  *      If not NULL, the stream from which to read the reparse data.  Otherwise,
341  *      the reparse data will be read from the unnamed stream of @inode.
342  *
343  * If the entire symbolic link target was placed in the buffer, returns the
344  * number of bytes written.  The resulting string is not null-terminated.  If
345  * the symbolic link target was too large to be placed in the buffer, the first
346  * @bufsize bytes of it are placed in the buffer and
347  * -ENAMETOOLONG is returned.  Otherwise, a negative errno value indicating
348  *  another error is returned.
349  */
350 ssize_t
351 wim_inode_readlink(const struct wim_inode * restrict inode,
352                    char * restrict buf, size_t bufsize,
353                    struct wim_lookup_table_entry *lte_override)
354 {
355         int ret;
356         struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
357         struct reparse_data rpdata;
358         char *link_target;
359         char *translated_target;
360         size_t link_target_len;
361         u16 rpbuflen;
362
363         wimlib_assert(inode_is_symlink(inode));
364
365         if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk, &rpbuflen,
366                                        lte_override))
367                 return -EIO;
368
369         if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata))
370                 return -EIO;
371
372         ret = utf16le_to_tstr(rpdata.substitute_name,
373                               rpdata.substitute_name_nbytes,
374                               &link_target, &link_target_len);
375         if (ret)
376                 return -errno;
377
378         translated_target = link_target;
379         ret = parse_substitute_name(rpdata.substitute_name,
380                                     rpdata.substitute_name_nbytes,
381                                     rpdata.rptag);
382         switch (ret) {
383         case SUBST_NAME_IS_RELATIVE_LINK:
384                 goto out_translate_slashes;
385         case SUBST_NAME_IS_VOLUME_JUNCTION:
386                 goto out_have_link;
387         case SUBST_NAME_IS_UNKNOWN:
388                 ERROR("Can't understand reparse point "
389                       "substitute name \"%s\"", link_target);
390                 ret = -EIO;
391                 goto out_free_link_target;
392         default:
393                 translated_target += ret;
394                 link_target_len -= ret;
395                 break;
396         }
397
398 out_translate_slashes:
399         for (size_t i = 0; i < link_target_len; i++)
400                 if (translated_target[i] == '\\')
401                         translated_target[i] = '/';
402 out_have_link:
403         if (link_target_len > bufsize) {
404                 link_target_len = bufsize;
405                 ret = -ENAMETOOLONG;
406         } else {
407                 ret = link_target_len;
408         }
409         memcpy(buf, translated_target, link_target_len);
410 out_free_link_target:
411         FREE(link_target);
412         return ret;
413 }
414
415 int
416 wim_inode_set_symlink(struct wim_inode *inode,
417                       const char *target,
418                       struct wim_lookup_table *lookup_table)
419
420 {
421         struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
422         struct reparse_data rpdata;
423         static const char abs_subst_name_prefix[12] = "\\\0?\0?\0\\\0C\0:\0";
424         static const char abs_print_name_prefix[4] = "C\0:\0";
425         utf16lechar *name_utf16le;
426         size_t name_utf16le_nbytes;
427         int ret;
428         u16 rpbuflen;
429
430         DEBUG("Creating reparse point data buffer for UNIX "
431               "symlink target \"%s\"", target);
432         memset(&rpdata, 0, sizeof(rpdata));
433         ret = tstr_to_utf16le(target, strlen(target),
434                               &name_utf16le, &name_utf16le_nbytes);
435         if (ret)
436                 return ret;
437
438         for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
439                 if (name_utf16le[i] == cpu_to_le16('/'))
440                         name_utf16le[i] = cpu_to_le16('\\');
441
442         /* Compatability notes:
443          *
444          * On UNIX, an absolute symbolic link begins with '/'; everything else
445          * is a relative symbolic link.  (Quite simple compared to the various
446          * ways to provide Windows paths.)
447          *
448          * To change a UNIX relative symbolic link to Windows format, we only
449          * need to translate it to UTF-16LE and replace forward slashes with
450          * backslashes.  We do not make any attempt to handle filename character
451          * problems, such as a link target that itself contains backslashes on
452          * UNIX.  Then, for these relative links, we set the reparse header
453          * @flags field to SYMBOLIC_LINK_RELATIVE.
454          *
455          * For UNIX absolute symbolic links, we must set the @flags field to 0.
456          * Then, there are multiple options as to actually represent the
457          * absolute link targets:
458          *
459          * (1) An absolute path beginning with one backslash character. similar
460          * to UNIX-style, just with a different path separator.  Print name same
461          * as substitute name.
462          *
463          * (2) Absolute path beginning with drive letter followed by a
464          * backslash.  Print name same as substitute name.
465          *
466          * (3) Absolute path beginning with drive letter followed by a
467          * backslash; substitute name prefixed with \??\, otherwise same as
468          * print name.
469          *
470          * We choose option (3) here, and we just assume C: for the drive
471          * letter.  The reasoning for this is:
472          *
473          * (1) Microsoft imagex.exe has a bug where it does not attempt to do
474          * reparse point fixups for these links, even though they are valid
475          * absolute links.  (Note: in this case prefixing the substitute name
476          * with \??\ does not work; it just makes the data unable to be restored
477          * at all.)
478          * (2) Microsoft imagex.exe will fail when doing reparse point fixups
479          * for these.  It apparently contains a bug that causes it to create an
480          * invalid reparse point, which then cannot be restored.
481          * (3) This is the only option I tested for which reparse point fixups
482          * worked properly in Microsoft imagex.exe.
483          *
484          * So option (3) it is.
485          */
486
487         rpdata.rptag = inode->i_reparse_tag;
488         if (target[0] == '/') {
489                 rpdata.substitute_name_nbytes = name_utf16le_nbytes +
490                                                 sizeof(abs_subst_name_prefix);
491                 rpdata.print_name_nbytes = name_utf16le_nbytes +
492                                            sizeof(abs_print_name_prefix);
493                 rpdata.substitute_name = alloca(rpdata.substitute_name_nbytes);
494                 rpdata.print_name = alloca(rpdata.print_name_nbytes);
495                 memcpy(rpdata.substitute_name, abs_subst_name_prefix,
496                        sizeof(abs_subst_name_prefix));
497                 memcpy(rpdata.print_name, abs_print_name_prefix,
498                        sizeof(abs_print_name_prefix));
499                 memcpy((void*)rpdata.substitute_name + sizeof(abs_subst_name_prefix),
500                        name_utf16le, name_utf16le_nbytes);
501                 memcpy((void*)rpdata.print_name + sizeof(abs_print_name_prefix),
502                        name_utf16le, name_utf16le_nbytes);
503         } else {
504                 rpdata.substitute_name_nbytes = name_utf16le_nbytes;
505                 rpdata.print_name_nbytes = name_utf16le_nbytes;
506                 rpdata.substitute_name = name_utf16le;
507                 rpdata.print_name = name_utf16le;
508                 rpdata.rpflags = SYMBOLIC_LINK_RELATIVE;
509         }
510
511         ret = make_reparse_buffer(&rpdata, (u8*)&rpbuf_disk, &rpbuflen);
512         if (ret == 0) {
513                 ret = inode_set_unnamed_stream(inode,
514                                                (u8*)&rpbuf_disk + 8,
515                                                rpbuflen - 8,
516                                                lookup_table);
517         }
518         FREE(name_utf16le);
519         return ret;
520 }
521
522 #endif /* !__WIN32__ */