From: Eric Biggers Date: Mon, 20 May 2013 23:25:25 +0000 (-0500) Subject: Path fixes/updates X-Git-Tag: v1.4.1~75 X-Git-Url: https://wimlib.net/git/?p=wimlib;a=commitdiff_plain;h=01c6f902b2a48a33af3f94e29791f85c68df91d0 Path fixes/updates - 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 --- diff --git a/include/wimlib/paths.h b/include/wimlib/paths.h index 311a8a0e..59c1a867 100644 --- a/include/wimlib/paths.h +++ b/include/wimlib/paths.h @@ -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; diff --git a/include/wimlib/util.h b/include/wimlib/util.h index 32936838..646fdafc 100644 --- a/include/wimlib/util.h +++ b/include/wimlib/util.h @@ -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 */ diff --git a/programs/imagex.c b/programs/imagex.c index 1ee31868..e5241358 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -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); diff --git a/src/capture_common.c b/src/capture_common.c index 0a6ba44d..8f382251 100644 --- a/src/capture_common.c +++ b/src/capture_common.c @@ -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; } diff --git a/src/dentry.c b/src/dentry.c index d7f18990..45093069 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -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 } /* diff --git a/src/extract.c b/src/extract.c index df144785..cfeb4898 100644 --- a/src/extract.c +++ b/src/extract.c @@ -55,7 +55,7 @@ #include #include -#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)) { diff --git a/src/paths.c b/src/paths.c index 040ed697..f9de6bfe 100644 --- a/src/paths.c +++ b/src/paths.c @@ -31,7 +31,8 @@ #include /* 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'); diff --git a/src/reparse.c b/src/reparse.c index 46590017..286a95a1 100644 --- a/src/reparse.c +++ b/src/reparse.c @@ -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__ diff --git a/src/update_image.c b/src/update_image.c index 8fa82c81..763f4dc4 100644 --- a/src/update_image.c +++ b/src/update_image.c @@ -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. diff --git a/src/win32_capture.c b/src/win32_capture.c index 58618102..43c09e9e 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -38,9 +38,12 @@ #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,