X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fdentry.c;h=862c6640c9bb54b9bef52610824604091b830e13;hp=13f36ca645e1e20f7ebfb02169a4a544db54d02c;hb=5f3757249c17cb9e2826f645e0f8618534c80fcc;hpb=edf0f7f5331e626b55819dcafa88e202cdb82a32 diff --git a/src/dentry.c b/src/dentry.c index 13f36ca6..862c6640 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -1,48 +1,70 @@ /* - * dentry.c - * - * In the WIM file format, the dentries are stored in the "metadata resource" - * section right after the security data. Each image in the WIM file has its - * own metadata resource with its own security data and dentry tree. Dentries - * in different images may share file resources by referring to the same lookup - * table entries. + * dentry.c - see description below */ /* * Copyright (C) 2012, 2013, 2014 Eric Biggers * - * This file is part of wimlib, a library for working with WIM files. + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This file is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this file; if not, see http://www.gnu.org/licenses/. + */ + +/* + * This file contains logic to deal with WIM directory entries, or "dentries": + * + * - Reading a dentry tree from a metadata resource in a WIM file + * - Writing a dentry tree to a metadata resource in a WIM file + * - Iterating through a tree of WIM dentries + * - Path lookup: translating a path into a WIM dentry or inode + * - Creating, modifying, and deleting WIM dentries + * + * Notes: + * + * - A WIM file can contain multiple images, each of which has an independent + * tree of dentries. "On disk", the dentry tree for an image is stored in + * the "metadata resource" for that image. * - * wimlib is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free Software - * Foundation; either version 3 of the License, or (at your option) any later - * version. + * - Multiple dentries in an image may correspond to the same inode, or "file". + * When this occurs, it means that the file has multiple names, or "hard + * links". A dentry is not a file, but rather the name of a file! * - * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * - Inodes are not represented explicitly in the WIM file format. Instead, + * the metadata resource provides a "hard link group ID" for each dentry. + * wimlib handles pulling out actual inodes from this information, but this + * occurs in inode_fixup.c and not in this file. * - * You should have received a copy of the GNU General Public License along with - * wimlib; if not, see http://www.gnu.org/licenses/. + * - wimlib does not allow *directory* hard links, so a WIM image really does + * have a *tree* of dentries (and not an arbitrary graph of dentries). + * + * - wimlib indexes dentries both case-insensitively and case-sensitively, + * allowing either behavior to be used for path lookup. + * + * - Multiple dentries in a directory might have the same case-insensitive + * name. But wimlib enforces that at most one dentry in a directory can have + * a given case-sensitive name. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include "wimlib.h" -#include "wimlib/case.h" +#include "wimlib/assert.h" #include "wimlib/dentry.h" +#include "wimlib/inode.h" #include "wimlib/encoding.h" #include "wimlib/endianness.h" -#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" -#include "wimlib/timestamp.h" #include @@ -84,7 +106,6 @@ struct wim_dentry_on_disk { le64 unused_1; le64 unused_2; - /* Creation time, last access time, and last write time, in * 100-nanosecond intervals since 12:00 a.m UTC January 1, 1601. They * should correspond to the times gotten by calling GetFileTime() on @@ -143,11 +164,8 @@ struct wim_dentry_on_disk { * not matter, as long as they are unique. * - However, due to bugs in Microsoft's software, it is actually NOT * guaranteed that directory entries that share the same hard link - * group ID are actually hard linked to each either. We have to - * handle this by using special code to use distinguishing features - * (which is possible because some information about the underlying - * inode is repeated in each dentry) to split up these fake hard link - * groups into what they actually are supposed to be. + * group ID are actually hard linked to each either. See + * inode_fixup.c for the code that handles this. */ union { struct { @@ -166,54 +184,59 @@ struct wim_dentry_on_disk { * dentry on-disk. */ le16 num_alternate_data_streams; - /* Length of this file's UTF-16LE encoded short name (8.3 DOS-compatible - * name), if present, in bytes, excluding the null terminator. If this - * file has no short name, then this field should be 0. */ + /* If nonzero, this is the length, in bytes, of this dentry's UTF-16LE + * encoded short name (8.3 DOS-compatible name), excluding the null + * terminator. If zero, then the long name of this dentry does not have + * a corresponding short name (but this does not exclude the possibility + * that another dentry for the same file has a short name). */ le16 short_name_nbytes; - /* Length of this file's UTF-16LE encoded "long" name, excluding the - * null terminator. If this file has no short name, then this field - * should be 0. It's expected that only the root dentry has this field - * set to 0. */ + /* If nonzero, this is the length, in bytes, of this dentry's UTF-16LE + * encoded "long" name, excluding the null terminator. If zero, then + * this file has no long name. The root dentry should not have a long + * name, but all other dentries in the image should have long names. */ le16 file_name_nbytes; - /* Followed by variable length file name, in UTF16-LE, if - * file_name_nbytes != 0. Includes null terminator. */ + /* Beginning of optional, variable-length fields */ + + /* If file_name_nbytes != 0, the next field will be the UTF-16LE encoded + * long file name. This will be null-terminated, so the size of this + * field will really be file_name_nbytes + 2. */ /*utf16lechar file_name[];*/ - /* Followed by variable length short name, in UTF16-LE, if - * short_name_nbytes != 0. Includes null terminator. */ + /* If short_name_nbytes != 0, the next field will be the UTF-16LE + * encoded short name. This will be null-terminated, so the size of + * this field will really be short_name_nbytes + 2. */ /*utf16lechar short_name[];*/ -} _packed_attribute; -/* Calculates the unaligned length, in bytes, of an on-disk WIM dentry that has - * a file name and short name that take the specified numbers of bytes. This - * excludes any alternate data stream entries that may follow the dentry. */ + /* If there is still space in the dentry (according to the 'length' + * field) after 8-byte alignment, then the remaining space will be a + * variable-length list of tagged metadata items. See tagged_items.c + * for more information. */ + /* u8 tagged_items[] _aligned_attribute(8); */ + +} _packed_attribute; + /* If num_alternate_data_streams != 0, then there are that many + * alternate data stream entries following the dentry, on an 8-byte + * aligned boundary. They are not counted in the 'length' field of the + * dentry. */ + +/* Calculate the minimum unaligned length, in bytes, of an on-disk WIM dentry + * that has names of the specified lengths. (Zero length means the + * corresponding name actually does not exist.) The returned value excludes + * tagged metadata items as well as any alternate data stream entries that may + * need to follow the dentry. */ static u64 -dentry_correct_length_unaligned(u16 file_name_nbytes, u16 short_name_nbytes) +dentry_min_len_with_names(u16 file_name_nbytes, u16 short_name_nbytes) { u64 length = sizeof(struct wim_dentry_on_disk); if (file_name_nbytes) - length += file_name_nbytes + 2; + length += (u32)file_name_nbytes + 2; if (short_name_nbytes) - length += short_name_nbytes + 2; + length += (u32)short_name_nbytes + 2; return length; } -/* Calculates the unaligned length, in bytes, of an on-disk WIM dentry, based on - * the file name length and short name length. Note that dentry->length is - * ignored; also, this excludes any alternate data stream entries that may - * follow the dentry. */ -static u64 -dentry_correct_length_aligned(const struct wim_dentry *dentry) -{ - u64 len; - - len = dentry_correct_length_unaligned(dentry->file_name_nbytes, - dentry->short_name_nbytes); - return (len + 7) & ~7; -} - static void do_dentry_set_name(struct wim_dentry *dentry, utf16lechar *file_name, size_t file_name_nbytes) @@ -229,9 +252,24 @@ do_dentry_set_name(struct wim_dentry *dentry, utf16lechar *file_name, } } -/* Sets the name of a WIM dentry from a UTF-16LE string. - * Only use this on dentries not inserted into the tree. Use rename_wim_path() - * to do a real rename. */ +/* + * Set the name of a WIM dentry from a UTF-16LE string. + * + * This sets the long name of the dentry. The short name will automatically be + * removed, since it may not be appropriate for the new long name. + * + * The @name string need not be null-terminated, since its length is specified + * in @name_nbytes. + * + * If @name_nbytes is 0, both the long and short names of the dentry will be + * removed. + * + * Only use this function on unlinked dentries, since it doesn't update the name + * indices. For dentries that are currently linked into the tree, use + * rename_wim_path(). + * + * Returns 0 or WIMLIB_ERR_NOMEM. + */ int dentry_set_name_utf16le(struct wim_dentry *dentry, const utf16lechar *name, size_t name_nbytes) @@ -248,9 +286,21 @@ dentry_set_name_utf16le(struct wim_dentry *dentry, const utf16lechar *name, } -/* Sets the name of a WIM dentry from a multibyte string. - * Only use this on dentries not inserted into the tree. Use rename_wim_path() - * to do a real rename. */ +/* + * Set the name of a WIM dentry from a 'tchar' string. + * + * This sets the long name of the dentry. The short name will automatically be + * removed, since it may not be appropriate for the new long name. + * + * If @name is NULL or empty, both the long and short names of the dentry will + * be removed. + * + * Only use this function on unlinked dentries, since it doesn't update the name + * indices. For dentries that are currently linked into the tree, use + * rename_wim_path(). + * + * Returns 0 or an error code resulting from a failed string conversion. + */ int dentry_set_name(struct wim_dentry *dentry, const tchar *name) { @@ -269,20 +319,22 @@ dentry_set_name(struct wim_dentry *dentry, const tchar *name) return 0; } -/* Returns the total length of a WIM alternate data stream entry on-disk, - * including the stream name, the null terminator, AND the padding after the - * entry to align the next ADS entry or dentry on an 8-byte boundary. */ +/* Return the length, in bytes, required for the specified alternate data stream + * (ADS) entry on-disk. This accounts for the fixed-length portion of the ADS + * entry, the {stream name and its null terminator} if present, and the padding + * after the entry to align the next ADS entry or dentry on an 8-byte boundary + * in the uncompressed metadata resource buffer. */ static u64 -ads_entry_total_length(const struct wim_ads_entry *entry) +ads_entry_out_total_length(const struct wim_ads_entry *entry) { u64 len = sizeof(struct wim_ads_entry_on_disk); if (entry->stream_name_nbytes) - len += entry->stream_name_nbytes + 2; + len += (u32)entry->stream_name_nbytes + 2; return (len + 7) & ~7; } /* - * Determine whether to include a "dummy" stream when writing a WIM dentry: + * Determine whether to include a "dummy" stream when writing a WIM dentry. * * Some versions of Microsoft's WIM software (the boot driver(s) in WinPE 3.0, * for example) contain a bug where they assume the first alternate data stream @@ -306,36 +358,37 @@ inode_needs_dummy_stream(const struct wim_inode *inode) when it was read in */ } -/* Calculate the total number of bytes that will be consumed when a WIM dentry - * is written. This includes base dentry and name fields as well as all - * alternate data stream entries and alignment bytes. */ +/* Calculate the total number of bytes that will be consumed when a dentry is + * written. This includes the fixed-length portion of the dentry, the name + * fields, any tagged metadata items, and any alternate data stream entries. + * Also includes all alignment bytes. */ u64 dentry_out_total_length(const struct wim_dentry *dentry) { - u64 length = dentry_correct_length_aligned(dentry); const struct wim_inode *inode = dentry->d_inode; + u64 len; - if (inode_needs_dummy_stream(inode)) - length += ads_entry_total_length(&(struct wim_ads_entry){}); + len = dentry_min_len_with_names(dentry->file_name_nbytes, + dentry->short_name_nbytes); + len = (len + 7) & ~7; - for (u16 i = 0; i < inode->i_num_ads; i++) - length += ads_entry_total_length(&inode->i_ads_entries[i]); + if (inode->i_extra_size) { + len += inode->i_extra_size; + len = (len + 7) & ~7; + } - return length; -} + if (unlikely(inode->i_num_ads)) { + if (inode_needs_dummy_stream(inode)) + len += ads_entry_out_total_length(&(struct wim_ads_entry){}); -/* Calculate the aligned, total length of a dentry, including all alternate data - * stream entries. Uses dentry->length. */ -static u64 -dentry_in_total_length(const struct wim_dentry *dentry) -{ - u64 length = dentry->length; - const struct wim_inode *inode = dentry->d_inode; - for (u16 i = 0; i < inode->i_num_ads; i++) - length += ads_entry_total_length(&inode->i_ads_entries[i]); - return (length + 7) & ~7; + for (u16 i = 0; i < inode->i_num_ads; i++) + len += ads_entry_out_total_length(&inode->i_ads_entries[i]); + } + + return len; } +/* Internal version of for_dentry_in_tree() that omits the NULL check */ static int do_for_dentry_in_tree(struct wim_dentry *dentry, int (*visitor)(struct wim_dentry *, void *), void *arg) @@ -355,7 +408,7 @@ do_for_dentry_in_tree(struct wim_dentry *dentry, return 0; } - +/* Internal version of for_dentry_in_tree_depth() that omits the NULL check */ static int do_for_dentry_in_tree_depth(struct wim_dentry *dentry, int (*visitor)(struct wim_dentry *, void *), void *arg) @@ -371,10 +424,25 @@ do_for_dentry_in_tree_depth(struct wim_dentry *dentry, return unlikely((*visitor)(dentry, 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. - * */ +/* + * Call a function on all dentries in a tree. + * + * @arg will be passed as the second argument to each invocation of @visitor. + * + * This function does a pre-order traversal --- that is, a parent will be + * visited before its children. It also will visit siblings in order of + * case-sensitive filename. Equivalently, this function visits the entire tree + * in the case-sensitive lexicographic order of the full paths. + * + * It is safe to pass NULL for @root, which means that the dentry tree is empty. + * In this case, this function does nothing. + * + * @visitor must not modify the structure of the dentry tree during the + * traversal. + * + * The return value will be 0 if all calls to @visitor returned 0. Otherwise, + * the return value will be the first nonzero value returned by @visitor. + */ int for_dentry_in_tree(struct wim_dentry *root, int (*visitor)(struct wim_dentry *, void *), void *arg) @@ -384,9 +452,10 @@ for_dentry_in_tree(struct wim_dentry *root, return do_for_dentry_in_tree(root, visitor, arg); } -/* Like for_dentry_in_tree(), but the visitor function is always called on a - * dentry's children before on itself. */ -int +/* Like for_dentry_in_tree(), but do a depth-first traversal of the dentry tree. + * That is, the visitor function will be called on a dentry's children before + * itself. It will be safe to free a dentry when visiting it. */ +static int for_dentry_in_tree_depth(struct wim_dentry *root, int (*visitor)(struct wim_dentry *, void *), void *arg) { @@ -395,7 +464,16 @@ for_dentry_in_tree_depth(struct wim_dentry *root, return do_for_dentry_in_tree_depth(root, visitor, arg); } -/* Calculate the full path of @dentry. */ +/* + * Calculate the full path to @dentry within the WIM image, if not already done. + * + * The full name will be saved in the cached value 'dentry->_full_path'. + * + * Whenever possible, use dentry_full_path() instead of calling this and + * accessing _full_path directly. + * + * Returns 0 or an error code resulting from a failed string conversion. + */ int calculate_dentry_full_path(struct wim_dentry *dentry) { @@ -411,7 +489,7 @@ calculate_dentry_full_path(struct wim_dentry *dentry) do { ulen += d->file_name_nbytes / sizeof(utf16lechar); ulen++; - d = d->parent; /* assumes d == d->parent for root */ + d = d->d_parent; /* assumes d == d->d_parent for root */ } while (!dentry_is_root(d)); utf16lechar ubuf[ulen]; @@ -422,7 +500,7 @@ calculate_dentry_full_path(struct wim_dentry *dentry) p -= d->file_name_nbytes / sizeof(utf16lechar); memcpy(p, d->file_name, d->file_name_nbytes); *--p = cpu_to_le16(WIM_PATH_SEPARATOR); - d = d->parent; /* assumes d == d->parent for root */ + d = d->d_parent; /* assumes d == d->d_parent for root */ } while (!dentry_is_root(d)); wimlib_assert(p == ubuf); @@ -431,6 +509,13 @@ calculate_dentry_full_path(struct wim_dentry *dentry) &dentry->_full_path, &dummy); } +/* + * Return the full path to the @dentry within the WIM image, or NULL if the full + * path could not be determined due to a string conversion error. + * + * The returned memory will be cached in the dentry, so the caller is not + * responsible for freeing it. + */ tchar * dentry_full_path(struct wim_dentry *dentry) { @@ -441,7 +526,6 @@ dentry_full_path(struct wim_dentry *dentry) static int dentry_calculate_subdir_offset(struct wim_dentry *dentry, void *_subdir_offset_p) { - if (dentry_is_directory(dentry)) { u64 *subdir_offset_p = _subdir_offset_p; struct wim_dentry *child; @@ -463,7 +547,18 @@ dentry_calculate_subdir_offset(struct wim_dentry *dentry, void *_subdir_offset_p } /* - * Calculates the subdir offsets for a directory tree. + * Calculate the subdir offsets for a dentry tree, in preparation of writing + * that dentry tree to a metadata resource. + * + * The subdir offset of each dentry is the offset in the uncompressed metadata + * resource at which its child dentries begin, or 0 if that dentry has no + * children. + * + * The caller must initialize *subdir_offset_p to the first subdir offset that + * is available to use after the root dentry is written. + * + * When this function returns, *subdir_offset_p will have been advanced past the + * size needed for the dentry tree within the uncompressed metadata resource. */ void calculate_subdir_offsets(struct wim_dentry *root, u64 *subdir_offset_p) @@ -518,8 +613,9 @@ _avl_dentry_compare_names(const struct avl_tree_node *n1, } /* Default case sensitivity behavior for searches with - * WIMLIB_CASE_PLATFORM_DEFAULT specified. This can be modified by - * wimlib_global_init(). */ + * WIMLIB_CASE_PLATFORM_DEFAULT specified. This can be modified by passing + * WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE or + * WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE to wimlib_global_init(). */ bool default_ignore_case = #ifdef __WIN32__ true @@ -615,8 +711,10 @@ get_dentry_child_with_utf16le_name(const struct wim_dentry *dentry, return child; } -/* Returns the child of @dentry that has the file name @name. Returns NULL if - * no child has the name. */ +/* Given a 'tchar' filename and a directory, look up the dentry for the file. + * If the filename was successfully converted to UTF-16LE and the dentry was + * found, return it; otherwise return NULL. This has configurable case + * sensitivity. */ struct wim_dentry * get_dentry_child_with_name(const struct wim_dentry *dentry, const tchar *name, CASE_SENSITIVITY_TYPE case_type) @@ -639,6 +737,8 @@ get_dentry_child_with_name(const struct wim_dentry *dentry, const tchar *name, return child; } +/* This is the UTF-16LE version of get_dentry(), currently private to this file + * because no one needs it besides get_dentry(). */ static struct wim_dentry * get_dentry_utf16le(WIMStruct *wim, const utf16lechar *path, CASE_SENSITIVITY_TYPE case_type) @@ -649,7 +749,7 @@ get_dentry_utf16le(WIMStruct *wim, const utf16lechar *path, /* 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); + cur_dentry = wim_get_current_root_dentry(wim); name_start = path; for (;;) { @@ -755,19 +855,19 @@ get_dentry(WIMStruct *wim, const tchar *path, CASE_SENSITIVITY_TYPE case_type) return dentry; } -/* Takes in a path of length @len in @buf, and transforms it into a string for - * the path of its parent directory. */ +/* Modify @path, which is a null-terminated string @len 'tchars' in length, + * in-place to produce the path to its parent directory. */ static void -to_parent_name(tchar *buf, size_t len) +to_parent_name(tchar *path, size_t len) { ssize_t i = (ssize_t)len - 1; - while (i >= 0 && buf[i] == WIM_PATH_SEPARATOR) + while (i >= 0 && path[i] == WIM_PATH_SEPARATOR) i--; - while (i >= 0 && buf[i] != WIM_PATH_SEPARATOR) + while (i >= 0 && path[i] != WIM_PATH_SEPARATOR) i--; - while (i >= 0 && buf[i] == WIM_PATH_SEPARATOR) + while (i >= 0 && path[i] == WIM_PATH_SEPARATOR) i--; - buf[i + 1] = T('\0'); + path[i + 1] = T('\0'); } /* Similar to get_dentry(), but returns the dentry named by @path with the last @@ -786,75 +886,18 @@ get_parent_dentry(WIMStruct *wim, const tchar *path, return get_dentry(wim, buf, case_type); } -#ifdef WITH_FUSE -/* Finds the dentry, lookup table entry, and stream index for a WIM file stream, - * given a path name. +/* + * Create an unlinked dentry. * - * Currently, lookups of this type are only needed if FUSE is enabled. */ -int -wim_pathname_to_stream(WIMStruct *wim, - const tchar *path, - int lookup_flags, - struct wim_dentry **dentry_ret, - struct wim_lookup_table_entry **lte_ret, - u16 *stream_idx_ret) -{ - struct wim_dentry *dentry; - struct wim_lookup_table_entry *lte; - u16 stream_idx; - const tchar *stream_name = NULL; - struct wim_inode *inode; - tchar *p = NULL; - - if (lookup_flags & LOOKUP_FLAG_ADS_OK) { - stream_name = path_stream_name(path); - if (stream_name) { - p = (tchar*)stream_name - 1; - *p = T('\0'); - } - } - - dentry = get_dentry(wim, path, WIMLIB_CASE_SENSITIVE); - if (p) - *p = T(':'); - if (!dentry) - return -errno; - - inode = dentry->d_inode; - - if (!inode->i_resolved) - if (inode_resolve_streams(inode, wim->lookup_table, false)) - return -EIO; - - if (!(lookup_flags & LOOKUP_FLAG_DIRECTORY_OK) - && inode_is_directory(inode)) - return -EISDIR; - - if (stream_name) { - struct wim_ads_entry *ads_entry; - u16 ads_idx; - ads_entry = inode_get_ads_entry(inode, stream_name, - &ads_idx); - if (ads_entry) { - stream_idx = ads_idx + 1; - lte = ads_entry->lte; - } else { - return -ENOENT; - } - } else { - lte = inode_unnamed_stream_resolved(inode, &stream_idx); - } - if (dentry_ret) - *dentry_ret = dentry; - if (lte_ret) - *lte_ret = lte; - if (stream_idx_ret) - *stream_idx_ret = stream_idx; - return 0; -} -#endif /* WITH_FUSE */ - -/* Creates an unlinked directory entry. */ + * @name specifies the long name to give the new dentry. If NULL or empty, the + * new dentry will be given no long name. + * + * The new dentry will have no short name and no associated inode. + * + * On success, returns 0 and a pointer to the new, allocated dentry is stored in + * *dentry_ret. On failure, returns WIMLIB_ERR_NOMEM or an error code resulting + * from a failed string conversion. + */ int new_dentry(const tchar *name, struct wim_dentry **dentry_ret) { @@ -865,23 +908,21 @@ new_dentry(const tchar *name, struct wim_dentry **dentry_ret) if (!dentry) return WIMLIB_ERR_NOMEM; - if (*name) { + if (name && *name) { ret = dentry_set_name(dentry, name); if (ret) { FREE(dentry); - ERROR("Failed to set name on new dentry with name \"%"TS"\"", - name); return ret; } } - dentry->parent = dentry; + dentry->d_parent = dentry; *dentry_ret = dentry; return 0; } static int _new_dentry_with_inode(const tchar *name, struct wim_dentry **dentry_ret, - bool timeless) + bool timeless) { struct wim_dentry *dentry; int ret; @@ -904,25 +945,32 @@ _new_dentry_with_inode(const tchar *name, struct wim_dentry **dentry_ret, return 0; } +/* Like new_dentry(), but also allocate an inode and associate it with the + * dentry. The timestamps for the inode will be set to the current time. */ int -new_dentry_with_timeless_inode(const tchar *name, struct wim_dentry **dentry_ret) +new_dentry_with_inode(const tchar *name, struct wim_dentry **dentry_ret) { - return _new_dentry_with_inode(name, dentry_ret, true); + return _new_dentry_with_inode(name, dentry_ret, false); } +/* Like new_dentry_with_inode(), but don't bother setting the timestamps for the + * new inode; instead, just leave them as 0, under the presumption that the + * caller will set them itself. */ int -new_dentry_with_inode(const tchar *name, struct wim_dentry **dentry_ret) +new_dentry_with_timeless_inode(const tchar *name, struct wim_dentry **dentry_ret) { - return _new_dentry_with_inode(name, dentry_ret, false); + return _new_dentry_with_inode(name, dentry_ret, true); } +/* Create an unnamed dentry with a new inode for a directory with the default + * metadata. */ int new_filler_directory(struct wim_dentry **dentry_ret) { int ret; struct wim_dentry *dentry; - ret = new_dentry_with_inode(T(""), &dentry); + ret = new_dentry_with_inode(NULL, &dentry); if (ret) return ret; /* Leave the inode number as 0; this is allowed for non @@ -982,19 +1030,22 @@ do_free_dentry_and_unref_streams(struct wim_dentry *dentry, void *lookup_table) } /* - * Recursively frees all directory entries in the specified tree. + * Free all dentries in a tree. * * @root: - * The root of the tree. + * The root of the dentry tree to free. If NULL, this function has no + * effect. * * @lookup_table: - * The lookup table for dentries. If non-NULL, the reference counts in the - * lookup table for the lookup table entries corresponding to the dentries - * will be decremented. + * A pointer to the lookup table for the WIM, or NULL if not specified. If + * specified, this function will decrement the reference counts of the + * single-instance streams referenced by the dentries. * - * This also puts references to the corresponding inodes. + * This function also releases references to the corresponding inodes. * - * This does *not* unlink @root from its parent directory (if it has one). + * This function does *not* unlink @root from its parent directory, if it has + * one. If @root has a parent, the caller must unlink @root before calling this + * function. */ void free_dentry_tree(struct wim_dentry *root, struct wim_lookup_table *lookup_table) @@ -1041,21 +1092,21 @@ dir_index_child_ci(struct wim_inode *dir, struct wim_dentry *child) return avl_tree_entry(duplicate, struct wim_dentry, d_index_node_ci); } -/* Removes the specified dentry from its directory's case-sensitive index. */ +/* Remove the specified dentry from its directory's case-sensitive index. */ static void dir_unindex_child(struct wim_inode *dir, struct wim_dentry *child) { avl_tree_remove(&dir->i_children, &child->d_index_node); } -/* Removes the specified dentry from its directory's case-insensitive index. */ +/* Remove the specified dentry from its directory's case-insensitive index. */ static void dir_unindex_child_ci(struct wim_inode *dir, struct wim_dentry *child) { avl_tree_remove(&dir->i_children_ci, &child->d_index_node_ci); } -/* Returns true iff the specified dentry is in its parent directory's +/* Return true iff the specified dentry is in its parent directory's * case-insensitive index. */ static bool dentry_in_ci_index(const struct wim_dentry *dentry) @@ -1064,10 +1115,13 @@ dentry_in_ci_index(const struct wim_dentry *dentry) } /* - * Links a dentry into the directory tree. + * Link a dentry into the tree. + * + * @parent: + * The dentry that will be the parent of @child. It must name a directory. * - * @parent: The dentry that will be the parent of @child. - * @child: The dentry to link. + * @child: + * The dentry to link. It must be currently unlinked. * * Returns NULL if successful. If @parent already contains a dentry with the * same case-sensitive name as @child, returns a pointer to this duplicate @@ -1096,20 +1150,23 @@ dentry_add_child(struct wim_dentry *parent, struct wim_dentry *child) } else { INIT_LIST_HEAD(&child->d_ci_conflict_list); } - child->parent = parent; + child->d_parent = parent; return NULL; } -/* Unlink a WIM dentry from the directory entry tree. */ +/* Unlink a dentry from the tree. */ void unlink_dentry(struct wim_dentry *dentry) { struct wim_inode *dir; - if (dentry_is_root(dentry)) + /* Do nothing if the dentry is root or it's already unlinked. Not + * actually necessary based on the current callers, but we do the check + * here to be safe. */ + if (unlikely(dentry->d_parent == dentry)) return; - dir = dentry->parent->d_inode; + dir = dentry->d_parent->d_inode; dir_unindex_child(dir, dentry); @@ -1119,7 +1176,7 @@ unlink_dentry(struct wim_dentry *dentry) if (!list_empty(&dentry->d_ci_conflict_list)) { /* Make a different case-insensitively-the-same dentry - * be the "representative" in the search index. */ + * be the "representative" in the search index. */ struct list_head *next; struct wim_dentry *other; struct wim_dentry *existing; @@ -1131,14 +1188,34 @@ unlink_dentry(struct wim_dentry *dentry) } } list_del(&dentry->d_ci_conflict_list); + + /* Not actually necessary, but to be safe don't retain the now-obsolete + * parent pointer. */ + dentry->d_parent = dentry; +} + +static int +read_extra_data(const u8 *p, const u8 *end, struct wim_inode *inode) +{ + while (((uintptr_t)p & 7) && p < end) + p++; + + if (unlikely(p < end)) { + inode->i_extra = memdup(p, end - p); + if (!inode->i_extra) + return WIMLIB_ERR_NOMEM; + inode->i_extra_size = end - p; + } + return 0; } -/* Reads a WIM directory entry, including all alternate data stream entries that - * follow it, from the WIM image's metadata resource. */ +/* Read a dentry, including all alternate data stream entries that follow it, + * from an uncompressed metadata resource buffer. */ static int read_dentry(const u8 * restrict buf, size_t buf_len, - u64 offset, struct wim_dentry **dentry_ret) + u64 *offset_p, struct wim_dentry **dentry_ret) { + u64 offset = *offset_p; u64 length; const u8 *p; const struct wim_dentry_on_disk *disk_dentry; @@ -1196,11 +1273,10 @@ read_dentry(const u8 * restrict buf, size_t buf_len, } /* Allocate new dentry structure, along with a preliminary inode. */ - ret = new_dentry_with_timeless_inode(T(""), &dentry); + ret = new_dentry_with_timeless_inode(NULL, &dentry); if (ret) return ret; - dentry->length = length; inode = dentry->d_inode; /* Read more fields: some into the dentry, and some into the inode. */ @@ -1247,15 +1323,15 @@ read_dentry(const u8 * restrict buf, size_t buf_len, * the length of the dentry is large enough to actually hold them. * * The calculated length here is unaligned to allow for the possibility - * that the dentry->length names an unaligned length, although this - * would be unexpected. */ - calculated_size = dentry_correct_length_unaligned(file_name_nbytes, - short_name_nbytes); + * that the dentry's length is unaligned, although this would be + * unexpected. */ + calculated_size = dentry_min_len_with_names(file_name_nbytes, + short_name_nbytes); - if (unlikely(dentry->length < calculated_size)) { + if (unlikely(length < calculated_size)) { ERROR("Unexpected end of directory entry! (Expected " "at least %"PRIu64" bytes, got %"PRIu64" bytes.)", - calculated_size, dentry->length); + calculated_size, length); ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE; goto err_free_dentry; } @@ -1266,31 +1342,37 @@ read_dentry(const u8 * restrict buf, size_t buf_len, /* Read the filename if present. Note: if the filename is empty, there * is no null terminator following it. */ if (file_name_nbytes) { - dentry->file_name = utf16le_dupz((const utf16lechar *)p, - file_name_nbytes); + dentry->file_name = utf16le_dupz(p, file_name_nbytes); if (dentry->file_name == NULL) { ret = WIMLIB_ERR_NOMEM; goto err_free_dentry; } dentry->file_name_nbytes = file_name_nbytes; - p += file_name_nbytes + 2; + p += (u32)file_name_nbytes + 2; } /* Read the short filename if present. Note: if there is no short * filename, there is no null terminator following it. */ if (short_name_nbytes) { - dentry->short_name = utf16le_dupz((const utf16lechar *)p, - short_name_nbytes); + dentry->short_name = utf16le_dupz(p, short_name_nbytes); if (dentry->short_name == NULL) { ret = WIMLIB_ERR_NOMEM; goto err_free_dentry; } dentry->short_name_nbytes = short_name_nbytes; - p += short_name_nbytes + 2; + p += (u32)short_name_nbytes + 2; } + /* Read extra data at end of dentry (but before alternate data stream + * entries). This may contain tagged items. */ + ret = read_extra_data(p, &buf[offset + length], inode); + if (ret) + goto err_free_dentry; + /* Align the dentry length. */ - dentry->length = (dentry->length + 7) & ~7; + length = (length + 7) & ~7; + + offset += length; /* Read the alternate data streams, if present. inode->i_num_ads tells * us how many they are, and they will directly follow the dentry in the @@ -1300,16 +1382,22 @@ read_dentry(const u8 * restrict buf, size_t buf_len, * aligned boundary, and the alternate data stream entries seem to NOT * be included in the dentry->length field for some reason. */ if (unlikely(inode->i_num_ads != 0)) { - ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE; - if (offset + dentry->length > buf_len || - (ret = read_ads_entries(&buf[offset + dentry->length], - inode, - buf_len - offset - dentry->length))) - { + size_t orig_bytes_remaining; + size_t bytes_remaining; + + if (offset > buf_len) { + ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE; goto err_free_dentry; } + bytes_remaining = buf_len - offset; + orig_bytes_remaining = bytes_remaining; + ret = read_ads_entries(&buf[offset], inode, &bytes_remaining); + if (ret) + goto err_free_dentry; + offset += (orig_bytes_remaining - bytes_remaining); } + *offset_p = offset; /* Sets offset of next dentry in directory */ *dentry_ret = dentry; return 0; @@ -1318,6 +1406,7 @@ err_free_dentry: return ret; } +/* Is the dentry named "." or ".." ? */ static bool dentry_is_dot_or_dotdot(const struct wim_dentry *dentry) { @@ -1342,8 +1431,8 @@ read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, /* Check for cyclic directory structure, which would cause infinite * recursion if not handled. */ - for (struct wim_dentry *d = dir->parent; - !dentry_is_root(d); d = d->parent) + for (struct wim_dentry *d = dir->d_parent; + !dentry_is_root(d); d = d->d_parent) { if (unlikely(d->subdir_offset == cur_offset)) { ERROR("Cyclic directory structure detected: children " @@ -1359,7 +1448,7 @@ read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, int ret; /* Read next child of @dir. */ - ret = read_dentry(buf, buf_len, cur_offset, &child); + ret = read_dentry(buf, buf_len, &cur_offset, &child); if (ret) return ret; @@ -1367,13 +1456,6 @@ read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, if (child == NULL) return 0; - /* Advance to the offset of the next child. Note: We need to - * advance by the TOTAL length of the dentry, not by the length - * child->length, which although it does take into account the - * padding, it DOES NOT take into account alternate stream - * entries. */ - cur_offset += dentry_in_total_length(child); - /* All dentries except the root should be named. */ if (unlikely(!dentry_has_long_name(child))) { WARNING("Ignoring unnamed dentry in " @@ -1423,7 +1505,7 @@ read_dentry_tree_recursive(const u8 * restrict buf, size_t buf_len, } /* - * Read a tree of dentries (directory entries) from a WIM metadata resource. + * Read a tree of dentries from a WIM metadata resource. * * @buf: * Buffer containing an uncompressed WIM metadata resource. @@ -1453,7 +1535,7 @@ read_dentry_tree(const u8 *buf, size_t buf_len, DEBUG("Reading dentry tree (root_offset=%"PRIu64")", root_offset); - ret = read_dentry(buf, buf_len, root_offset, &root); + ret = read_dentry(buf, buf_len, &root_offset, &root); if (ret) return ret; @@ -1463,12 +1545,7 @@ read_dentry_tree(const u8 *buf, size_t buf_len, { WARNING("The root directory has a nonempty name; " "removing it."); - FREE(root->file_name); - FREE(root->short_name); - root->file_name = NULL; - root->short_name = NULL; - root->file_name_nbytes = 0; - root->short_name_nbytes = 0; + dentry_set_name(root, NULL); } if (unlikely(!dentry_is_directory(root))) { @@ -1495,11 +1572,17 @@ err_free_dentry_tree: } /* - * Writes a WIM alternate data stream (ADS) entry to an output buffer. + * Write a WIM alternate data stream (ADS) entry to an output buffer. * - * @ads_entry: The ADS entry structure. - * @hash: The hash field to use (instead of the one in the ADS entry). - * @p: The memory location to write the data to. + * @ads_entry: + * The ADS entry to write. + * + * @hash: + * The hash field to use (instead of the one stored directly in the ADS + * entry, which isn't valid if the inode has been "resolved"). + * + * @p: + * The memory location to which to write the data. * * Returns a pointer to the byte after the last byte written. */ @@ -1517,7 +1600,7 @@ write_ads_entry(const struct wim_ads_entry *ads_entry, p += sizeof(struct wim_ads_entry_on_disk); if (ads_entry->stream_name_nbytes) { p = mempcpy(p, ads_entry->stream_name, - ads_entry->stream_name_nbytes + 2); + (u32)ads_entry->stream_name_nbytes + 2); } /* Align to 8-byte boundary */ while ((uintptr_t)p & 7) @@ -1527,13 +1610,18 @@ write_ads_entry(const struct wim_ads_entry *ads_entry, } /* - * Writes a WIM dentry to an output buffer. + * Write a WIM dentry to an output buffer. + * + * This includes any alternate data stream entries that may follow the dentry + * itself. + * + * @dentry: + * The dentry to write. * - * @dentry: The dentry structure. - * @p: The memory location to write the data to. + * @p: + * The memory location to which to write the data. * - * Returns the pointer to the byte after the last byte we wrote as part of the - * dentry, including any alternate data stream entries. + * Returns a pointer to the byte following the last written. */ static u8 * write_dentry(const struct wim_dentry * restrict dentry, u8 * restrict p) @@ -1555,8 +1643,10 @@ write_dentry(const struct wim_dentry * restrict dentry, u8 * restrict p) disk_dentry->attributes = cpu_to_le32(inode->i_attributes); disk_dentry->security_id = cpu_to_le32(inode->i_security_id); disk_dentry->subdir_offset = cpu_to_le64(dentry->subdir_offset); + disk_dentry->unused_1 = cpu_to_le64(0); disk_dentry->unused_2 = cpu_to_le64(0); + disk_dentry->creation_time = cpu_to_le64(inode->i_creation_time); disk_dentry->last_access_time = cpu_to_le64(inode->i_last_access_time); disk_dentry->last_write_time = cpu_to_le64(inode->i_last_write_time); @@ -1586,15 +1676,22 @@ write_dentry(const struct wim_dentry * restrict dentry, u8 * restrict p) wimlib_assert(dentry_is_root(dentry) != dentry_has_long_name(dentry)); if (dentry_has_long_name(dentry)) - p = mempcpy(p, dentry->file_name, dentry->file_name_nbytes + 2); + p = mempcpy(p, dentry->file_name, (u32)dentry->file_name_nbytes + 2); if (dentry_has_short_name(dentry)) - p = mempcpy(p, dentry->short_name, dentry->short_name_nbytes + 2); + p = mempcpy(p, dentry->short_name, (u32)dentry->short_name_nbytes + 2); /* Align to 8-byte boundary */ while ((uintptr_t)p & 7) *p++ = 0; + if (inode->i_extra_size) { + /* Extra tagged items --- not usually present. */ + p = mempcpy(p, inode->i_extra, inode->i_extra_size); + while ((uintptr_t)p & 7) + *p++ = 0; + } + disk_dentry->length = cpu_to_le64(p - orig_p); if (use_dummy_stream) { @@ -1631,18 +1728,26 @@ write_dir_dentries(struct wim_dentry *dir, void *_pp) return 0; } -/* Writes a directory tree to the metadata resource. +/* + * Write a directory tree to the metadata resource. + * + * @root: + * The root of a dentry tree on which calculate_subdir_offsets() has been + * called. This cannot be NULL; if the dentry tree is empty, the caller is + * expected to first generate a dummy root directory. * - * @root: Root of the dentry tree. - * @p: Pointer to a buffer with enough space for the dentry tree. + * @p: + * Pointer to a buffer with enough space for the dentry tree. This size + * must have been obtained by calculate_subdir_offsets(). * - * Returns pointer to the byte after the last byte we wrote. + * Returns a pointer to the byte following the last written. */ u8 * write_dentry_tree(struct wim_dentry *root, u8 *p) { DEBUG("Writing dentry tree."); - wimlib_assert(dentry_is_root(root)); + + wimlib_assert(root != NULL); /* write root dentry and end-of-directory entry following it */ p = write_dentry(root, p);