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