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