X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fdentry.c;h=af363c45187794f0a382c33f29965dd3b4ea555b;hp=8670d2745fdd9b240a442d63c770b6ea9054b54f;hb=0ddd8e57f633908c771b606cc606f434c15ac832;hpb=60c3d12c1867fcfa1d96352b1a3882b5eaa92a9b diff --git a/src/dentry.c b/src/dentry.c index 8670d274..af363c45 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -37,6 +37,7 @@ #include "wimlib/error.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" +#include "wimlib/paths.h" #include "wimlib/resource.h" #include "wimlib/security.h" #include "wimlib/sha1.h" @@ -435,6 +436,24 @@ for_dentry_tree_in_rbtree(struct rb_node *node, return 0; } +/* + * Iterate over all children of @dentry, calling the function @visitor, passing + * it a child dentry and the extra argument @arg. + * + * Note: this function iterates over ALL child dentries, even those with the + * same case-insensitive name. + * + * Note: this function clobbers the tmp_list field of the child dentries. */ +int +for_dentry_child(const struct wim_dentry *dentry, + int (*visitor)(struct wim_dentry *, void *), + void *arg) +{ + return for_dentry_in_rbtree(dentry->d_inode->i_children.rb_node, + visitor, + arg); +} + /* Calls a function on all directory entries in a WIM dentry tree. Logically, * this is a pre-order traversal (the function is called on a parent dentry * before its children), but sibling dentries will be visited in order as well. @@ -616,141 +635,132 @@ calculate_subdir_offsets(struct wim_dentry *dentry, u64 *subdir_offset_p) } } -/* Case-sensitive UTF-16LE dentry or stream name comparison. Used on both UNIX - * (always) and Windows (sometimes) */ -static int -compare_utf16le_names_case_sensitive(const utf16lechar *name1, size_t nbytes1, - const utf16lechar *name2, size_t nbytes2) -{ - /* Return the result if the strings differ up to their minimum length. - * Note that we cannot use strcmp() or strncmp() here, as the strings - * are in UTF-16LE format. */ - int result = memcmp(name1, name2, min(nbytes1, nbytes2)); - if (result) - return result; - - /* The strings are the same up to their minimum length, so return a - * result based on their lengths. */ - if (nbytes1 < nbytes2) - return -1; - else if (nbytes1 > nbytes2) - return 1; - else - return 0; -} - -#ifdef __WIN32__ -/* Windoze: Case-insensitive UTF-16LE dentry or stream name comparison */ -static int -compare_utf16le_names_case_insensitive(const utf16lechar *name1, size_t nbytes1, - const utf16lechar *name2, size_t nbytes2) -{ - /* Return the result if the strings differ up to their minimum length. - * */ - int result = _wcsnicmp((const wchar_t*)name1, (const wchar_t*)name2, - min(nbytes1 / 2, nbytes2 / 2)); - if (result) - return result; - - /* The strings are the same up to their minimum length, so return a - * result based on their lengths. */ - if (nbytes1 < nbytes2) - return -1; - else if (nbytes1 > nbytes2) - return 1; - else - return 0; -} -#endif /* __WIN32__ */ - -#ifdef __WIN32__ -# define compare_utf16le_names compare_utf16le_names_case_insensitive -#else -# define compare_utf16le_names compare_utf16le_names_case_sensitive -#endif - - -#ifdef __WIN32__ static int dentry_compare_names_case_insensitive(const struct wim_dentry *d1, const struct wim_dentry *d2) { - return compare_utf16le_names_case_insensitive(d1->file_name, - d1->file_name_nbytes, - d2->file_name, - d2->file_name_nbytes); + return cmp_utf16le_strings(d1->file_name, + d1->file_name_nbytes / 2, + d2->file_name, + d2->file_name_nbytes / 2, + true); } -#endif /* __WIN32__ */ static int dentry_compare_names_case_sensitive(const struct wim_dentry *d1, const struct wim_dentry *d2) { - return compare_utf16le_names_case_sensitive(d1->file_name, - d1->file_name_nbytes, - d2->file_name, - d2->file_name_nbytes); + return cmp_utf16le_strings(d1->file_name, + d1->file_name_nbytes / 2, + d2->file_name, + d2->file_name_nbytes / 2, + false); } -#ifdef __WIN32__ -# define dentry_compare_names dentry_compare_names_case_insensitive -#else -# define dentry_compare_names dentry_compare_names_case_sensitive -#endif - /* Return %true iff the alternate data stream entry @entry has the UTF-16LE * stream name @name that has length @name_nbytes bytes. */ static inline bool ads_entry_has_name(const struct wim_ads_entry *entry, - const utf16lechar *name, size_t name_nbytes) + const utf16lechar *name, size_t name_nbytes, + bool ignore_case) { - return !compare_utf16le_names(name, name_nbytes, - entry->stream_name, - entry->stream_name_nbytes); + return 0 == cmp_utf16le_strings(name, + name_nbytes / 2, + entry->stream_name, + entry->stream_name_nbytes / 2, + ignore_case); } +/* Default case sensitivity behavior for searches with + * WIMLIB_CASE_PLATFORM_DEFAULT specified. This can be modified by + * wimlib_global_init(). */ +bool default_ignore_case = +#ifdef __WIN32__ + true +#else + false +#endif +; + +static bool +will_ignore_case(CASE_SENSITIVITY_TYPE case_type) +{ + if (case_type == WIMLIB_CASE_SENSITIVE) + return false; + if (case_type == WIMLIB_CASE_INSENSITIVE) + return true; + + return default_ignore_case; +} + + /* 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) + size_t name_nbytes, + CASE_SENSITIVITY_TYPE case_ctype) { 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 + bool ignore_case = will_ignore_case(case_ctype); + + if (ignore_case) + node = dentry->d_inode->i_children_case_insensitive.rb_node; + else + node = dentry->d_inode->i_children.rb_node; 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); - if (result < 0) + if (ignore_case) + child = rb_entry(node, struct wim_dentry, rb_node_case_insensitive); + else + child = rb_entry(node, struct wim_dentry, rb_node); + + int result = cmp_utf16le_strings(name, + name_nbytes / 2, + child->file_name, + child->file_name_nbytes / 2, + ignore_case); + if (result < 0) { node = node->rb_left; - else if (result > 0) + } else if (result > 0) { node = node->rb_right; - else { - #ifdef __WIN32__ - if (!list_empty(&child->case_insensitive_conflict_list)) - { - WARNING("Result of case-insensitive lookup is ambiguous " - "(returning \"%ls\" instead of \"%ls\")", - child->file_name, - container_of(child->case_insensitive_conflict_list.next, - struct wim_dentry, - case_insensitive_conflict_list)->file_name); - } - #endif + } else if (!ignore_case || + list_empty(&child->case_insensitive_conflict_list)) { + return child; + } else { + /* Multiple dentries have the same case-insensitive + * name, and a case-insensitive lookup is being + * performed. Choose the dentry with the same + * case-sensitive name, if one exists; otherwise print a + * warning and choose one arbitrarily. */ + struct wim_dentry *alt = child; + size_t num_alts = 0; + + do { + num_alts++; + if (0 == cmp_utf16le_strings(name, + name_nbytes / 2, + alt->file_name, + alt->file_name_nbytes / 2, + false)) + return alt; + alt = list_entry(alt->case_insensitive_conflict_list.next, + struct wim_dentry, + case_insensitive_conflict_list); + } while (alt != child); + + WARNING("Result of case-insensitive lookup is ambiguous\n" + " (returning \"%"TS"\" of %zu " + "possible files, including \"%"TS"\")", + dentry_full_path(child), + num_alts, + dentry_full_path(list_entry(child->case_insensitive_conflict_list.next, + struct wim_dentry, + case_insensitive_conflict_list))); return child; } } @@ -760,11 +770,13 @@ get_dentry_child_with_utf16le_name(const struct wim_dentry *dentry, /* Returns the child of @dentry that has the file name @name. Returns NULL if * no child has the name. */ struct wim_dentry * -get_dentry_child_with_name(const struct wim_dentry *dentry, const tchar *name) +get_dentry_child_with_name(const struct wim_dentry *dentry, const tchar *name, + CASE_SENSITIVITY_TYPE case_type) { #if TCHAR_IS_UTF16LE return get_dentry_child_with_utf16le_name(dentry, name, - tstrlen(name) * sizeof(tchar)); + tstrlen(name) * sizeof(tchar), + case_type); #else utf16lechar *utf16le_name; size_t utf16le_name_nbytes; @@ -778,7 +790,8 @@ get_dentry_child_with_name(const struct wim_dentry *dentry, const tchar *name) } else { child = get_dentry_child_with_utf16le_name(dentry, utf16le_name, - utf16le_name_nbytes); + utf16le_name_nbytes, + case_type); FREE(utf16le_name); } return child; @@ -786,54 +799,111 @@ get_dentry_child_with_name(const struct wim_dentry *dentry, const tchar *name) } static struct wim_dentry * -get_dentry_utf16le(WIMStruct *wim, const utf16lechar *path) +get_dentry_utf16le(WIMStruct *wim, const utf16lechar *path, + CASE_SENSITIVITY_TYPE case_type) { - struct wim_dentry *cur_dentry, *parent_dentry; - const utf16lechar *p, *pp; + struct wim_dentry *cur_dentry; + const utf16lechar *name_start, *name_end; - cur_dentry = parent_dentry = wim_root_dentry(wim); - if (cur_dentry == NULL) { - errno = ENOENT; - return NULL; - } - p = path; - while (1) { - while (*p == cpu_to_le16(WIM_PATH_SEPARATOR)) - p++; - if (*p == cpu_to_le16('\0')) - break; - pp = p; - 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, - (void*)pp - (void*)p); - if (cur_dentry == NULL) - break; - p = pp; - parent_dentry = cur_dentry; - } - if (cur_dentry == NULL) { - if (dentry_is_directory(parent_dentry)) + /* Start with the root directory of the image. Note: this will be NULL + * if an image has been added directly with wimlib_add_empty_image() but + * no files have been added yet; in that case we fail with ENOENT. */ + cur_dentry = wim_root_dentry(wim); + + name_start = path; + for (;;) { + if (cur_dentry == NULL) { errno = ENOENT; - else + return NULL; + } + + if (*name_start && !dentry_is_directory(cur_dentry)) { errno = ENOTDIR; + return NULL; + } + + while (*name_start == cpu_to_le16(WIM_PATH_SEPARATOR)) + name_start++; + + if (!*name_start) + return cur_dentry; + + name_end = name_start; + do { + ++name_end; + } while (*name_end != cpu_to_le16(WIM_PATH_SEPARATOR) && *name_end); + + cur_dentry = get_dentry_child_with_utf16le_name(cur_dentry, + name_start, + (u8*)name_end - (u8*)name_start, + case_type); + name_start = name_end; } - return cur_dentry; } /* - * Returns the dentry in the currently selected WIM image named by @path - * starting from the root of the WIM image, or NULL if there is no such dentry. + * WIM path lookup: translate a path in the currently selected WIM image to the + * corresponding dentry, if it exists. + * + * @wim + * The WIMStruct for the WIM. The search takes place in the currently + * selected image. + * + * @path + * The path to look up, given relative to the root of the WIM image. + * Characters with value WIM_PATH_SEPARATOR are taken to be path + * separators. Leading path separators are ignored, whereas one or more + * trailing path separators cause the path to only match a directory. + * + * @case_type + * The case-sensitivity behavior of this function, as one of the following + * constants: + * + * - WIMLIB_CASE_SENSITIVE: Perform the search case sensitively. This means + * that names must match exactly. + * + * - WIMLIB_CASE_INSENSITIVE: Perform the search case insensitively. This + * means that names are considered to match if they are equal when + * transformed to upper case. If a path component matches multiple names + * case-insensitively, the name that matches the path component + * case-sensitively is chosen, if existent; otherwise one + * case-insensitively matching name is chosen arbitrarily. + * + * - WIMLIB_CASE_PLATFORM_DEFAULT: Perform either case-sensitive or + * case-insensitive search, depending on the value of the global variable + * default_ignore_case. + * + * In any case, no Unicode normalization is done before comparing strings. + * + * Returns a pointer to the dentry that is the result of the lookup, or NULL if + * no such dentry exists. If NULL is returned, errno is set to one of the + * following values: * - * On Windows, the search is done case-insensitively. + * ENOTDIR if one of the path components used as a directory existed but + * was not, in fact, a directory. + * + * ENOENT otherwise. + * + * Additional notes: + * + * - This function does not consider a reparse point to be a directory, even + * if it has FILE_ATTRIBUTE_DIRECTORY set. + * + * - This function does not dereference symbolic links or junction points + * when performing the search. + * + * - Since this function ignores leading slashes, the empty path is valid and + * names the root directory of the WIM image. + * + * - An image added with wimlib_add_empty_image() does not have a root + * directory yet, and this function will fail with ENOENT for any path on + * such an image. */ struct wim_dentry * -get_dentry(WIMStruct *wim, const tchar *path) +get_dentry(WIMStruct *wim, const tchar *path, CASE_SENSITIVITY_TYPE case_type) { #if TCHAR_IS_UTF16LE - return get_dentry_utf16le(wim, path); + return get_dentry_utf16le(wim, path, case_type); #else utf16lechar *path_utf16le; size_t path_utf16le_nbytes; @@ -844,23 +914,12 @@ get_dentry(WIMStruct *wim, const tchar *path) &path_utf16le, &path_utf16le_nbytes); if (ret) return NULL; - dentry = get_dentry_utf16le(wim, path_utf16le); + dentry = get_dentry_utf16le(wim, path_utf16le, case_type); FREE(path_utf16le); return dentry; #endif } -struct wim_inode * -wim_pathname_to_inode(WIMStruct *wim, const tchar *path) -{ - struct wim_dentry *dentry; - dentry = get_dentry(wim, path); - if (dentry) - return dentry->d_inode; - else - return NULL; -} - /* Takes in a path of length @len in @buf, and transforms it into a string for * the path of its parent directory. */ static void @@ -876,17 +935,20 @@ to_parent_name(tchar *buf, size_t len) buf[i + 1] = T('\0'); } -/* Returns the dentry that corresponds to the parent directory of @path, or NULL - * if the dentry is not found. */ +/* Similar to get_dentry(), but returns the dentry named by @path with the last + * component stripped off. + * + * Note: The returned dentry is NOT guaranteed to be a directory. */ struct wim_dentry * -get_parent_dentry(WIMStruct *wim, const tchar *path) +get_parent_dentry(WIMStruct *wim, const tchar *path, + CASE_SENSITIVITY_TYPE case_type) { size_t path_len = tstrlen(path); tchar buf[path_len + 1]; tmemcpy(buf, path, path_len + 1); to_parent_name(buf, path_len); - return get_dentry(wim, buf); + return get_dentry(wim, buf, case_type); } /* Prints the full path of a dentry. */ @@ -1252,8 +1314,6 @@ 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 @@ -1289,7 +1349,6 @@ dentry_add_child_case_insensitive(struct wim_dentry *parent, rb_insert_color(&child->rb_node_case_insensitive, root); return NULL; } -#endif /* * Links a dentry into the directory tree. @@ -1333,7 +1392,7 @@ dentry_add_child(struct wim_dentry * restrict parent, rb_link_node(&child->rb_node, rb_parent, new); rb_insert_color(&child->rb_node, root); -#ifdef __WIN32__ + /* Case insensitive child dentry index */ { struct wim_dentry *existing; existing = dentry_add_child_case_insensitive(parent, child); @@ -1345,7 +1404,6 @@ dentry_add_child(struct wim_dentry * restrict parent, INIT_LIST_HEAD(&child->case_insensitive_conflict_list); } } -#endif return NULL; } @@ -1358,7 +1416,7 @@ unlink_dentry(struct wim_dentry *dentry) 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, @@ -1377,7 +1435,76 @@ unlink_dentry(struct wim_dentry *dentry) } } list_del(&dentry->case_insensitive_conflict_list); -#endif +} + +static int +free_dentry_full_path(struct wim_dentry *dentry, void *_ignore) +{ + FREE(dentry->_full_path); + dentry->_full_path = NULL; + return 0; +} + +/* Rename a file or directory in the WIM. */ +int +rename_wim_path(WIMStruct *wim, const tchar *from, const tchar *to, + CASE_SENSITIVITY_TYPE case_type) +{ + struct wim_dentry *src; + struct wim_dentry *dst; + struct wim_dentry *parent_of_dst; + int ret; + + /* This rename() implementation currently only supports actual files + * (not alternate data streams) */ + + src = get_dentry(wim, from, case_type); + if (!src) + return -errno; + + dst = get_dentry(wim, to, case_type); + + if (dst) { + /* Destination file exists */ + + if (src == dst) /* Same file */ + return 0; + + if (!dentry_is_directory(src)) { + /* Cannot rename non-directory to directory. */ + if (dentry_is_directory(dst)) + return -EISDIR; + } else { + /* Cannot rename directory to a non-directory or a non-empty + * directory */ + if (!dentry_is_directory(dst)) + return -ENOTDIR; + if (dentry_has_children(dst)) + return -ENOTEMPTY; + } + parent_of_dst = dst->parent; + } else { + /* Destination does not exist */ + parent_of_dst = get_parent_dentry(wim, to, case_type); + if (!parent_of_dst) + return -errno; + + if (!dentry_is_directory(parent_of_dst)) + return -ENOTDIR; + } + + ret = set_dentry_name(src, path_basename(to)); + if (ret) + return -ENOMEM; + if (dst) { + unlink_dentry(dst); + free_dentry_tree(dst, wim->lookup_table); + } + unlink_dentry(src); + dentry_add_child(parent_of_dst, src); + if (src->_full_path) + for_dentry_in_tree(src, free_dentry_full_path, NULL); + return 0; } /* @@ -1425,7 +1552,8 @@ inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name, do { if (ads_entry_has_name(&inode->i_ads_entries[i], stream_name_utf16le, - stream_name_utf16le_nbytes)) + stream_name_utf16le_nbytes, + default_ignore_case)) { if (idx_ret) *idx_ret = i; @@ -2109,7 +2237,7 @@ read_dentry_tree(const u8 * restrict metadata_resource, for (parent = dentry->parent; !dentry_is_root(parent); parent = parent->parent) { if (unlikely(parent->subdir_offset == cur_offset)) { - ERROR("Cyclic directory structure directed: children " + ERROR("Cyclic directory structure detected: children " "of \"%"TS"\" coincide with children of \"%"TS"\"", dentry_full_path(dentry), dentry_full_path(parent)); @@ -2569,7 +2697,7 @@ image_do_iterate_dir_tree(WIMStruct *wim) struct image_iterate_dir_tree_ctx *ctx = wim->private; struct wim_dentry *dentry; - dentry = get_dentry(wim, ctx->path); + dentry = get_dentry(wim, ctx->path, WIMLIB_CASE_PLATFORM_DEFAULT); if (dentry == NULL) return WIMLIB_ERR_PATH_DOES_NOT_EXIST; return do_iterate_dir_tree(wim, dentry, ctx->flags, ctx->cb, ctx->user_ctx); @@ -2577,10 +2705,16 @@ image_do_iterate_dir_tree(WIMStruct *wim) /* API function documented in wimlib.h */ WIMLIBAPI int -wimlib_iterate_dir_tree(WIMStruct *wim, int image, const tchar *path, +wimlib_iterate_dir_tree(WIMStruct *wim, int image, const tchar *_path, int flags, wimlib_iterate_dir_tree_callback_t cb, void *user_ctx) { + tchar *path; + int ret; + + path = canonicalize_wim_path(_path); + if (path == NULL) + return WIMLIB_ERR_NOMEM; struct image_iterate_dir_tree_ctx ctx = { .path = path, .flags = flags, @@ -2588,7 +2722,9 @@ wimlib_iterate_dir_tree(WIMStruct *wim, int image, const tchar *path, .user_ctx = user_ctx, }; wim->private = &ctx; - return for_image(wim, image, image_do_iterate_dir_tree); + ret = for_image(wim, image, image_do_iterate_dir_tree); + FREE(path); + return ret; } /* Returns %true iff the metadata of @inode and @template_inode are reasonably @@ -2737,7 +2873,8 @@ dentry_reference_template(struct wim_dentry *dentry, void *_args) if (ret) return ret; - template_dentry = get_dentry(template_wim, dentry->_full_path); + template_dentry = get_dentry(template_wim, dentry->_full_path, + WIMLIB_CASE_SENSITIVE); if (template_dentry == NULL) { DEBUG("\"%"TS"\": newly added file", dentry->_full_path); return 0;