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;
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 */
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,
#ifdef __WIN32__
win32_release_capture_privileges();
#endif
-out_free_cmds:
free(cmds);
out_free_cmd_file_contents:
free(cmd_file_contents);
{
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;
}
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;
}
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
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;
}
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);
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,
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);
}
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,
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');
}
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.
*
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;
}
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
}
/*
#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,
/* + 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");
}
}
return 0;
}
+#define WINDOWS_NT_MAX_PATH 32768
+
/*
* extract_tree - Extract a file or directory tree from the currently selected
* WIM image.
/* 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
}
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)) {
#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)
{
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;
}
-/* 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)
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)
{
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');
#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
/* 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__
/* 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) {
* 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.
#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;
};
* 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);
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);
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;
}
}
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)
{
" 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,
int ret;
struct win32_capture_state state;
unsigned vol_flags;
+ DWORD dret;
if (!win32func_FindFirstStreamW) {
WARNING("Running on Windows XP or earlier; "
}
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 &&
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,