]> wimlib.net Git - wimlib/blob - src/reparse.c
Always specify AT_UNNAMED when opening unnamed stream with libntfs-3g
[wimlib] / src / reparse.c
1 /*
2  * reparse.c - Reparse point handling
3  */
4
5 /*
6  * Copyright (C) 2012, 2013, 2015 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/blob_table.h"
30 #include "wimlib/endianness.h"
31 #include "wimlib/encoding.h"
32 #include "wimlib/error.h"
33 #include "wimlib/inode.h"
34 #include "wimlib/reparse.h"
35 #include "wimlib/resource.h"
36
37 /* Reconstruct the header of a reparse point buffer.  This is necessary because
38  * only reparse data is stored in WIM files.  The reparse tag is instead stored
39  * in the on-disk WIM dentry, and the reparse data length is equal to the size
40  * of the blob in which the reparse data was stored.  */
41 void
42 complete_reparse_point(struct reparse_buffer_disk *rpbuf,
43                        const struct wim_inode *inode, u16 blob_size)
44 {
45         rpbuf->rptag = cpu_to_le32(inode->i_reparse_tag);
46         rpbuf->rpdatalen = cpu_to_le16(blob_size);
47         rpbuf->rpreserved = cpu_to_le16(inode->i_rp_reserved);
48 }
49
50 /* Parse the buffer for a symbolic link or junction reparse point and fill in a
51  * 'struct link_reparse_point'.  */
52 int
53 parse_link_reparse_point(const struct reparse_buffer_disk *rpbuf, u16 rpbuflen,
54                          struct link_reparse_point *link)
55 {
56         u16 substitute_name_offset;
57         u16 print_name_offset;
58         const u8 *data;
59
60         link->rptag = le32_to_cpu(rpbuf->rptag);
61
62         /* Not a symbolic link or junction?  */
63         if (link->rptag != WIM_IO_REPARSE_TAG_SYMLINK &&
64             link->rptag != WIM_IO_REPARSE_TAG_MOUNT_POINT)
65                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
66
67         /* Is the buffer too small to be a symlink or a junction?  */
68         if (rpbuflen < offsetof(struct reparse_buffer_disk, link.junction.data))
69                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
70
71         link->rpreserved = le16_to_cpu(rpbuf->rpreserved);
72         link->substitute_name_nbytes = le16_to_cpu(rpbuf->link.substitute_name_nbytes);
73         substitute_name_offset = le16_to_cpu(rpbuf->link.substitute_name_offset);
74         link->print_name_nbytes = le16_to_cpu(rpbuf->link.print_name_nbytes);
75         print_name_offset = le16_to_cpu(rpbuf->link.print_name_offset);
76
77         /* The names must be properly sized and aligned.  */
78         if ((substitute_name_offset | print_name_offset |
79              link->substitute_name_nbytes | link->print_name_nbytes) & 1)
80                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
81
82         if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
83                 if (rpbuflen < offsetof(struct reparse_buffer_disk, link.symlink.data))
84                         return WIMLIB_ERR_INVALID_REPARSE_DATA;
85                 link->symlink_flags = le32_to_cpu(rpbuf->link.symlink.flags);
86                 data = rpbuf->link.symlink.data;
87         } else {
88                 data = rpbuf->link.junction.data;
89         }
90
91         /* Verify that the names don't overflow the buffer.  */
92         if ((data - (const u8 *)rpbuf) + substitute_name_offset +
93             link->substitute_name_nbytes > rpbuflen)
94                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
95
96         if ((data - (const u8 *)rpbuf) + print_name_offset +
97             link->print_name_nbytes > rpbuflen)
98                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
99
100         /* Save the name pointers.  */
101         link->substitute_name = (utf16lechar *)&data[substitute_name_offset];
102         link->print_name = (utf16lechar *)&data[print_name_offset];
103         return 0;
104 }
105
106 /* Translate a 'struct link_reparse_point' into a reparse point buffer.  */
107 int
108 make_link_reparse_point(const struct link_reparse_point *link,
109                         struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret)
110 {
111         u8 *data;
112
113         if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK)
114                 data = rpbuf->link.symlink.data;
115         else if (link->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
116                 data = rpbuf->link.junction.data;
117         else /* Callers should forbid this case, but check anyway.  */
118                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
119
120         /* Check if the names are too long to fit in a reparse point.  */
121         if ((data - (u8 *)rpbuf) + link->substitute_name_nbytes +
122             link->print_name_nbytes +
123             2 * sizeof(utf16lechar) > REPARSE_POINT_MAX_SIZE)
124                 return WIMLIB_ERR_INVALID_REPARSE_DATA;
125
126         rpbuf->rptag = cpu_to_le32(link->rptag);
127         rpbuf->rpreserved = cpu_to_le16(link->rpreserved);
128         rpbuf->link.substitute_name_offset = cpu_to_le16(0);
129         rpbuf->link.substitute_name_nbytes = cpu_to_le16(link->substitute_name_nbytes);
130         rpbuf->link.print_name_offset = cpu_to_le16(link->substitute_name_nbytes +
131                                                     sizeof(utf16lechar));
132         rpbuf->link.print_name_nbytes = cpu_to_le16(link->print_name_nbytes);
133
134         if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK)
135                 rpbuf->link.symlink.flags = cpu_to_le32(link->symlink_flags);
136
137         /* We null-terminate the substitute and print names, although this isn't
138          * strictly necessary.  Note that the nbytes fields do not include the
139          * null terminators.  */
140         data = mempcpy(data, link->substitute_name, link->substitute_name_nbytes);
141         *(utf16lechar *)data = cpu_to_le16(0);
142         data += sizeof(utf16lechar);
143         data = mempcpy(data, link->print_name, link->print_name_nbytes);
144         *(utf16lechar *)data = cpu_to_le16(0);
145         data += sizeof(utf16lechar);
146         rpbuf->rpdatalen = cpu_to_le16(data - rpbuf->rpdata);
147
148         *rpbuflen_ret = data - (u8 *)rpbuf;
149         return 0;
150 }
151
152 /* UNIX symlink <=> Windows reparse point translation  */
153 #ifndef __WIN32__
154
155 /* Retrieve the inode's reparse point buffer into @rpbuf and @rpbuflen_ret.
156  * This gets the reparse data from @blob if specified, otherwise from the
157  * inode's reparse point stream.  The inode's streams must be resolved.  */
158 static int
159 wim_inode_get_reparse_point(const struct wim_inode *inode,
160                             struct reparse_buffer_disk *rpbuf,
161                             u16 *rpbuflen_ret,
162                             const struct blob_descriptor *blob)
163 {
164         int ret;
165         u16 blob_size = 0;
166
167         if (!blob) {
168                 const struct wim_inode_stream *strm;
169
170                 strm = inode_get_unnamed_stream(inode, STREAM_TYPE_REPARSE_POINT);
171                 if (strm)
172                         blob = stream_blob_resolved(strm);
173         }
174
175         if (blob) {
176                 if (blob->size > REPARSE_DATA_MAX_SIZE)
177                         return WIMLIB_ERR_INVALID_REPARSE_DATA;
178                 blob_size = blob->size;
179                 ret = read_blob_into_buf(blob, rpbuf->rpdata);
180                 if (ret)
181                         return ret;
182         }
183
184         complete_reparse_point(rpbuf, inode, blob_size);
185
186         *rpbuflen_ret = REPARSE_DATA_OFFSET + blob_size;
187         return 0;
188 }
189
190 static void
191 copy(char **buf_p, size_t *bufsize_p, const char *src, size_t src_size)
192 {
193         size_t n = min(*bufsize_p, src_size);
194         memcpy(*buf_p, src, n);
195         *buf_p += n;
196         *bufsize_p -= n;
197 }
198
199 /*
200  * Get a UNIX-style symlink target from the WIM inode for a reparse point.
201  *
202  * @inode
203  *      The inode from which to read the symlink.  If not a symbolic link or
204  *      junction reparse point, then -EINVAL will be returned.
205  * @buf
206  *      Buffer into which to place the link target.
207  * @bufsize
208  *      Available space in @buf, in bytes.
209  * @blob
210  *      If not NULL, the blob from which to read the reparse data.  Otherwise,
211  *      the reparse data will be read from the reparse point stream of @inode.
212  * @altroot
213  *      If @altroot_len != 0 and the link is an absolute link that was stored as
214  *      "fixed", then prepend this path to the link target.
215  * @altroot_len
216  *      Length of the @altroot string or 0.
217  *
218  * Similar to POSIX readlink(), this function writes as much of the symlink
219  * target as possible (up to @bufsize bytes) to @buf with no null terminator and
220  * returns the number of bytes written or a negative errno value on error.  Note
221  * that the target is truncated and @bufsize is returned in the overflow case.
222  */
223 int
224 wim_inode_readlink(const struct wim_inode *inode, char *buf, size_t bufsize,
225                    const struct blob_descriptor *blob,
226                    const char *altroot, size_t altroot_len)
227 {
228         struct reparse_buffer_disk rpbuf;
229         u16 rpbuflen;
230         struct link_reparse_point link;
231         char *target_buffer;
232         char *target;
233         size_t target_len;
234         char *buf_ptr;
235         bool rpfix_ok = false;
236
237         /* Not a symbolic link or junction?  */
238         if (!inode_is_symlink(inode))
239                 return -EINVAL;
240
241         /* Retrieve the native Windows "substitute name".  */
242
243         if (wim_inode_get_reparse_point(inode, &rpbuf, &rpbuflen, blob))
244                 return -EIO;
245
246         if (parse_link_reparse_point(&rpbuf, rpbuflen, &link))
247                 return -EINVAL;
248
249         /* Translate the substitute name to the current multibyte encoding.  */
250         if (utf16le_to_tstr(link.substitute_name, link.substitute_name_nbytes,
251                             &target_buffer, &target_len))
252                 return -errno;
253         target = target_buffer;
254
255         /*
256          * The substitute name is a native Windows NT path. There are two cases:
257          *
258          * 1. The reparse point is a symlink (rptag=WIM_IO_REPARSE_TAG_SYMLINK)
259          *    and SYMBOLIC_LINK_RELATIVE is set.  Windows resolves the path
260          *    relative to the directory containing the reparse point file.  In
261          *    this case, we just translate the path separators.
262          * 2. Otherwise, Windows resolves the path from the root of the Windows
263          *    NT kernel object namespace.  In this case, we attempt to strip the
264          *    device name, in addition to translating the path separators; e.g.
265          *    "\??\C:\Users\Public" is translated to "/Users/Public".
266          *
267          * Also in case (2) the link target may have been stored as "fixed",
268          * meaning that with the device portion stripped off it is effectively
269          * "relative to the root of the WIM image".  If this is the case, and if
270          * the caller provided an alternate root directory, then rewrite the
271          * link to be relative to that directory.
272          */
273         if (!link_is_relative_symlink(&link)) {
274                 static const char *const nt_root_dirs[] = {
275                         "\\??\\", "\\DosDevices\\", "\\Device\\",
276                 };
277                 for (size_t i = 0; i < ARRAY_LEN(nt_root_dirs); i++) {
278                         size_t len = strlen(nt_root_dirs[i]);
279                         if (!strncmp(target, nt_root_dirs[i], len)) {
280                                 char *p = target + len;
281                                 while (*p == '\\')
282                                         p++;
283                                 while (*p && *p != '\\')
284                                         p++;
285                                 target_len -= (p - target);
286                                 target = p;
287                                 break;
288                         }
289                 }
290
291                 if (!(inode->i_rp_flags & WIM_RP_FLAG_NOT_FIXED))
292                         rpfix_ok = true;
293         }
294
295         /* Translate backslashes (Windows NT path separator) to forward slashes
296          * (UNIX path separator).  In addition, translate forwards slashes to
297          * backslashes; this enables lossless handling of UNIX symbolic link
298          * targets that contain the backslash character.  */
299         for (char *p = target; *p; p++) {
300                 if (*p == '\\')
301                         *p = '/';
302                 else if (*p == '/')
303                         *p = '\\';
304         }
305
306         /* Copy as much of the link target as possible to the output buffer and
307          * return the number of bytes copied.  */
308         buf_ptr = buf;
309         if (rpfix_ok && altroot_len != 0) {
310                 copy(&buf_ptr, &bufsize, altroot, altroot_len);
311         } else if (target_len == 0) {
312                 /* An absolute link target that was made relative to the same
313                  * directory pointed to will end up empty if the original target
314                  * did not have a trailing slash.  Here, we are reading this
315                  * adjusted link target without prefixing it.  This usually
316                  * doesn't happen, but if it does then we need to change it to
317                  * "/" so that it is a valid target.  */
318                 target = "/";
319                 target_len = 1;
320         }
321         copy(&buf_ptr, &bufsize, target, target_len);
322         FREE(target_buffer);
323         return buf_ptr - buf;
324 }
325
326 /* Given a UNIX-style symbolic link target, create a Windows-style reparse point
327  * buffer and assign it to the specified inode.  */
328 int
329 wim_inode_set_symlink(struct wim_inode *inode, const char *_target,
330                       struct blob_table *blob_table)
331
332 {
333         int ret;
334         utf16lechar *target;
335         size_t target_nbytes;
336         struct link_reparse_point link;
337         struct reparse_buffer_disk rpbuf;
338         u16 rpbuflen;
339
340         /* Translate the link target to UTF-16LE.  */
341         ret = tstr_to_utf16le(_target, strlen(_target), &target, &target_nbytes);
342         if (ret)
343                 return ret;
344
345         /* Translate forward slashes (UNIX path separator) to backslashes
346          * (Windows NT path separator).  In addition, translate backslashes to
347          * forward slashes; this enables lossless handling of UNIX symbolic link
348          * targets that contain the backslash character.  */
349         for (utf16lechar *p = target; *p; p++) {
350                 if (*p == cpu_to_le16('/'))
351                         *p = cpu_to_le16('\\');
352                 else if (*p == cpu_to_le16('\\'))
353                         *p = cpu_to_le16('/');
354         }
355
356         link.rptag = WIM_IO_REPARSE_TAG_SYMLINK;
357         link.rpreserved = 0;
358
359         /* Note: an absolute link that was rewritten to be relative to another
360          * directory is assumed to either be empty or to have a leading slash.
361          * See unix_relativize_link_target().  */
362         if (*target == cpu_to_le16('\\') || !*target) {
363                 /*
364                  * UNIX link target was absolute.  In this case we represent the
365                  * link as a symlink reparse point with SYMBOLIC_LINK_RELATIVE
366                  * cleared.  For this to work we need to assign it a path that
367                  * can be resolved from the root of the Windows NT kernel object
368                  * namespace.  We do this by using "\??\C:" as a dummy prefix.
369                  *
370                  * Note that we could instead represent UNIX absolute links by
371                  * setting SYMBOLIC_LINK_RELATIVE and then leaving the path
372                  * backslash-prefixed like "\Users\Public".  On Windows this is
373                  * valid and denotes a path relative to the root of the
374                  * filesystem on which the reparse point resides.  The problem
375                  * with this is that neither WIMGAPI nor wimlib (on Windows)
376                  * will do "reparse point fixups" when extracting such links
377                  * (modifying the link target to point into the actual
378                  * extraction directory).  So for the greatest cross-platform
379                  * consistency, we have to use the fake C: drive approach.
380                  */
381                 static const utf16lechar prefix[6] = {
382                         cpu_to_le16('\\'),
383                         cpu_to_le16('?'),
384                         cpu_to_le16('?'),
385                         cpu_to_le16('\\'),
386                         cpu_to_le16('C'),
387                         cpu_to_le16(':'),
388                 };
389
390                 /* Do not show \??\ in print name  */
391                 const size_t num_unprintable_chars = 4;
392
393                 link.symlink_flags = 0;
394                 link.substitute_name_nbytes = sizeof(prefix) + target_nbytes;
395                 link.substitute_name = alloca(link.substitute_name_nbytes);
396                 memcpy(link.substitute_name, prefix, sizeof(prefix));
397                 memcpy(link.substitute_name + ARRAY_LEN(prefix), target, target_nbytes);
398                 link.print_name_nbytes = link.substitute_name_nbytes -
399                                          (num_unprintable_chars * sizeof(utf16lechar));
400                 link.print_name = link.substitute_name + num_unprintable_chars;
401         } else {
402                 /* UNIX link target was relative.  In this case we represent the
403                  * link as a symlink reparse point with SYMBOLIC_LINK_RELATIVE
404                  * set.  This causes Windows to interpret the link relative to
405                  * the directory containing the reparse point file.  */
406                 link.symlink_flags = SYMBOLIC_LINK_RELATIVE;
407                 link.substitute_name_nbytes = target_nbytes;
408                 link.substitute_name = target;
409                 link.print_name_nbytes = target_nbytes;
410                 link.print_name = target;
411         }
412
413         /* Generate the reparse buffer.  */
414         ret = make_link_reparse_point(&link, &rpbuf, &rpbuflen);
415         if (ret)
416                 goto out_free_target;
417
418         /* Save the reparse data with the inode.  */
419         ret = WIMLIB_ERR_NOMEM;
420         if (!inode_add_stream_with_data(inode,
421                                         STREAM_TYPE_REPARSE_POINT,
422                                         NO_STREAM_NAME,
423                                         rpbuf.rpdata,
424                                         rpbuflen - REPARSE_DATA_OFFSET,
425                                         blob_table))
426                 goto out_free_target;
427
428         /* The inode is now a reparse point.  */
429         inode->i_reparse_tag = link.rptag;
430         inode->i_attributes &= ~FILE_ATTRIBUTE_NORMAL;
431         inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
432
433         ret = 0;
434 out_free_target:
435         FREE(target);
436         return ret;
437 }
438
439 #endif /* !__WIN32__ */