Path fixes/updates
authorEric Biggers <ebiggers3@gmail.com>
Mon, 20 May 2013 23:25:25 +0000 (18:25 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Mon, 20 May 2013 23:25:25 +0000 (18:25 -0500)
- Use \\?\ prefixed paths when adding files on Windows
- Use \ as WIM path separator on Windows rather than / (both still accepted as
  input)
- Fix per-directory case-insensitive index

include/wimlib/paths.h
include/wimlib/util.h
programs/imagex.c
src/capture_common.c
src/dentry.c
src/extract.c
src/paths.c
src/reparse.c
src/update_image.c
src/win32_capture.c

index 311a8a0..59c1a86 100644 (file)
@@ -13,9 +13,6 @@ path_basename_with_len(const tchar *path, size_t len);
 extern const tchar *
 path_stream_name(const tchar *path);
 
-extern void
-zap_backslashes(tchar *s);
-
 extern tchar *
 canonicalize_wim_path(const tchar *wim_path) _malloc_attribute;
 
index 3293683..646fdaf 100644 (file)
@@ -122,10 +122,28 @@ hash_u64(u64 n)
        return n * 0x9e37fffffffc0001ULL;
 }
 
+/* is_any_path_separator() - characters treated as path separators in WIM path
+ * specifications and capture configuration files (the former will be translated
+ * to WIM_PATH_SEPARATOR; the latter will be translated to
+ * OS_PREFERRED_PATH_SEPARATOR)
+ *
+ * OS_PREFERRED_PATH_SEPARATOR - preferred (or only) path separator on the
+ * operating system.  Used when constructing filesystem paths to extract or
+ * archive.
+ *
+ * WIM_PATH_SEPARATOR - character treated as path separator for WIM paths.
+ * Currently needs to be '/' on UNIX for the WIM mounting code to work properly.
+ */
+
 #ifdef __WIN32__
 #  define OS_PREFERRED_PATH_SEPARATOR L'\\'
+#  define is_any_path_separator(c) ((c) == L'/' || (c) == L'\\')
 #else
 #  define OS_PREFERRED_PATH_SEPARATOR '/'
+#  define is_any_path_separator(c) ((c) == '/' || (c) == '\\')
 #endif
 
+#define WIM_PATH_SEPARATOR OS_PREFERRED_PATH_SEPARATOR
+
+
 #endif /* _WIMLIB_UTIL_H */
index 1ee3186..e524135 100644 (file)
@@ -1083,8 +1083,8 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
                percent_done = TO_PERCENT(info->integrity.completed_bytes,
                                          info->integrity.total_bytes);
-               tprintf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" "TS" "
-                       "of %"PRIu64" "TS" (%u%%) done"),
+               tprintf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
+                       "of %"PRIu64" %"TS" (%u%%) done"),
                        info->integrity.filename,
                        info->integrity.completed_bytes >> unit_shift,
                        unit_name,
@@ -3176,7 +3176,6 @@ out_release_privs:
 #ifdef __WIN32__
        win32_release_capture_privileges();
 #endif
-out_free_cmds:
        free(cmds);
 out_free_cmd_file_contents:
        free(cmd_file_contents);
index 0a6ba44..8f38225 100644 (file)
@@ -43,18 +43,18 @@ canonicalize_pattern(const tchar *pat, tchar **canonical_pat_ret)
 {
        tchar *canonical_pat;
 
-       if (pat[0] != T('/') && pat[0] != T('\\') &&
+       if (!is_any_path_separator(pat[0]) &&
            pat[0] != T('\0') && pat[1] == T(':'))
        {
                /* Pattern begins with drive letter */
-               if (pat[2] != T('/') && pat[2] != T('\\')) {
+               if (!is_any_path_separator(pat[2])) {
                        /* Something like c:file, which is actually a path
                         * relative to the current working directory on the c:
                         * drive.  We require paths with drive letters to be
                         * absolute. */
                        ERROR("Invalid path \"%"TS"\"; paths including drive letters "
                              "must be absolute!", pat);
-                       ERROR("Maybe try \"%"TC":/%"TS"\"?",
+                       ERROR("Maybe try \"%"TC":\\%"TS"\"?",
                              pat[0], pat + 2);
                        return WIMLIB_ERR_INVALID_CAPTURE_CONFIG;
                }
@@ -67,7 +67,12 @@ canonicalize_pattern(const tchar *pat, tchar **canonical_pat_ret)
        canonical_pat = canonicalize_fs_path(pat);
        if (!canonical_pat)
                return WIMLIB_ERR_NOMEM;
-       zap_backslashes(canonical_pat);
+
+       /* Translate all possible path separators into the operating system's
+        * preferred path separator. */
+       for (tchar *p = canonical_pat; *p; p++)
+               if (is_any_path_separator(*p))
+                       *p = OS_PREFERRED_PATH_SEPARATOR;
        *canonical_pat_ret = canonical_pat;
        return 0;
 }
@@ -146,11 +151,11 @@ match_pattern(const tchar *path,
                const tchar *pat = list->pats[i];
                const tchar *string;
 
-               if (*pat == T('/')) {
+               if (*pat == OS_PREFERRED_PATH_SEPARATOR) {
                        /* Absolute path from root of capture */
                        string = path;
                } else {
-                       if (tstrchr(pat, T('/')))
+                       if (tstrchr(pat, OS_PREFERRED_PATH_SEPARATOR))
                                /* Relative path from root of capture */
                                string = path + 1;
                        else
@@ -196,7 +201,7 @@ exclude_path(const tchar *path, size_t path_len,
        if (exclude_prefix) {
                wimlib_assert(path_len >= config->_prefix_num_tchars);
                if (!tmemcmp(config->_prefix, path, config->_prefix_num_tchars) &&
-                   path[config->_prefix_num_tchars] == T('/'))
+                   path[config->_prefix_num_tchars] == OS_PREFERRED_PATH_SEPARATOR)
                {
                        path += config->_prefix_num_tchars;
                }
index d7f1899..4509306 100644 (file)
@@ -349,7 +349,8 @@ calculate_dentry_full_path(struct wim_dentry *dentry)
                return 0;
 
        if (dentry_is_root(dentry)) {
-               full_path = TSTRDUP(T("/"));
+               static const tchar _root_path[] = {WIM_PATH_SEPARATOR, T('\0')};
+               full_path = TSTRDUP(_root_path);
                if (!full_path)
                        return WIMLIB_ERR_NOMEM;
                full_path_nbytes = 1 * sizeof(tchar);
@@ -393,7 +394,7 @@ calculate_dentry_full_path(struct wim_dentry *dentry)
                if (!full_path)
                        return WIMLIB_ERR_NOMEM;
                memcpy(full_path, parent_full_path, parent_full_path_nbytes);
-               full_path[parent_full_path_nbytes / sizeof(tchar)] = T('/');
+               full_path[parent_full_path_nbytes / sizeof(tchar)] = WIM_PATH_SEPARATOR;
        #if TCHAR_IS_UTF16LE
                memcpy(&full_path[parent_full_path_nbytes / sizeof(tchar) + 1],
                       dentry->file_name,
@@ -572,15 +573,29 @@ ads_entry_has_name(const struct wim_ads_entry *entry,
                                      entry->stream_name_nbytes);
 }
 
+/* Given a UTF-16LE filename and a directory, look up the dentry for the file.
+ * Return it if found, otherwise NULL.  This is case-sensitive on UNIX and
+ * case-insensitive on Windows. */
 struct wim_dentry *
 get_dentry_child_with_utf16le_name(const struct wim_dentry *dentry,
                                   const utf16lechar *name,
                                   size_t name_nbytes)
 {
-       struct rb_node *node = dentry->d_inode->i_children.rb_node;
+       struct rb_node *node;
+
+#ifdef __WIN32__
+       node = dentry->d_inode->i_children_case_insensitive.rb_node;
+#else
+       node = dentry->d_inode->i_children.rb_node;
+#endif
+
        struct wim_dentry *child;
        while (node) {
+       #ifdef __WIN32__
+               child = rb_entry(node, struct wim_dentry, rb_node_case_insensitive);
+       #else
                child = rbnode_dentry(node);
+       #endif
                int result = compare_utf16le_names(name, name_nbytes,
                                                   child->file_name,
                                                   child->file_name_nbytes);
@@ -647,12 +662,13 @@ get_dentry_utf16le(WIMStruct *wim, const utf16lechar *path)
        }
        p = path;
        while (1) {
-               while (*p == cpu_to_le16('/'))
+               while (*p == cpu_to_le16(WIM_PATH_SEPARATOR))
                        p++;
                if (*p == cpu_to_le16('\0'))
                        break;
                pp = p;
-               while (*pp != cpu_to_le16('/') && *pp != cpu_to_le16('\0'))
+               while (*pp != cpu_to_le16(WIM_PATH_SEPARATOR) &&
+                      *pp != cpu_to_le16('\0'))
                        pp++;
 
                cur_dentry = get_dentry_child_with_utf16le_name(parent_dentry, p,
@@ -711,11 +727,11 @@ static void
 to_parent_name(tchar *buf, size_t len)
 {
        ssize_t i = (ssize_t)len - 1;
-       while (i >= 0 && buf[i] == T('/'))
+       while (i >= 0 && buf[i] == WIM_PATH_SEPARATOR)
                i--;
-       while (i >= 0 && buf[i] != T('/'))
+       while (i >= 0 && buf[i] != WIM_PATH_SEPARATOR)
                i--;
-       while (i >= 0 && buf[i] == T('/'))
+       while (i >= 0 && buf[i] == WIM_PATH_SEPARATOR)
                i--;
        buf[i + 1] = T('\0');
 }
@@ -1092,6 +1108,45 @@ free_dentry_tree(struct wim_dentry *root, struct wim_lookup_table *lookup_table)
        for_dentry_in_tree_depth(root, do_free_dentry, lookup_table);
 }
 
+#ifdef __WIN32__
+
+/* Insert a dentry into the case insensitive index for a directory.
+ *
+ * This is a red-black tree, but when multiple dentries share the same
+ * case-insensitive name, only one is inserted into the tree itself; the rest
+ * are connected in a list.
+ */
+static struct wim_dentry *
+dentry_add_child_case_insensitive(struct wim_dentry *parent,
+                                 struct wim_dentry *child)
+{
+       struct rb_root *root;
+       struct rb_node **new;
+       struct rb_node *rb_parent;
+
+       root = &parent->d_inode->i_children_case_insensitive;
+       new = &root->rb_node;
+       rb_parent = NULL;
+       while (*new) {
+               struct wim_dentry *this = container_of(*new, struct wim_dentry,
+                                                      rb_node_case_insensitive);
+               int result = dentry_compare_names_case_insensitive(child, this);
+
+               rb_parent = *new;
+
+               if (result < 0)
+                       new = &((*new)->rb_left);
+               else if (result > 0)
+                       new = &((*new)->rb_right);
+               else
+                       return this;
+       }
+       rb_link_node(&child->rb_node_case_insensitive, rb_parent, new);
+       rb_insert_color(&child->rb_node_case_insensitive, root);
+       return NULL;
+}
+#endif
+
 /*
  * Links a dentry into the directory tree.
  *
@@ -1135,31 +1190,17 @@ dentry_add_child(struct wim_dentry * restrict parent,
        rb_insert_color(&child->rb_node, root);
 
 #ifdef __WIN32__
-       /* Case insensitive child dentry index */
-       root = &parent->d_inode->i_children_case_insensitive;
-       new = &root->rb_node;
-       rb_parent = NULL;
-       while (*new) {
-               struct wim_dentry *this = container_of(*new, struct wim_dentry,
-                                                      rb_node_case_insensitive);
-               int result = dentry_compare_names_case_insensitive(child, this);
-
-               rb_parent = *new;
-
-               if (result < 0)
-                       new = &((*new)->rb_left);
-               else if (result > 0)
-                       new = &((*new)->rb_right);
-               else {
+       {
+               struct wim_dentry *existing;
+               existing = dentry_add_child_case_insensitive(parent, child);
+               if (existing) {
                        list_add(&child->case_insensitive_conflict_list,
-                                &this->case_insensitive_conflict_list);
-                       return NULL;
-
+                                &existing->case_insensitive_conflict_list);
+                       child->rb_node_case_insensitive.__rb_parent_color = 0;
+               } else {
+                       INIT_LIST_HEAD(&child->case_insensitive_conflict_list);
                }
        }
-       rb_link_node(&child->rb_node_case_insensitive, rb_parent, new);
-       rb_insert_color(&child->rb_node_case_insensitive, root);
-       INIT_LIST_HEAD(&child->case_insensitive_conflict_list);
 #endif
        return NULL;
 }
@@ -1168,14 +1209,31 @@ dentry_add_child(struct wim_dentry * restrict parent,
 void
 unlink_dentry(struct wim_dentry *dentry)
 {
-       if (!dentry_is_root(dentry)) {
-               rb_erase(&dentry->rb_node, &dentry->parent->d_inode->i_children);
-       #ifdef __WIN32__
+       struct wim_dentry *parent = dentry->parent;
+
+       if (parent == dentry)
+               return;
+       rb_erase(&dentry->rb_node, &parent->d_inode->i_children);
+#ifdef __WIN32__
+       if (dentry->rb_node_case_insensitive.__rb_parent_color) {
+               /* This dentry was in the case-insensitive red-black tree. */
                rb_erase(&dentry->rb_node_case_insensitive,
-                        &dentry->parent->d_inode->i_children_case_insensitive);
-               list_del(&dentry->case_insensitive_conflict_list);
-       #endif
+                        &parent->d_inode->i_children_case_insensitive);
+               if (!list_empty(&dentry->case_insensitive_conflict_list)) {
+                       /* Make a different case-insensitively-the-same dentry
+                        * be the "representative" in the red-black tree. */
+                       struct list_head *next;
+                       struct wim_dentry *other;
+                       struct wim_dentry *existing;
+
+                       next = dentry->case_insensitive_conflict_list.next;
+                       other = list_entry(next, struct wim_dentry, case_insensitive_conflict_list);
+                       existing = dentry_add_child_case_insensitive(parent, other);
+                       wimlib_assert(existing == NULL);
+               }
        }
+       list_del(&dentry->case_insensitive_conflict_list);
+#endif
 }
 
 /*
index df14478..cfeb489 100644 (file)
@@ -55,7 +55,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#define MAX_LONG_PATH_WARNINGS 5
+#define MAX_EXTRACT_LONG_PATH_WARNINGS 5
 
 static int
 do_apply_op(struct wim_dentry *dentry, struct apply_args *args,
@@ -105,11 +105,11 @@ do_apply_op(struct wim_dentry *dentry, struct apply_args *args,
        /* + 1 for '\0', -4 for \\?\.  */
        if (extraction_path_nchars + 1 - 4 > MAX_PATH) {
                if (dentry->needs_extraction &&
-                   args->num_long_paths < MAX_LONG_PATH_WARNINGS)
+                   args->num_long_paths < MAX_EXTRACT_LONG_PATH_WARNINGS)
                {
                        WARNING("Path \"%ls\" exceeds MAX_PATH and will not be accessible "
                                "to most Windows software", extraction_path);
-                       if (++args->num_long_paths == MAX_LONG_PATH_WARNINGS)
+                       if (++args->num_long_paths == MAX_EXTRACT_LONG_PATH_WARNINGS)
                                WARNING("Suppressing further warnings about long paths");
                }
        }
@@ -640,6 +640,8 @@ dentry_reset_needs_extraction(struct wim_dentry *dentry, void *_ignore)
        return 0;
 }
 
+#define WINDOWS_NT_MAX_PATH 32768
+
 /*
  * extract_tree - Extract a file or directory tree from the currently selected
  *               WIM image.
@@ -698,27 +700,24 @@ extract_tree(WIMStruct *wim, const tchar *wim_source_path, const tchar *target,
        /* Work around defective behavior in Windows where paths longer than 260
         * characters are not supported by default; instead they need to be
         * turned into absolute paths and prefixed with "\\?\".  */
-       args.target_lowlevel_path = MALLOC(32768 * sizeof(wchar_t));
+       args.target_lowlevel_path = MALLOC(WINDOWS_NT_MAX_PATH * sizeof(wchar_t));
        if (!args.target_lowlevel_path)
        {
                ret = WIMLIB_ERR_NOMEM;
                goto out;
        }
-       args.target_lowlevel_path[0] = L'\\';
-       args.target_lowlevel_path[1] = L'\\';
-       args.target_lowlevel_path[2] = L'?';
-       args.target_lowlevel_path[3] = L'\\';
        args.target_lowlevel_path_nchars =
-               GetFullPathName(args.target, 32768 - 4,
+               GetFullPathName(args.target, WINDOWS_NT_MAX_PATH - 4,
                                &args.target_lowlevel_path[4], NULL);
 
        if (args.target_lowlevel_path_nchars == 0 ||
-           args.target_lowlevel_path_nchars >= 32768 - 4)
+           args.target_lowlevel_path_nchars >= WINDOWS_NT_MAX_PATH - 4)
        {
                WARNING("Can't get full path name for \"%ls\"", args.target);
                FREE(args.target_lowlevel_path);
                args.target_lowlevel_path = NULL;
        } else {
+               wmemcpy(args.target_lowlevel_path, L"\\\\?\\", 4);
                args.target_lowlevel_path_nchars += 4;
        }
 #endif
@@ -1178,7 +1177,7 @@ extract_all_images(WIMStruct *wim,
        }
 
        tmemcpy(buf, target, output_path_len);
-       buf[output_path_len] = T('/');
+       buf[output_path_len] = OS_PREFERRED_PATH_SEPARATOR;
        for (image = 1; image <= wim->hdr.image_count; image++) {
                image_name = wimlib_get_image_name(wim, image);
                if (image_name_ok_as_dir(image_name)) {
index 040ed69..f9de6bf 100644 (file)
@@ -31,7 +31,8 @@
 #include <string.h>
 
 /* Like the basename() function, but does not modify @path; it just returns a
- * pointer to it. */
+ * pointer to it.  This assumes the path separator is the
+ * OS_PREFERRED_PATH_SEPARATOR. */
 const tchar *
 path_basename(const tchar *path)
 {
@@ -48,12 +49,12 @@ path_basename_with_len(const tchar *path, size_t len)
        while (1) {
                if (p == path - 1)
                        return T("");
-               if (*p != T('/'))
+               if (*p != OS_PREFERRED_PATH_SEPARATOR)
                        break;
                p--;
        }
 
-       while ((p != path - 1) && *p != T('/'))
+       while ((p != path - 1) && *p != OS_PREFERRED_PATH_SEPARATOR)
                p--;
 
        return p + 1;
@@ -76,19 +77,6 @@ path_stream_name(const tchar *path)
 }
 
 
-/* Translate backslashes to forward slashes in-place. */
-void
-zap_backslashes(tchar *s)
-{
-       if (s) {
-               while (*s != T('\0')) {
-                       if (*s == T('\\'))
-                               *s = T('/');
-                       s++;
-               }
-       }
-}
-
 /* Duplicate a path; return empty string for NULL input. */
 tchar *
 canonicalize_fs_path(const tchar *fs_path)
@@ -98,8 +86,16 @@ canonicalize_fs_path(const tchar *fs_path)
        return TSTRDUP(fs_path);
 }
 
-/* Duplicate a path, with backslashes translated into forward slashes; return
- * empty string for NULL input;  also strip leading and trailing slashes. */
+/* 
+ * canonicalize_wim_path - Given a user-provided path to a file within a WIM
+ * image, translate it into a "canonical" path.
+ *
+ * To do this, we translate all supported path separators
+ * (is_any_path_separator()) into the WIM_PATH_SEPARATOR, and strip any leading
+ * and trailing slashes.  The returned string is allocated.  Note that there
+ * still may be consecutive path separators within the string.  Furthermore, the
+ * string may be empty, which indicates the root dentry of the WIM image.
+ */
 tchar *
 canonicalize_wim_path(const tchar *wim_path)
 {
@@ -109,14 +105,16 @@ canonicalize_wim_path(const tchar *wim_path)
        if (wim_path == NULL) {
                wim_path = T("");
        } else {
-               while (*wim_path == T('/') || *wim_path == T('\\'))
+               while (is_any_path_separator(*wim_path))
                        wim_path++;
        }
        canonical_path = TSTRDUP(wim_path);
        if (canonical_path) {
-               zap_backslashes(canonical_path);
+               for (p = canonical_path; *p; p++)
+                       if (is_any_path_separator(*p))
+                               *p = WIM_PATH_SEPARATOR;
                for (p = tstrchr(canonical_path, T('\0')) - 1;
-                    p >= canonical_path && *p == T('/');
+                    p >= canonical_path && *p == WIM_PATH_SEPARATOR;
                     p--)
                {
                        *p = T('\0');
index 4659001..286a95a 100644 (file)
@@ -516,12 +516,13 @@ unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
 
 #endif /* !defined(__WIN32__) */
 
+/* is_rp_path_separator() - characters treated as path separators in absolute
+ * symbolic link targets */
+
 #ifdef __WIN32__
-#  define RP_PATH_SEPARATOR L'\\'
 #  define is_rp_path_separator(c) ((c) == L'\\' || (c) == L'/')
 #  define os_get_ino_and_dev win32_get_file_and_vol_ids
 #else
-#  define RP_PATH_SEPARATOR '/'
 #  define is_rp_path_separator(c) ((c) == '/')
 #  define os_get_ino_and_dev unix_get_ino_and_dev
 #endif
@@ -563,7 +564,7 @@ capture_fixup_absolute_symlink(tchar *dest,
                        /* Link points inside capture root.  Return abbreviated
                         * path. */
                        if (*p == T('\0'))
-                               *(p - 1) = RP_PATH_SEPARATOR;
+                               *(p - 1) = OS_PREFERRED_PATH_SEPARATOR;
                        while (p - 1 >= dest && is_rp_path_separator(*(p - 1)))
                                p--;
                #ifdef __WIN32__
index 8fa82c8..763f4dc 100644 (file)
@@ -134,7 +134,7 @@ attach_branch(struct wim_dentry **root_p, struct wim_dentry *branch,
        /* Walk the path to the branch, creating filler directories as needed.
         * */
        parent = *root_p;
-       while ((slash = tstrchr(target_path, T('/')))) {
+       while ((slash = tstrchr(target_path, WIM_PATH_SEPARATOR))) {
                *slash = T('\0');
                dentry = get_dentry_child_with_name(parent, target_path);
                if (!dentry) {
@@ -150,7 +150,7 @@ attach_branch(struct wim_dentry **root_p, struct wim_dentry *branch,
                 * trailing slashes were tripped.  */
                do {
                        ++target_path;
-               } while (*target_path == T('/'));
+               } while (*target_path == WIM_PATH_SEPARATOR);
        }
 
        /* If the target path already existed, overlay the branch onto it.
index 5861810..43c09e9 100644 (file)
 
 #define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1
 #define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1
+#define MAX_CAPTURE_LONG_PATH_WARNINGS 5
+
 struct win32_capture_state {
        unsigned long num_get_sd_access_denied;
        unsigned long num_get_sacl_priv_notheld;
+       unsigned long num_long_path_warnings;
 };
 
 
@@ -368,7 +371,7 @@ win32_recurse_directory(struct wim_dentry *root,
         * opendir(), FindFirstFileW has file globbing built into it.  But this
         * isn't what we actually want, so just add a dummy glob to get all
         * entries. */
-       dir_path[dir_path_num_chars] = L'/';
+       dir_path[dir_path_num_chars] = OS_PREFERRED_PATH_SEPARATOR;
        dir_path[dir_path_num_chars + 1] = L'*';
        dir_path[dir_path_num_chars + 2] = L'\0';
        hFind = FindFirstFileW(dir_path, &dat);
@@ -394,7 +397,7 @@ win32_recurse_directory(struct wim_dentry *root,
                        continue;
                size_t filename_len = wcslen(dat.cFileName);
 
-               dir_path[dir_path_num_chars] = L'/';
+               dir_path[dir_path_num_chars] = OS_PREFERRED_PATH_SEPARATOR;
                wmemcpy(dir_path + dir_path_num_chars + 1,
                        dat.cFileName,
                        filename_len + 1);
@@ -759,7 +762,9 @@ win32_capture_stream(const wchar_t *path,
                    path[0] != L'\\')
                {
                        spath_nchars += 2;
-                       relpath_prefix = L"./";
+                       static const wchar_t _relpath_prefix[] =
+                               {L'.', OS_PREFERRED_PATH_SEPARATOR, L'\0'};
+                       relpath_prefix = _relpath_prefix;
                }
        }
 
@@ -949,6 +954,18 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                goto out;
        }
 
+#if 0
+       if (path_num_chars >= 4 &&
+           !wmemcmp(path, L"\\\\?\\", 4) &&
+           path_num_chars + 1 - 4 > MAX_PATH &&
+           state->num_long_path_warnings < MAX_CAPTURE_LONG_PATH_WARNINGS)
+       {
+               WARNING("Path \"%ls\" exceeds MAX_PATH", path);
+               if (++state->num_long_path_warnings == MAX_CAPTURE_LONG_PATH_WARNINGS)
+                       WARNING("Suppressing further warnings about long paths.");
+       }
+#endif
+
        if ((params->add_flags & WIMLIB_ADD_FLAG_VERBOSE)
            && params->progress_func)
        {
@@ -1114,6 +1131,8 @@ win32_do_capture_warnings(const struct win32_capture_state *state,
 "          descriptors.\n");
 }
 
+#define WINDOWS_NT_MAX_PATH 32768
+
 /* Win32 version of capturing a directory tree */
 int
 win32_build_dentry_tree(struct wim_dentry **root_ret,
@@ -1125,6 +1144,7 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        int ret;
        struct win32_capture_state state;
        unsigned vol_flags;
+       DWORD dret;
 
        if (!win32func_FindFirstStreamW) {
                WARNING("Running on Windows XP or earlier; "
@@ -1132,7 +1152,7 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        }
 
        path_nchars = wcslen(root_disk_path);
-       if (path_nchars > 32767)
+       if (path_nchars > WINDOWS_NT_MAX_PATH)
                return WIMLIB_ERR_INVALID_PARAM;
 
        if (GetFileAttributesW(root_disk_path) == INVALID_FILE_ATTRIBUTES &&
@@ -1151,15 +1171,31 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
 
        win32_get_vol_flags(root_disk_path, &vol_flags);
 
-       /* There is no check for overflow later when this buffer is being used!
-        * But the max path length on NTFS is 32767 characters, and paths need
-        * to be written specially to even go past 260 characters, so we should
-        * be okay with 32770 characters. */
-       path = MALLOC(32770 * sizeof(wchar_t));
+       /* WARNING: There is no check for overflow later when this buffer is
+        * being used!  But it's as long as the maximum path length understood
+        * by Windows NT (which is NOT the same as MAX_PATH). */
+       path = MALLOC(WINDOWS_NT_MAX_PATH * sizeof(wchar_t));
        if (!path)
                return WIMLIB_ERR_NOMEM;
 
-       wmemcpy(path, root_disk_path, path_nchars + 1);
+       /* Work around defective behavior in Windows where paths longer than 260
+        * characters are not supported by default; instead they need to be
+        * turned into absolute paths and prefixed with "\\?\".  */
+
+       if (wcsncmp(root_disk_path, L"\\\\?\\", 4)) {
+               dret = GetFullPathName(root_disk_path, WINDOWS_NT_MAX_PATH - 4,
+                                      &path[4], NULL);
+
+               if (dret == 0 || dret >= WINDOWS_NT_MAX_PATH - 4) {
+                       WARNING("Can't get full path name for \"%ls\"", root_disk_path);
+                       wmemcpy(path, root_disk_path, path_nchars + 1);
+               } else {
+                       wmemcpy(path, L"\\\\?\\", 4);
+                       path_nchars = 4 + dret;
+               }
+       } else {
+               wmemcpy(path, root_disk_path, path_nchars + 1);
+       }
 
        memset(&state, 0, sizeof(state));
        ret = win32_build_dentry_tree_recursive(root_ret, path,