]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
win32_capture.c: Exclude files pending deletion
[wimlib] / src / win32_capture.c
index ace087da33abed497ce67d6942e9e2b73a020440..4bccfa1412f6c4902f8551f4cc0e5f806db754fa 100644 (file)
@@ -45,7 +45,6 @@
 struct winnt_scan_stats {
        unsigned long num_get_sd_access_denied;
        unsigned long num_get_sacl_priv_notheld;
-       unsigned long num_long_path_warnings;
 };
 
 static inline const wchar_t *
@@ -84,9 +83,7 @@ winnt_openat(HANDLE cur_dir, const wchar_t *path, size_t path_nchars,
 
 retry:
        status = (*func_NtOpenFile)(h_ret, perms, &attr, &iosb,
-                                   FILE_SHARE_READ |
-                                           FILE_SHARE_WRITE |
-                                           FILE_SHARE_DELETE,
+                                   FILE_SHARE_VALID_FLAGS,
                                    FILE_OPEN_REPARSE_POINT |
                                            FILE_OPEN_FOR_BACKUP_INTENT |
                                            FILE_SYNCHRONOUS_IO_NONALERT |
@@ -409,11 +406,18 @@ winnt_recurse_directory(HANDLE h,
                                                           info->FileName[1] == L'.'))
                        {
                                wchar_t *p;
+                               wchar_t *filename;
                                struct wim_dentry *child;
 
                                p = full_path + full_path_nchars;
-                               *p++ = L'\\';
-                               p = wmempcpy(p, info->FileName,
+                               /* Only add a backslash if we don't already have
+                                * one.  This prevents a duplicate backslash
+                                * from being added when the path to the capture
+                                * dir had a trailing backslash.  */
+                               if (*(p - 1) != L'\\')
+                                       *p++ = L'\\';
+                               filename = p;
+                               p = wmempcpy(filename, info->FileName,
                                             info->FileNameLength / 2);
                                *p = '\0';
 
@@ -422,7 +426,7 @@ winnt_recurse_directory(HANDLE h,
                                                        h,
                                                        full_path,
                                                        p - full_path,
-                                                       full_path + full_path_nchars + 1,
+                                                       filename,
                                                        info->FileNameLength / 2,
                                                        params,
                                                        stats,
@@ -454,137 +458,252 @@ out_free_buf:
        return ret;
 }
 
-/* Reparse point fixup status code */
+/* Reparse point fixup status code  */
 enum rp_status {
-       /* Reparse point corresponded to an absolute symbolic link or junction
-        * point that pointed outside the directory tree being captured, and
-        * therefore was excluded. */
-       RP_EXCLUDED       = 0x0,
-
-       /* Reparse point was not fixed as it was either a relative symbolic
-        * link, a mount point, or something else we could not understand. */
-       RP_NOT_FIXED      = 0x1,
-
-       /* Reparse point corresponded to an absolute symbolic link or junction
-        * point that pointed inside the directory tree being captured, where
-        * the target was specified by a "full" \??\ prefixed path, and
-        * therefore was fixed to be relative to the root of the directory tree
-        * being captured. */
-       RP_FIXED_FULLPATH = 0x2,
-
-       /* Same as RP_FIXED_FULLPATH, except the absolute link target did not
-        * have the \??\ prefix.  It may have begun with a drive letter though.
-        * */
-       RP_FIXED_ABSPATH  = 0x4,
-
-       /* Either RP_FIXED_FULLPATH or RP_FIXED_ABSPATH. */
-       RP_FIXED          = RP_FIXED_FULLPATH | RP_FIXED_ABSPATH,
+       /* Reparse point will be captured literally (no fixup)  */
+       RP_NOT_FIXED    = -1,
+
+       /* Reparse point will be captured with fixup  */
+       RP_FIXED        = -2,
 };
 
-/* Given the "substitute name" target of a Windows reparse point, try doing a
- * fixup where we change it to be absolute relative to the root of the directory
- * tree being captured.
+static bool
+file_has_ino_and_dev(HANDLE h, u64 ino, u64 dev)
+{
+       NTSTATUS status;
+       IO_STATUS_BLOCK iosb;
+       FILE_INTERNAL_INFORMATION int_info;
+       FILE_FS_VOLUME_INFORMATION vol_info;
+
+       status = (*func_NtQueryInformationFile)(h, &iosb,
+                                               &int_info, sizeof(int_info),
+                                               FileInternalInformation);
+       if (!NT_SUCCESS(status))
+               return false;
+
+       if (int_info.IndexNumber.QuadPart != ino)
+               return false;
+
+       status = (*func_NtQueryVolumeInformationFile)(h, &iosb,
+                                                     &vol_info, sizeof(vol_info),
+                                                     FileFsVolumeInformation);
+       if (!(NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW))
+               return false;
+
+       if (iosb.Information <
+            offsetof(FILE_FS_VOLUME_INFORMATION, VolumeSerialNumber) +
+            sizeof(vol_info.VolumeSerialNumber))
+               return false;
+
+       return (vol_info.VolumeSerialNumber == dev);
+}
+
+/*
+ * Given an (expected) NT namespace symbolic link or junction target @target of
+ * length @target_nbytes, determine if a prefix of the target points to a file
+ * identified by @capture_root_ino and @capture_root_dev.
+ *
+ * If yes, return a pointer to the portion of the link following this prefix.
  *
- * Note that this is only executed when WIMLIB_ADD_FLAG_RPFIX has been
- * set.
+ * If no, return NULL.
  *
- * @capture_root_ino and @capture_root_dev indicate the inode number and device
- * of the root of the directory tree being captured.  They are meant to identify
- * this directory (as an alternative to its actual path, which could potentially
- * be reached via multiple destinations due to other symbolic links).  This may
- * not work properly on FAT, which doesn't seem to supply proper inode numbers
- * or file IDs.  However, FAT doesn't support reparse points so this function
- * wouldn't even be called anyway.
+ * If the link target does not appear to be a valid NT namespace path, return
+ * @target itself.
  */
-static enum rp_status
-winnt_capture_maybe_rpfix_target(wchar_t *target, u16 *target_nbytes_p,
-                                u64 capture_root_ino, u64 capture_root_dev,
-                                u32 rptag)
+static const wchar_t *
+winnt_get_root_relative_target(const wchar_t *target, size_t target_nbytes,
+                              u64 capture_root_ino, u64 capture_root_dev)
 {
-       u16 target_nchars = *target_nbytes_p / 2;
-       size_t stripped_chars;
-       wchar_t *orig_target;
-       int ret;
+       UNICODE_STRING name;
+       OBJECT_ATTRIBUTES attr;
+       IO_STATUS_BLOCK iosb;
+       NTSTATUS status;
+       const wchar_t *target_end;
+       const wchar_t *p;
 
-       ret = parse_substitute_name(target, *target_nbytes_p, rptag);
-       if (ret < 0)
-               return RP_NOT_FIXED;
-       stripped_chars = ret;
-       if (stripped_chars)
-               stripped_chars -= 2;
-       target[target_nchars] = L'\0';
-       orig_target = target;
-       target = capture_fixup_absolute_symlink(target + stripped_chars,
-                                               capture_root_ino, capture_root_dev);
-       if (!target)
-               return RP_EXCLUDED;
-       target_nchars = wcslen(target);
-       wmemmove(orig_target + stripped_chars, target, target_nchars + 1);
-       *target_nbytes_p = (target_nchars + stripped_chars) * sizeof(wchar_t);
-       if (stripped_chars)
-               return RP_FIXED_FULLPATH;
-       else
-               return RP_FIXED_ABSPATH;
+       target_end = target + (target_nbytes / sizeof(wchar_t));
+
+       /* Empty path??? */
+       if (target_end == target)
+               return target;
+
+       /* No leading slash???  */
+       if (target[0] != L'\\')
+               return target;
+
+       /* UNC path???  */
+       if ((target_end - target) >= 2 &&
+           target[0] == L'\\' && target[1] == L'\\')
+               return target;
+
+       attr.Length = sizeof(attr);
+       attr.RootDirectory = NULL;
+       attr.ObjectName = &name;
+       attr.Attributes = 0;
+       attr.SecurityDescriptor = NULL;
+       attr.SecurityQualityOfService = NULL;
+
+       name.Buffer = (wchar_t *)target;
+       name.Length = 0;
+       p = target;
+       do {
+               HANDLE h;
+               const wchar_t *orig_p = p;
+
+               /* Skip non-backslashes  */
+               while (p != target_end && *p != L'\\')
+                       p++;
+
+               /* Skip backslashes  */
+               while (p != target_end && *p == L'\\')
+                       p++;
+
+               /* Append path component  */
+               name.Length += (p - orig_p) * sizeof(wchar_t);
+               name.MaximumLength = name.Length;
+
+               /* Try opening the file  */
+               status = (*func_NtOpenFile) (&h,
+                                            FILE_READ_ATTRIBUTES | FILE_TRAVERSE,
+                                            &attr,
+                                            &iosb,
+                                            FILE_SHARE_VALID_FLAGS,
+                                            FILE_OPEN_FOR_BACKUP_INTENT);
+
+               if (NT_SUCCESS(status)) {
+                       /* Reset root directory  */
+                       if (attr.RootDirectory)
+                               (*func_NtClose)(attr.RootDirectory);
+                       attr.RootDirectory = h;
+                       name.Buffer = (wchar_t *)p;
+                       name.Length = 0;
+
+                       if (file_has_ino_and_dev(h, capture_root_ino,
+                                                capture_root_dev))
+                               goto out_close_root_dir;
+               }
+       } while (p != target_end);
+
+       p = NULL;
+
+out_close_root_dir:
+       if (attr.RootDirectory)
+               (*func_NtClose)(attr.RootDirectory);
+       return p;
+}
+
+static int
+winnt_rpfix_progress(struct add_image_params *params, const wchar_t *path,
+                    const struct reparse_data *rpdata,
+                    enum wimlib_progress_msg msg)
+{
+       size_t print_name_nchars = rpdata->print_name_nbytes / sizeof(wchar_t);
+       wchar_t print_name0[print_name_nchars + 1];
+
+       wmemcpy(print_name0, rpdata->print_name, print_name_nchars);
+       print_name0[print_name_nchars] = L'\0';
+
+       params->progress.scan.cur_path = printable_path(path);
+       params->progress.scan.symlink_target = print_name0;
+       return do_capture_progress(params, msg, NULL);
 }
 
-/* Returns: `enum rp_status' value on success; negative WIMLIB_ERR_* value on
- * failure. */
 static int
-winnt_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
-                       u64 capture_root_ino, u64 capture_root_dev,
-                       const wchar_t *path, struct add_image_params *params)
+winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
+               u64 capture_root_ino, u64 capture_root_dev,
+               const wchar_t *path, struct add_image_params *params)
 {
        struct reparse_data rpdata;
+       const wchar_t *rel_target;
        int ret;
-       enum rp_status rp_status;
 
-       ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata);
-       if (ret)
-               return -ret;
-
-       rp_status = winnt_capture_maybe_rpfix_target(rpdata.substitute_name,
-                                                    &rpdata.substitute_name_nbytes,
-                                                    capture_root_ino,
-                                                    capture_root_dev,
-                                                    le32_to_cpu(*(le32*)rpbuf));
-       if (rp_status & RP_FIXED) {
-               wimlib_assert(rpdata.substitute_name_nbytes % 2 == 0);
-               utf16lechar substitute_name_copy[rpdata.substitute_name_nbytes / 2];
-               wmemcpy(substitute_name_copy, rpdata.substitute_name,
-                       rpdata.substitute_name_nbytes / 2);
-               rpdata.substitute_name = substitute_name_copy;
-               rpdata.print_name = substitute_name_copy;
-               rpdata.print_name_nbytes = rpdata.substitute_name_nbytes;
-               if (rp_status == RP_FIXED_FULLPATH) {
-                       /* "full path", meaning \??\ prefixed.  We should not
-                        * include this prefix in the print name, as it is
-                        * apparently meant for the filesystem driver only. */
-                       rpdata.print_name += 4;
-                       rpdata.print_name_nbytes -= 8;
-               }
-               ret = make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
-               if (ret == 0)
-                       ret = rp_status;
-               else
-                       ret = -ret;
-       } else {
-               if (rp_status == RP_EXCLUDED) {
-                       /* Ignoring absolute symbolic link or junction point
-                        * that points out of the tree to be captured.  */
-                       size_t print_name_nchars = rpdata.print_name_nbytes / 2;
-                       wchar_t print_name0[print_name_nchars + 1];
-                       print_name0[print_name_nchars] = L'\0';
-                       wmemcpy(print_name0, rpdata.print_name, print_name_nchars);
-
-                       params->progress.scan.cur_path = printable_path(path);
-                       params->progress.scan.symlink_target = print_name0;
-                       do_capture_progress(params,
-                                           WIMLIB_SCAN_DENTRY_EXCLUDED_SYMLINK,
-                                           NULL);
-               }
-               ret = rp_status;
+       if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) {
+               /* Couldn't even understand the reparse data.  Don't try the
+                * fixup.  */
+               return RP_NOT_FIXED;
        }
-       return ret;
+
+       /*
+        * Don't do reparse point fixups on relative symbolic links.
+        *
+        * On Windows, a relative symbolic link is supposed to be identifiable
+        * by having reparse tag WIM_IO_REPARSE_TAG_SYMLINK and flags
+        * SYMBOLIC_LINK_RELATIVE.  We will use this information, although this
+        * may not always do what the user expects, since drive-relative
+        * symbolic links such as "\Users\Public" have SYMBOLIC_LINK_RELATIVE
+        * set, in addition to truely relative symbolic links such as "Users" or
+        * "Users\Public".  However, WIMGAPI (as of Windows 8.1) has this same
+        * behavior.
+        *
+        * Otherwise, as far as I can tell, the targets of symbolic links that
+        * are NOT relative, as well as junctions (note: a mountpoint is the
+        * sames thing as a junction), must be NT namespace paths, for example:
+        *
+        *     - \??\e:\Users\Public
+        *     - \DosDevices\e:\Users\Public
+        *     - \Device\HardDiskVolume4\Users\Public
+        *     - \??\Volume{c47cb07c-946e-4155-b8f7-052e9cec7628}\Users\Public
+        *     - \DosDevices\Volume{c47cb07c-946e-4155-b8f7-052e9cec7628}\Users\Public
+        */
+       if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+           (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE))
+               return RP_NOT_FIXED;
+
+       rel_target = winnt_get_root_relative_target(rpdata.substitute_name,
+                                                   rpdata.substitute_name_nbytes,
+                                                   capture_root_ino,
+                                                   capture_root_dev);
+       if (!rel_target) {
+               /* Target points outside of the tree being captured.  Don't
+                * adjust it.  */
+               ret = winnt_rpfix_progress(params, path, &rpdata,
+                                          WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK);
+               if (ret)
+                       return ret;
+               return RP_NOT_FIXED;
+       }
+
+       if (rel_target == rpdata.substitute_name) {
+               /* Weird target --- keep the reparse point and don't mess with
+                * it.  */
+               return RP_NOT_FIXED;
+       }
+
+       /* We have an absolute target pointing within the directory being
+        * captured, @rel_target is the suffix of the link target that is the
+        * part relative to the directory being captured.
+        *
+        * We will cut off the prefix before this part (which is the path to the
+        * directory being captured) and add a dummy prefix.  Since the process
+        * will need to be reversed when applying the image, it shouldn't matter
+        * what exactly the prefix is, as long as it looks like an absolute
+        * path.
+        */
+
+       {
+               size_t rel_target_nbytes =
+                       rpdata.substitute_name_nbytes - ((const u8 *)rel_target -
+                                                        (const u8 *)rpdata.substitute_name);
+               size_t rel_target_nchars = rel_target_nbytes / sizeof(wchar_t);
+
+               wchar_t tmp[rel_target_nchars + 7];
+
+               wmemcpy(tmp, L"\\??\\X:\\", 7);
+               wmemcpy(tmp + 7, rel_target, rel_target_nchars);
+
+               rpdata.substitute_name = tmp;
+               rpdata.substitute_name_nbytes = rel_target_nbytes + (7 * sizeof(wchar_t));
+               rpdata.print_name = tmp + 4;
+               rpdata.print_name_nbytes = rel_target_nbytes + (3 * sizeof(wchar_t));
+
+               if (make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p))
+                       return RP_NOT_FIXED;
+       }
+       ret = winnt_rpfix_progress(params, path, &rpdata,
+                                  WIMLIB_SCAN_DENTRY_FIXED_SYMLINK);
+       if (ret)
+               return ret;
+       return RP_FIXED;
 }
 
 /*
@@ -592,66 +711,57 @@ winnt_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
  * fixing the targets of absolute symbolic links and junction points to be
  * relative to the root of capture.
  *
- * @h:     Open handle to the reparse point.
- * @path:   Path to the reparse point file.
- * @params: Additional parameters, including whether to do reparse point fixups
- *          or not.
- * @rpbuf:  Buffer of length at least REPARSE_POINT_MAX_SIZE bytes into which
- *          the reparse point buffer will be loaded.
- * @rpbuflen_ret:  On success, the length of the reparse point buffer in bytes
- *                 is written to this location.
- *
- * Returns:
- *     On success, returns an `enum rp_status' value that indicates if and/or
- *     how the reparse point fixup was done.
+ * @h:
+ *     Open handle to the reparse point file.
+ * @path:
+ *     Path to the reparse point file.
+ * @params:
+ *     Capture parameters.  add_flags, capture_root_ino, capture_root_dev,
+ *     progfunc, progctx, and progress are used.
+ * @rpbuf:
+ *     Buffer of length at least REPARSE_POINT_MAX_SIZE bytes into which the
+ *     reparse point buffer will be loaded.
+ * @rpbuflen_ret:
+ *     On success, the length of the reparse point buffer in bytes is written
+ *     to this location.
  *
- *     On failure, returns a negative value that is a negated WIMLIB_ERR_*
- *     code.
+ * On success, returns a negative `enum rp_status' value.
+ * On failure, returns a positive error code.
  */
 static int
 winnt_get_reparse_data(HANDLE h, const wchar_t *path,
                       struct add_image_params *params,
                       u8 *rpbuf, u16 *rpbuflen_ret)
 {
-       DWORD bytesReturned;
+       DWORD bytes_returned;
        u32 reparse_tag;
        int ret;
        u16 rpbuflen;
 
        if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT,
-                            NULL, /* "Not used with this operation; set to NULL" */
-                            0, /* "Not used with this operation; set to 0" */
-                            rpbuf, /* "A pointer to a buffer that
-                                                  receives the reparse point data */
-                            REPARSE_POINT_MAX_SIZE, /* "The size of the output
-                                                       buffer, in bytes */
-                            &bytesReturned,
-                            NULL))
+                            NULL, 0, rpbuf, REPARSE_POINT_MAX_SIZE,
+                            &bytes_returned, NULL))
        {
                set_errno_from_GetLastError();
-               return -WIMLIB_ERR_READ;
+               return WIMLIB_ERR_READ;
        }
 
-       if (bytesReturned < 8 || bytesReturned > REPARSE_POINT_MAX_SIZE) {
+       if (unlikely(bytes_returned < 8)) {
                errno = EINVAL;
-               return -WIMLIB_ERR_INVALID_REPARSE_DATA;
+               return WIMLIB_ERR_INVALID_REPARSE_DATA;
        }
 
-       rpbuflen = bytesReturned;
+       rpbuflen = bytes_returned;
        reparse_tag = le32_to_cpu(*(le32*)rpbuf);
+       ret = RP_NOT_FIXED;
        if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX &&
            (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
             reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT))
        {
-               /* Try doing reparse point fixup */
-               ret = winnt_capture_try_rpfix(rpbuf,
-                                             &rpbuflen,
-                                             params->capture_root_ino,
-                                             params->capture_root_dev,
-                                             path,
-                                             params);
-       } else {
-               ret = RP_NOT_FIXED;
+               ret = winnt_try_rpfix(rpbuf, &rpbuflen,
+                                     params->capture_root_ino,
+                                     params->capture_root_dev,
+                                     path, params);
        }
        *rpbuflen_ret = rpbuflen;
        return ret;
@@ -987,10 +1097,7 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        if (should_exclude_path(full_path + params->capture_root_nchars,
                                full_path_nchars - params->capture_root_nchars,
                                params->config))
-       {
-               ret = 0;
                goto out_progress;
-       }
 
        /* Open the file.  */
        status = winnt_openat(cur_dir,
@@ -1003,11 +1110,19 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                                        SYNCHRONIZE,
                              &h);
        if (unlikely(!NT_SUCCESS(status))) {
-               set_errno_from_nt_status(status);
-               ERROR_WITH_ERRNO("\"%ls\": Can't open file "
-                                "(status=0x%08"PRIx32")",
-                                printable_path(full_path), (u32)status);
-               ret = WIMLIB_ERR_OPEN;
+               if (status == STATUS_DELETE_PENDING) {
+                       WARNING("\"%ls\": Deletion pending; skipping file",
+                               printable_path(full_path));
+                       ret = 0;
+               } else {
+                       set_errno_from_nt_status(status);
+                       ERROR_WITH_ERRNO("\"%ls\": Can't open file "
+                                        "(status=0x%08"PRIx32")",
+                                        printable_path(full_path), (u32)status);
+                       ret = WIMLIB_ERR_OPEN;
+               }
+               /* XXX: Provide option to exclude files that fail with
+                * STATUS_SHARING_VIOLATION?  */
                goto out;
        }
 
@@ -1096,19 +1211,17 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                rpbuf = alloca(REPARSE_POINT_MAX_SIZE);
                ret = winnt_get_reparse_data(h, full_path, params,
                                             rpbuf, &rpbuflen);
-               if (ret < 0) {
-                       /* WIMLIB_ERR_* (inverted) */
-                       ret = -ret;
+               switch (ret) {
+               case RP_FIXED:
+                       not_rpfixed = 0;
+                       break;
+               case RP_NOT_FIXED:
+                       not_rpfixed = 1;
+                       break;
+               default:
                        ERROR_WITH_ERRNO("\"%ls\": Can't get reparse data",
                                         printable_path(full_path));
                        goto out;
-               } else if (ret & RP_FIXED) {
-                       not_rpfixed = 0;
-               } else if (ret == RP_EXCLUDED) {
-                       ret = 0;
-                       goto out;
-               } else {
-                       not_rpfixed = 1;
                }
        }
 
@@ -1148,7 +1261,6 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
        if (inode->i_nlink > 1) {
                /* Shared inode (hard link); skip reading per-inode information.
                 */
-               ret = 0;
                goto out_progress;
        }
 
@@ -1237,9 +1349,9 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
 out_progress:
        params->progress.scan.cur_path = printable_path(full_path);
        if (likely(root))
-               do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK, inode);
+               ret = do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK, inode);
        else
-               do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
+               ret = do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
 out:
        if (likely(h != INVALID_HANDLE_VALUE))
                (*func_NtClose)(h);
@@ -1281,9 +1393,8 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
                        struct add_image_params *params)
 {
        wchar_t *path;
-       DWORD dret;
-       size_t path_nchars;
        int ret;
+       UNICODE_STRING ntpath;
        struct winnt_scan_stats stats;
 
        /* WARNING: There is no check for overflow later when this buffer is
@@ -1293,33 +1404,31 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        if (!path)
                return WIMLIB_ERR_NOMEM;
 
-       /* Translate into full path.  */
-       dret = GetFullPathName(root_disk_path, WINDOWS_NT_MAX_PATH - 3,
-                              &path[4], NULL);
+       ret = win32_path_to_nt_path(root_disk_path, &ntpath);
+       if (ret)
+               goto out_free_path;
 
-       if (unlikely(dret == 0 || dret >= WINDOWS_NT_MAX_PATH - 3)) {
-               ERROR("Can't get full path name for \"%ls\"", root_disk_path);
-               return WIMLIB_ERR_UNSUPPORTED;
+       if (ntpath.Length < 4 * sizeof(wchar_t) ||
+           ntpath.Length > WINDOWS_NT_MAX_PATH * sizeof(wchar_t) ||
+           wmemcmp(ntpath.Buffer, L"\\??\\", 4))
+       {
+               ERROR("\"%ls\": unrecognized path format", root_disk_path);
+               ret = WIMLIB_ERR_INVALID_PARAM;
+               goto out_free_path;
        }
 
-       /* Add \??\ prefix to form the NT namespace path.  */
-       wmemcpy(path, L"\\??\\", 4);
-       path_nchars = dret + 4;
-
-       /* Strip trailing slashes.  If we don't do this, we may create a path
-       * with multiple consecutive backslashes, which for some reason causes
-       * Windows to report that the file cannot be found.  */
-       while (unlikely(path[path_nchars - 1] == L'\\' &&
-                       path[path_nchars - 2] != L':'))
-               path[--path_nchars] = L'\0';
+       params->capture_root_nchars = ntpath.Length / sizeof(wchar_t);
+       wmemcpy(path, ntpath.Buffer, params->capture_root_nchars);
+       path[params->capture_root_nchars] = L'\0';
 
-       params->capture_root_nchars = path_nchars;
+       HeapFree(GetProcessHeap(), 0, ntpath.Buffer);
 
        memset(&stats, 0, sizeof(stats));
 
        ret = winnt_build_dentry_tree_recursive(root_ret, NULL,
-                                               path, path_nchars, L"", 0,
-                                               params, &stats, 0);
+                                               path, params->capture_root_nchars,
+                                               L"", 0, params, &stats, 0);
+out_free_path:
        FREE(path);
        if (ret == 0)
                winnt_do_scan_warnings(root_disk_path, &stats);