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