From: Eric Biggers Date: Mon, 13 Jan 2014 04:50:01 +0000 (-0600) Subject: Refactor some of the dentry, inode, and lookup table code X-Git-Tag: v1.6.1~62 X-Git-Url: https://wimlib.net/git/?p=wimlib;a=commitdiff_plain;h=5d3d469e410dc5f4a28814ad231336fc174cba56 Refactor some of the dentry, inode, and lookup table code - Remove hardlink.c. Its code is now split between inode.c and inode_fixup.c. - Factor inode-related code out of dentry.h and dentry.c and place in inode.h and inode.c. - Move wimlib_reference_resource_files() and wimlib_reference_resources() implementations to reference.c. - Move wimlib_iterate_dir_tree() implementation to iterate_dir.c. - Move wimlib_reference_template_image() to template.c. - Move wim_pathname_to_stream() to dentry.c as it deals primarily with path walking. - Remove 'unhashed_streams' member from 'struct wim_lookup_table'. It now is stored separately in 'struct add_image_params'. - Rename some *resource functions to *stream. - Rename set_dentry_name() => dentry_set_name(). - Move 'struct ntfs_location' definition to ntfs_3g.h. - Move UNIX data definitions to new header unix_data.h. - Move case sensitivity definitions to new header case.h. --- diff --git a/Makefile.am b/Makefile.am index 157e5709..6e2ee776 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,9 +33,11 @@ libwim_la_SOURCES = \ src/export_image.c \ src/extract.c \ src/file_io.c \ - src/hardlink.c \ src/header.c \ + src/inode.c \ + src/inode_fixup.c \ src/integrity.c \ + src/iterate_dir.c \ src/join.c \ src/lookup_table.c \ src/lzms-common.c \ @@ -57,10 +59,12 @@ libwim_la_SOURCES = \ src/paths.c \ src/resource.c \ src/rbtree.c \ + src/reference.c \ src/security.c \ src/sha1.c \ src/split.c \ src/reparse.c \ + src/template.c \ src/timestamp.c \ src/update_image.c \ src/util.c \ @@ -75,6 +79,7 @@ libwim_la_SOURCES = \ include/wimlib/assert.h \ include/wimlib/callback.h \ include/wimlib/capture.h \ + include/wimlib/case.h \ include/wimlib/compiler.h \ include/wimlib/compressor_ops.h \ include/wimlib/compress_common.h \ @@ -88,6 +93,8 @@ libwim_la_SOURCES = \ include/wimlib/file_io.h \ include/wimlib/glob.h \ include/wimlib/header.h \ + include/wimlib/inode.h \ + include/wimlib/inode_table.h \ include/wimlib/integrity.h \ include/wimlib/list.h \ include/wimlib/lookup_table.h \ @@ -108,6 +115,7 @@ libwim_la_SOURCES = \ include/wimlib/sha1.h \ include/wimlib/timestamp.h \ include/wimlib/types.h \ + include/wimlib/unix_data.h \ include/wimlib/util.h \ include/wimlib/version.h \ include/wimlib/wildcard.h \ diff --git a/include/wimlib/capture.h b/include/wimlib/capture.h index 9f9e4617..4d3a60ab 100644 --- a/include/wimlib/capture.h +++ b/include/wimlib/capture.h @@ -2,6 +2,7 @@ #define _WIMLIB_CAPTURE_H #include "wimlib.h" +#include "wimlib/inode_table.h" #include "wimlib/list.h" #include "wimlib/security.h" #include "wimlib/util.h" @@ -10,35 +11,16 @@ struct wim_lookup_table; struct wim_dentry; struct wim_inode; -/* Hash table to find inodes, given an inode number (in the case of reading - * a WIM images), or both an inode number and a device number (in the case of - * capturing a WIM image). */ -struct wim_inode_table { - /* Fields for the hash table */ - struct hlist_head *array; - u64 num_entries; - u64 capacity; - - /* - * Linked list of "extra" inodes. These may be: - * - * - inodes with link count 1, which are all allowed to have 0 for their - * inode number, meaning we cannot insert them into the hash table. - * - * - Groups we create ourselves by splitting a nominal inode due to - * inconsistencies in the dentries. These inodes will share an inode - * number with some other inode until assign_inode_numbers() is - * called. - */ - struct list_head extra_inodes; -}; - /* Common parameters to implementations of building an in-memory dentry tree * from an on-disk directory structure. */ struct add_image_params { - /* Pointer to the lookup table of the WIM. */ + /* Pointer to the lookup table of the WIM. */ struct wim_lookup_table *lookup_table; + /* List of streams that have been added so far, but without their SHA1 + * message digests being calculated (as a shortcut). */ + struct list_head *unhashed_streams; + /* Hash table of inodes that have been captured for this tree so far. */ struct wim_inode_table inode_table; @@ -103,12 +85,6 @@ extern void inode_table_prepare_inode_list(struct wim_inode_table *table, struct list_head *head); -static inline void -destroy_inode_table(struct wim_inode_table *table) -{ - FREE(table->array); -} - #ifdef WITH_NTFS_3G /* ntfs-3g_capture.c */ diff --git a/include/wimlib/case.h b/include/wimlib/case.h new file mode 100644 index 00000000..c55fd0d4 --- /dev/null +++ b/include/wimlib/case.h @@ -0,0 +1,32 @@ +#ifndef _WIMLIB_CASE_H +#define _WIMLIB_CASE_H + +#include + +/* Note: the NTFS-3g headers define CASE_SENSITIVE, hence the WIMLIB prefix. */ +typedef enum { + /* Use either case-sensitive or case-insensitive search, depending on + * the variable @default_ignore_case. */ + WIMLIB_CASE_PLATFORM_DEFAULT = 0, + + /* Use case-sensitive search. */ + WIMLIB_CASE_SENSITIVE = 1, + + /* Use case-insensitive search. */ + WIMLIB_CASE_INSENSITIVE = 2, +} CASE_SENSITIVITY_TYPE; + +extern bool default_ignore_case; + +static inline 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; +} + +#endif /* _WIMLIB_CASE_H */ diff --git a/include/wimlib/dentry.h b/include/wimlib/dentry.h index f61060e4..fa35869b 100644 --- a/include/wimlib/dentry.h +++ b/include/wimlib/dentry.h @@ -1,97 +1,22 @@ #ifndef _WIMLIB_DENTRY_H #define _WIMLIB_DENTRY_H +#include "wimlib/case.h" #include "wimlib/compiler.h" +#include "wimlib/inode.h" #include "wimlib/list.h" #include "wimlib/rbtree.h" -#include "wimlib/sha1.h" #include "wimlib/types.h" -#include -#include /* uid_t, gid_t */ - +struct wim_inode; struct wim_lookup_table; struct wim_lookup_table_entry; -struct wimfs_fd; -struct wim_inode; struct wim_security_data; -/* Size of the struct wim_dentry up to and including the file_name_len. */ -#define WIM_DENTRY_DISK_SIZE 102 - -/* - * Reparse tags documented at - * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx - */ -#define WIM_IO_REPARSE_TAG_RESERVED_ZERO 0x00000000 -#define WIM_IO_REPARSE_TAG_RESERVED_ONE 0x00000001 -#define WIM_IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 -#define WIM_IO_REPARSE_TAG_HSM 0xC0000004 -#define WIM_IO_REPARSE_TAG_HSM2 0x80000006 -#define WIM_IO_REPARSE_TAG_DRIVER_EXTENDER 0x80000005 -#define WIM_IO_REPARSE_TAG_SIS 0x80000007 -#define WIM_IO_REPARSE_TAG_DFS 0x8000000A -#define WIM_IO_REPARSE_TAG_DFSR 0x80000012 -#define WIM_IO_REPARSE_TAG_FILTER_MANAGER 0x8000000B -#define WIM_IO_REPARSE_TAG_SYMLINK 0xA000000C - -#define FILE_ATTRIBUTE_READONLY 0x00000001 -#define FILE_ATTRIBUTE_HIDDEN 0x00000002 -#define FILE_ATTRIBUTE_SYSTEM 0x00000004 -#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 -#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 -#define FILE_ATTRIBUTE_DEVICE 0x00000040 -#define FILE_ATTRIBUTE_NORMAL 0x00000080 -#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 -#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 -#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 -#define FILE_ATTRIBUTE_COMPRESSED 0x00000800 -#define FILE_ATTRIBUTE_OFFLINE 0x00001000 -#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 -#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 -#define FILE_ATTRIBUTE_VIRTUAL 0x00010000 - - -/* Alternate data stream entry. - * - * We read this from disk in the read_ads_entries() function; see that function - * for more explanation. */ -struct wim_ads_entry { - union { - /* SHA-1 message digest of stream contents */ - u8 hash[SHA1_HASH_SIZE]; - - /* The corresponding lookup table entry (only for resolved - * streams) */ - struct wim_lookup_table_entry *lte; - }; - - /* Length of UTF16-encoded stream name, in bytes, not including the - * terminating null character; or 0 if the stream is unnamed. */ - u16 stream_name_nbytes; - - /* Number to identify an alternate data stream even after it's possibly - * been moved or renamed. */ - u32 stream_id; - - /* Stream name (UTF-16LE), null-terminated, or NULL if the stream is - * unnamed. */ - utf16lechar *stream_name; - - /* Reserved field. We read it into memory so we can write it out - * unchanged. */ - u64 reserved; -}; - - -static inline bool -ads_entries_have_same_name(const struct wim_ads_entry *entry_1, - const struct wim_ads_entry *entry_2) -{ - return entry_1->stream_name_nbytes == entry_2->stream_name_nbytes && - memcmp(entry_1->stream_name, entry_2->stream_name, - entry_1->stream_name_nbytes) == 0; -} +/* Base size of a WIM dentry in the on-disk format, up to and including the file + * name length. This does not include the variable-length file name, short + * name, alternate data stream entries, and padding to 8-byte boundaries. */ +#define WIM_DENTRY_DISK_SIZE 102 /* * In-memory structure for a WIM directory entry (dentry). There is a directory @@ -233,183 +158,6 @@ struct wim_dentry { #define rbnode_dentry(node) container_of(node, struct wim_dentry, rb_node) -/* - * WIM inode. - * - * As mentioned in the comment above `struct wim_dentry', in the WIM file that - * is no on-disk analogue of a real inode, as most of these fields are - * duplicated in the dentries. Instead, a `struct wim_inode' is something we - * create ourselves to simplify the handling of hard links. - */ -struct wim_inode { - /* If i_resolved == 0: - * SHA1 message digest of the contents of the unnamed-data stream - * of this inode. - * - * If i_resolved == 1: - * Pointer to the lookup table entry for the unnamed data stream - * of this inode, or NULL. - * - * i_hash corresponds to the 'unnamed_stream_hash' field of the `struct - * wim_dentry_on_disk' and the additional caveats documented about that - * field apply here (for example, the quirks regarding all-zero hashes). - */ - union { - u8 i_hash[SHA1_HASH_SIZE]; - struct wim_lookup_table_entry *i_lte; - }; - - /* Corresponds to the 'attributes' field of `struct wim_dentry_on_disk'; - * bitwise OR of the FILE_ATTRIBUTE_* flags that give the attributes of - * this inode. */ - u32 i_attributes; - - /* Root of a red-black tree storing the child dentries of this inode, if - * any. Keyed by wim_dentry->file_name, case sensitively. */ - struct rb_root i_children; - - /* Root of a red-black tree storing the children of this inode, if any. - * Keyed by wim_dentry->file_name, case insensitively. */ - struct rb_root i_children_case_insensitive; - - /* List of dentries that are aliases for this inode. There will be - * i_nlink dentries in this list. */ - struct list_head i_dentry; - - /* Field to place this inode into a list. */ - union { - /* Hash list node- used in hardlink.c when the inodes are placed - * into a hash table keyed by inode number and optionally device - * number, in order to detect dentries that are aliases for the - * same inode. */ - struct hlist_node i_hlist; - - /* Normal list node- used to connect all the inodes of a WIM image - * into a single linked list referenced from the - * `struct wim_image_metadata' for that image. */ - struct list_head i_list; - }; - - /* Number of dentries that are aliases for this inode. */ - u32 i_nlink; - - /* Number of alternate data streams (ADS) associated with this inode */ - u16 i_num_ads; - - /* Flag that indicates whether this inode's streams have been - * "resolved". By default, the inode starts as "unresolved", meaning - * that the i_hash field, along with the hash field of any associated - * wim_ads_entry's, are valid and should be used as keys in the WIM - * lookup table to find the associated `struct wim_lookup_table_entry'. - * But if the inode has been resolved, then each of these fields is - * replaced with a pointer directly to the appropriate `struct - * wim_lookup_table_entry', or NULL if the stream is empty. */ - u8 i_resolved : 1; - - /* Flag used to mark this inode as visited; this is used when visiting - * all the inodes in a dentry tree exactly once. It will be 0 by - * default and must be cleared following the tree traversal, even in - * error paths. */ - u8 i_visited : 1; - - /* Set if the DOS name of an inode has already been extracted. */ - u8 i_dos_name_extracted : 1; - - /* 1 iff all ADS entries of this inode are named or if this inode - * has no ADS entries */ - u8 i_canonical_streams : 1; - - /* Pointer to a malloc()ed array of i_num_ads alternate data stream - * entries for this inode. */ - struct wim_ads_entry *i_ads_entries; - - /* Creation time, last access time, and last write time for this inode, in - * 100-nanosecond intervals since 12:00 a.m UTC January 1, 1601. They - * should correspond to the times gotten by calling GetFileTime() on - * Windows. */ - u64 i_creation_time; - u64 i_last_access_time; - u64 i_last_write_time; - - /* Corresponds to 'security_id' in `struct wim_dentry_on_disk': The - * index of this inode's security descriptor in the WIM image's table of - * security descriptors, or -1. Note: in verify_inode(), called - * whenever a WIM image is loaded, out-of-bounds indices are set to -1, - * so the extraction code does not need to do bounds checks. */ - int32_t i_security_id; - - /* Identity of a reparse point. See - * http://msdn.microsoft.com/en-us/library/windows/desktop/aa365503(v=vs.85).aspx - * for what a reparse point is. */ - u32 i_reparse_tag; - - /* Unused/unknown fields that we just read into memory so we can - * re-write them unchanged. */ - u32 i_rp_unknown_1; - u16 i_rp_unknown_2; - - /* Corresponds to not_rpfixed in `struct wim_dentry_on_disk': Set to 0 - * if reparse point fixups have been done. Otherwise set to 1. Note: - * this actually may reflect the SYMBOLIC_LINK_RELATIVE flag. - */ - u16 i_not_rpfixed; - - /* Inode number; corresponds to hard_link_group_id in the `struct - * wim_dentry_on_disk'. */ - u64 i_ino; - - union { - /* Device number, used only during image capture, so we can - * identify hard linked files by the combination of inode number - * and device number (rather than just inode number, which could - * be ambigious if the captured tree spans a mountpoint). Set - * to 0 otherwise. */ - u64 i_devno; - - struct { - - /* Used only during image extraction: pointer to the first path - * (malloc()ed buffer) at which this inode has been extracted. - * Freed and set to NULL after the extraction is done (either - * success or failure). */ - tchar *i_extracted_file; - - /** Used only during image extraction: "cookie" that - * identifies this extracted file (inode), for example - * an inode number. Only used if supported by the - * extraction mode. */ - u64 extract_cookie; - }; - -#ifdef WITH_FUSE - /* Used only during image mount: Table of file descriptors that - * have been opened to this inode. The table is automatically - * freed when the last file descriptor is closed. */ - struct wimfs_fd **i_fds; -#endif - }; - -#ifdef WITH_FUSE - u16 i_num_opened_fds; - u16 i_num_allocated_fds; -#endif - - /* Next alternate data stream ID to be assigned */ - u32 i_next_stream_id; -}; - -#define inode_for_each_dentry(dentry, inode) \ - list_for_each_entry((dentry), &(inode)->i_dentry, d_alias) - -#define inode_add_dentry(dentry, inode) \ - list_add_tail(&(dentry)->d_alias, &(inode)->i_dentry) - -#define inode_first_dentry(inode) \ - container_of(inode->i_dentry.next, struct wim_dentry, d_alias) - -#define inode_first_full_path(inode) \ - dentry_full_path(inode_first_dentry(inode)) - static inline bool dentry_is_first_in_inode(const struct wim_dentry *dentry) { @@ -443,23 +191,7 @@ extern void calculate_subdir_offsets(struct wim_dentry *dentry, u64 *subdir_offset_p); extern int -set_dentry_name(struct wim_dentry *dentry, const tchar *new_name); - - -/* Note: the NTFS-3g headers define CASE_SENSITIVE, hence the WIMLIB prefix. */ -typedef enum { - /* Use either case-sensitive or case-insensitive search, depending on - * the variable @default_ignore_case. */ - WIMLIB_CASE_PLATFORM_DEFAULT = 0, - - /* Use case-sensitive search. */ - WIMLIB_CASE_SENSITIVE = 1, - - /* Use case-insensitive search. */ - WIMLIB_CASE_INSENSITIVE = 2, -} CASE_SENSITIVITY_TYPE; - -extern bool default_ignore_case; +dentry_set_name(struct wim_dentry *dentry, const tchar *new_name); extern struct wim_dentry * get_dentry(struct WIMStruct *wim, const tchar *path, @@ -480,6 +212,20 @@ extern struct wim_dentry * get_parent_dentry(struct WIMStruct *wim, const tchar *path, CASE_SENSITIVITY_TYPE case_type); +#ifdef WITH_FUSE + +#define LOOKUP_FLAG_ADS_OK 0x00000001 +#define LOOKUP_FLAG_DIRECTORY_OK 0x00000002 + +extern 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); +#endif + extern int print_dentry(struct wim_dentry *dentry, void *lookup_table); @@ -495,9 +241,6 @@ calculate_dentry_tree_full_paths(struct wim_dentry *root); extern tchar * dentry_full_path(struct wim_dentry *dentry); -extern struct wim_inode * -new_timeless_inode(void) _malloc_attribute; - extern int new_dentry(const tchar *name, struct wim_dentry **dentry_ret); @@ -513,9 +256,6 @@ dentry_tree_clear_inode_visited(struct wim_dentry *root); extern int new_filler_directory(const tchar *name, struct wim_dentry **dentry_ret); -extern void -free_inode(struct wim_inode *inode); - extern void free_dentry(struct wim_dentry *dentry); @@ -537,85 +277,6 @@ extern int rename_wim_path(WIMStruct *wim, const tchar *from, const tchar *to, CASE_SENSITIVITY_TYPE case_type); -extern struct wim_ads_entry * -inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name, - u16 *idx_ret); - -extern struct wim_ads_entry * -inode_add_ads_utf16le(struct wim_inode *inode, - const utf16lechar *stream_name, - size_t stream_name_nbytes); - -extern struct wim_ads_entry * -inode_add_ads(struct wim_inode *dentry, const tchar *stream_name); - -extern int -inode_add_ads_with_data(struct wim_inode *inode, const tchar *name, - const void *value, size_t size, - struct wim_lookup_table *lookup_table); - -bool -inode_has_named_stream(const struct wim_inode *inode); - -extern int -inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len, - struct wim_lookup_table *lookup_table); - -extern void -inode_remove_ads(struct wim_inode *inode, u16 idx, - struct wim_lookup_table *lookup_table); - - -#define WIMLIB_UNIX_DATA_TAG "$$__wimlib_UNIX_data" -#define WIMLIB_UNIX_DATA_TAG_NBYTES (sizeof(WIMLIB_UNIX_DATA_TAG) - 1) - -#define WIMLIB_UNIX_DATA_TAG_UTF16LE "$\0$\0_\0_\0w\0i\0m\0l\0i\0b\0_\0U\0N\0I\0X\0_\0d\0a\0t\0a\0" -#define WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES (sizeof(WIMLIB_UNIX_DATA_TAG_UTF16LE) - 1) - -static inline bool -ads_entry_is_unix_data(const struct wim_ads_entry *entry) -{ - return (entry->stream_name_nbytes == - WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES) && - !memcmp(entry->stream_name, WIMLIB_UNIX_DATA_TAG_UTF16LE, - WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES); -} - -static inline bool -ads_entry_is_named_stream(const struct wim_ads_entry *entry) -{ - return entry->stream_name_nbytes != 0 && !ads_entry_is_unix_data(entry); -} - -#ifndef __WIN32__ -/* Format for special alternate data stream entries to store UNIX data for files - * and directories (see: WIMLIB_ADD_FLAG_UNIX_DATA) */ -struct wimlib_unix_data { - u16 version; /* Must be 0 */ - u16 uid; - u16 gid; - u16 mode; -} _packed_attribute; - -#define NO_UNIX_DATA (-1) -#define BAD_UNIX_DATA (-2) -extern int -inode_get_unix_data(const struct wim_inode *inode, - struct wimlib_unix_data *unix_data, - u16 *stream_idx_ret); - -#define UNIX_DATA_UID 0x1 -#define UNIX_DATA_GID 0x2 -#define UNIX_DATA_MODE 0x4 -#define UNIX_DATA_ALL (UNIX_DATA_UID | UNIX_DATA_GID | UNIX_DATA_MODE) -#define UNIX_DATA_CREATE 0x8 -extern int -inode_set_unix_data(struct wim_inode *inode, uid_t uid, gid_t gid, mode_t mode, - struct wim_lookup_table *lookup_table, int which); -#endif /* !__WIN32__ */ - -extern bool -inode_has_unix_data(const struct wim_inode *inode); extern int read_dentry(const u8 * restrict metadata_resource, @@ -637,44 +298,12 @@ dentry_is_root(const struct wim_dentry *dentry) return dentry->parent == dentry; } -static inline bool -inode_is_directory(const struct wim_inode *inode) -{ - return (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | - FILE_ATTRIBUTE_REPARSE_POINT)) - == FILE_ATTRIBUTE_DIRECTORY; -} - -static inline bool -inode_is_encrypted_directory(const struct wim_inode *inode) -{ - return ((inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | - FILE_ATTRIBUTE_ENCRYPTED)) - == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ENCRYPTED)); -} - static inline bool dentry_is_directory(const struct wim_dentry *dentry) { return inode_is_directory(dentry->d_inode); } -/* For our purposes, we consider "real" symlinks and "junction points" to both - * be symlinks. */ -static inline bool -inode_is_symlink(const struct wim_inode *inode) -{ - return (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) - && (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT); -} - -static inline bool -inode_has_children(const struct wim_inode *inode) -{ - return inode->i_children.rb_node != NULL; -} - static inline bool dentry_has_children(const struct wim_dentry *dentry) { @@ -693,13 +322,6 @@ dentry_has_long_name(const struct wim_dentry *dentry) return dentry->file_name_nbytes != 0; } -extern void -inode_ref_streams(struct wim_inode *inode); - extern int dentry_tree_fix_inodes(struct wim_dentry *root, struct list_head *inode_list); - -extern int -verify_inode(struct wim_inode *inode, const struct wim_security_data *sd); - #endif /* _WIMLIB_DENTRY_H */ diff --git a/include/wimlib/encoding.h b/include/wimlib/encoding.h index 59914ae3..20cb8d83 100644 --- a/include/wimlib/encoding.h +++ b/include/wimlib/encoding.h @@ -47,4 +47,9 @@ cmp_utf16le_strings(const utf16lechar *s1, size_t n1, const utf16lechar *s2, size_t n2, bool ignore_case); +extern int +get_utf16le_string(const tchar *name, utf16lechar **name_utf16le_ret, + u16 *name_utf16le_nbytes_ret); + + #endif /* _WIMLIB_ENCODING_H */ diff --git a/include/wimlib/inode.h b/include/wimlib/inode.h new file mode 100644 index 00000000..468bdbac --- /dev/null +++ b/include/wimlib/inode.h @@ -0,0 +1,511 @@ +#ifndef _WIMLIB_INODE_H +#define _WIMLIB_INODE_H + +#include "wimlib/assert.h" +#include "wimlib/list.h" +#include "wimlib/lookup_table.h" +#include "wimlib/rbtree.h" +#include "wimlib/sha1.h" +#include "wimlib/unix_data.h" + +#include + +struct wim_ads_entry; +struct wim_dentry; +struct wim_security_data; +struct wim_lookup_table; +struct wimfs_fd; + +/* + * WIM inode. + * + * As mentioned in the comment above `struct wim_dentry', in the WIM file that + * is no on-disk analogue of a real inode, as most of these fields are + * duplicated in the dentries. Instead, a `struct wim_inode' is something we + * create ourselves to simplify the handling of hard links. + */ +struct wim_inode { + /* If i_resolved == 0: + * SHA1 message digest of the contents of the unnamed-data stream + * of this inode. + * + * If i_resolved == 1: + * Pointer to the lookup table entry for the unnamed data stream + * of this inode, or NULL. + * + * i_hash corresponds to the 'unnamed_stream_hash' field of the `struct + * wim_dentry_on_disk' and the additional caveats documented about that + * field apply here (for example, the quirks regarding all-zero hashes). + */ + union { + u8 i_hash[SHA1_HASH_SIZE]; + struct wim_lookup_table_entry *i_lte; + }; + + /* Corresponds to the 'attributes' field of `struct wim_dentry_on_disk'; + * bitwise OR of the FILE_ATTRIBUTE_* flags that give the attributes of + * this inode. */ + u32 i_attributes; + + /* Root of a red-black tree storing the child dentries of this inode, if + * any. Keyed by wim_dentry->file_name, case sensitively. */ + struct rb_root i_children; + + /* Root of a red-black tree storing the children of this inode, if any. + * Keyed by wim_dentry->file_name, case insensitively. */ + struct rb_root i_children_case_insensitive; + + /* List of dentries that are aliases for this inode. There will be + * i_nlink dentries in this list. */ + struct list_head i_dentry; + + /* Field to place this inode into a list. */ + union { + /* Hash list node- used in hardlink.c when the inodes are placed + * into a hash table keyed by inode number and optionally device + * number, in order to detect dentries that are aliases for the + * same inode. */ + struct hlist_node i_hlist; + + /* Normal list node- used to connect all the inodes of a WIM image + * into a single linked list referenced from the + * `struct wim_image_metadata' for that image. */ + struct list_head i_list; + }; + + /* Number of dentries that are aliases for this inode. */ + u32 i_nlink; + + /* Number of alternate data streams (ADS) associated with this inode */ + u16 i_num_ads; + + /* Flag that indicates whether this inode's streams have been + * "resolved". By default, the inode starts as "unresolved", meaning + * that the i_hash field, along with the hash field of any associated + * wim_ads_entry's, are valid and should be used as keys in the WIM + * lookup table to find the associated `struct wim_lookup_table_entry'. + * But if the inode has been resolved, then each of these fields is + * replaced with a pointer directly to the appropriate `struct + * wim_lookup_table_entry', or NULL if the stream is empty. */ + u8 i_resolved : 1; + + /* Flag used to mark this inode as visited; this is used when visiting + * all the inodes in a dentry tree exactly once. It will be 0 by + * default and must be cleared following the tree traversal, even in + * error paths. */ + u8 i_visited : 1; + + /* Set if the DOS name of an inode has already been extracted. */ + u8 i_dos_name_extracted : 1; + + /* 1 iff all ADS entries of this inode are named or if this inode + * has no ADS entries */ + u8 i_canonical_streams : 1; + + /* Pointer to a malloc()ed array of i_num_ads alternate data stream + * entries for this inode. */ + struct wim_ads_entry *i_ads_entries; + + /* Creation time, last access time, and last write time for this inode, in + * 100-nanosecond intervals since 12:00 a.m UTC January 1, 1601. They + * should correspond to the times gotten by calling GetFileTime() on + * Windows. */ + u64 i_creation_time; + u64 i_last_access_time; + u64 i_last_write_time; + + /* Corresponds to 'security_id' in `struct wim_dentry_on_disk': The + * index of this inode's security descriptor in the WIM image's table of + * security descriptors, or -1. Note: in verify_inode(), called + * whenever a WIM image is loaded, out-of-bounds indices are set to -1, + * so the extraction code does not need to do bounds checks. */ + int32_t i_security_id; + + /* Identity of a reparse point. See + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa365503(v=vs.85).aspx + * for what a reparse point is. */ + u32 i_reparse_tag; + + /* Unused/unknown fields that we just read into memory so we can + * re-write them unchanged. */ + u32 i_rp_unknown_1; + u16 i_rp_unknown_2; + + /* Corresponds to not_rpfixed in `struct wim_dentry_on_disk': Set to 0 + * if reparse point fixups have been done. Otherwise set to 1. Note: + * this actually may reflect the SYMBOLIC_LINK_RELATIVE flag. + */ + u16 i_not_rpfixed; + + /* Inode number; corresponds to hard_link_group_id in the `struct + * wim_dentry_on_disk'. */ + u64 i_ino; + + union { + /* Device number, used only during image capture, so we can + * identify hard linked files by the combination of inode number + * and device number (rather than just inode number, which could + * be ambigious if the captured tree spans a mountpoint). Set + * to 0 otherwise. */ + u64 i_devno; + + struct { + + /* Used only during image extraction: pointer to the first path + * (malloc()ed buffer) at which this inode has been extracted. + * Freed and set to NULL after the extraction is done (either + * success or failure). */ + tchar *i_extracted_file; + + /** Used only during image extraction: "cookie" that + * identifies this extracted file (inode), for example + * an inode number. Only used if supported by the + * extraction mode. */ + u64 extract_cookie; + }; + +#ifdef WITH_FUSE + /* Used only during image mount: Table of file descriptors that + * have been opened to this inode. The table is automatically + * freed when the last file descriptor is closed. */ + struct wimfs_fd **i_fds; +#endif + }; + +#ifdef WITH_FUSE + u16 i_num_opened_fds; + u16 i_num_allocated_fds; +#endif + + /* Next alternate data stream ID to be assigned */ + u32 i_next_stream_id; +}; + +/* Alternate data stream entry. + * + * We read this from disk in the read_ads_entries() function; see that function + * for more explanation. */ +struct wim_ads_entry { + union { + /* SHA-1 message digest of stream contents */ + u8 hash[SHA1_HASH_SIZE]; + + /* The corresponding lookup table entry (only for resolved + * streams) */ + struct wim_lookup_table_entry *lte; + }; + + /* Length of UTF16-encoded stream name, in bytes, not including the + * terminating null character; or 0 if the stream is unnamed. */ + u16 stream_name_nbytes; + + /* Number to identify an alternate data stream even after it's possibly + * been moved or renamed. */ + u32 stream_id; + + /* Stream name (UTF-16LE), null-terminated, or NULL if the stream is + * unnamed. */ + utf16lechar *stream_name; + + /* Reserved field. We read it into memory so we can write it out + * unchanged. */ + u64 reserved; +}; + +/* WIM alternate data stream entry (on-disk format) */ +struct wim_ads_entry_on_disk { + /* Length of the entry, in bytes. This apparently includes all + * fixed-length fields, plus the stream name and null terminator if + * present, and the padding up to an 8 byte boundary. wimlib is a + * little less strict when reading the entries, and only requires that + * the number of bytes from this field is at least as large as the size + * of the fixed length fields and stream name without null terminator. + * */ + le64 length; + + le64 reserved; + + /* SHA1 message digest of the uncompressed stream; or, alternatively, + * can be all zeroes if the stream has zero length. */ + u8 hash[SHA1_HASH_SIZE]; + + /* Length of the stream name, in bytes. 0 if the stream is unnamed. */ + le16 stream_name_nbytes; + + /* Stream name in UTF-16LE. It is @stream_name_nbytes bytes long, + * excluding the the null terminator. There is a null terminator + * character if @stream_name_nbytes != 0; i.e., if this stream is named. + * */ + utf16lechar stream_name[]; +} _packed_attribute; + +#define WIM_ADS_ENTRY_DISK_SIZE 38 + +/* + * Reparse tags documented at + * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx + */ +#define WIM_IO_REPARSE_TAG_RESERVED_ZERO 0x00000000 +#define WIM_IO_REPARSE_TAG_RESERVED_ONE 0x00000001 +#define WIM_IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 +#define WIM_IO_REPARSE_TAG_HSM 0xC0000004 +#define WIM_IO_REPARSE_TAG_HSM2 0x80000006 +#define WIM_IO_REPARSE_TAG_DRIVER_EXTENDER 0x80000005 +#define WIM_IO_REPARSE_TAG_SIS 0x80000007 +#define WIM_IO_REPARSE_TAG_DFS 0x8000000A +#define WIM_IO_REPARSE_TAG_DFSR 0x80000012 +#define WIM_IO_REPARSE_TAG_FILTER_MANAGER 0x8000000B +#define WIM_IO_REPARSE_TAG_SYMLINK 0xA000000C + +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#define FILE_ATTRIBUTE_HIDDEN 0x00000002 +#define FILE_ATTRIBUTE_SYSTEM 0x00000004 +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 +#define FILE_ATTRIBUTE_DEVICE 0x00000040 +#define FILE_ATTRIBUTE_NORMAL 0x00000080 +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 +#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 +#define FILE_ATTRIBUTE_COMPRESSED 0x00000800 +#define FILE_ATTRIBUTE_OFFLINE 0x00001000 +#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 +#define FILE_ATTRIBUTE_VIRTUAL 0x00010000 + +extern struct wim_inode * +new_inode(void) _malloc_attribute; + +extern struct wim_inode * +new_timeless_inode(void) _malloc_attribute; + +extern void +put_inode(struct wim_inode *inode); + +extern void +free_inode(struct wim_inode *inode); + +/* Iterate through each alias of an inode. */ +#define inode_for_each_dentry(dentry, inode) \ + list_for_each_entry((dentry), &(inode)->i_dentry, d_alias) + +/* Add a new alias for an inode. Does not increment i_nlink; that must be done + * separately. */ +#define inode_add_dentry(dentry, inode) \ + list_add_tail(&(dentry)->d_alias, &(inode)->i_dentry) + +/* Return an alias of an inode. */ +#define inode_first_dentry(inode) \ + container_of(inode->i_dentry.next, struct wim_dentry, d_alias) + +/* Return the full path of an alias of an inode, or NULL if it could not be + * determined. */ +#define inode_first_full_path(inode) \ + dentry_full_path(inode_first_dentry(inode)) + +extern struct wim_ads_entry * +inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name, + u16 *idx_ret); + +extern struct wim_ads_entry * +inode_add_ads_utf16le(struct wim_inode *inode, + const utf16lechar *stream_name, + size_t stream_name_nbytes); + +extern struct wim_ads_entry * +inode_add_ads(struct wim_inode *dentry, const tchar *stream_name); + +extern int +inode_add_ads_with_data(struct wim_inode *inode, const tchar *name, + const void *value, size_t size, + struct wim_lookup_table *lookup_table); + +extern bool +inode_has_named_stream(const struct wim_inode *inode); + +extern int +inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len, + struct wim_lookup_table *lookup_table); + +extern void +inode_remove_ads(struct wim_inode *inode, u16 idx, + struct wim_lookup_table *lookup_table); + +static inline bool +ads_entry_is_unix_data(const struct wim_ads_entry *entry) +{ + return (entry->stream_name_nbytes == + WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES) && + !memcmp(entry->stream_name, WIMLIB_UNIX_DATA_TAG_UTF16LE, + WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES); +} + +static inline bool +ads_entry_is_named_stream(const struct wim_ads_entry *entry) +{ + return entry->stream_name_nbytes != 0 && !ads_entry_is_unix_data(entry); +} + +/* Is the inode a directory? + * This doesn't count directories with reparse data. + * wimlib only allows inodes of this type to have children. + */ +static inline bool +inode_is_directory(const struct wim_inode *inode) +{ + return (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_REPARSE_POINT)) + == FILE_ATTRIBUTE_DIRECTORY; +} + +/* Is the inode a directory with the encrypted attribute set? + * This currently returns true for encrypted directories even if they have + * reparse data (not sure if such files can even exist). */ +static inline bool +inode_is_encrypted_directory(const struct wim_inode *inode) +{ + return ((inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_ENCRYPTED)) + == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ENCRYPTED)); +} + +/* Is the inode a symbolic link? + * This returns true iff the inode is a reparse point that is either a "real" + * symbolic link or a junction point. */ +static inline bool +inode_is_symlink(const struct wim_inode *inode) +{ + return (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT); +} + +/* Does the inode have children? + * Currently (based on read_dentry_tree()), this can only return true for inodes + * for which inode_is_directory() returns true. However, if a directory is + * empty, this returns false. */ +static inline bool +inode_has_children(const struct wim_inode *inode) +{ + return inode->i_children.rb_node != NULL; +} + +extern int +inode_resolve_streams(struct wim_inode *inode, struct wim_lookup_table *table, + bool force); + +extern int +stream_not_found_error(const struct wim_inode *inode, const u8 *hash); + +extern void +inode_unresolve_streams(struct wim_inode *inode); + +static inline struct wim_lookup_table_entry * +inode_stream_lte_resolved(const struct wim_inode *inode, unsigned stream_idx) +{ + wimlib_assert(inode->i_resolved); + wimlib_assert(stream_idx <= inode->i_num_ads); + if (stream_idx == 0) + return inode->i_lte; + else + return inode->i_ads_entries[stream_idx - 1].lte; +} + +static inline struct wim_lookup_table_entry * +inode_stream_lte_unresolved(const struct wim_inode *inode, unsigned stream_idx, + const struct wim_lookup_table *table) +{ + wimlib_assert(!inode->i_resolved); + wimlib_assert(stream_idx <= inode->i_num_ads); + if (table == NULL) + return NULL; + if (stream_idx == 0) + return lookup_stream(table, inode->i_hash); + else + return lookup_stream(table, inode->i_ads_entries[ stream_idx - 1].hash); +} + +extern struct wim_lookup_table_entry * +inode_stream_lte(const struct wim_inode *inode, unsigned stream_idx, + const struct wim_lookup_table *table); + +static inline const u8 * +inode_stream_hash_unresolved(const struct wim_inode *inode, unsigned stream_idx) +{ + wimlib_assert(!inode->i_resolved); + wimlib_assert(stream_idx <= inode->i_num_ads); + if (stream_idx == 0) + return inode->i_hash; + else + return inode->i_ads_entries[stream_idx - 1].hash; +} + + +static inline const u8 * +inode_stream_hash_resolved(const struct wim_inode *inode, unsigned stream_idx) +{ + struct wim_lookup_table_entry *lte; + lte = inode_stream_lte_resolved(inode, stream_idx); + if (lte) + return lte->hash; + else + return zero_hash; +} + +/* + * Returns the hash for stream @stream_idx of the inode, where stream_idx = 0 + * means the default un-named file stream, and stream_idx >= 1 corresponds to an + * alternate data stream. + * + * This works for both resolved and un-resolved dentries. + */ +static inline const u8 * +inode_stream_hash(const struct wim_inode *inode, unsigned stream_idx) +{ + if (inode->i_resolved) + return inode_stream_hash_resolved(inode, stream_idx); + else + return inode_stream_hash_unresolved(inode, stream_idx); +} + +static inline u16 +inode_stream_name_nbytes(const struct wim_inode *inode, unsigned stream_idx) +{ + wimlib_assert(stream_idx <= inode->i_num_ads); + if (stream_idx == 0) + return 0; + else + return inode->i_ads_entries[stream_idx - 1].stream_name_nbytes; +} + +extern struct wim_lookup_table_entry * +inode_unnamed_stream_resolved(const struct wim_inode *inode, u16 *stream_idx_ret); + +extern struct wim_lookup_table_entry * +inode_unnamed_lte_resolved(const struct wim_inode *inode); + +extern struct wim_lookup_table_entry * +inode_unnamed_lte_unresolved(const struct wim_inode *inode, + const struct wim_lookup_table *table); + +extern struct wim_lookup_table_entry * +inode_unnamed_lte(const struct wim_inode *inode, const struct wim_lookup_table *table); + +extern const u8 * +inode_unnamed_stream_hash(const struct wim_inode *inode); + +extern int +read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode, + size_t nbytes_remaining); + +extern int +verify_inode(struct wim_inode *inode, const struct wim_security_data *sd); + +extern void +inode_ref_streams(struct wim_inode *inode); + +/* inode_fixup.c */ +extern int +dentry_tree_fix_inodes(struct wim_dentry *root, struct list_head *inode_list); + +#endif /* _WIMLIB_INODE_H */ diff --git a/include/wimlib/inode_table.h b/include/wimlib/inode_table.h new file mode 100644 index 00000000..abfc28bb --- /dev/null +++ b/include/wimlib/inode_table.h @@ -0,0 +1,48 @@ +#ifndef _WIMLIB_INODE_TABLE_H +#define _WIMLIB_INODE_TABLE_H + +#include "wimlib/list.h" +#include "wimlib/types.h" + +struct wim_dentry; + +/* Hash table to find inodes, given an inode number (in the case of reading + * a WIM images), or both an inode number and a device number (in the case of + * capturing a WIM image). */ +struct wim_inode_table { + /* Fields for the hash table */ + struct hlist_head *array; + size_t num_entries; + size_t capacity; + + /* + * Linked list of "extra" inodes. These may be: + * + * - inodes with link count 1, which are all allowed to have 0 for their + * inode number, meaning we cannot insert them into the hash table. + * + * - Groups we create ourselves by splitting a nominal inode due to + * inconsistencies in the dentries. These inodes will share an inode + * number with some other inode until assign_inode_numbers() is + * called. + */ + struct list_head extra_inodes; +}; + + +extern int +init_inode_table(struct wim_inode_table *table, size_t capacity); + +extern int +inode_table_new_dentry(struct wim_inode_table *table, const tchar *name, + u64 ino, u64 devno, bool noshare, + struct wim_dentry **dentry_ret); + +extern void +inode_table_prepare_inode_list(struct wim_inode_table *table, + struct list_head *head); + +extern void +destroy_inode_table(struct wim_inode_table *table); + +#endif /* _WIMLIB_INODE_TABLE_H */ diff --git a/include/wimlib/lookup_table.h b/include/wimlib/lookup_table.h index c9637385..82138e35 100644 --- a/include/wimlib/lookup_table.h +++ b/include/wimlib/lookup_table.h @@ -1,48 +1,10 @@ #ifndef _WIMLIB_LOOKUP_TABLE_H #define _WIMLIB_LOOKUP_TABLE_H -#include "wimlib/assert.h" -#include "wimlib/dentry.h" #include "wimlib/list.h" +#include "wimlib/resource.h" #include "wimlib/sha1.h" #include "wimlib/types.h" -#include "wimlib/wim.h" - -#define LOOKUP_FLAG_ADS_OK 0x00000001 -#define LOOKUP_FLAG_DIRECTORY_OK 0x00000002 - - -/* The lookup table of a WIM file maps SHA1 message digests to streams of data. - * Here, the in-memory structure is implemented as a hash table. - * - * Given a SHA1 message digest, the mapped-to stream is specified by an offset - * in the WIM, an uncompressed and compressed size, and resource flags (see - * 'struct resource_entry'). But, we associate additional information, such as - * a reference count, with each stream, so the actual mapping is from SHA1 - * message digests to 'struct wim_lookup_table_entry's, each of which contains - * an embedded 'struct resource_entry'. - * - * Note: Everything will break horribly if there is a SHA1 collision. - */ -struct wim_lookup_table { - struct hlist_head *array; - u64 num_entries; - u64 capacity; - struct list_head *unhashed_streams; -}; - -#ifdef WITH_NTFS_3G - -struct _ntfs_volume; - -struct ntfs_location { - tchar *path; - utf16lechar *stream_name; - u16 stream_name_nchars; - struct _ntfs_volume *ntfs_vol; - bool is_reparse_point; -}; -#endif /* An enumerated type that identifies where the stream corresponding to this * lookup table entry is actually located. @@ -51,8 +13,7 @@ struct ntfs_location { * RESOURCE_IN_WIM since all the streams will initially be located in the WIM. * However, to handle situations such as image capture and image mount, we allow * the actual location of the stream to be somewhere else, such as an external - * file. - */ + * file. */ enum resource_location { /* The lookup table entry does not yet correspond to a stream; this is a * temporary state only. */ @@ -96,7 +57,6 @@ enum resource_location { * appropriate Windows API. */ RESOURCE_WIN32_ENCRYPTED, #endif - }; /* Specification for a stream, which may be the contents of a file (unnamed data @@ -180,8 +140,8 @@ struct wim_lookup_table_entry { u32 out_refcnt; #ifdef WITH_FUSE - /* Number of times this stream has been opened; used only during - * mounting. */ + /* Number of open file descriptors to this stream during a FUSE mount of + * the containing image. */ u16 num_opened_fds; #endif @@ -272,29 +232,16 @@ struct wim_lookup_table_entry { struct list_head unhashed_list; }; -static inline bool -lte_is_partial(const struct wim_lookup_table_entry * lte) -{ - return lte->resource_location == RESOURCE_IN_WIM && - lte->size != lte->rspec->uncompressed_size; -} - -static inline bool -lte_filename_valid(const struct wim_lookup_table_entry *lte) -{ - return lte->resource_location == RESOURCE_IN_FILE_ON_DISK - #ifdef __WIN32__ - || lte->resource_location == RESOURCE_WIN32_ENCRYPTED - #endif - #ifdef WITH_FUSE - || lte->resource_location == RESOURCE_IN_STAGING_FILE - #endif - ; -} +/* Functions to allocate and free lookup tables */ extern struct wim_lookup_table * new_lookup_table(size_t capacity) _malloc_attribute; +extern void +free_lookup_table(struct wim_lookup_table *table); + +/* Functions to read or write the lookup table from/to a WIM file */ + extern int read_wim_lookup_table(WIMStruct *wim); @@ -305,21 +252,7 @@ write_wim_lookup_table_from_stream_list(struct list_head *stream_list, struct wim_reshdr *out_reshdr, int write_resource_flags); -extern void -free_lookup_table(struct wim_lookup_table *table); - -extern void -lookup_table_insert(struct wim_lookup_table *table, struct wim_lookup_table_entry *lte); - -/* Unlinks a lookup table entry from the table; does not free it. */ -static inline void -lookup_table_unlink(struct wim_lookup_table *table, struct wim_lookup_table_entry *lte) -{ - wimlib_assert(!lte->unhashed); - hlist_del(<e->hash_list); - wimlib_assert(table->num_entries != 0); - table->num_entries--; -} +/* Functions to create, clone, print, and free lookup table entries */ extern struct wim_lookup_table_entry * new_lookup_table_entry(void) _malloc_attribute; @@ -331,50 +264,65 @@ clone_lookup_table_entry(const struct wim_lookup_table_entry *lte) extern void print_lookup_table_entry(const struct wim_lookup_table_entry *lte, FILE *out); +extern void +lte_decrement_refcnt(struct wim_lookup_table_entry *lte, + struct wim_lookup_table *table); +#ifdef WITH_FUSE +extern void +lte_decrement_num_opened_fds(struct wim_lookup_table_entry *lte); +#endif + extern void free_lookup_table_entry(struct wim_lookup_table_entry *lte); +/* Functions to insert and delete entries from a lookup table */ + extern void -lte_to_wimlib_resource_entry(const struct wim_lookup_table_entry *lte, - struct wimlib_resource_entry *wentry); +lookup_table_insert(struct wim_lookup_table *table, + struct wim_lookup_table_entry *lte); + +extern void +lookup_table_unlink(struct wim_lookup_table *table, + struct wim_lookup_table_entry *lte); + +/* Function to lookup a stream by SHA1 message digest */ +extern struct wim_lookup_table_entry * +lookup_stream(const struct wim_lookup_table *table, const u8 hash[]); + +/* Functions to iterate through the entries of a lookup table */ extern int for_lookup_table_entry(struct wim_lookup_table *table, int (*visitor)(struct wim_lookup_table_entry *, void *), void *arg); -extern int -sort_stream_list(struct list_head *stream_list, - size_t list_head_offset, - int (*compar)(const void *, const void*)); - -extern int -sort_stream_list_by_sequential_order(struct list_head *stream_list, - size_t list_head_offset); - extern int for_lookup_table_entry_pos_sorted(struct wim_lookup_table *table, int (*visitor)(struct wim_lookup_table_entry *, void *), void *arg); -extern struct wim_lookup_table_entry * -lookup_resource(const struct wim_lookup_table *table, const u8 hash[]); -extern 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); + +/* Function to get a resource entry in stable format */ + +struct wimlib_resource_entry; extern void -lte_decrement_refcnt(struct wim_lookup_table_entry *lte, - struct wim_lookup_table *table); -#ifdef WITH_FUSE -extern void -lte_decrement_num_opened_fds(struct wim_lookup_table_entry *lte); -#endif +lte_to_wimlib_resource_entry(const struct wim_lookup_table_entry *lte, + struct wimlib_resource_entry *wentry); + +/* Functions to sort a list of lookup table entries */ +extern int +sort_stream_list(struct list_head *stream_list, + size_t list_head_offset, + int (*compar)(const void *, const void*)); + +extern int +sort_stream_list_by_sequential_order(struct list_head *stream_list, + size_t list_head_offset); + +/* Utility functions */ extern int lte_zero_out_refcnt(struct wim_lookup_table_entry *lte, void *ignore); @@ -385,6 +333,26 @@ lte_zero_real_refcnt(struct wim_lookup_table_entry *lte, void *ignore); extern int lte_free_extracted_file(struct wim_lookup_table_entry *lte, void *ignore); +static inline bool +lte_is_partial(const struct wim_lookup_table_entry * lte) +{ + return lte->resource_location == RESOURCE_IN_WIM && + lte->size != lte->rspec->uncompressed_size; +} + +static inline bool +lte_filename_valid(const struct wim_lookup_table_entry *lte) +{ + return lte->resource_location == RESOURCE_IN_FILE_ON_DISK + #ifdef __WIN32__ + || lte->resource_location == RESOURCE_WIN32_ENCRYPTED + #endif + #ifdef WITH_FUSE + || lte->resource_location == RESOURCE_IN_STAGING_FILE + #endif + ; +} + static inline void lte_bind_wim_resource_spec(struct wim_lookup_table_entry *lte, struct wim_resource_spec *rspec) @@ -401,122 +369,21 @@ lte_unbind_wim_resource_spec(struct wim_lookup_table_entry *lte) lte->resource_location = RESOURCE_NONEXISTENT; } -extern int -inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table, - bool force); - -extern int -resource_not_found_error(const struct wim_inode *inode, const u8 *hash); - -extern void -inode_unresolve_ltes(struct wim_inode *inode); - -static inline struct wim_lookup_table_entry * -inode_stream_lte_resolved(const struct wim_inode *inode, unsigned stream_idx) -{ - wimlib_assert(inode->i_resolved); - wimlib_assert(stream_idx <= inode->i_num_ads); - if (stream_idx == 0) - return inode->i_lte; - else - return inode->i_ads_entries[stream_idx - 1].lte; -} - -static inline struct wim_lookup_table_entry * -inode_stream_lte_unresolved(const struct wim_inode *inode, unsigned stream_idx, - const struct wim_lookup_table *table) -{ - wimlib_assert(!inode->i_resolved); - wimlib_assert(stream_idx <= inode->i_num_ads); - if (!table) - return NULL; - if (stream_idx == 0) - return lookup_resource(table, inode->i_hash); - else - return lookup_resource(table, - inode->i_ads_entries[ - stream_idx - 1].hash); -} - -extern struct wim_lookup_table_entry * -inode_stream_lte(const struct wim_inode *inode, unsigned stream_idx, - const struct wim_lookup_table *table); - -static inline const u8 * -inode_stream_hash_unresolved(const struct wim_inode *inode, unsigned stream_idx) -{ - wimlib_assert(!inode->i_resolved); - wimlib_assert(stream_idx <= inode->i_num_ads); - if (stream_idx == 0) - return inode->i_hash; - else - return inode->i_ads_entries[stream_idx - 1].hash; -} - - -static inline const u8 * -inode_stream_hash_resolved(const struct wim_inode *inode, unsigned stream_idx) -{ - struct wim_lookup_table_entry *lte; - lte = inode_stream_lte_resolved(inode, stream_idx); - if (lte) - return lte->hash; - else - return zero_hash; -} - -/* - * Returns the hash for stream @stream_idx of the inode, where stream_idx = 0 - * means the default un-named file stream, and stream_idx >= 1 corresponds to an - * alternate data stream. - * - * This works for both resolved and un-resolved dentries. - */ -static inline const u8 * -inode_stream_hash(const struct wim_inode *inode, unsigned stream_idx) -{ - if (inode->i_resolved) - return inode_stream_hash_resolved(inode, stream_idx); - else - return inode_stream_hash_unresolved(inode, stream_idx); -} - -static inline u16 -inode_stream_name_nbytes(const struct wim_inode *inode, unsigned stream_idx) -{ - wimlib_assert(stream_idx <= inode->i_num_ads); - if (stream_idx == 0) - return 0; - else - return inode->i_ads_entries[stream_idx - 1].stream_name_nbytes; -} extern struct wim_lookup_table_entry * -inode_unnamed_stream_resolved(const struct wim_inode *inode, u16 *stream_idx_ret); - -extern struct wim_lookup_table_entry * -inode_unnamed_lte_resolved(const struct wim_inode *inode); - -extern struct wim_lookup_table_entry * -inode_unnamed_lte_unresolved(const struct wim_inode *inode, - const struct wim_lookup_table *table); - -extern struct wim_lookup_table_entry * -inode_unnamed_lte(const struct wim_inode *inode, const struct wim_lookup_table *table); - -extern const u8 * -inode_unnamed_stream_hash(const struct wim_inode *inode); +new_stream_from_data_buffer(const void *buffer, size_t size, + struct wim_lookup_table *lookup_table); static inline void -lookup_table_insert_unhashed(struct wim_lookup_table *table, - struct wim_lookup_table_entry *lte, - struct wim_inode *back_inode, - u32 back_stream_id) +add_unhashed_stream(struct wim_lookup_table_entry *lte, + struct wim_inode *back_inode, + u32 back_stream_id, + struct list_head *unhashed_streams) { lte->unhashed = 1; lte->back_inode = back_inode; lte->back_stream_id = back_stream_id; - list_add_tail(<e->unhashed_list, table->unhashed_streams); + list_add_tail(<e->unhashed_list, unhashed_streams); } extern int diff --git a/include/wimlib/metadata.h b/include/wimlib/metadata.h index 31b95cef..7f1c7c02 100644 --- a/include/wimlib/metadata.h +++ b/include/wimlib/metadata.h @@ -9,7 +9,7 @@ struct _ntfs_volume; #endif -/* Metadata for a WIM image */ +/* Metadata for a WIM image */ struct wim_image_metadata { /* Number of WIMStruct's that are sharing this image metadata (from @@ -102,10 +102,9 @@ extern int append_image_metadata(WIMStruct *wim, struct wim_image_metadata *imd); extern struct wim_image_metadata * -new_image_metadata(void); +new_image_metadata(void) _malloc_attribute; extern struct wim_image_metadata ** -new_image_metadata_array(unsigned num_images); - +new_image_metadata_array(unsigned num_images) _malloc_attribute; #endif /* _WIMLIB_METADATA_H */ diff --git a/include/wimlib/ntfs_3g.h b/include/wimlib/ntfs_3g.h index 00b9fbdd..499c1aa5 100644 --- a/include/wimlib/ntfs_3g.h +++ b/include/wimlib/ntfs_3g.h @@ -7,6 +7,17 @@ struct wim_lookup_table_entry; struct _ntfs_volume; +#ifdef WITH_NTFS_3G +struct _ntfs_volume; +struct ntfs_location { + tchar *path; + utf16lechar *stream_name; + u16 stream_name_nchars; + struct _ntfs_volume *ntfs_vol; + bool is_reparse_point; +}; +#endif + extern void libntfs3g_global_init(void); diff --git a/include/wimlib/unix_data.h b/include/wimlib/unix_data.h new file mode 100644 index 00000000..bf0ee46c --- /dev/null +++ b/include/wimlib/unix_data.h @@ -0,0 +1,45 @@ +#ifndef _WIMLIB_UNIX_DATA_H +#define _WIMLIB_UNIX_DATA_H + +#include "wimlib/types.h" +struct wim_inode; +struct wim_lookup_table; + +#define WIMLIB_UNIX_DATA_TAG "$$__wimlib_UNIX_data" +#define WIMLIB_UNIX_DATA_TAG_NBYTES (sizeof(WIMLIB_UNIX_DATA_TAG) - 1) + +#define WIMLIB_UNIX_DATA_TAG_UTF16LE "$\0$\0_\0_\0w\0i\0m\0l\0i\0b\0_\0U\0N\0I\0X\0_\0d\0a\0t\0a\0" +#define WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES (sizeof(WIMLIB_UNIX_DATA_TAG_UTF16LE) - 1) + +extern bool +inode_has_unix_data(const struct wim_inode *inode); + +#ifndef __WIN32__ +/* Format for special alternate data stream entries to store UNIX data for files + * and directories (see: WIMLIB_ADD_FLAG_UNIX_DATA) */ +struct wimlib_unix_data { + u16 version; /* Must be 0 */ + u16 uid; + u16 gid; + u16 mode; +} _packed_attribute; + +#define NO_UNIX_DATA (-1) +#define BAD_UNIX_DATA (-2) +extern int +inode_get_unix_data(const struct wim_inode *inode, + struct wimlib_unix_data *unix_data, + u16 *stream_idx_ret); + +#define UNIX_DATA_UID 0x1 +#define UNIX_DATA_GID 0x2 +#define UNIX_DATA_MODE 0x4 +#define UNIX_DATA_ALL (UNIX_DATA_UID | UNIX_DATA_GID | UNIX_DATA_MODE) +#define UNIX_DATA_CREATE 0x8 +extern int +inode_set_unix_data(struct wim_inode *inode, u16 uid, u16 gid, u16 mode, + struct wim_lookup_table *lookup_table, int which); + +#endif /* __WIN32__ */ + +#endif /* _WIMLIB_UNIX_DATA_H */ diff --git a/include/wimlib/util.h b/include/wimlib/util.h index 2f630d57..b2d7b5df 100644 --- a/include/wimlib/util.h +++ b/include/wimlib/util.h @@ -5,7 +5,6 @@ #include "wimlib/compiler.h" #include -#include #ifndef min #define min(a, b) ({ typeof(a) __a = (a); typeof(b) __b = (b); \ diff --git a/src/dentry.c b/src/dentry.c index af363c45..62b35cf8 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -31,6 +31,7 @@ #endif #include "wimlib.h" +#include "wimlib/case.h" #include "wimlib/dentry.h" #include "wimlib/encoding.h" #include "wimlib/endianness.h" @@ -45,35 +46,6 @@ #include -/* WIM alternate data stream entry (on-disk format) */ -struct wim_ads_entry_on_disk { - /* Length of the entry, in bytes. This apparently includes all - * fixed-length fields, plus the stream name and null terminator if - * present, and the padding up to an 8 byte boundary. wimlib is a - * little less strict when reading the entries, and only requires that - * the number of bytes from this field is at least as large as the size - * of the fixed length fields and stream name without null terminator. - * */ - le64 length; - - le64 reserved; - - /* SHA1 message digest of the uncompressed stream; or, alternatively, - * can be all zeroes if the stream has zero length. */ - u8 hash[SHA1_HASH_SIZE]; - - /* Length of the stream name, in bytes. 0 if the stream is unnamed. */ - le16 stream_name_nbytes; - - /* Stream name in UTF-16LE. It is @stream_name_nbytes bytes long, - * excluding the the null terminator. There is a null terminator - * character if @stream_name_nbytes != 0; i.e., if this stream is named. - * */ - utf16lechar stream_name[]; -} _packed_attribute; - -#define WIM_ADS_ENTRY_DISK_SIZE 38 - /* On-disk format of a WIM dentry (directory entry), located in the metadata * resource for a WIM image. */ struct wim_dentry_on_disk { @@ -214,8 +186,6 @@ struct wim_dentry_on_disk { /*utf16lechar short_name[];*/ } _packed_attribute; -#define WIM_DENTRY_DISK_SIZE 102 - /* 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. */ @@ -244,50 +214,15 @@ dentry_correct_length_aligned(const struct wim_dentry *dentry) return (len + 7) & ~7; } -/* Duplicates a string of system-dependent encoding into a UTF-16LE string and - * returns the string and its length, in bytes, in the pointer arguments. Frees - * any existing string at the return location before overwriting it. */ -static int -get_utf16le_name(const tchar *name, utf16lechar **name_utf16le_ret, - u16 *name_utf16le_nbytes_ret) -{ - utf16lechar *name_utf16le; - size_t name_utf16le_nbytes; - int ret; -#if TCHAR_IS_UTF16LE - name_utf16le_nbytes = tstrlen(name) * sizeof(utf16lechar); - name_utf16le = MALLOC(name_utf16le_nbytes + sizeof(utf16lechar)); - if (name_utf16le == NULL) - return WIMLIB_ERR_NOMEM; - memcpy(name_utf16le, name, name_utf16le_nbytes + sizeof(utf16lechar)); - ret = 0; -#else - - ret = tstr_to_utf16le(name, tstrlen(name), &name_utf16le, - &name_utf16le_nbytes); - if (ret == 0) { - if (name_utf16le_nbytes > 0xffff) { - FREE(name_utf16le); - ERROR("Multibyte string \"%"TS"\" is too long!", name); - ret = WIMLIB_ERR_INVALID_UTF8_STRING; - } - } -#endif - if (ret == 0) { - FREE(*name_utf16le_ret); - *name_utf16le_ret = name_utf16le; - *name_utf16le_nbytes_ret = name_utf16le_nbytes; - } - return ret; -} - -/* Sets the name of a WIM dentry from a multibyte string. */ +/* 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. */ int -set_dentry_name(struct wim_dentry *dentry, const tchar *new_name) +dentry_set_name(struct wim_dentry *dentry, const tchar *new_name) { int ret; - ret = get_utf16le_name(new_name, &dentry->file_name, - &dentry->file_name_nbytes); + ret = get_utf16le_string(new_name, &dentry->file_name, + &dentry->file_name_nbytes); if (ret == 0) { /* Clear the short name and recalculate the dentry length */ if (dentry_has_short_name(dentry)) { @@ -326,7 +261,8 @@ ads_entry_total_length(const struct wim_ads_entry *entry) * though there is already a field in the dentry itself for the unnamed stream * reference, which then goes to waste. */ -static inline bool inode_needs_dummy_stream(const struct wim_inode *inode) +static inline bool +inode_needs_dummy_stream(const struct wim_inode *inode) { return (inode->i_num_ads > 0 && inode->i_num_ads < 0xffff && /* overflow check */ @@ -657,20 +593,6 @@ dentry_compare_names_case_sensitive(const struct wim_dentry *d1, false); } -/* 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, - bool ignore_case) -{ - 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(). */ @@ -682,18 +604,6 @@ bool default_ignore_case = #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. */ @@ -951,6 +861,76 @@ 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. + * + * 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; + goto out; + } else { + return -ENOENT; + } + } else { + lte = inode_unnamed_stream_resolved(inode, &stream_idx); + } +out: + 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 */ + /* Prints the full path of a dentry. */ int print_dentry_full_path(struct wim_dentry *dentry, void *_ignore) @@ -1072,35 +1052,6 @@ dentry_common_init(struct wim_dentry *dentry) memset(dentry, 0, sizeof(struct wim_dentry)); } -struct wim_inode * -new_timeless_inode(void) -{ - struct wim_inode *inode = CALLOC(1, sizeof(struct wim_inode)); - if (inode) { - inode->i_security_id = -1; - inode->i_nlink = 1; - inode->i_next_stream_id = 1; - inode->i_not_rpfixed = 1; - inode->i_canonical_streams = 1; - INIT_LIST_HEAD(&inode->i_list); - INIT_LIST_HEAD(&inode->i_dentry); - } - return inode; -} - -static struct wim_inode * -new_inode(void) -{ - struct wim_inode *inode = new_timeless_inode(); - if (inode) { - u64 now = get_wim_timestamp(); - inode->i_creation_time = now; - inode->i_last_access_time = now; - inode->i_last_write_time = now; - } - return inode; -} - /* Creates an unlinked directory entry. */ int new_dentry(const tchar *name, struct wim_dentry **dentry_ret) @@ -1113,7 +1064,7 @@ new_dentry(const tchar *name, struct wim_dentry **dentry_ret) return WIMLIB_ERR_NOMEM; dentry_common_init(dentry); - ret = set_dentry_name(dentry, name); + ret = dentry_set_name(dentry, name); if (ret == 0) { dentry->parent = dentry; *dentry_ret = dentry; @@ -1125,7 +1076,6 @@ new_dentry(const tchar *name, struct wim_dentry **dentry_ret) return ret; } - static int _new_dentry_with_inode(const tchar *name, struct wim_dentry **dentry_ret, bool timeless) @@ -1194,75 +1144,10 @@ dentry_tree_clear_inode_visited(struct wim_dentry *root) for_dentry_in_tree(root, dentry_clear_inode_visited, NULL); } -static int -init_ads_entry(struct wim_ads_entry *ads_entry, const void *name, - size_t name_nbytes, bool is_utf16le) -{ - int ret = 0; - memset(ads_entry, 0, sizeof(*ads_entry)); - - if (is_utf16le) { - utf16lechar *p = MALLOC(name_nbytes + sizeof(utf16lechar)); - if (p == NULL) - return WIMLIB_ERR_NOMEM; - memcpy(p, name, name_nbytes); - p[name_nbytes / 2] = cpu_to_le16(0); - ads_entry->stream_name = p; - ads_entry->stream_name_nbytes = name_nbytes; - } else { - if (name && *(const tchar*)name != T('\0')) { - ret = get_utf16le_name(name, &ads_entry->stream_name, - &ads_entry->stream_name_nbytes); - } - } - return ret; -} - -static void -destroy_ads_entry(struct wim_ads_entry *ads_entry) -{ - FREE(ads_entry->stream_name); -} - -/* Frees an inode. */ -void -free_inode(struct wim_inode *inode) -{ - if (inode) { - if (inode->i_ads_entries) { - for (u16 i = 0; i < inode->i_num_ads; i++) - destroy_ads_entry(&inode->i_ads_entries[i]); - FREE(inode->i_ads_entries); - } - /* HACK: This may instead delete the inode from i_list, but the - * hlist_del() behaves the same as list_del(). */ - if (!hlist_unhashed(&inode->i_hlist)) - hlist_del(&inode->i_hlist); - FREE(inode); - } -} - -/* Decrements link count on an inode and frees it if the link count reaches 0. - * */ -static void -put_inode(struct wim_inode *inode) -{ - wimlib_assert(inode->i_nlink != 0); - if (--inode->i_nlink == 0) { - #ifdef WITH_FUSE - if (inode->i_num_opened_fds == 0) - #endif - { - free_inode(inode); - } - } -} - /* Frees a WIM dentry. * * The corresponding inode (if any) is freed only if its link count is - * decremented to 0. - */ + * decremented to 0. */ void free_dentry(struct wim_dentry *dentry) { @@ -1493,7 +1378,7 @@ rename_wim_path(WIMStruct *wim, const tchar *from, const tchar *to, return -ENOTDIR; } - ret = set_dentry_name(src, path_basename(to)); + ret = dentry_set_name(src, path_basename(to)); if (ret) return -ENOMEM; if (dst) { @@ -1507,445 +1392,6 @@ rename_wim_path(WIMStruct *wim, const tchar *from, const tchar *to, return 0; } -/* - * Returns the alternate data stream entry belonging to @inode that has the - * stream name @stream_name, or NULL if the inode has no alternate data stream - * with that name. - * - * If @p stream_name is the empty string, NULL is returned --- that is, this - * function will not return "unnamed" alternate data stream entries. - */ -struct wim_ads_entry * -inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name, - u16 *idx_ret) -{ - if (inode->i_num_ads == 0) { - return NULL; - } else { - size_t stream_name_utf16le_nbytes; - u16 i; - struct wim_ads_entry *result; - - if (stream_name[0] == T('\0')) - return NULL; - - #if TCHAR_IS_UTF16LE - const utf16lechar *stream_name_utf16le; - - stream_name_utf16le = stream_name; - stream_name_utf16le_nbytes = tstrlen(stream_name) * sizeof(tchar); - #else - utf16lechar *stream_name_utf16le; - - { - int ret = tstr_to_utf16le(stream_name, - tstrlen(stream_name) * - sizeof(tchar), - &stream_name_utf16le, - &stream_name_utf16le_nbytes); - if (ret) - return NULL; - } - #endif - i = 0; - result = NULL; - do { - if (ads_entry_has_name(&inode->i_ads_entries[i], - stream_name_utf16le, - stream_name_utf16le_nbytes, - default_ignore_case)) - { - if (idx_ret) - *idx_ret = i; - result = &inode->i_ads_entries[i]; - break; - } - } while (++i != inode->i_num_ads); - #if !TCHAR_IS_UTF16LE - FREE(stream_name_utf16le); - #endif - return result; - } -} - -static struct wim_ads_entry * -do_inode_add_ads(struct wim_inode *inode, const void *stream_name, - size_t stream_name_nbytes, bool is_utf16le) -{ - u16 num_ads; - struct wim_ads_entry *ads_entries; - struct wim_ads_entry *new_entry; - - wimlib_assert(stream_name_nbytes != 0); - - if (inode->i_num_ads >= 0xfffe) { - ERROR("Too many alternate data streams in one inode!"); - return NULL; - } - num_ads = inode->i_num_ads + 1; - ads_entries = REALLOC(inode->i_ads_entries, - num_ads * sizeof(inode->i_ads_entries[0])); - if (ads_entries == NULL) { - ERROR("Failed to allocate memory for new alternate data stream"); - return NULL; - } - inode->i_ads_entries = ads_entries; - - new_entry = &inode->i_ads_entries[num_ads - 1]; - if (init_ads_entry(new_entry, stream_name, stream_name_nbytes, is_utf16le)) - return NULL; - new_entry->stream_id = inode->i_next_stream_id++; - inode->i_num_ads = num_ads; - return new_entry; -} - -struct wim_ads_entry * -inode_add_ads_utf16le(struct wim_inode *inode, - const utf16lechar *stream_name, - size_t stream_name_nbytes) -{ - DEBUG("Add alternate data stream \"%"WS"\"", stream_name); - return do_inode_add_ads(inode, stream_name, stream_name_nbytes, true); -} - -/* - * Add an alternate stream entry to a WIM inode. On success, returns a pointer - * to the new entry; on failure, returns NULL. - * - * @stream_name must be a nonempty string. - */ -struct wim_ads_entry * -inode_add_ads(struct wim_inode *inode, const tchar *stream_name) -{ - DEBUG("Add alternate data stream \"%"TS"\"", stream_name); - return do_inode_add_ads(inode, stream_name, - tstrlen(stream_name) * sizeof(tchar), - TCHAR_IS_UTF16LE); -} - -static struct wim_lookup_table_entry * -add_stream_from_data_buffer(const void *buffer, size_t size, - struct wim_lookup_table *lookup_table) -{ - u8 hash[SHA1_HASH_SIZE]; - struct wim_lookup_table_entry *lte, *existing_lte; - - sha1_buffer(buffer, size, hash); - existing_lte = lookup_resource(lookup_table, hash); - if (existing_lte) { - wimlib_assert(existing_lte->size == size); - lte = existing_lte; - lte->refcnt++; - } else { - void *buffer_copy; - lte = new_lookup_table_entry(); - if (lte == NULL) - return NULL; - buffer_copy = memdup(buffer, size); - if (buffer_copy == NULL) { - free_lookup_table_entry(lte); - return NULL; - } - lte->resource_location = RESOURCE_IN_ATTACHED_BUFFER; - lte->attached_buffer = buffer_copy; - lte->size = size; - copy_hash(lte->hash, hash); - lookup_table_insert(lookup_table, lte); - } - return lte; -} - -int -inode_add_ads_with_data(struct wim_inode *inode, const tchar *name, - const void *value, size_t size, - struct wim_lookup_table *lookup_table) -{ - struct wim_ads_entry *new_ads_entry; - - wimlib_assert(inode->i_resolved); - - new_ads_entry = inode_add_ads(inode, name); - if (new_ads_entry == NULL) - return WIMLIB_ERR_NOMEM; - - new_ads_entry->lte = add_stream_from_data_buffer(value, size, - lookup_table); - if (new_ads_entry->lte == NULL) { - inode_remove_ads(inode, new_ads_entry - inode->i_ads_entries, - lookup_table); - return WIMLIB_ERR_NOMEM; - } - return 0; -} - -bool -inode_has_named_stream(const struct wim_inode *inode) -{ - for (u16 i = 0; i < inode->i_num_ads; i++) - if (ads_entry_is_named_stream(&inode->i_ads_entries[i])) - return true; - return false; -} - -/* Set the unnamed stream of a WIM inode, given a data buffer containing the - * stream contents. */ -int -inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len, - struct wim_lookup_table *lookup_table) -{ - inode->i_lte = add_stream_from_data_buffer(data, len, lookup_table); - if (inode->i_lte == NULL) - return WIMLIB_ERR_NOMEM; - inode->i_resolved = 1; - return 0; -} - -/* Remove an alternate data stream from a WIM inode */ -void -inode_remove_ads(struct wim_inode *inode, u16 idx, - struct wim_lookup_table *lookup_table) -{ - struct wim_ads_entry *ads_entry; - struct wim_lookup_table_entry *lte; - - wimlib_assert(idx < inode->i_num_ads); - wimlib_assert(inode->i_resolved); - - ads_entry = &inode->i_ads_entries[idx]; - - DEBUG("Remove alternate data stream \"%"WS"\"", ads_entry->stream_name); - - lte = ads_entry->lte; - if (lte) - lte_decrement_refcnt(lte, lookup_table); - - destroy_ads_entry(ads_entry); - - memmove(&inode->i_ads_entries[idx], - &inode->i_ads_entries[idx + 1], - (inode->i_num_ads - idx - 1) * sizeof(inode->i_ads_entries[0])); - inode->i_num_ads--; -} - -bool -inode_has_unix_data(const struct wim_inode *inode) -{ - for (u16 i = 0; i < inode->i_num_ads; i++) - if (ads_entry_is_unix_data(&inode->i_ads_entries[i])) - return true; - return false; -} - -#ifndef __WIN32__ -int -inode_get_unix_data(const struct wim_inode *inode, - struct wimlib_unix_data *unix_data, - u16 *stream_idx_ret) -{ - const struct wim_ads_entry *ads_entry; - const struct wim_lookup_table_entry *lte; - size_t size; - int ret; - - wimlib_assert(inode->i_resolved); - - ads_entry = inode_get_ads_entry((struct wim_inode*)inode, - WIMLIB_UNIX_DATA_TAG, NULL); - if (ads_entry == NULL) - return NO_UNIX_DATA; - - if (stream_idx_ret) - *stream_idx_ret = ads_entry - inode->i_ads_entries; - - lte = ads_entry->lte; - if (lte == NULL) - return NO_UNIX_DATA; - - size = lte->size; - if (size != sizeof(struct wimlib_unix_data)) - return BAD_UNIX_DATA; - - ret = read_full_stream_into_buf(lte, unix_data); - if (ret) - return ret; - - if (unix_data->version != 0) - return BAD_UNIX_DATA; - return 0; -} - -int -inode_set_unix_data(struct wim_inode *inode, uid_t uid, gid_t gid, mode_t mode, - struct wim_lookup_table *lookup_table, int which) -{ - struct wimlib_unix_data unix_data; - int ret; - bool have_good_unix_data = false; - bool have_unix_data = false; - u16 stream_idx; - - if (!(which & UNIX_DATA_CREATE)) { - ret = inode_get_unix_data(inode, &unix_data, &stream_idx); - if (ret == 0 || ret == BAD_UNIX_DATA || ret > 0) - have_unix_data = true; - if (ret == 0) - have_good_unix_data = true; - } - unix_data.version = 0; - if (which & UNIX_DATA_UID || !have_good_unix_data) - unix_data.uid = uid; - if (which & UNIX_DATA_GID || !have_good_unix_data) - unix_data.gid = gid; - if (which & UNIX_DATA_MODE || !have_good_unix_data) - unix_data.mode = mode; - ret = inode_add_ads_with_data(inode, WIMLIB_UNIX_DATA_TAG, - &unix_data, - sizeof(struct wimlib_unix_data), - lookup_table); - if (ret == 0 && have_unix_data) - inode_remove_ads(inode, stream_idx, lookup_table); - return ret; -} -#endif /* !__WIN32__ */ - -/* - * Reads the alternate data stream entries of a WIM dentry. - * - * @p: - * Pointer to buffer that starts with the first alternate stream entry. - * - * @inode: - * Inode to load the alternate data streams into. @inode->i_num_ads must - * have been set to the number of alternate data streams that are expected. - * - * @remaining_size: - * Number of bytes of data remaining in the buffer pointed to by @p. - * - * On success, inode->i_ads_entries is set to an array of `struct - * wim_ads_entry's of length inode->i_num_ads. On failure, @inode is not - * modified. - * - * Return values: - * WIMLIB_ERR_SUCCESS (0) - * WIMLIB_ERR_INVALID_METADATA_RESOURCE - * WIMLIB_ERR_NOMEM - */ -static int -read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode, - size_t nbytes_remaining) -{ - u16 num_ads; - struct wim_ads_entry *ads_entries; - int ret; - - BUILD_BUG_ON(sizeof(struct wim_ads_entry_on_disk) != WIM_ADS_ENTRY_DISK_SIZE); - - /* Allocate an array for our in-memory representation of the alternate - * data stream entries. */ - num_ads = inode->i_num_ads; - ads_entries = CALLOC(num_ads, sizeof(inode->i_ads_entries[0])); - if (ads_entries == NULL) - goto out_of_memory; - - /* Read the entries into our newly allocated buffer. */ - for (u16 i = 0; i < num_ads; i++) { - u64 length; - struct wim_ads_entry *cur_entry; - const struct wim_ads_entry_on_disk *disk_entry = - (const struct wim_ads_entry_on_disk*)p; - - cur_entry = &ads_entries[i]; - ads_entries[i].stream_id = i + 1; - - /* Do we have at least the size of the fixed-length data we know - * need? */ - if (nbytes_remaining < sizeof(struct wim_ads_entry_on_disk)) - goto out_invalid; - - /* Read the length field */ - length = le64_to_cpu(disk_entry->length); - - /* Make sure the length field is neither so small it doesn't - * include all the fixed-length data nor so large it overflows - * the metadata resource buffer. */ - if (length < sizeof(struct wim_ads_entry_on_disk) || - length > nbytes_remaining) - goto out_invalid; - - /* Read the rest of the fixed-length data. */ - - cur_entry->reserved = le64_to_cpu(disk_entry->reserved); - copy_hash(cur_entry->hash, disk_entry->hash); - cur_entry->stream_name_nbytes = le16_to_cpu(disk_entry->stream_name_nbytes); - - /* If stream_name_nbytes != 0, this is a named stream. - * Otherwise this is an unnamed stream, or in some cases (bugs - * in Microsoft's software I guess) a meaningless entry - * distinguished from the real unnamed stream entry, if any, by - * the fact that the real unnamed stream entry has a nonzero - * hash field. */ - if (cur_entry->stream_name_nbytes) { - /* The name is encoded in UTF16-LE, which uses 2-byte - * coding units, so the length of the name had better be - * an even number of bytes... */ - if (cur_entry->stream_name_nbytes & 1) - goto out_invalid; - - /* Add the length of the stream name to get the length - * we actually need to read. Make sure this isn't more - * than the specified length of the entry. */ - if (sizeof(struct wim_ads_entry_on_disk) + - cur_entry->stream_name_nbytes > length) - goto out_invalid; - - cur_entry->stream_name = MALLOC(cur_entry->stream_name_nbytes + 2); - if (cur_entry->stream_name == NULL) - goto out_of_memory; - - memcpy(cur_entry->stream_name, - disk_entry->stream_name, - cur_entry->stream_name_nbytes); - cur_entry->stream_name[cur_entry->stream_name_nbytes / 2] = cpu_to_le16(0); - } else { - /* Mark inode as having weird stream entries. */ - inode->i_canonical_streams = 0; - } - - /* It's expected that the size of every ADS entry is a multiple - * of 8. However, to be safe, I'm allowing the possibility of - * an ADS entry at the very end of the metadata resource ending - * un-aligned. So although we still need to increment the input - * pointer by @length to reach the next ADS entry, it's possible - * that less than @length is actually remaining in the metadata - * resource. We should set the remaining bytes to 0 if this - * happens. */ - length = (length + 7) & ~(u64)7; - p += length; - if (nbytes_remaining < length) - nbytes_remaining = 0; - else - nbytes_remaining -= length; - } - inode->i_ads_entries = ads_entries; - inode->i_next_stream_id = inode->i_num_ads + 1; - ret = 0; - goto out; -out_of_memory: - ret = WIMLIB_ERR_NOMEM; - goto out_free_ads_entries; -out_invalid: - ERROR("An alternate data stream entry is invalid"); - ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE; -out_free_ads_entries: - if (ads_entries) { - for (u16 i = 0; i < num_ads; i++) - destroy_ads_entry(&ads_entries[i]); - FREE(ads_entries); - } -out: - return ret; -} - /* * Reads a WIM directory entry, including all alternate data stream entries that * follow it, from the WIM image's metadata resource. @@ -2507,431 +1953,3 @@ write_dentry_tree(const struct wim_dentry * restrict root, u8 * restrict p) /* Recursively write the rest of the dentry tree. */ return write_dentry_tree_recursive(root, p); } - - -static int -init_wimlib_dentry(struct wimlib_dir_entry *wdentry, - struct wim_dentry *dentry, - const WIMStruct *wim, - int flags) -{ - int ret; - size_t dummy; - const struct wim_inode *inode = dentry->d_inode; - struct wim_lookup_table_entry *lte; - const u8 *hash; - -#if TCHAR_IS_UTF16LE - wdentry->filename = dentry->file_name; - wdentry->dos_name = dentry->short_name; -#else - if (dentry_has_long_name(dentry)) { - ret = utf16le_to_tstr(dentry->file_name, - dentry->file_name_nbytes, - (tchar**)&wdentry->filename, - &dummy); - if (ret) - return ret; - } - if (dentry_has_short_name(dentry)) { - ret = utf16le_to_tstr(dentry->short_name, - dentry->short_name_nbytes, - (tchar**)&wdentry->dos_name, - &dummy); - if (ret) - return ret; - } -#endif - ret = calculate_dentry_full_path(dentry); - if (ret) - return ret; - wdentry->full_path = dentry->_full_path; - - for (struct wim_dentry *d = dentry; !dentry_is_root(d); d = d->parent) - wdentry->depth++; - - if (inode->i_security_id >= 0) { - const struct wim_security_data *sd = wim_const_security_data(wim); - wdentry->security_descriptor = sd->descriptors[inode->i_security_id]; - wdentry->security_descriptor_size = sd->sizes[inode->i_security_id]; - } - wdentry->reparse_tag = inode->i_reparse_tag; - wdentry->num_links = inode->i_nlink; - wdentry->attributes = inode->i_attributes; - wdentry->hard_link_group_id = inode->i_ino; - wdentry->creation_time = wim_timestamp_to_timespec(inode->i_creation_time); - wdentry->last_write_time = wim_timestamp_to_timespec(inode->i_last_write_time); - wdentry->last_access_time = wim_timestamp_to_timespec(inode->i_last_access_time); - - lte = inode_unnamed_lte(inode, wim->lookup_table); - if (lte) { - lte_to_wimlib_resource_entry(lte, &wdentry->streams[0].resource); - } else if (!is_zero_hash(hash = inode_unnamed_stream_hash(inode))) { - if (flags & WIMLIB_ITERATE_DIR_TREE_FLAG_RESOURCES_NEEDED) - return resource_not_found_error(inode, hash); - copy_hash(wdentry->streams[0].resource.sha1_hash, hash); - wdentry->streams[0].resource.is_missing = 1; - } - - for (unsigned i = 0; i < inode->i_num_ads; i++) { - if (!ads_entry_is_named_stream(&inode->i_ads_entries[i])) - continue; - lte = inode_stream_lte(inode, i + 1, wim->lookup_table); - wdentry->num_named_streams++; - if (lte) { - lte_to_wimlib_resource_entry(lte, &wdentry->streams[ - wdentry->num_named_streams].resource); - } else if (!is_zero_hash(hash = inode_stream_hash(inode, i + 1))) { - if (flags & WIMLIB_ITERATE_DIR_TREE_FLAG_RESOURCES_NEEDED) - return resource_not_found_error(inode, hash); - copy_hash(wdentry->streams[ - wdentry->num_named_streams].resource.sha1_hash, hash); - wdentry->streams[ - wdentry->num_named_streams].resource.is_missing = 1; - } - #if TCHAR_IS_UTF16LE - wdentry->streams[wdentry->num_named_streams].stream_name = - inode->i_ads_entries[i].stream_name; - #else - size_t dummy; - - ret = utf16le_to_tstr(inode->i_ads_entries[i].stream_name, - inode->i_ads_entries[i].stream_name_nbytes, - (tchar**)&wdentry->streams[ - wdentry->num_named_streams].stream_name, - &dummy); - if (ret) - return ret; - #endif - } - return 0; -} - -static void -free_wimlib_dentry(struct wimlib_dir_entry *wdentry) -{ -#if !TCHAR_IS_UTF16LE - FREE((tchar*)wdentry->filename); - FREE((tchar*)wdentry->dos_name); - for (unsigned i = 1; i <= wdentry->num_named_streams; i++) - FREE((tchar*)wdentry->streams[i].stream_name); -#endif - FREE(wdentry); -} - -struct iterate_dir_tree_ctx { - WIMStruct *wim; - int flags; - wimlib_iterate_dir_tree_callback_t cb; - void *user_ctx; -}; - -static int -do_iterate_dir_tree(WIMStruct *wim, - struct wim_dentry *dentry, int flags, - wimlib_iterate_dir_tree_callback_t cb, - void *user_ctx); - -static int -call_do_iterate_dir_tree(struct wim_dentry *dentry, void *_ctx) -{ - struct iterate_dir_tree_ctx *ctx = _ctx; - return do_iterate_dir_tree(ctx->wim, dentry, ctx->flags, - ctx->cb, ctx->user_ctx); -} - -static int -do_iterate_dir_tree(WIMStruct *wim, - struct wim_dentry *dentry, int flags, - wimlib_iterate_dir_tree_callback_t cb, - void *user_ctx) -{ - struct wimlib_dir_entry *wdentry; - int ret = WIMLIB_ERR_NOMEM; - - - wdentry = CALLOC(1, sizeof(struct wimlib_dir_entry) + - (1 + dentry->d_inode->i_num_ads) * - sizeof(struct wimlib_stream_entry)); - if (wdentry == NULL) - goto out; - - ret = init_wimlib_dentry(wdentry, dentry, wim, flags); - if (ret) - goto out_free_wimlib_dentry; - - if (!(flags & WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN)) { - ret = (*cb)(wdentry, user_ctx); - if (ret) - goto out_free_wimlib_dentry; - } - - if (flags & (WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE | - WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN)) - { - struct iterate_dir_tree_ctx ctx = { - .wim = wim, - .flags = flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN, - .cb = cb, - .user_ctx = user_ctx, - }; - ret = for_dentry_child(dentry, call_do_iterate_dir_tree, &ctx); - } -out_free_wimlib_dentry: - free_wimlib_dentry(wdentry); -out: - return ret; -} - -struct image_iterate_dir_tree_ctx { - const tchar *path; - int flags; - wimlib_iterate_dir_tree_callback_t cb; - void *user_ctx; -}; - - -static int -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, 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); -} - -/* API function documented in wimlib.h */ -WIMLIBAPI int -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, - .cb = cb, - .user_ctx = user_ctx, - }; - wim->private = &ctx; - 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 - * consistent with them being the same, unmodified file. */ -static bool -inode_metadata_consistent(const struct wim_inode *inode, - const struct wim_inode *template_inode, - const struct wim_lookup_table *template_lookup_table) -{ - /* Must have exact same creation time and last write time. */ - if (inode->i_creation_time != template_inode->i_creation_time || - inode->i_last_write_time != template_inode->i_last_write_time) - return false; - - /* Last access time may have stayed the same or increased, but certainly - * shouldn't have decreased. */ - if (inode->i_last_access_time < template_inode->i_last_access_time) - return false; - - /* Must have same number of alternate data stream entries. */ - if (inode->i_num_ads != template_inode->i_num_ads) - return false; - - /* If the stream entries for the inode are for some reason not resolved, - * then the hashes are already available and the point of this function - * is defeated. */ - if (!inode->i_resolved) - return false; - - /* Iterate through each stream and do some more checks. */ - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - const struct wim_lookup_table_entry *lte, *template_lte; - - lte = inode_stream_lte_resolved(inode, i); - template_lte = inode_stream_lte(template_inode, i, - template_lookup_table); - - /* Compare stream sizes. */ - if (lte && template_lte) { - if (lte->size != template_lte->size) - return false; - - /* If hash happens to be available, compare with template. */ - if (!lte->unhashed && !template_lte->unhashed && - !hashes_equal(lte->hash, template_lte->hash)) - return false; - - } else if (lte && lte->size) { - return false; - } else if (template_lte && template_lte->size) { - return false; - } - } - - /* All right, barring a full checksum and given that the inodes share a - * path and the user isn't trying to trick us, these inodes most likely - * refer to the same file. */ - return true; -} - -/** - * Given an inode @inode that has been determined to be "the same" as another - * inode @template_inode in either the same WIM or another WIM, retrieve some - * useful stream information (e.g. checksums) from @template_inode. - * - * This assumes that the streams for @inode have been resolved (to point - * directly to the appropriate `struct wim_lookup_table_entry's) but do not - * necessarily have checksum information filled in. - */ -static int -inode_copy_checksums(struct wim_inode *inode, - struct wim_inode *template_inode, - WIMStruct *wim, - WIMStruct *template_wim) -{ - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - struct wim_lookup_table_entry *lte, *template_lte; - struct wim_lookup_table_entry *replace_lte; - - lte = inode_stream_lte_resolved(inode, i); - template_lte = inode_stream_lte(template_inode, i, - template_wim->lookup_table); - - /* Only take action if both entries exist, the entry for @inode - * has no checksum calculated, but the entry for @template_inode - * does. */ - if (lte == NULL || template_lte == NULL || - !lte->unhashed || template_lte->unhashed) - continue; - - wimlib_assert(lte->refcnt == inode->i_nlink); - - /* If the WIM of the template image is the same as the WIM of - * the new image, then @template_lte can be used directly. - * - * Otherwise, look for a stream with the same hash in the WIM of - * the new image. If found, use it; otherwise re-use the entry - * being discarded, filling in the hash. */ - - if (wim == template_wim) - replace_lte = template_lte; - else - replace_lte = lookup_resource(wim->lookup_table, - template_lte->hash); - - list_del(<e->unhashed_list); - if (replace_lte) { - free_lookup_table_entry(lte); - } else { - copy_hash(lte->hash, template_lte->hash); - lte->unhashed = 0; - lookup_table_insert(wim->lookup_table, lte); - lte->refcnt = 0; - replace_lte = lte; - } - - if (i == 0) - inode->i_lte = replace_lte; - else - inode->i_ads_entries[i - 1].lte = replace_lte; - - replace_lte->refcnt += inode->i_nlink; - } - return 0; -} - -struct reference_template_args { - WIMStruct *wim; - WIMStruct *template_wim; -}; - -static int -dentry_reference_template(struct wim_dentry *dentry, void *_args) -{ - int ret; - struct wim_dentry *template_dentry; - struct wim_inode *inode, *template_inode; - struct reference_template_args *args = _args; - WIMStruct *wim = args->wim; - WIMStruct *template_wim = args->template_wim; - - if (dentry->d_inode->i_visited) - return 0; - - ret = calculate_dentry_full_path(dentry); - if (ret) - return ret; - - 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; - } - - inode = dentry->d_inode; - template_inode = template_dentry->d_inode; - - if (inode_metadata_consistent(inode, template_inode, - template_wim->lookup_table)) { - /*DEBUG("\"%"TS"\": No change detected", dentry->_full_path);*/ - ret = inode_copy_checksums(inode, template_inode, - wim, template_wim); - inode->i_visited = 1; - } else { - DEBUG("\"%"TS"\": change detected!", dentry->_full_path); - ret = 0; - } - return ret; -} - -/* API function documented in wimlib.h */ -WIMLIBAPI int -wimlib_reference_template_image(WIMStruct *wim, int new_image, - WIMStruct *template_wim, int template_image, - int flags, wimlib_progress_func_t progress_func) -{ - int ret; - struct wim_image_metadata *new_imd; - - if (wim == NULL || template_wim == NULL) - return WIMLIB_ERR_INVALID_PARAM; - - if (wim == template_wim && new_image == template_image) - return WIMLIB_ERR_INVALID_PARAM; - - if (new_image < 1 || new_image > wim->hdr.image_count) - return WIMLIB_ERR_INVALID_IMAGE; - - if (!wim_has_metadata(wim)) - return WIMLIB_ERR_METADATA_NOT_FOUND; - - new_imd = wim->image_metadata[new_image - 1]; - if (!new_imd->modified) - return WIMLIB_ERR_INVALID_PARAM; - - ret = select_wim_image(template_wim, template_image); - if (ret) - return ret; - - struct reference_template_args args = { - .wim = wim, - .template_wim = template_wim, - }; - - ret = for_dentry_in_tree(new_imd->root_dentry, - dentry_reference_template, &args); - dentry_tree_clear_inode_visited(new_imd->root_dentry); - return ret; -} diff --git a/src/encoding.c b/src/encoding.c index fec6e06a..cf5dd66e 100644 --- a/src/encoding.c +++ b/src/encoding.c @@ -528,3 +528,41 @@ cmp_utf16le_strings(const utf16lechar *s1, size_t n1, return 0; return (n1 < n2) ? -1 : 1; } + +/* Duplicates a string of system-dependent encoding into a UTF-16LE string and + * returns the string and its length, in bytes, in the pointer arguments. Frees + * any existing string at the return location before overwriting it. */ +int +get_utf16le_string(const tchar *name, utf16lechar **name_utf16le_ret, + u16 *name_utf16le_nbytes_ret) +{ + utf16lechar *name_utf16le; + size_t name_utf16le_nbytes; + int ret; +#if TCHAR_IS_UTF16LE + name_utf16le_nbytes = tstrlen(name) * sizeof(utf16lechar); + name_utf16le = MALLOC(name_utf16le_nbytes + sizeof(utf16lechar)); + if (name_utf16le == NULL) + return WIMLIB_ERR_NOMEM; + memcpy(name_utf16le, name, name_utf16le_nbytes + sizeof(utf16lechar)); + ret = 0; +#else + + ret = tstr_to_utf16le(name, tstrlen(name), &name_utf16le, + &name_utf16le_nbytes); + if (ret == 0) { + if (name_utf16le_nbytes > 0xffff) { + FREE(name_utf16le); + ERROR("Multibyte string \"%"TS"\" is too long!", name); + ret = WIMLIB_ERR_INVALID_UTF8_STRING; + } + } +#endif + if (ret == 0) { + FREE(*name_utf16le_ret); + *name_utf16le_ret = name_utf16le; + *name_utf16le_nbytes_ret = name_utf16le_nbytes; + } + return ret; +} + diff --git a/src/export_image.c b/src/export_image.c index 87ecefa7..56725e66 100644 --- a/src/export_image.c +++ b/src/export_image.c @@ -28,6 +28,7 @@ #include "wimlib.h" #include "wimlib/dentry.h" #include "wimlib/error.h" +#include "wimlib/inode.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" #include "wimlib/xml.h" @@ -42,7 +43,7 @@ inode_export_streams(struct wim_inode *inode, const u8 *hash; struct wim_lookup_table_entry *src_lte, *dest_lte; - inode_unresolve_ltes(inode); + inode_unresolve_streams(inode); for (i = 0; i <= inode->i_num_ads; i++) { /* Retrieve SHA1 message digest of stream to export. */ @@ -52,14 +53,14 @@ inode_export_streams(struct wim_inode *inode, /* Search for the stream (via SHA1 message digest) in the * destination WIM. */ - dest_lte = lookup_resource(dest_lookup_table, hash); + dest_lte = lookup_stream(dest_lookup_table, hash); if (!dest_lte) { /* Stream not yet present in destination WIM. Search * for it in the source WIM, then export it into the * destination WIM. */ - src_lte = lookup_resource(src_lookup_table, hash); + src_lte = lookup_stream(src_lookup_table, hash); if (!src_lte) - return resource_not_found_error(inode, hash); + return stream_not_found_error(inode, hash); dest_lte = clone_lookup_table_entry(src_lte); if (!dest_lte) diff --git a/src/extract.c b/src/extract.c index 1e31cdb0..9a43e630 100644 --- a/src/extract.c +++ b/src/extract.c @@ -93,7 +93,7 @@ dentry_resolve_and_zero_lte_refcnt(struct wim_dentry *dentry, void *_ctx) * "resolve" the inode's streams anyway by allocating new entries. */ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) force = true; - ret = inode_resolve_ltes(inode, ctx->wim->lookup_table, force); + ret = inode_resolve_streams(inode, ctx->wim->lookup_table, force); if (ret) return ret; for (unsigned i = 0; i <= inode->i_num_ads; i++) { @@ -1508,7 +1508,7 @@ extract_streams_from_pipe(struct apply_ctx *ctx) if ((found_lte->resource_location != RESOURCE_NONEXISTENT) && !(found_lte->flags & WIM_RESHDR_FLAG_METADATA) - && (needed_lte = lookup_resource(lookup_table, found_lte->hash)) + && (needed_lte = lookup_stream(lookup_table, found_lte->hash)) && (needed_lte->out_refcnt)) { tchar *tmpfile_name = NULL; diff --git a/src/inode.c b/src/inode.c new file mode 100644 index 00000000..539d5667 --- /dev/null +++ b/src/inode.c @@ -0,0 +1,995 @@ +/* + * inode.c + */ + +/* + * Copyright (C) 2012, 2013 Eric Biggers + * + * This file is part of wimlib, a library for working with WIM files. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with wimlib; if not, see http://www.gnu.org/licenses/. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "wimlib/assert.h" +#include "wimlib/case.h" +#include "wimlib/dentry.h" /* Only for dentry_full_path(). Otherwise the code + in this file doesn't care about file names/paths. + */ +#include "wimlib/encoding.h" +#include "wimlib/endianness.h" +#include "wimlib/error.h" +#include "wimlib/inode.h" +#include "wimlib/inode_table.h" +#include "wimlib/lookup_table.h" +#include "wimlib/security.h" +#include "wimlib/timestamp.h" + +/* Allocate a new inode. Set the timestamps to the current time. */ +struct wim_inode * +new_inode(void) +{ + struct wim_inode *inode = new_timeless_inode(); + if (inode) { + u64 now = get_wim_timestamp(); + inode->i_creation_time = now; + inode->i_last_access_time = now; + inode->i_last_write_time = now; + } + return inode; +} + + +/* Allocate a new inode. Leave the timestamps zeroed out. */ +struct wim_inode * +new_timeless_inode(void) +{ + struct wim_inode *inode = CALLOC(1, sizeof(struct wim_inode)); + if (inode) { + inode->i_security_id = -1; + inode->i_nlink = 1; + inode->i_next_stream_id = 1; + inode->i_not_rpfixed = 1; + inode->i_canonical_streams = 1; + INIT_LIST_HEAD(&inode->i_list); + INIT_LIST_HEAD(&inode->i_dentry); + } + return inode; +} + +/* Decrement link count on an inode. */ +void +put_inode(struct wim_inode *inode) +{ + wimlib_assert(inode->i_nlink != 0); + if (--inode->i_nlink == 0) { + /* If FUSE mounts are enabled, we must keep a unlinked inode + * around until all file descriptors to it have been closed. + * inode_put_fd() in mount_image.c handles dropping a file + * descriptor. */ + #ifdef WITH_FUSE + if (inode->i_num_opened_fds == 0) + #endif + free_inode(inode); + } +} + +/* De-allocate memory for an alternate data stream entry. */ +static void +destroy_ads_entry(struct wim_ads_entry *ads_entry) +{ + FREE(ads_entry->stream_name); +} + +/* Free an inode. Only use this if there can't be other links to the inode or + * if it doesn't matter if there are. */ +void +free_inode(struct wim_inode *inode) +{ + if (inode == NULL) + return; + + if (inode->i_ads_entries) { + for (u16 i = 0; i < inode->i_num_ads; i++) + destroy_ads_entry(&inode->i_ads_entries[i]); + FREE(inode->i_ads_entries); + } + /* HACK: This may instead delete the inode from i_list, but hlist_del() + * behaves the same as list_del(). */ + if (!hlist_unhashed(&inode->i_hlist)) + hlist_del(&inode->i_hlist); + FREE(inode); +} + +/* 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, + bool ignore_case) +{ + return 0 == cmp_utf16le_strings(name, + name_nbytes / 2, + entry->stream_name, + entry->stream_name_nbytes / 2, + ignore_case); +} + +/* + * Returns the alternate data stream entry belonging to @inode that has the + * stream name @stream_name, or NULL if the inode has no alternate data stream + * with that name. + * + * If @p stream_name is the empty string, NULL is returned --- that is, this + * function will not return "unnamed" alternate data stream entries. + */ +struct wim_ads_entry * +inode_get_ads_entry(struct wim_inode *inode, const tchar *stream_name, + u16 *idx_ret) +{ + if (inode->i_num_ads == 0) { + return NULL; + } else { + size_t stream_name_utf16le_nbytes; + u16 i; + struct wim_ads_entry *result; + + if (stream_name[0] == T('\0')) + return NULL; + + #if TCHAR_IS_UTF16LE + const utf16lechar *stream_name_utf16le; + + stream_name_utf16le = stream_name; + stream_name_utf16le_nbytes = tstrlen(stream_name) * sizeof(tchar); + #else + utf16lechar *stream_name_utf16le; + + { + int ret = tstr_to_utf16le(stream_name, + tstrlen(stream_name) * + sizeof(tchar), + &stream_name_utf16le, + &stream_name_utf16le_nbytes); + if (ret) + return NULL; + } + #endif + i = 0; + result = NULL; + do { + if (ads_entry_has_name(&inode->i_ads_entries[i], + stream_name_utf16le, + stream_name_utf16le_nbytes, + default_ignore_case)) + { + if (idx_ret) + *idx_ret = i; + result = &inode->i_ads_entries[i]; + break; + } + } while (++i != inode->i_num_ads); + #if !TCHAR_IS_UTF16LE + FREE(stream_name_utf16le); + #endif + return result; + } +} + +static int +init_ads_entry(struct wim_ads_entry *ads_entry, const void *name, + size_t name_nbytes, bool is_utf16le) +{ + int ret = 0; + memset(ads_entry, 0, sizeof(*ads_entry)); + + if (is_utf16le) { + utf16lechar *p = MALLOC(name_nbytes + sizeof(utf16lechar)); + if (p == NULL) + return WIMLIB_ERR_NOMEM; + memcpy(p, name, name_nbytes); + p[name_nbytes / 2] = cpu_to_le16(0); + ads_entry->stream_name = p; + ads_entry->stream_name_nbytes = name_nbytes; + } else { + if (name && *(const tchar*)name != T('\0')) { + ret = get_utf16le_string(name, &ads_entry->stream_name, + &ads_entry->stream_name_nbytes); + } + } + return ret; +} + +static struct wim_ads_entry * +do_inode_add_ads(struct wim_inode *inode, const void *stream_name, + size_t stream_name_nbytes, bool is_utf16le) +{ + u16 num_ads; + struct wim_ads_entry *ads_entries; + struct wim_ads_entry *new_entry; + + wimlib_assert(stream_name_nbytes != 0); + + if (inode->i_num_ads >= 0xfffe) { + ERROR("Too many alternate data streams in one inode!"); + return NULL; + } + num_ads = inode->i_num_ads + 1; + ads_entries = REALLOC(inode->i_ads_entries, + num_ads * sizeof(inode->i_ads_entries[0])); + if (ads_entries == NULL) { + ERROR("Failed to allocate memory for new alternate data stream"); + return NULL; + } + inode->i_ads_entries = ads_entries; + + new_entry = &inode->i_ads_entries[num_ads - 1]; + if (init_ads_entry(new_entry, stream_name, stream_name_nbytes, is_utf16le)) + return NULL; + new_entry->stream_id = inode->i_next_stream_id++; + inode->i_num_ads = num_ads; + return new_entry; +} + +struct wim_ads_entry * +inode_add_ads_utf16le(struct wim_inode *inode, + const utf16lechar *stream_name, + size_t stream_name_nbytes) +{ + DEBUG("Add alternate data stream \"%"WS"\"", stream_name); + return do_inode_add_ads(inode, stream_name, stream_name_nbytes, true); +} + +/* + * Add an alternate stream entry to a WIM inode. On success, returns a pointer + * to the new entry; on failure, returns NULL. + * + * @stream_name must be a nonempty string. + */ +struct wim_ads_entry * +inode_add_ads(struct wim_inode *inode, const tchar *stream_name) +{ + DEBUG("Add alternate data stream \"%"TS"\"", stream_name); + return do_inode_add_ads(inode, stream_name, + tstrlen(stream_name) * sizeof(tchar), + TCHAR_IS_UTF16LE); +} + +int +inode_add_ads_with_data(struct wim_inode *inode, const tchar *name, + const void *value, size_t size, + struct wim_lookup_table *lookup_table) +{ + struct wim_ads_entry *new_ads_entry; + + wimlib_assert(inode->i_resolved); + + new_ads_entry = inode_add_ads(inode, name); + if (new_ads_entry == NULL) + return WIMLIB_ERR_NOMEM; + + new_ads_entry->lte = new_stream_from_data_buffer(value, size, + lookup_table); + if (new_ads_entry->lte == NULL) { + inode_remove_ads(inode, new_ads_entry - inode->i_ads_entries, + lookup_table); + return WIMLIB_ERR_NOMEM; + } + return 0; +} + +bool +inode_has_named_stream(const struct wim_inode *inode) +{ + for (u16 i = 0; i < inode->i_num_ads; i++) + if (ads_entry_is_named_stream(&inode->i_ads_entries[i])) + return true; + return false; +} + +/* Set the unnamed stream of a WIM inode, given a data buffer containing the + * stream contents. */ +int +inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len, + struct wim_lookup_table *lookup_table) +{ + inode->i_lte = new_stream_from_data_buffer(data, len, lookup_table); + if (inode->i_lte == NULL) + return WIMLIB_ERR_NOMEM; + inode->i_resolved = 1; + return 0; +} + +/* Remove an alternate data stream from a WIM inode */ +void +inode_remove_ads(struct wim_inode *inode, u16 idx, + struct wim_lookup_table *lookup_table) +{ + struct wim_ads_entry *ads_entry; + struct wim_lookup_table_entry *lte; + + wimlib_assert(idx < inode->i_num_ads); + wimlib_assert(inode->i_resolved); + + ads_entry = &inode->i_ads_entries[idx]; + + DEBUG("Remove alternate data stream \"%"WS"\"", ads_entry->stream_name); + + lte = ads_entry->lte; + if (lte) + lte_decrement_refcnt(lte, lookup_table); + + destroy_ads_entry(ads_entry); + + memmove(&inode->i_ads_entries[idx], + &inode->i_ads_entries[idx + 1], + (inode->i_num_ads - idx - 1) * sizeof(inode->i_ads_entries[0])); + inode->i_num_ads--; +} + +bool +inode_has_unix_data(const struct wim_inode *inode) +{ + for (u16 i = 0; i < inode->i_num_ads; i++) + if (ads_entry_is_unix_data(&inode->i_ads_entries[i])) + return true; + return false; +} + +#ifndef __WIN32__ +int +inode_get_unix_data(const struct wim_inode *inode, + struct wimlib_unix_data *unix_data, + u16 *stream_idx_ret) +{ + const struct wim_ads_entry *ads_entry; + const struct wim_lookup_table_entry *lte; + size_t size; + int ret; + + wimlib_assert(inode->i_resolved); + + ads_entry = inode_get_ads_entry((struct wim_inode*)inode, + WIMLIB_UNIX_DATA_TAG, NULL); + if (ads_entry == NULL) + return NO_UNIX_DATA; + + if (stream_idx_ret) + *stream_idx_ret = ads_entry - inode->i_ads_entries; + + lte = ads_entry->lte; + if (lte == NULL) + return NO_UNIX_DATA; + + size = lte->size; + if (size != sizeof(struct wimlib_unix_data)) + return BAD_UNIX_DATA; + + ret = read_full_stream_into_buf(lte, unix_data); + if (ret) + return ret; + + if (unix_data->version != 0) + return BAD_UNIX_DATA; + return 0; +} + +int +inode_set_unix_data(struct wim_inode *inode, u16 uid, u16 gid, u16 mode, + struct wim_lookup_table *lookup_table, int which) +{ + struct wimlib_unix_data unix_data; + int ret; + bool have_good_unix_data = false; + bool have_unix_data = false; + u16 stream_idx; + + if (!(which & UNIX_DATA_CREATE)) { + ret = inode_get_unix_data(inode, &unix_data, &stream_idx); + if (ret == 0 || ret == BAD_UNIX_DATA || ret > 0) + have_unix_data = true; + if (ret == 0) + have_good_unix_data = true; + } + unix_data.version = 0; + if (which & UNIX_DATA_UID || !have_good_unix_data) + unix_data.uid = uid; + if (which & UNIX_DATA_GID || !have_good_unix_data) + unix_data.gid = gid; + if (which & UNIX_DATA_MODE || !have_good_unix_data) + unix_data.mode = mode; + ret = inode_add_ads_with_data(inode, WIMLIB_UNIX_DATA_TAG, + &unix_data, + sizeof(struct wimlib_unix_data), + lookup_table); + if (ret == 0 && have_unix_data) + inode_remove_ads(inode, stream_idx, lookup_table); + return ret; +} +#endif /* __WIN32__ */ + +/* + * Resolve an inode's lookup table entries. + * + * This replaces the SHA1 hash fields (which are used to lookup an entry in the + * lookup table) with pointers directly to the lookup table entries. + * + * If @force is %false: + * If any needed SHA1 message digests are not found in the lookup table, + * WIMLIB_ERR_RESOURCE_NOT_FOUND is returned and the inode is left + * unmodified. + * If @force is %true: + * If any needed SHA1 message digests are not found in the lookup table, + * new entries are allocated and inserted into the lookup table. + */ +int +inode_resolve_streams(struct wim_inode *inode, struct wim_lookup_table *table, + bool force) +{ + const u8 *hash; + + if (!inode->i_resolved) { + struct wim_lookup_table_entry *lte, *ads_lte; + + /* Resolve the default file stream */ + lte = NULL; + hash = inode->i_hash; + if (!is_zero_hash(hash)) { + lte = lookup_stream(table, hash); + if (!lte) { + if (force) { + lte = new_lookup_table_entry(); + if (!lte) + return WIMLIB_ERR_NOMEM; + copy_hash(lte->hash, hash); + lookup_table_insert(table, lte); + } else { + goto stream_not_found; + } + } + } + + /* Resolve the alternate data streams */ + struct wim_lookup_table_entry *ads_ltes[inode->i_num_ads]; + for (u16 i = 0; i < inode->i_num_ads; i++) { + struct wim_ads_entry *cur_entry; + + ads_lte = NULL; + cur_entry = &inode->i_ads_entries[i]; + hash = cur_entry->hash; + if (!is_zero_hash(hash)) { + ads_lte = lookup_stream(table, hash); + if (!ads_lte) { + if (force) { + ads_lte = new_lookup_table_entry(); + if (!ads_lte) + return WIMLIB_ERR_NOMEM; + copy_hash(ads_lte->hash, hash); + lookup_table_insert(table, ads_lte); + } else { + goto stream_not_found; + } + } + } + ads_ltes[i] = ads_lte; + } + inode->i_lte = lte; + for (u16 i = 0; i < inode->i_num_ads; i++) + inode->i_ads_entries[i].lte = ads_ltes[i]; + inode->i_resolved = 1; + } + return 0; + +stream_not_found: + return stream_not_found_error(inode, hash); +} + +void +inode_unresolve_streams(struct wim_inode *inode) +{ + if (inode->i_resolved) { + if (inode->i_lte) + copy_hash(inode->i_hash, inode->i_lte->hash); + else + zero_out_hash(inode->i_hash); + + for (u16 i = 0; i < inode->i_num_ads; i++) { + if (inode->i_ads_entries[i].lte) + copy_hash(inode->i_ads_entries[i].hash, + inode->i_ads_entries[i].lte->hash); + else + zero_out_hash(inode->i_ads_entries[i].hash); + } + inode->i_resolved = 0; + } +} + +/* + * Returns the lookup table entry for stream @stream_idx of the inode, where + * stream_idx = 0 means the default un-named file stream, and stream_idx >= 1 + * corresponds to an alternate data stream. + * + * This works for both resolved and un-resolved inodes. + */ +struct wim_lookup_table_entry * +inode_stream_lte(const struct wim_inode *inode, unsigned stream_idx, + const struct wim_lookup_table *table) +{ + if (inode->i_resolved) + return inode_stream_lte_resolved(inode, stream_idx); + else + return inode_stream_lte_unresolved(inode, stream_idx, table); +} + +struct wim_lookup_table_entry * +inode_unnamed_stream_resolved(const struct wim_inode *inode, u16 *stream_idx_ret) +{ + wimlib_assert(inode->i_resolved); + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + if (inode_stream_name_nbytes(inode, i) == 0 && + !is_zero_hash(inode_stream_hash_resolved(inode, i))) + { + *stream_idx_ret = i; + return inode_stream_lte_resolved(inode, i); + } + } + *stream_idx_ret = 0; + return NULL; +} + +struct wim_lookup_table_entry * +inode_unnamed_lte_resolved(const struct wim_inode *inode) +{ + u16 stream_idx; + return inode_unnamed_stream_resolved(inode, &stream_idx); +} + +struct wim_lookup_table_entry * +inode_unnamed_lte_unresolved(const struct wim_inode *inode, + const struct wim_lookup_table *table) +{ + wimlib_assert(!inode->i_resolved); + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + if (inode_stream_name_nbytes(inode, i) == 0 && + !is_zero_hash(inode_stream_hash_unresolved(inode, i))) + { + return inode_stream_lte_unresolved(inode, i, table); + } + } + return NULL; +} + +/* Return the lookup table entry for the unnamed data stream of an inode, or + * NULL if there is none. + * + * You'd think this would be easier than it actually is, since the unnamed data + * stream should be the one referenced from the inode itself. Alas, if there + * are named data streams, Microsoft's "imagex.exe" program will put the unnamed + * data stream in one of the alternate data streams instead of inside the WIM + * dentry itself. So we need to check the alternate data streams too. + * + * Also, note that a dentry may appear to have more than one unnamed stream, but + * if the SHA1 message digest is all 0's then the corresponding stream does not + * really "count" (this is the case for the inode's own file stream when the + * file stream that should be there is actually in one of the alternate stream + * entries.). This is despite the fact that we may need to extract such a + * missing entry as an empty file or empty named data stream. + */ +struct wim_lookup_table_entry * +inode_unnamed_lte(const struct wim_inode *inode, + const struct wim_lookup_table *table) +{ + if (inode->i_resolved) + return inode_unnamed_lte_resolved(inode); + else + return inode_unnamed_lte_unresolved(inode, table); +} + +/* Returns the SHA1 message digest of the unnamed data stream of a WIM inode, or + * 'zero_hash' if the unnamed data stream is missing has all zeroes in its SHA1 + * message digest field. */ +const u8 * +inode_unnamed_stream_hash(const struct wim_inode *inode) +{ + const u8 *hash; + + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + if (inode_stream_name_nbytes(inode, i) == 0) { + hash = inode_stream_hash(inode, i); + if (!is_zero_hash(hash)) + return hash; + } + } + return zero_hash; +} + +/* Given an unhashed stream, get the pointer to it in an inode. + * As this is only for unhashed streams, there can only be one such pointer. */ +struct wim_lookup_table_entry ** +retrieve_lte_pointer(struct wim_lookup_table_entry *lte) +{ + wimlib_assert(lte->unhashed); + struct wim_inode *inode = lte->back_inode; + u32 stream_id = lte->back_stream_id; + if (stream_id == 0) + return &inode->i_lte; + else + for (u16 i = 0; i < inode->i_num_ads; i++) + if (inode->i_ads_entries[i].stream_id == stream_id) + return &inode->i_ads_entries[i].lte; + wimlib_assert(0); + return NULL; +} + +int +stream_not_found_error(const struct wim_inode *inode, const u8 *hash) +{ + if (wimlib_print_errors) { + ERROR("\"%"TS"\": stream not found", inode_first_full_path(inode)); + tfprintf(stderr, T(" SHA-1 message digest of missing stream:\n ")); + print_hash(hash, stderr); + tputc(T('\n'), stderr); + } + return WIMLIB_ERR_RESOURCE_NOT_FOUND; +} + +/* + * Reads the alternate data stream entries of a WIM dentry. + * + * @p: + * Pointer to buffer that starts with the first alternate stream entry. + * + * @inode: + * Inode to load the alternate data streams into. @inode->i_num_ads must + * have been set to the number of alternate data streams that are expected. + * + * @remaining_size: + * Number of bytes of data remaining in the buffer pointed to by @p. + * + * On success, inode->i_ads_entries is set to an array of `struct + * wim_ads_entry's of length inode->i_num_ads. On failure, @inode is not + * modified. + * + * Return values: + * WIMLIB_ERR_SUCCESS (0) + * WIMLIB_ERR_INVALID_METADATA_RESOURCE + * WIMLIB_ERR_NOMEM + */ +int +read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode, + size_t nbytes_remaining) +{ + u16 num_ads; + struct wim_ads_entry *ads_entries; + int ret; + + BUILD_BUG_ON(sizeof(struct wim_ads_entry_on_disk) != WIM_ADS_ENTRY_DISK_SIZE); + + /* Allocate an array for our in-memory representation of the alternate + * data stream entries. */ + num_ads = inode->i_num_ads; + ads_entries = CALLOC(num_ads, sizeof(inode->i_ads_entries[0])); + if (ads_entries == NULL) + goto out_of_memory; + + /* Read the entries into our newly allocated buffer. */ + for (u16 i = 0; i < num_ads; i++) { + u64 length; + struct wim_ads_entry *cur_entry; + const struct wim_ads_entry_on_disk *disk_entry = + (const struct wim_ads_entry_on_disk*)p; + + cur_entry = &ads_entries[i]; + ads_entries[i].stream_id = i + 1; + + /* Do we have at least the size of the fixed-length data we know + * need? */ + if (nbytes_remaining < sizeof(struct wim_ads_entry_on_disk)) + goto out_invalid; + + /* Read the length field */ + length = le64_to_cpu(disk_entry->length); + + /* Make sure the length field is neither so small it doesn't + * include all the fixed-length data nor so large it overflows + * the metadata resource buffer. */ + if (length < sizeof(struct wim_ads_entry_on_disk) || + length > nbytes_remaining) + goto out_invalid; + + /* Read the rest of the fixed-length data. */ + + cur_entry->reserved = le64_to_cpu(disk_entry->reserved); + copy_hash(cur_entry->hash, disk_entry->hash); + cur_entry->stream_name_nbytes = le16_to_cpu(disk_entry->stream_name_nbytes); + + /* If stream_name_nbytes != 0, this is a named stream. + * Otherwise this is an unnamed stream, or in some cases (bugs + * in Microsoft's software I guess) a meaningless entry + * distinguished from the real unnamed stream entry, if any, by + * the fact that the real unnamed stream entry has a nonzero + * hash field. */ + if (cur_entry->stream_name_nbytes) { + /* The name is encoded in UTF16-LE, which uses 2-byte + * coding units, so the length of the name had better be + * an even number of bytes... */ + if (cur_entry->stream_name_nbytes & 1) + goto out_invalid; + + /* Add the length of the stream name to get the length + * we actually need to read. Make sure this isn't more + * than the specified length of the entry. */ + if (sizeof(struct wim_ads_entry_on_disk) + + cur_entry->stream_name_nbytes > length) + goto out_invalid; + + cur_entry->stream_name = MALLOC(cur_entry->stream_name_nbytes + 2); + if (cur_entry->stream_name == NULL) + goto out_of_memory; + + memcpy(cur_entry->stream_name, + disk_entry->stream_name, + cur_entry->stream_name_nbytes); + cur_entry->stream_name[cur_entry->stream_name_nbytes / 2] = cpu_to_le16(0); + } else { + /* Mark inode as having weird stream entries. */ + inode->i_canonical_streams = 0; + } + + /* It's expected that the size of every ADS entry is a multiple + * of 8. However, to be safe, I'm allowing the possibility of + * an ADS entry at the very end of the metadata resource ending + * un-aligned. So although we still need to increment the input + * pointer by @length to reach the next ADS entry, it's possible + * that less than @length is actually remaining in the metadata + * resource. We should set the remaining bytes to 0 if this + * happens. */ + length = (length + 7) & ~(u64)7; + p += length; + if (nbytes_remaining < length) + nbytes_remaining = 0; + else + nbytes_remaining -= length; + } + inode->i_ads_entries = ads_entries; + inode->i_next_stream_id = inode->i_num_ads + 1; + ret = 0; + goto out; +out_of_memory: + ret = WIMLIB_ERR_NOMEM; + goto out_free_ads_entries; +out_invalid: + ERROR("An alternate data stream entry is invalid"); + ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE; +out_free_ads_entries: + if (ads_entries) { + for (u16 i = 0; i < num_ads; i++) + destroy_ads_entry(&ads_entries[i]); + FREE(ads_entries); + } +out: + return ret; +} + +/* + * Verify a WIM inode: + * + * - Check to make sure the security ID is valid + * - Check to make sure there is at most one unnamed stream + * - Check to make sure there is at most one DOS name. + * + * Return values: + * WIMLIB_ERR_SUCCESS (0) + */ +int +verify_inode(struct wim_inode *inode, const struct wim_security_data *sd) +{ + struct wim_dentry *dentry; + + /* Check the security ID. -1 is valid and means "no security + * descriptor". Anything else has to be a valid index into the WIM + * image's security descriptors table. */ + if (inode->i_security_id < -1 || + (inode->i_security_id >= 0 && + inode->i_security_id >= sd->num_entries)) + { + WARNING("\"%"TS"\" has an invalid security ID (%d)", + inode_first_full_path(inode), inode->i_security_id); + inode->i_security_id = -1; + } + + /* Make sure there is only one unnamed data stream. */ + unsigned num_unnamed_streams = 0; + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + const u8 *hash; + hash = inode_stream_hash(inode, i); + if (inode_stream_name_nbytes(inode, i) == 0 && !is_zero_hash(hash)) + num_unnamed_streams++; + } + if (num_unnamed_streams > 1) { + WARNING("\"%"TS"\" has multiple (%u) un-named streams", + inode_first_full_path(inode), num_unnamed_streams); + } + + /* Files cannot have multiple DOS names, even if they have multiple + * names in multiple directories (i.e. hard links). + * Source: NTFS-3g authors. */ + struct wim_dentry *dentry_with_dos_name = NULL; + inode_for_each_dentry(dentry, inode) { + if (dentry_has_short_name(dentry)) { + if (dentry_with_dos_name) { + /* This was previously an error, but if we + * capture a WIM from UDF on Windows, hard links + * are supported but DOS names are automatically + * generated for all names for an inode. */ + #if 0 + ERROR("Hard-linked file has a DOS name at " + "both `%"TS"' and `%"TS"'", + dentry_full_path(dentry_with_dos_name), + dentry_full_path(dentry)); + return WIMLIB_ERR_INVALID_METADATA_RESOURCE; + #else + dentry->dos_name_invalid = 1; + #endif + } + dentry_with_dos_name = dentry; + } + } + return 0; +} + +void +inode_ref_streams(struct wim_inode *inode) +{ + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + struct wim_lookup_table_entry *lte; + lte = inode_stream_lte_resolved(inode, i); + if (lte) + lte->refcnt++; + } +} + +int +init_inode_table(struct wim_inode_table *table, size_t capacity) +{ + table->array = CALLOC(capacity, sizeof(table->array[0])); + if (table->array == NULL) { + ERROR("Cannot initalize inode table: out of memory"); + return WIMLIB_ERR_NOMEM; + } + table->num_entries = 0; + table->capacity = capacity; + INIT_LIST_HEAD(&table->extra_inodes); + return 0; +} + +void +destroy_inode_table(struct wim_inode_table *table) +{ + FREE(table->array); +} + +static struct wim_inode * +inode_table_get_inode(struct wim_inode_table *table, u64 ino, u64 devno) +{ + u64 hash = hash_u64(hash_u64(ino) + hash_u64(devno)); + size_t pos = hash % table->capacity; + struct wim_inode *inode; + struct hlist_node *cur; + + hlist_for_each_entry(inode, cur, &table->array[pos], i_hlist) { + if (inode->i_ino == ino && inode->i_devno == devno) { + DEBUG("Using existing inode {devno=%"PRIu64", ino=%"PRIu64"}", + devno, ino); + inode->i_nlink++; + return inode; + } + } + inode = new_timeless_inode(); + if (inode) { + inode->i_ino = ino; + inode->i_devno = devno; + hlist_add_head(&inode->i_hlist, &table->array[pos]); + table->num_entries++; + } + return inode; +} + + +/* Given a directory entry with the name @name for the file with the inode + * number @ino and device number @devno, create a new WIM dentry with an + * associated inode, where the inode is shared if an inode with the same @ino + * and @devno has already been created. On success, the new WIM dentry is + * written to *dentry_ret, and its inode has i_nlink > 1 if a previously + * existing inode was used. + */ +int +inode_table_new_dentry(struct wim_inode_table *table, const tchar *name, + u64 ino, u64 devno, bool noshare, + struct wim_dentry **dentry_ret) +{ + struct wim_dentry *dentry; + struct wim_inode *inode; + int ret; + + if (noshare) { + /* File that cannot be hardlinked--- Return a new inode with its + * inode and device numbers left at 0. */ + ret = new_dentry_with_timeless_inode(name, &dentry); + if (ret) + return ret; + list_add_tail(&dentry->d_inode->i_list, &table->extra_inodes); + } else { + /* File that can be hardlinked--- search the table for an + * existing inode matching the inode number and device; + * otherwise create a new inode. */ + ret = new_dentry(name, &dentry); + if (ret) + return ret; + inode = inode_table_get_inode(table, ino, devno); + if (!inode) { + free_dentry(dentry); + return WIMLIB_ERR_NOMEM; + } + /* If using an existing inode, we need to gain a reference to + * each of its streams. */ + if (inode->i_nlink > 1) + inode_ref_streams(inode); + dentry->d_inode = inode; + inode_add_dentry(dentry, inode); + } + *dentry_ret = dentry; + return 0; +} + + + +/* Assign consecutive inode numbers to a new set of inodes from the inode table, + * and append the inodes to a single list @head that contains the inodes already + * existing in the WIM image. */ +void +inode_table_prepare_inode_list(struct wim_inode_table *table, + struct list_head *head) +{ + struct wim_inode *inode, *tmp_inode; + struct hlist_node *cur, *tmp; + u64 cur_ino = 1; + + /* Re-assign inode numbers in the existing list to avoid duplicates. */ + list_for_each_entry(inode, head, i_list) + inode->i_ino = cur_ino++; + + /* Assign inode numbers to the new inodes and move them to the image's + * inode list. */ + for (size_t i = 0; i < table->capacity; i++) { + hlist_for_each_entry_safe(inode, cur, tmp, &table->array[i], i_hlist) + { + inode->i_ino = cur_ino++; + inode->i_devno = 0; + list_add_tail(&inode->i_list, head); + } + INIT_HLIST_HEAD(&table->array[i]); + } + list_for_each_entry_safe(inode, tmp_inode, &table->extra_inodes, i_list) + { + inode->i_ino = cur_ino++; + inode->i_devno = 0; + list_add_tail(&inode->i_list, head); + } + INIT_LIST_HEAD(&table->extra_inodes); + table->num_entries = 0; +} diff --git a/src/hardlink.c b/src/inode_fixup.c similarity index 74% rename from src/hardlink.c rename to src/inode_fixup.c index 3f316f1d..5f203b7b 100644 --- a/src/hardlink.c +++ b/src/inode_fixup.c @@ -1,7 +1,7 @@ /* - * hardlink.c + * inode_fixup.c * - * Code to deal with hard links in WIMs. + * See dentry_tree_fix_inodes() for description. */ /* @@ -27,47 +27,13 @@ # include "config.h" #endif -#include "wimlib/capture.h" #include "wimlib/dentry.h" #include "wimlib/error.h" +#include "wimlib/inode.h" +#include "wimlib/inode_table.h" #include "wimlib/lookup_table.h" -/* NULL NULL - * ^ ^ - * dentry | | - * / \ ----------- ----------- - * | dentry<---| struct | | struct |---> dentry - * \ / | wim_inode| | wim_inode| - * dentry ------------ ------------ - * ^ ^ - * | | - * | | dentry - * ----------- ----------- / \ - * dentry<---| struct | | struct |---> dentry dentry - * / | wim_inode| | wim_inode| \ / - * dentry ------------ ------------ dentry - * ^ ^ - * | | - * ----------------- - * wim_inode_table->array | idx 0 | idx 1 | - * ----------------- - */ - - -int -init_inode_table(struct wim_inode_table *table, size_t capacity) -{ - table->array = CALLOC(capacity, sizeof(table->array[0])); - if (!table->array) { - ERROR("Cannot initalize inode table: out of memory"); - return WIMLIB_ERR_NOMEM; - } - table->num_entries = 0; - table->capacity = capacity; - INIT_LIST_HEAD(&table->extra_inodes); - return 0; -} - +/* Manual link count of inode (normally we can just check i_nlink) */ static inline size_t inode_link_count(const struct wim_inode *inode) { @@ -78,137 +44,6 @@ inode_link_count(const struct wim_inode *inode) return size; } -/* Insert a dentry into the inode table based on the inode number of the - * attached inode (which came from the hard link group ID field of the on-disk - * WIM dentry) */ -static int -inode_table_insert(struct wim_dentry *dentry, void *_table) -{ - struct wim_inode_table *table = _table; - struct wim_inode *d_inode = dentry->d_inode; - - if (d_inode->i_ino == 0) { - /* A dentry with a hard link group ID of 0 indicates that it's - * in a hard link group by itself. Add it to the list of extra - * inodes rather than inserting it into the hash lists. */ - list_add_tail(&d_inode->i_list, &table->extra_inodes); - } else { - size_t pos; - struct wim_inode *inode; - struct hlist_node *cur; - - /* Try adding this dentry to an existing inode */ - pos = d_inode->i_ino % table->capacity; - hlist_for_each_entry(inode, cur, &table->array[pos], i_hlist) { - if (inode->i_ino == d_inode->i_ino) { - if (unlikely((inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) || - (d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))) - { - ERROR("Unsupported directory hard link " - "\"%"TS"\" <=> \"%"TS"\"", - dentry_full_path(dentry), - dentry_full_path(inode_first_dentry(inode))); - return WIMLIB_ERR_INVALID_METADATA_RESOURCE; - } - inode_add_dentry(dentry, inode); - return 0; - } - } - - /* No inode in the table has the same number as this one, so add - * it to the table. */ - hlist_add_head(&d_inode->i_hlist, &table->array[pos]); - - /* XXX Make the table grow when too many entries have been - * inserted. */ - table->num_entries++; - } - return 0; -} - -static struct wim_inode * -inode_table_get_inode(struct wim_inode_table *table, u64 ino, u64 devno) -{ - u64 hash = hash_u64(hash_u64(ino) + hash_u64(devno)); - size_t pos = hash % table->capacity; - struct wim_inode *inode; - struct hlist_node *cur; - - hlist_for_each_entry(inode, cur, &table->array[pos], i_hlist) { - if (inode->i_ino == ino && inode->i_devno == devno) { - DEBUG("Using existing inode {devno=%"PRIu64", ino=%"PRIu64"}", - devno, ino); - inode->i_nlink++; - return inode; - } - } - inode = new_timeless_inode(); - if (inode) { - inode->i_ino = ino; - inode->i_devno = devno; - hlist_add_head(&inode->i_hlist, &table->array[pos]); - table->num_entries++; - } - return inode; -} - -void -inode_ref_streams(struct wim_inode *inode) -{ - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - struct wim_lookup_table_entry *lte; - lte = inode_stream_lte_resolved(inode, i); - if (lte) - lte->refcnt++; - } -} - -/* Given a directory entry with the name @name for the file with the inode - * number @ino and device number @devno, create a new WIM dentry with an - * associated inode, where the inode is shared if an inode with the same @ino - * and @devno has already been created. On success, the new WIM dentry is - * written to *dentry_ret, and its inode has i_nlink > 1 if a previously - * existing inode was used. - */ -int -inode_table_new_dentry(struct wim_inode_table *table, const tchar *name, - u64 ino, u64 devno, bool noshare, - struct wim_dentry **dentry_ret) -{ - struct wim_dentry *dentry; - struct wim_inode *inode; - int ret; - - if (noshare) { - /* File that cannot be hardlinked--- Return a new inode with its - * inode and device numbers left at 0. */ - ret = new_dentry_with_timeless_inode(name, &dentry); - if (ret) - return ret; - list_add_tail(&dentry->d_inode->i_list, &table->extra_inodes); - } else { - /* File that can be hardlinked--- search the table for an - * existing inode matching the inode number and device; - * otherwise create a new inode. */ - ret = new_dentry(name, &dentry); - if (ret) - return ret; - inode = inode_table_get_inode(table, ino, devno); - if (!inode) { - free_dentry(dentry); - return WIMLIB_ERR_NOMEM; - } - /* If using an existing inode, we need to gain a reference to - * each of its streams. */ - if (inode->i_nlink > 1) - inode_ref_streams(inode); - dentry->d_inode = inode; - inode_add_dentry(dentry, inode); - } - *dentry_ret = dentry; - return 0; -} - static inline void print_inode_dentries(const struct wim_inode *inode) { @@ -228,6 +63,15 @@ inconsistent_inode(const struct wim_inode *inode) } } +static bool +ads_entries_have_same_name(const struct wim_ads_entry *entry_1, + const struct wim_ads_entry *entry_2) +{ + return entry_1->stream_name_nbytes == entry_2->stream_name_nbytes && + memcmp(entry_1->stream_name, entry_2->stream_name, + entry_1->stream_name_nbytes) == 0; +} + static bool ref_inodes_consistent(const struct wim_inode * restrict ref_inode_1, const struct wim_inode * restrict ref_inode_2) @@ -490,6 +334,55 @@ fix_inodes(struct wim_inode_table *table, struct list_head *inode_list, return 0; } +/* Insert a dentry into the inode table based on the inode number of the + * attached inode (which came from the hard link group ID field of the on-disk + * WIM dentry) */ +static int +inode_table_insert(struct wim_dentry *dentry, void *_table) +{ + struct wim_inode_table *table = _table; + struct wim_inode *d_inode = dentry->d_inode; + + if (d_inode->i_ino == 0) { + /* A dentry with a hard link group ID of 0 indicates that it's + * in a hard link group by itself. Add it to the list of extra + * inodes rather than inserting it into the hash lists. */ + list_add_tail(&d_inode->i_list, &table->extra_inodes); + } else { + size_t pos; + struct wim_inode *inode; + struct hlist_node *cur; + + /* Try adding this dentry to an existing inode */ + pos = d_inode->i_ino % table->capacity; + hlist_for_each_entry(inode, cur, &table->array[pos], i_hlist) { + if (inode->i_ino == d_inode->i_ino) { + if (unlikely((inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) || + (d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))) + { + ERROR("Unsupported directory hard link " + "\"%"TS"\" <=> \"%"TS"\"", + dentry_full_path(dentry), + dentry_full_path(inode_first_dentry(inode))); + return WIMLIB_ERR_INVALID_METADATA_RESOURCE; + } + inode_add_dentry(dentry, inode); + return 0; + } + } + + /* No inode in the table has the same number as this one, so add + * it to the table. */ + hlist_add_head(&d_inode->i_hlist, &table->array[pos]); + + /* XXX Make the table grow when too many entries have been + * inserted. */ + table->num_entries++; + } + return 0; +} + + /* * dentry_tree_fix_inodes(): * @@ -577,39 +470,3 @@ out_destroy_inode_table_raw: out: return ret; } - -/* Assign consecutive inode numbers to a new set of inodes from the inode table, - * and append the inodes to a single list @head that contains the inodes already - * existing in the WIM image. */ -void -inode_table_prepare_inode_list(struct wim_inode_table *table, - struct list_head *head) -{ - struct wim_inode *inode, *tmp_inode; - struct hlist_node *cur, *tmp; - u64 cur_ino = 1; - - /* Re-assign inode numbers in the existing list to avoid duplicates. */ - list_for_each_entry(inode, head, i_list) - inode->i_ino = cur_ino++; - - /* Assign inode numbers to the new inodes and move them to the image's - * inode list. */ - for (size_t i = 0; i < table->capacity; i++) { - hlist_for_each_entry_safe(inode, cur, tmp, &table->array[i], i_hlist) - { - inode->i_ino = cur_ino++; - inode->i_devno = 0; - list_add_tail(&inode->i_list, head); - } - INIT_HLIST_HEAD(&table->array[i]); - } - list_for_each_entry_safe(inode, tmp_inode, &table->extra_inodes, i_list) - { - inode->i_ino = cur_ino++; - inode->i_devno = 0; - list_add_tail(&inode->i_list, head); - } - INIT_LIST_HEAD(&table->extra_inodes); - table->num_entries = 0; -} diff --git a/src/iterate_dir.c b/src/iterate_dir.c new file mode 100644 index 00000000..042c76ac --- /dev/null +++ b/src/iterate_dir.c @@ -0,0 +1,258 @@ +/* + * iterate_dir.c + * + * Iterate through files in a WIM image. + * This is the stable API; internal code can just use for_dentry_in_tree(). + */ + +/* + * Copyright (C) 2013 Eric Biggers + * + * This file is part of wimlib, a library for working with WIM files. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with wimlib; if not, see http://www.gnu.org/licenses/. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "wimlib.h" +#include "wimlib/dentry.h" +#include "wimlib/encoding.h" +#include "wimlib/lookup_table.h" +#include "wimlib/metadata.h" +#include "wimlib/paths.h" +#include "wimlib/security.h" +#include "wimlib/timestamp.h" +#include "wimlib/util.h" +#include "wimlib/wim.h" + +static int +init_wimlib_dentry(struct wimlib_dir_entry *wdentry, + struct wim_dentry *dentry, + const WIMStruct *wim, + int flags) +{ + int ret; + size_t dummy; + const struct wim_inode *inode = dentry->d_inode; + struct wim_lookup_table_entry *lte; + const u8 *hash; + +#if TCHAR_IS_UTF16LE + wdentry->filename = dentry->file_name; + wdentry->dos_name = dentry->short_name; +#else + if (dentry_has_long_name(dentry)) { + ret = utf16le_to_tstr(dentry->file_name, + dentry->file_name_nbytes, + (tchar**)&wdentry->filename, + &dummy); + if (ret) + return ret; + } + if (dentry_has_short_name(dentry)) { + ret = utf16le_to_tstr(dentry->short_name, + dentry->short_name_nbytes, + (tchar**)&wdentry->dos_name, + &dummy); + if (ret) + return ret; + } +#endif + ret = calculate_dentry_full_path(dentry); + if (ret) + return ret; + wdentry->full_path = dentry->_full_path; + + for (struct wim_dentry *d = dentry; !dentry_is_root(d); d = d->parent) + wdentry->depth++; + + if (inode->i_security_id >= 0) { + const struct wim_security_data *sd = wim_const_security_data(wim); + wdentry->security_descriptor = sd->descriptors[inode->i_security_id]; + wdentry->security_descriptor_size = sd->sizes[inode->i_security_id]; + } + wdentry->reparse_tag = inode->i_reparse_tag; + wdentry->num_links = inode->i_nlink; + wdentry->attributes = inode->i_attributes; + wdentry->hard_link_group_id = inode->i_ino; + wdentry->creation_time = wim_timestamp_to_timespec(inode->i_creation_time); + wdentry->last_write_time = wim_timestamp_to_timespec(inode->i_last_write_time); + wdentry->last_access_time = wim_timestamp_to_timespec(inode->i_last_access_time); + + lte = inode_unnamed_lte(inode, wim->lookup_table); + if (lte) { + lte_to_wimlib_resource_entry(lte, &wdentry->streams[0].resource); + } else if (!is_zero_hash(hash = inode_unnamed_stream_hash(inode))) { + if (flags & WIMLIB_ITERATE_DIR_TREE_FLAG_RESOURCES_NEEDED) + return stream_not_found_error(inode, hash); + copy_hash(wdentry->streams[0].resource.sha1_hash, hash); + wdentry->streams[0].resource.is_missing = 1; + } + + for (unsigned i = 0; i < inode->i_num_ads; i++) { + if (!ads_entry_is_named_stream(&inode->i_ads_entries[i])) + continue; + lte = inode_stream_lte(inode, i + 1, wim->lookup_table); + wdentry->num_named_streams++; + if (lte) { + lte_to_wimlib_resource_entry(lte, &wdentry->streams[ + wdentry->num_named_streams].resource); + } else if (!is_zero_hash(hash = inode_stream_hash(inode, i + 1))) { + if (flags & WIMLIB_ITERATE_DIR_TREE_FLAG_RESOURCES_NEEDED) + return stream_not_found_error(inode, hash); + copy_hash(wdentry->streams[ + wdentry->num_named_streams].resource.sha1_hash, hash); + wdentry->streams[ + wdentry->num_named_streams].resource.is_missing = 1; + } + #if TCHAR_IS_UTF16LE + wdentry->streams[wdentry->num_named_streams].stream_name = + inode->i_ads_entries[i].stream_name; + #else + size_t dummy; + + ret = utf16le_to_tstr(inode->i_ads_entries[i].stream_name, + inode->i_ads_entries[i].stream_name_nbytes, + (tchar**)&wdentry->streams[ + wdentry->num_named_streams].stream_name, + &dummy); + if (ret) + return ret; + #endif + } + return 0; +} + +static void +free_wimlib_dentry(struct wimlib_dir_entry *wdentry) +{ +#if !TCHAR_IS_UTF16LE + FREE((tchar*)wdentry->filename); + FREE((tchar*)wdentry->dos_name); + for (unsigned i = 1; i <= wdentry->num_named_streams; i++) + FREE((tchar*)wdentry->streams[i].stream_name); +#endif + FREE(wdentry); +} + +struct iterate_dir_tree_ctx { + WIMStruct *wim; + int flags; + wimlib_iterate_dir_tree_callback_t cb; + void *user_ctx; +}; + +static int +do_iterate_dir_tree(WIMStruct *wim, + struct wim_dentry *dentry, int flags, + wimlib_iterate_dir_tree_callback_t cb, + void *user_ctx); + +static int +call_do_iterate_dir_tree(struct wim_dentry *dentry, void *_ctx) +{ + struct iterate_dir_tree_ctx *ctx = _ctx; + return do_iterate_dir_tree(ctx->wim, dentry, ctx->flags, + ctx->cb, ctx->user_ctx); +} + +static int +do_iterate_dir_tree(WIMStruct *wim, + struct wim_dentry *dentry, int flags, + wimlib_iterate_dir_tree_callback_t cb, + void *user_ctx) +{ + struct wimlib_dir_entry *wdentry; + int ret = WIMLIB_ERR_NOMEM; + + + wdentry = CALLOC(1, sizeof(struct wimlib_dir_entry) + + (1 + dentry->d_inode->i_num_ads) * + sizeof(struct wimlib_stream_entry)); + if (wdentry == NULL) + goto out; + + ret = init_wimlib_dentry(wdentry, dentry, wim, flags); + if (ret) + goto out_free_wimlib_dentry; + + if (!(flags & WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN)) { + ret = (*cb)(wdentry, user_ctx); + if (ret) + goto out_free_wimlib_dentry; + } + + if (flags & (WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE | + WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN)) + { + struct iterate_dir_tree_ctx ctx = { + .wim = wim, + .flags = flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_CHILDREN, + .cb = cb, + .user_ctx = user_ctx, + }; + ret = for_dentry_child(dentry, call_do_iterate_dir_tree, &ctx); + } +out_free_wimlib_dentry: + free_wimlib_dentry(wdentry); +out: + return ret; +} + +struct image_iterate_dir_tree_ctx { + const tchar *path; + int flags; + wimlib_iterate_dir_tree_callback_t cb; + void *user_ctx; +}; + + +static int +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, 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); +} + +/* API function documented in wimlib.h */ +WIMLIBAPI int +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, + .cb = cb, + .user_ctx = user_ctx, + }; + wim->private = &ctx; + ret = for_image(wim, image, image_do_iterate_dir_tree); + FREE(path); + return ret; +} diff --git a/src/lookup_table.c b/src/lookup_table.c index 815d576f..4cae97ce 100644 --- a/src/lookup_table.c +++ b/src/lookup_table.c @@ -28,22 +28,34 @@ # include "config.h" #endif +#include "wimlib/assert.h" #include "wimlib/endianness.h" #include "wimlib/error.h" -#include "wimlib/file_io.h" -#include "wimlib/glob.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" -#include "wimlib/paths.h" +#include "wimlib/ntfs_3g.h" #include "wimlib/resource.h" #include "wimlib/util.h" #include "wimlib/write.h" -#include #include -#ifdef WITH_FUSE -# include /* for unlink() */ -#endif +#include +#include /* for unlink() */ + +/* WIM lookup table: + * + * This is a logical mapping from SHA1 message digests to the data streams + * contained in a WIM. + * + * Here it is implemented as a hash table. + * + * Note: Everything will break horribly if there is a SHA1 collision. + */ +struct wim_lookup_table { + struct hlist_head *array; + size_t num_entries; + size_t capacity; +}; struct wim_lookup_table * new_lookup_table(size_t capacity) @@ -51,21 +63,48 @@ new_lookup_table(size_t capacity) struct wim_lookup_table *table; struct hlist_head *array; - table = CALLOC(1, sizeof(struct wim_lookup_table)); - if (table) { - array = CALLOC(capacity, sizeof(array[0])); - if (array) { - table->num_entries = 0; - table->capacity = capacity; - table->array = array; - } else { - FREE(table); - table = NULL; - ERROR("Failed to allocate memory for lookup table " - "with capacity %zu", capacity); - } + table = MALLOC(sizeof(struct wim_lookup_table)); + if (table == NULL) + goto oom; + + array = CALLOC(capacity, sizeof(array[0])); + if (array == NULL) { + FREE(table); + goto oom; } + + table->num_entries = 0; + table->capacity = capacity; + table->array = array; return table; + +oom: + ERROR("Failed to allocate memory for lookup table " + "with capacity %zu", capacity); + return NULL; +} + +static int +do_free_lookup_table_entry(struct wim_lookup_table_entry *entry, void *ignore) +{ + free_lookup_table_entry(entry); + return 0; +} + +void +free_lookup_table(struct wim_lookup_table *table) +{ + DEBUG("Freeing lookup table."); + if (table == NULL) + return; + + if (table->array) { + for_lookup_table_entry(table, + do_free_lookup_table_entry, + NULL); + FREE(table->array); + } + FREE(table); } struct wim_lookup_table_entry * @@ -74,14 +113,14 @@ new_lookup_table_entry(void) struct wim_lookup_table_entry *lte; lte = CALLOC(1, sizeof(struct wim_lookup_table_entry)); - if (lte == NULL) { - ERROR("Out of memory (tried to allocate %zu bytes for " - "lookup table entry)", - sizeof(struct wim_lookup_table_entry)); + if (lte == NULL) return NULL; - } + lte->refcnt = 1; + + /* lte->resource_location = RESOURCE_NONEXISTENT */ BUILD_BUG_ON(RESOURCE_NONEXISTENT != 0); + return lte; } @@ -144,6 +183,7 @@ clone_lookup_table_entry(const struct wim_lookup_table_entry *old) break; } return new; + out_free: free_lookup_table_entry(new); return NULL; @@ -189,28 +229,51 @@ free_lookup_table_entry(struct wim_lookup_table_entry *lte) } } -static int -do_free_lookup_table_entry(struct wim_lookup_table_entry *entry, void *ignore) +/* Decrements the reference count for the lookup table entry @lte. If its + * reference count reaches 0, it is unlinked from the lookup table. If, + * furthermore, the entry has no opened file descriptors associated with it, the + * entry is freed. */ +void +lte_decrement_refcnt(struct wim_lookup_table_entry *lte, + struct wim_lookup_table *table) { - free_lookup_table_entry(entry); - return 0; -} + wimlib_assert(lte->refcnt != 0); + if (--lte->refcnt == 0) { + if (lte->unhashed) { + list_del(<e->unhashed_list); + #ifdef WITH_FUSE + /* If the stream has been extracted to a staging file + * for a FUSE mount, unlink the staging file. (Note + * that there still may be open file descriptors to it.) + * */ + if (lte->resource_location == RESOURCE_IN_STAGING_FILE) + unlink(lte->staging_file_name); + #endif + } else { + lookup_table_unlink(table, lte); + } + + /* If FUSE mounts are enabled, we don't actually free the entry + * until the last file descriptor has been closed by + * lte_decrement_num_opened_fds(). */ +#ifdef WITH_FUSE + if (lte->num_opened_fds == 0) +#endif + free_lookup_table_entry(lte); + } +} +#ifdef WITH_FUSE void -free_lookup_table(struct wim_lookup_table *table) +lte_decrement_num_opened_fds(struct wim_lookup_table_entry *lte) { - DEBUG("Freeing lookup table."); - if (table) { - if (table->array) { - for_lookup_table_entry(table, - do_free_lookup_table_entry, - NULL); - FREE(table->array); - } - FREE(table); - } + wimlib_assert(lte->num_opened_fds != 0); + + if (--lte->num_opened_fds == 0 && lte->refcnt == 0) + free_lookup_table_entry(lte); } +#endif static void lookup_table_insert_raw(struct wim_lookup_table *table, @@ -248,13 +311,7 @@ enlarge_lookup_table(struct wim_lookup_table *table) FREE(old_array); } - -/* - * Inserts an entry into the lookup table. - * - * @table: A pointer to the lookup table. - * @lte: A pointer to the entry to insert. - */ +/* Inserts an entry into the lookup table. */ void lookup_table_insert(struct wim_lookup_table *table, struct wim_lookup_table_entry *lte) @@ -264,49 +321,33 @@ lookup_table_insert(struct wim_lookup_table *table, enlarge_lookup_table(table); } -static void -finalize_lte(struct wim_lookup_table_entry *lte) -{ - #ifdef WITH_FUSE - if (lte->resource_location == RESOURCE_IN_STAGING_FILE) { - unlink(lte->staging_file_name); - list_del(<e->unhashed_list); - } - #endif - free_lookup_table_entry(lte); -} - -/* Decrements the reference count for the lookup table entry @lte. If its - * reference count reaches 0, it is unlinked from the lookup table. If, - * furthermore, the entry has no opened file descriptors associated with it, the - * entry is freed. */ +/* Unlinks a lookup table entry from the table; does not free it. */ void -lte_decrement_refcnt(struct wim_lookup_table_entry *lte, - struct wim_lookup_table *table) +lookup_table_unlink(struct wim_lookup_table *table, + struct wim_lookup_table_entry *lte) { - wimlib_assert(lte != NULL); - wimlib_assert(lte->refcnt != 0); - if (--lte->refcnt == 0) { - if (lte->unhashed) - list_del(<e->unhashed_list); - else - lookup_table_unlink(table, lte); - #ifdef WITH_FUSE - if (lte->num_opened_fds == 0) - #endif - finalize_lte(lte); - } + wimlib_assert(!lte->unhashed); + wimlib_assert(table->num_entries != 0); + + hlist_del(<e->hash_list); + table->num_entries--; } -#ifdef WITH_FUSE -void -lte_decrement_num_opened_fds(struct wim_lookup_table_entry *lte) +/* Given a SHA1 message digest, return the corresponding entry in the WIM's + * lookup table, or NULL if there is none. */ +struct wim_lookup_table_entry * +lookup_stream(const struct wim_lookup_table *table, const u8 hash[]) { - if (lte->num_opened_fds != 0) - if (--lte->num_opened_fds == 0 && lte->refcnt == 0) - finalize_lte(lte); + size_t i; + struct wim_lookup_table_entry *lte; + struct hlist_node *pos; + + i = *(size_t*)hash % table->capacity; + hlist_for_each_entry(lte, pos, &table->array[i], hash_list) + if (hashes_equal(hash, lte->hash)) + return lte; + return NULL; } -#endif /* Calls a function on all the entries in the WIM lookup table. Stop early and * return nonzero if any call to the function returns nonzero. */ @@ -814,7 +855,7 @@ read_wim_lookup_table(WIMStruct *wim) /* Lookup table entry for a stream that is not a metadata * resource. */ - duplicate_entry = lookup_resource(table, cur_entry->hash); + duplicate_entry = lookup_stream(table, cur_entry->hash); if (duplicate_entry) { if (wimlib_print_errors) { WARNING("The WIM lookup table contains two entries with the " @@ -994,6 +1035,98 @@ lte_free_extracted_file(struct wim_lookup_table_entry *lte, void *_ignore) return 0; } +/* Allocate a stream entry for the contents of the buffer, or re-use an existing + * entry in @lookup_table for the same stream. */ +struct wim_lookup_table_entry * +new_stream_from_data_buffer(const void *buffer, size_t size, + struct wim_lookup_table *lookup_table) +{ + u8 hash[SHA1_HASH_SIZE]; + struct wim_lookup_table_entry *lte, *existing_lte; + + sha1_buffer(buffer, size, hash); + existing_lte = lookup_stream(lookup_table, hash); + if (existing_lte) { + wimlib_assert(existing_lte->size == size); + lte = existing_lte; + lte->refcnt++; + } else { + void *buffer_copy; + lte = new_lookup_table_entry(); + if (lte == NULL) + return NULL; + buffer_copy = memdup(buffer, size); + if (buffer_copy == NULL) { + free_lookup_table_entry(lte); + return NULL; + } + lte->resource_location = RESOURCE_IN_ATTACHED_BUFFER; + lte->attached_buffer = buffer_copy; + lte->size = size; + copy_hash(lte->hash, hash); + lookup_table_insert(lookup_table, lte); + } + return lte; +} + +/* Calculate the SHA1 message digest of a stream and move it from the list of + * unhashed streams to the stream lookup table, possibly joining it with an + * existing lookup table entry for an identical stream. + * + * @lte: An unhashed lookup table entry. + * @lookup_table: Lookup table for the WIM. + * @lte_ret: On success, write a pointer to the resulting lookup table + * entry to this location. This will be the same as @lte + * if it was inserted into the lookup table, or different if + * a duplicate stream was found. + * + * Returns 0 on success; nonzero if there is an error reading the stream. + */ +int +hash_unhashed_stream(struct wim_lookup_table_entry *lte, + struct wim_lookup_table *lookup_table, + struct wim_lookup_table_entry **lte_ret) +{ + int ret; + struct wim_lookup_table_entry *duplicate_lte; + struct wim_lookup_table_entry **back_ptr; + + wimlib_assert(lte->unhashed); + + /* back_ptr must be saved because @back_inode and @back_stream_id are in + * union with the SHA1 message digest and will no longer be valid once + * the SHA1 has been calculated. */ + back_ptr = retrieve_lte_pointer(lte); + + ret = sha1_stream(lte); + if (ret) + return ret; + + /* Look for a duplicate stream */ + duplicate_lte = lookup_stream(lookup_table, lte->hash); + list_del(<e->unhashed_list); + if (duplicate_lte) { + /* We have a duplicate stream. Transfer the reference counts + * from this stream to the duplicate and update the reference to + * this stream (in an inode or ads_entry) to point to the + * duplicate. The caller is responsible for freeing @lte if + * needed. */ + wimlib_assert(!(duplicate_lte->unhashed)); + wimlib_assert(duplicate_lte->size == lte->size); + duplicate_lte->refcnt += lte->refcnt; + lte->refcnt = 0; + *back_ptr = duplicate_lte; + lte = duplicate_lte; + } else { + /* No duplicate stream, so we need to insert this stream into + * the lookup table and treat it as a hashed stream. */ + lookup_table_insert(lookup_table, lte); + lte->unhashed = 0; + } + *lte_ret = lte; + return 0; +} + void print_lookup_table_entry(const struct wim_lookup_table_entry *lte, FILE *out) { @@ -1140,555 +1273,3 @@ wimlib_iterate_lookup_table(WIMStruct *wim, int flags, } return for_lookup_table_entry(wim->lookup_table, do_iterate_lte, &ctx); } - -/* Given a SHA1 message digest, return the corresponding entry in the WIM's - * lookup table, or NULL if there is none. */ -struct wim_lookup_table_entry * -lookup_resource(const struct wim_lookup_table *table, const u8 hash[]) -{ - size_t i; - struct wim_lookup_table_entry *lte; - struct hlist_node *pos; - - wimlib_assert(table != NULL); - wimlib_assert(hash != NULL); - - i = *(size_t*)hash % table->capacity; - hlist_for_each_entry(lte, pos, &table->array[i], hash_list) - if (hashes_equal(hash, lte->hash)) - return lte; - return NULL; -} - -#ifdef WITH_FUSE -/* - * Finds the dentry, lookup table entry, and stream index for a WIM file stream, - * given a path name. - * - * This is only for pre-resolved inodes. - */ -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_ltes(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; - goto out; - } else { - return -ENOENT; - } - } else { - lte = inode_unnamed_stream_resolved(inode, &stream_idx); - } -out: - if (dentry_ret) - *dentry_ret = dentry; - if (lte_ret) - *lte_ret = lte; - if (stream_idx_ret) - *stream_idx_ret = stream_idx; - return 0; -} -#endif - -int -resource_not_found_error(const struct wim_inode *inode, const u8 *hash) -{ - if (wimlib_print_errors) { - ERROR("\"%"TS"\": resource not found", inode_first_full_path(inode)); - tfprintf(stderr, T(" SHA-1 message digest of missing resource:\n ")); - print_hash(hash, stderr); - tputc(T('\n'), stderr); - } - return WIMLIB_ERR_RESOURCE_NOT_FOUND; -} - -/* - * Resolve an inode's lookup table entries. - * - * This replaces the SHA1 hash fields (which are used to lookup an entry in the - * lookup table) with pointers directly to the lookup table entries. - * - * If @force is %false: - * If any needed SHA1 message digests are not found in the lookup table, - * WIMLIB_ERR_RESOURCE_NOT_FOUND is returned and the inode is left - * unmodified. - * If @force is %true: - * If any needed SHA1 message digests are not found in the lookup table, - * new entries are allocated and inserted into the lookup table. - */ -int -inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table, - bool force) -{ - const u8 *hash; - - if (!inode->i_resolved) { - struct wim_lookup_table_entry *lte, *ads_lte; - - /* Resolve the default file stream */ - lte = NULL; - hash = inode->i_hash; - if (!is_zero_hash(hash)) { - lte = lookup_resource(table, hash); - if (!lte) { - if (force) { - lte = new_lookup_table_entry(); - if (!lte) - return WIMLIB_ERR_NOMEM; - copy_hash(lte->hash, hash); - lookup_table_insert(table, lte); - } else { - goto resource_not_found; - } - } - } - - /* Resolve the alternate data streams */ - struct wim_lookup_table_entry *ads_ltes[inode->i_num_ads]; - for (u16 i = 0; i < inode->i_num_ads; i++) { - struct wim_ads_entry *cur_entry; - - ads_lte = NULL; - cur_entry = &inode->i_ads_entries[i]; - hash = cur_entry->hash; - if (!is_zero_hash(hash)) { - ads_lte = lookup_resource(table, hash); - if (!ads_lte) { - if (force) { - ads_lte = new_lookup_table_entry(); - if (!ads_lte) - return WIMLIB_ERR_NOMEM; - copy_hash(ads_lte->hash, hash); - lookup_table_insert(table, ads_lte); - } else { - goto resource_not_found; - } - } - } - ads_ltes[i] = ads_lte; - } - inode->i_lte = lte; - for (u16 i = 0; i < inode->i_num_ads; i++) - inode->i_ads_entries[i].lte = ads_ltes[i]; - inode->i_resolved = 1; - } - return 0; - -resource_not_found: - return resource_not_found_error(inode, hash); -} - -void -inode_unresolve_ltes(struct wim_inode *inode) -{ - if (inode->i_resolved) { - if (inode->i_lte) - copy_hash(inode->i_hash, inode->i_lte->hash); - else - zero_out_hash(inode->i_hash); - - for (u16 i = 0; i < inode->i_num_ads; i++) { - if (inode->i_ads_entries[i].lte) - copy_hash(inode->i_ads_entries[i].hash, - inode->i_ads_entries[i].lte->hash); - else - zero_out_hash(inode->i_ads_entries[i].hash); - } - inode->i_resolved = 0; - } -} - -/* - * Returns the lookup table entry for stream @stream_idx of the inode, where - * stream_idx = 0 means the default un-named file stream, and stream_idx >= 1 - * corresponds to an alternate data stream. - * - * This works for both resolved and un-resolved inodes. - */ -struct wim_lookup_table_entry * -inode_stream_lte(const struct wim_inode *inode, unsigned stream_idx, - const struct wim_lookup_table *table) -{ - if (inode->i_resolved) - return inode_stream_lte_resolved(inode, stream_idx); - else - return inode_stream_lte_unresolved(inode, stream_idx, table); -} - -struct wim_lookup_table_entry * -inode_unnamed_stream_resolved(const struct wim_inode *inode, u16 *stream_idx_ret) -{ - wimlib_assert(inode->i_resolved); - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - if (inode_stream_name_nbytes(inode, i) == 0 && - !is_zero_hash(inode_stream_hash_resolved(inode, i))) - { - *stream_idx_ret = i; - return inode_stream_lte_resolved(inode, i); - } - } - *stream_idx_ret = 0; - return NULL; -} - -struct wim_lookup_table_entry * -inode_unnamed_lte_resolved(const struct wim_inode *inode) -{ - u16 stream_idx; - return inode_unnamed_stream_resolved(inode, &stream_idx); -} - -struct wim_lookup_table_entry * -inode_unnamed_lte_unresolved(const struct wim_inode *inode, - const struct wim_lookup_table *table) -{ - wimlib_assert(!inode->i_resolved); - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - if (inode_stream_name_nbytes(inode, i) == 0 && - !is_zero_hash(inode_stream_hash_unresolved(inode, i))) - { - return inode_stream_lte_unresolved(inode, i, table); - } - } - return NULL; -} - -/* Return the lookup table entry for the unnamed data stream of an inode, or - * NULL if there is none. - * - * You'd think this would be easier than it actually is, since the unnamed data - * stream should be the one referenced from the inode itself. Alas, if there - * are named data streams, Microsoft's "imagex.exe" program will put the unnamed - * data stream in one of the alternate data streams instead of inside the WIM - * dentry itself. So we need to check the alternate data streams too. - * - * Also, note that a dentry may appear to have more than one unnamed stream, but - * if the SHA1 message digest is all 0's then the corresponding stream does not - * really "count" (this is the case for the inode's own file stream when the - * file stream that should be there is actually in one of the alternate stream - * entries.). This is despite the fact that we may need to extract such a - * missing entry as an empty file or empty named data stream. - */ -struct wim_lookup_table_entry * -inode_unnamed_lte(const struct wim_inode *inode, - const struct wim_lookup_table *table) -{ - if (inode->i_resolved) - return inode_unnamed_lte_resolved(inode); - else - return inode_unnamed_lte_unresolved(inode, table); -} - -/* Returns the SHA1 message digest of the unnamed data stream of a WIM inode, or - * 'zero_hash' if the unnamed data stream is missing has all zeroes in its SHA1 - * message digest field. */ -const u8 * -inode_unnamed_stream_hash(const struct wim_inode *inode) -{ - const u8 *hash; - - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - if (inode_stream_name_nbytes(inode, i) == 0) { - hash = inode_stream_hash(inode, i); - if (!is_zero_hash(hash)) - return hash; - } - } - return zero_hash; -} - -struct wim_lookup_table_entry ** -retrieve_lte_pointer(struct wim_lookup_table_entry *lte) -{ - wimlib_assert(lte->unhashed); - struct wim_inode *inode = lte->back_inode; - u32 stream_id = lte->back_stream_id; - if (stream_id == 0) - return &inode->i_lte; - else - for (u16 i = 0; i < inode->i_num_ads; i++) - if (inode->i_ads_entries[i].stream_id == stream_id) - return &inode->i_ads_entries[i].lte; - wimlib_assert(0); - return NULL; -} - -/* Calculate the SHA1 message digest of a stream and move it from the list of - * unhashed streams to the stream lookup table, possibly joining it with an - * existing lookup table entry for an identical stream. - * - * @lte: An unhashed lookup table entry. - * @lookup_table: Lookup table for the WIM. - * @lte_ret: On success, write a pointer to the resulting lookup table - * entry to this location. This will be the same as @lte - * if it was inserted into the lookup table, or different if - * a duplicate stream was found. - * - * Returns 0 on success; nonzero if there is an error reading the stream. - */ -int -hash_unhashed_stream(struct wim_lookup_table_entry *lte, - struct wim_lookup_table *lookup_table, - struct wim_lookup_table_entry **lte_ret) -{ - int ret; - struct wim_lookup_table_entry *duplicate_lte; - struct wim_lookup_table_entry **back_ptr; - - wimlib_assert(lte->unhashed); - - /* back_ptr must be saved because @back_inode and @back_stream_id are in - * union with the SHA1 message digest and will no longer be valid once - * the SHA1 has been calculated. */ - back_ptr = retrieve_lte_pointer(lte); - - ret = sha1_stream(lte); - if (ret) - return ret; - - /* Look for a duplicate stream */ - duplicate_lte = lookup_resource(lookup_table, lte->hash); - list_del(<e->unhashed_list); - if (duplicate_lte) { - /* We have a duplicate stream. Transfer the reference counts - * from this stream to the duplicate and update the reference to - * this stream (in an inode or ads_entry) to point to the - * duplicate. The caller is responsible for freeing @lte if - * needed. */ - wimlib_assert(!(duplicate_lte->unhashed)); - wimlib_assert(duplicate_lte->size == lte->size); - duplicate_lte->refcnt += lte->refcnt; - lte->refcnt = 0; - *back_ptr = duplicate_lte; - lte = duplicate_lte; - } else { - /* No duplicate stream, so we need to insert this stream into - * the lookup table and treat it as a hashed stream. */ - lookup_table_insert(lookup_table, lte); - lte->unhashed = 0; - } - *lte_ret = lte; - return 0; -} - -static int -lte_clone_if_new(struct wim_lookup_table_entry *lte, void *_lookup_table) -{ - struct wim_lookup_table *lookup_table = _lookup_table; - - if (lookup_resource(lookup_table, lte->hash)) - return 0; /* Resource already present. */ - - lte = clone_lookup_table_entry(lte); - if (lte == NULL) - return WIMLIB_ERR_NOMEM; - lte->out_refcnt = 1; - lookup_table_insert(lookup_table, lte); - return 0; -} - -static int -lte_delete_if_new(struct wim_lookup_table_entry *lte, void *_lookup_table) -{ - struct wim_lookup_table *lookup_table = _lookup_table; - - if (lte->out_refcnt) { - lookup_table_unlink(lookup_table, lte); - free_lookup_table_entry(lte); - } - return 0; -} - -/* API function documented in wimlib.h */ -WIMLIBAPI int -wimlib_reference_resources(WIMStruct *wim, - WIMStruct **resource_wims, unsigned num_resource_wims, - int ref_flags) -{ - int ret; - unsigned i; - - if (wim == NULL) - return WIMLIB_ERR_INVALID_PARAM; - - if (num_resource_wims != 0 && resource_wims == NULL) - return WIMLIB_ERR_INVALID_PARAM; - - for (i = 0; i < num_resource_wims; i++) - if (resource_wims[i] == NULL) - return WIMLIB_ERR_INVALID_PARAM; - - for_lookup_table_entry(wim->lookup_table, lte_zero_out_refcnt, NULL); - - for (i = 0; i < num_resource_wims; i++) { - ret = for_lookup_table_entry(resource_wims[i]->lookup_table, - lte_clone_if_new, - wim->lookup_table); - if (ret) - goto out_rollback; - } - return 0; - -out_rollback: - for_lookup_table_entry(wim->lookup_table, lte_delete_if_new, - wim->lookup_table); - return ret; -} - -static int -reference_resource_paths(WIMStruct *wim, - const tchar * const *resource_wimfiles, - unsigned num_resource_wimfiles, - int ref_flags, - int open_flags, - wimlib_progress_func_t progress_func) -{ - WIMStruct **resource_wims; - unsigned i; - int ret; - - resource_wims = CALLOC(num_resource_wimfiles, sizeof(resource_wims[0])); - if (!resource_wims) - return WIMLIB_ERR_NOMEM; - - for (i = 0; i < num_resource_wimfiles; i++) { - DEBUG("Referencing resources from path \"%"TS"\"", - resource_wimfiles[i]); - ret = wimlib_open_wim(resource_wimfiles[i], open_flags, - &resource_wims[i], progress_func); - if (ret) - goto out_free_resource_wims; - } - - ret = wimlib_reference_resources(wim, resource_wims, - num_resource_wimfiles, ref_flags); - if (ret) - goto out_free_resource_wims; - - for (i = 0; i < num_resource_wimfiles; i++) - list_add_tail(&resource_wims[i]->subwim_node, &wim->subwims); - - ret = 0; - goto out_free_array; - -out_free_resource_wims: - for (i = 0; i < num_resource_wimfiles; i++) - wimlib_free(resource_wims[i]); -out_free_array: - FREE(resource_wims); - return ret; -} - -static int -reference_resource_glob(WIMStruct *wim, const tchar *refglob, - int ref_flags, int open_flags, - wimlib_progress_func_t progress_func) -{ - glob_t globbuf; - int ret; - - /* Note: glob() is replaced in Windows native builds. */ - ret = tglob(refglob, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf); - if (ret) { - if (ret == GLOB_NOMATCH) { - if (ref_flags & WIMLIB_REF_FLAG_GLOB_ERR_ON_NOMATCH) { - ERROR("Found no files for glob \"%"TS"\"", refglob); - return WIMLIB_ERR_GLOB_HAD_NO_MATCHES; - } else { - return reference_resource_paths(wim, - &refglob, - 1, - ref_flags, - open_flags, - progress_func); - } - } else { - ERROR_WITH_ERRNO("Failed to process glob \"%"TS"\"", refglob); - if (ret == GLOB_NOSPACE) - return WIMLIB_ERR_NOMEM; - else - return WIMLIB_ERR_READ; - } - } - - ret = reference_resource_paths(wim, - (const tchar * const *)globbuf.gl_pathv, - globbuf.gl_pathc, - ref_flags, - open_flags, - progress_func); - globfree(&globbuf); - return ret; -} - -/* API function documented in wimlib.h */ -WIMLIBAPI int -wimlib_reference_resource_files(WIMStruct *wim, - const tchar * const * resource_wimfiles_or_globs, - unsigned count, - int ref_flags, - int open_flags, - wimlib_progress_func_t progress_func) -{ - unsigned i; - int ret; - - if (ref_flags & WIMLIB_REF_FLAG_GLOB_ENABLE) { - for (i = 0; i < count; i++) { - ret = reference_resource_glob(wim, - resource_wimfiles_or_globs[i], - ref_flags, - open_flags, - progress_func); - if (ret) - return ret; - } - return 0; - } else { - return reference_resource_paths(wim, resource_wimfiles_or_globs, - count, ref_flags, - open_flags, progress_func); - } -} diff --git a/src/mount_image.c b/src/mount_image.c index 1a8443e3..7c480ebc 100644 --- a/src/mount_image.c +++ b/src/mount_image.c @@ -41,6 +41,8 @@ #include "wimlib/encoding.h" #include "wimlib/file_io.h" +#include "wimlib/dentry.h" +#include "wimlib/inode.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" #include "wimlib/paths.h" @@ -152,12 +154,6 @@ wimfs_get_WIMStruct(void) return wimfs_get_context()->wim; } -static inline bool -wimfs_ctx_readonly(const struct wimfs_context *ctx) -{ - return (ctx->mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) == 0; -} - static inline int get_lookup_flags(const struct wimfs_context *ctx) { @@ -178,7 +174,6 @@ flags_writable(int open_flags) * @stream_id: ID of the stream we're opening * @lte: Lookup table entry for the stream (may be NULL) * @fd_ret: Return the allocated file descriptor if successful. - * @readonly: True if this is a read-only mount. * * Return 0 iff successful or error code if unsuccessful. */ @@ -186,8 +181,7 @@ static int alloc_wimfs_fd(struct wim_inode *inode, u32 stream_id, struct wim_lookup_table_entry *lte, - struct wimfs_fd **fd_ret, - bool readonly) + struct wimfs_fd **fd_ret) { static const u16 fds_per_alloc = 8; static const u16 max_fds = 0xffff; @@ -236,7 +230,7 @@ alloc_wimfs_fd(struct wim_inode *inode, *fd_ret = fd; inode->i_fds[i] = fd; inode->i_num_opened_fds++; - if (lte && !readonly) + if (lte) lte->num_opened_fds++; DEBUG("Allocated fd (idx = %u)", fd->idx); ret = 0; @@ -678,8 +672,8 @@ extract_resource_to_staging_dir(struct wim_inode *inode, new_lte->staging_file_name = staging_file_name; new_lte->size = size; - lookup_table_insert_unhashed(ctx->wim->lookup_table, new_lte, - inode, stream_id); + add_unhashed_stream(new_lte, inode, stream_id, + &wim_get_current_image_metadata(ctx->wim)->unhashed_streams); *retrieve_lte_pointer(new_lte) = new_lte; *lte = new_lte; return 0; @@ -1872,8 +1866,7 @@ wimfs_open(const char *path, struct fuse_file_info *fi) *back_ptr = lte; } - ret = alloc_wimfs_fd(inode, stream_id, lte, &fd, - wimfs_ctx_readonly(ctx)); + ret = alloc_wimfs_fd(inode, stream_id, lte, &fd); if (ret) return ret; @@ -1907,7 +1900,7 @@ wimfs_opendir(const char *path, struct fuse_file_info *fi) return -errno; if (!inode_is_directory(inode)) return -ENOTDIR; - ret = alloc_wimfs_fd(inode, 0, NULL, &fd, wimfs_ctx_readonly(ctx)); + ret = alloc_wimfs_fd(inode, 0, NULL, &fd); fi->fh = (uintptr_t)fd; return ret; } @@ -2431,15 +2424,12 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, imd = wim_get_current_image_metadata(wim); - if (imd->refcnt != 1) { - ERROR("Cannot mount image that was just exported with " - "wimlib_export_image()"); - return WIMLIB_ERR_INVALID_PARAM; - } - if (imd->modified) { - ERROR("Cannot mount image that was added " - "with wimlib_add_image()"); + /* wimfs_read() only supports a limited number of stream + * locations, not including RESOURCE_IN_FILE_ON_DISK, + * RESOURCE_IN_NTFS_VOLUME, etc. that might appear if files were + * added to the WIM image. */ + ERROR("Cannot mount an image with newly added files!"); return WIMLIB_ERR_INVALID_PARAM; } @@ -2463,7 +2453,6 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, ctx.default_uid = getuid(); ctx.default_gid = getgid(); wimlib_assert(list_empty(&imd->unhashed_streams)); - ctx.wim->lookup_table->unhashed_streams = &imd->unhashed_streams; if (mount_flags & WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS) ctx.default_lookup_flags = LOOKUP_FLAG_ADS_OK; diff --git a/src/ntfs-3g_capture.c b/src/ntfs-3g_capture.c index 92f3b7e1..11cbc9dd 100644 --- a/src/ntfs-3g_capture.c +++ b/src/ntfs-3g_capture.c @@ -155,14 +155,13 @@ out: } -/* Load the streams from a file or reparse point in the NTFS volume into the WIM - * lookup table */ +/* Load the streams from a file or reparse point in the NTFS volume */ static int capture_ntfs_streams(struct wim_inode *inode, ntfs_inode *ni, char *path, size_t path_len, - struct wim_lookup_table *lookup_table, + struct list_head *unhashed_streams, ntfs_volume *vol, ATTR_TYPES type) { @@ -274,8 +273,8 @@ capture_ntfs_streams(struct wim_inode *inode, new_ads_entry->lte = lte; } if (lte) { - lookup_table_insert_unhashed(lookup_table, lte, - inode, stream_id); + add_unhashed_stream(lte, inode, + stream_id, unhashed_streams); } } if (errno == ENOENT) { @@ -605,7 +604,7 @@ build_dentry_tree_ntfs_recursive(struct wim_dentry **root_ret, * - Reparse points: capture reparse data only */ ret = capture_ntfs_streams(inode, ni, path, path_len, - params->lookup_table, vol, stream_type); + params->unhashed_streams, vol, stream_type); if (ret) goto out; diff --git a/src/reference.c b/src/reference.c new file mode 100644 index 00000000..1a83a4a6 --- /dev/null +++ b/src/reference.c @@ -0,0 +1,214 @@ +/* + * reference.c + * + * Reference resources from external WIM file(s). + */ + +/* + * Copyright (C) 2013 Eric Biggers + * + * This file is part of wimlib, a library for working with WIM files. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with wimlib; if not, see http://www.gnu.org/licenses/. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "wimlib.h" +#include "wimlib/error.h" +#include "wimlib/glob.h" +#include "wimlib/lookup_table.h" +#include "wimlib/wim.h" + +static int +lte_clone_if_new(struct wim_lookup_table_entry *lte, void *_lookup_table) +{ + struct wim_lookup_table *lookup_table = _lookup_table; + + if (lookup_stream(lookup_table, lte->hash)) + return 0; /* Resource already present. */ + + lte = clone_lookup_table_entry(lte); + if (lte == NULL) + return WIMLIB_ERR_NOMEM; + lte->out_refcnt = 1; + lookup_table_insert(lookup_table, lte); + return 0; +} + +static int +lte_delete_if_new(struct wim_lookup_table_entry *lte, void *_lookup_table) +{ + struct wim_lookup_table *lookup_table = _lookup_table; + + if (lte->out_refcnt) { + lookup_table_unlink(lookup_table, lte); + free_lookup_table_entry(lte); + } + return 0; +} + +/* API function documented in wimlib.h */ +WIMLIBAPI int +wimlib_reference_resources(WIMStruct *wim, + WIMStruct **resource_wims, unsigned num_resource_wims, + int ref_flags) +{ + int ret; + unsigned i; + + if (wim == NULL) + return WIMLIB_ERR_INVALID_PARAM; + + if (num_resource_wims != 0 && resource_wims == NULL) + return WIMLIB_ERR_INVALID_PARAM; + + for (i = 0; i < num_resource_wims; i++) + if (resource_wims[i] == NULL) + return WIMLIB_ERR_INVALID_PARAM; + + for_lookup_table_entry(wim->lookup_table, lte_zero_out_refcnt, NULL); + + for (i = 0; i < num_resource_wims; i++) { + ret = for_lookup_table_entry(resource_wims[i]->lookup_table, + lte_clone_if_new, + wim->lookup_table); + if (ret) + goto out_rollback; + } + return 0; + +out_rollback: + for_lookup_table_entry(wim->lookup_table, lte_delete_if_new, + wim->lookup_table); + return ret; +} + +static int +reference_resource_paths(WIMStruct *wim, + const tchar * const *resource_wimfiles, + unsigned num_resource_wimfiles, + int ref_flags, + int open_flags, + wimlib_progress_func_t progress_func) +{ + WIMStruct **resource_wims; + unsigned i; + int ret; + + resource_wims = CALLOC(num_resource_wimfiles, sizeof(resource_wims[0])); + if (!resource_wims) + return WIMLIB_ERR_NOMEM; + + for (i = 0; i < num_resource_wimfiles; i++) { + DEBUG("Referencing resources from path \"%"TS"\"", + resource_wimfiles[i]); + ret = wimlib_open_wim(resource_wimfiles[i], open_flags, + &resource_wims[i], progress_func); + if (ret) + goto out_free_resource_wims; + } + + ret = wimlib_reference_resources(wim, resource_wims, + num_resource_wimfiles, ref_flags); + if (ret) + goto out_free_resource_wims; + + for (i = 0; i < num_resource_wimfiles; i++) + list_add_tail(&resource_wims[i]->subwim_node, &wim->subwims); + + ret = 0; + goto out_free_array; + +out_free_resource_wims: + for (i = 0; i < num_resource_wimfiles; i++) + wimlib_free(resource_wims[i]); +out_free_array: + FREE(resource_wims); + return ret; +} + +static int +reference_resource_glob(WIMStruct *wim, const tchar *refglob, + int ref_flags, int open_flags, + wimlib_progress_func_t progress_func) +{ + glob_t globbuf; + int ret; + + /* Note: glob() is replaced in Windows native builds. */ + ret = tglob(refglob, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf); + if (ret) { + if (ret == GLOB_NOMATCH) { + if (ref_flags & WIMLIB_REF_FLAG_GLOB_ERR_ON_NOMATCH) { + ERROR("Found no files for glob \"%"TS"\"", refglob); + return WIMLIB_ERR_GLOB_HAD_NO_MATCHES; + } else { + return reference_resource_paths(wim, + &refglob, + 1, + ref_flags, + open_flags, + progress_func); + } + } else { + ERROR_WITH_ERRNO("Failed to process glob \"%"TS"\"", refglob); + if (ret == GLOB_NOSPACE) + return WIMLIB_ERR_NOMEM; + else + return WIMLIB_ERR_READ; + } + } + + ret = reference_resource_paths(wim, + (const tchar * const *)globbuf.gl_pathv, + globbuf.gl_pathc, + ref_flags, + open_flags, + progress_func); + globfree(&globbuf); + return ret; +} + +/* API function documented in wimlib.h */ +WIMLIBAPI int +wimlib_reference_resource_files(WIMStruct *wim, + const tchar * const * resource_wimfiles_or_globs, + unsigned count, + int ref_flags, + int open_flags, + wimlib_progress_func_t progress_func) +{ + unsigned i; + int ret; + + if (ref_flags & WIMLIB_REF_FLAG_GLOB_ENABLE) { + for (i = 0; i < count; i++) { + ret = reference_resource_glob(wim, + resource_wimfiles_or_globs[i], + ref_flags, + open_flags, + progress_func); + if (ret) + return ret; + } + return 0; + } else { + return reference_resource_paths(wim, resource_wimfiles_or_globs, + count, ref_flags, + open_flags, progress_func); + } +} diff --git a/src/resource.c b/src/resource.c index 116873ff..c7312df8 100644 --- a/src/resource.c +++ b/src/resource.c @@ -27,12 +27,14 @@ #endif #include "wimlib.h" +#include "wimlib/assert.h" #include "wimlib/endianness.h" #include "wimlib/error.h" #include "wimlib/file_io.h" #include "wimlib/lookup_table.h" #include "wimlib/resource.h" #include "wimlib/sha1.h" +#include "wimlib/wim.h" #ifdef __WIN32__ /* for read_win32_file_prefix(), read_win32_encrypted_file_prefix() */ diff --git a/src/template.c b/src/template.c new file mode 100644 index 00000000..7348258e --- /dev/null +++ b/src/template.c @@ -0,0 +1,244 @@ +/* + * template.c + * + * API to reference a template image to optimize later writing of a WIM file. + */ + +/* + * Copyright (C) 2013 Eric Biggers + * + * This file is part of wimlib, a library for working with WIM files. + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with wimlib; if not, see http://www.gnu.org/licenses/. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "wimlib.h" +#include "wimlib/dentry.h" +#include "wimlib/error.h" +#include "wimlib/lookup_table.h" +#include "wimlib/metadata.h" +#include "wimlib/util.h" + +/* Returns %true iff the metadata of @inode and @template_inode are reasonably + * consistent with them being the same, unmodified file. */ +static bool +inode_metadata_consistent(const struct wim_inode *inode, + const struct wim_inode *template_inode, + const struct wim_lookup_table *template_lookup_table) +{ + /* Must have exact same creation time and last write time. */ + if (inode->i_creation_time != template_inode->i_creation_time || + inode->i_last_write_time != template_inode->i_last_write_time) + return false; + + /* Last access time may have stayed the same or increased, but certainly + * shouldn't have decreased. */ + if (inode->i_last_access_time < template_inode->i_last_access_time) + return false; + + /* Must have same number of alternate data stream entries. */ + if (inode->i_num_ads != template_inode->i_num_ads) + return false; + + /* If the stream entries for the inode are for some reason not resolved, + * then the hashes are already available and the point of this function + * is defeated. */ + if (!inode->i_resolved) + return false; + + /* Iterate through each stream and do some more checks. */ + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + const struct wim_lookup_table_entry *lte, *template_lte; + + lte = inode_stream_lte_resolved(inode, i); + template_lte = inode_stream_lte(template_inode, i, + template_lookup_table); + + /* Compare stream sizes. */ + if (lte && template_lte) { + if (lte->size != template_lte->size) + return false; + + /* If hash happens to be available, compare with template. */ + if (!lte->unhashed && !template_lte->unhashed && + !hashes_equal(lte->hash, template_lte->hash)) + return false; + + } else if (lte && lte->size) { + return false; + } else if (template_lte && template_lte->size) { + return false; + } + } + + /* All right, barring a full checksum and given that the inodes share a + * path and the user isn't trying to trick us, these inodes most likely + * refer to the same file. */ + return true; +} + +/** + * Given an inode @inode that has been determined to be "the same" as another + * inode @template_inode in either the same WIM or another WIM, retrieve some + * useful stream information (e.g. checksums) from @template_inode. + * + * This assumes that the streams for @inode have been resolved (to point + * directly to the appropriate `struct wim_lookup_table_entry's) but do not + * necessarily have checksum information filled in. + */ +static int +inode_copy_checksums(struct wim_inode *inode, + struct wim_inode *template_inode, + WIMStruct *wim, + WIMStruct *template_wim) +{ + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + struct wim_lookup_table_entry *lte, *template_lte; + struct wim_lookup_table_entry *replace_lte; + + lte = inode_stream_lte_resolved(inode, i); + template_lte = inode_stream_lte(template_inode, i, + template_wim->lookup_table); + + /* Only take action if both entries exist, the entry for @inode + * has no checksum calculated, but the entry for @template_inode + * does. */ + if (lte == NULL || template_lte == NULL || + !lte->unhashed || template_lte->unhashed) + continue; + + wimlib_assert(lte->refcnt == inode->i_nlink); + + /* If the WIM of the template image is the same as the WIM of + * the new image, then @template_lte can be used directly. + * + * Otherwise, look for a stream with the same hash in the WIM of + * the new image. If found, use it; otherwise re-use the entry + * being discarded, filling in the hash. */ + + if (wim == template_wim) + replace_lte = template_lte; + else + replace_lte = lookup_stream(wim->lookup_table, + template_lte->hash); + + list_del(<e->unhashed_list); + if (replace_lte) { + free_lookup_table_entry(lte); + } else { + copy_hash(lte->hash, template_lte->hash); + lte->unhashed = 0; + lookup_table_insert(wim->lookup_table, lte); + lte->refcnt = 0; + replace_lte = lte; + } + + if (i == 0) + inode->i_lte = replace_lte; + else + inode->i_ads_entries[i - 1].lte = replace_lte; + + replace_lte->refcnt += inode->i_nlink; + } + return 0; +} + +struct reference_template_args { + WIMStruct *wim; + WIMStruct *template_wim; +}; + +static int +dentry_reference_template(struct wim_dentry *dentry, void *_args) +{ + int ret; + struct wim_dentry *template_dentry; + struct wim_inode *inode, *template_inode; + struct reference_template_args *args = _args; + WIMStruct *wim = args->wim; + WIMStruct *template_wim = args->template_wim; + + if (dentry->d_inode->i_visited) + return 0; + + ret = calculate_dentry_full_path(dentry); + if (ret) + return ret; + + 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; + } + + inode = dentry->d_inode; + template_inode = template_dentry->d_inode; + + if (inode_metadata_consistent(inode, template_inode, + template_wim->lookup_table)) { + /*DEBUG("\"%"TS"\": No change detected", dentry->_full_path);*/ + ret = inode_copy_checksums(inode, template_inode, + wim, template_wim); + inode->i_visited = 1; + } else { + DEBUG("\"%"TS"\": change detected!", dentry->_full_path); + ret = 0; + } + return ret; +} + +/* API function documented in wimlib.h */ +WIMLIBAPI int +wimlib_reference_template_image(WIMStruct *wim, int new_image, + WIMStruct *template_wim, int template_image, + int flags, wimlib_progress_func_t progress_func) +{ + int ret; + struct wim_image_metadata *new_imd; + + if (wim == NULL || template_wim == NULL) + return WIMLIB_ERR_INVALID_PARAM; + + if (wim == template_wim && new_image == template_image) + return WIMLIB_ERR_INVALID_PARAM; + + if (new_image < 1 || new_image > wim->hdr.image_count) + return WIMLIB_ERR_INVALID_IMAGE; + + if (!wim_has_metadata(wim)) + return WIMLIB_ERR_METADATA_NOT_FOUND; + + new_imd = wim->image_metadata[new_image - 1]; + if (!new_imd->modified) + return WIMLIB_ERR_INVALID_PARAM; + + ret = select_wim_image(template_wim, template_image); + if (ret) + return ret; + + struct reference_template_args args = { + .wim = wim, + .template_wim = template_wim, + }; + + ret = for_dentry_in_tree(new_imd->root_dentry, + dentry_reference_template, &args); + dentry_tree_clear_inode_visited(new_imd->root_dentry); + return ret; +} diff --git a/src/unix_apply.c b/src/unix_apply.c index 0a2a04f4..21a804a5 100644 --- a/src/unix_apply.c +++ b/src/unix_apply.c @@ -29,9 +29,9 @@ #include "wimlib/apply.h" #include "wimlib/error.h" -#include "wimlib/lookup_table.h" #include "wimlib/resource.h" #include "wimlib/timestamp.h" +#include "wimlib/unix_data.h" #include #include diff --git a/src/unix_capture.c b/src/unix_capture.c index 3b22a2c0..dd79b658 100644 --- a/src/unix_capture.c +++ b/src/unix_capture.c @@ -45,7 +45,7 @@ static int unix_capture_regular_file(const char *path, u64 size, struct wim_inode *inode, - struct wim_lookup_table *lookup_table) + struct list_head *unhashed_streams) { inode->i_attributes = FILE_ATTRIBUTE_NORMAL; @@ -65,7 +65,7 @@ unix_capture_regular_file(const char *path, lte->file_on_disk = file_on_disk; lte->resource_location = RESOURCE_IN_FILE_ON_DISK; lte->size = size; - lookup_table_insert_unhashed(lookup_table, lte, inode, 0); + add_unhashed_stream(lte, inode, 0, unhashed_streams); inode->i_lte = lte; } return 0; @@ -278,7 +278,7 @@ unix_build_dentry_tree_recursive(struct wim_dentry **root_ret, params->add_flags &= ~WIMLIB_ADD_FLAG_ROOT; if (S_ISREG(stbuf.st_mode)) ret = unix_capture_regular_file(path, stbuf.st_size, - inode, params->lookup_table); + inode, params->unhashed_streams); else if (S_ISDIR(stbuf.st_mode)) ret = unix_capture_directory(root, path, path_len, params); else { diff --git a/src/update_image.c b/src/update_image.c index bd8702e2..b7a3abdf 100644 --- a/src/update_image.c +++ b/src/update_image.c @@ -235,8 +235,8 @@ execute_add_command(WIMStruct *wim, goto out_destroy_inode_table; INIT_LIST_HEAD(&unhashed_streams); - wim->lookup_table->unhashed_streams = &unhashed_streams; params.lookup_table = wim->lookup_table; + params.unhashed_streams = &unhashed_streams; params.config = config; params.add_flags = add_flags; params.extra_arg = extra_arg; @@ -262,7 +262,7 @@ execute_add_command(WIMStruct *wim, * the root of each branch from a capture * source. (This will also set the root dentry * of the entire image to be unnamed.) */ - ret = set_dentry_name(branch, + ret = dentry_set_name(branch, path_basename(wim_target_path)); if (ret) goto out_ntfs_umount; diff --git a/src/verify.c b/src/verify.c index a9de1146..063834fb 100644 --- a/src/verify.c +++ b/src/verify.c @@ -1,7 +1,7 @@ /* * verify.c * - * Verify WIM inodes and stream reference counts. + * Verify stream reference counts. */ /* @@ -33,73 +33,6 @@ #include "wimlib/metadata.h" #include "wimlib/security.h" -/* - * Verify a WIM inode: - * - * - Check to make sure the security ID is valid - * - Check to make sure there is at most one unnamed stream - * - Check to make sure there is at most one DOS name. - * - * Return values: - * WIMLIB_ERR_SUCCESS (0) - */ -int -verify_inode(struct wim_inode *inode, const struct wim_security_data *sd) -{ - struct wim_dentry *dentry; - - /* Check the security ID. -1 is valid and means "no security - * descriptor". Anything else has to be a valid index into the WIM - * image's security descriptors table. */ - if (inode->i_security_id < -1 || - (inode->i_security_id >= 0 && - inode->i_security_id >= sd->num_entries)) - { - WARNING("\"%"TS"\" has an invalid security ID (%d)", - inode_first_full_path(inode), inode->i_security_id); - inode->i_security_id = -1; - } - - /* Make sure there is only one unnamed data stream. */ - unsigned num_unnamed_streams = 0; - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - const u8 *hash; - hash = inode_stream_hash(inode, i); - if (inode_stream_name_nbytes(inode, i) == 0 && !is_zero_hash(hash)) - num_unnamed_streams++; - } - if (num_unnamed_streams > 1) { - WARNING("\"%"TS"\" has multiple (%u) un-named streams", - inode_first_full_path(inode), num_unnamed_streams); - } - - /* Files cannot have multiple DOS names, even if they have multiple - * names in multiple directories (i.e. hard links). - * Source: NTFS-3g authors. */ - struct wim_dentry *dentry_with_dos_name = NULL; - inode_for_each_dentry(dentry, inode) { - if (dentry_has_short_name(dentry)) { - if (dentry_with_dos_name) { - /* This was previously an error, but if we - * capture a WIM from UDF on Windows, hard links - * are supported but DOS names are automatically - * generated for all names for an inode. */ - #if 0 - ERROR("Hard-linked file has a DOS name at " - "both `%"TS"' and `%"TS"'", - dentry_full_path(dentry_with_dos_name), - dentry_full_path(dentry)); - return WIMLIB_ERR_INVALID_METADATA_RESOURCE; - #else - dentry->dos_name_invalid = 1; - #endif - } - dentry_with_dos_name = dentry; - } - } - return 0; -} - static int lte_fix_refcnt(struct wim_lookup_table_entry *lte, void *ctr) { diff --git a/src/win32_capture.c b/src/win32_capture.c index 6f29828c..cf5c65c9 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -30,6 +30,7 @@ #include "wimlib/win32_common.h" #include "wimlib/capture.h" +#include "wimlib/dentry.h" #include "wimlib/endianness.h" #include "wimlib/error.h" #include "wimlib/lookup_table.h" @@ -795,7 +796,8 @@ win32_get_encrypted_file_size(const wchar_t *path, u64 *size_ret) * * @inode: WIM inode to save the stream into. * - * @lookup_table: Stream lookup table for the WIM. + * @unhashed_streams: List of unhashed streams that have been added to the WIM + * image. * * @dat: A `WIN32_FIND_STREAM_DATA' structure that specifies the * stream name. @@ -806,7 +808,7 @@ static int win32_capture_stream(const wchar_t *path, size_t path_num_chars, struct wim_inode *inode, - struct wim_lookup_table *lookup_table, + struct list_head *unhashed_streams, WIN32_FIND_STREAM_DATA *dat) { struct wim_ads_entry *ads_entry; @@ -916,7 +918,7 @@ win32_capture_stream(const wchar_t *path, stream_id = 0; inode->i_lte = lte; } - lookup_table_insert_unhashed(lookup_table, lte, inode, stream_id); + add_unhashed_stream(lte, inode, stream_id, unhashed_streams); ret = 0; out_free_spath: FREE(spath); @@ -946,7 +948,7 @@ win32_capture_streams(HANDLE *hFile_p, const wchar_t *path, size_t path_num_chars, struct wim_inode *inode, - struct wim_lookup_table *lookup_table, + struct list_head *unhashed_streams, u64 file_size, unsigned vol_flags) { @@ -1032,7 +1034,7 @@ win32_capture_streams(HANDLE *hFile_p, /* Capture the stream. */ ret = win32_capture_stream(path, path_num_chars, inode, - lookup_table, &dat); + unhashed_streams, &dat); if (ret) goto out_free_buf; } @@ -1089,7 +1091,7 @@ use_FindFirstStream: do { ret = win32_capture_stream(path, path_num_chars, - inode, lookup_table, + inode, unhashed_streams, &dat); if (ret) goto out_find_close; @@ -1116,7 +1118,7 @@ unnamed_only: wcscpy(dat.cStreamName, L"::$DATA"); dat.StreamSize.QuadPart = file_size; return win32_capture_stream(path, path_num_chars, - inode, lookup_table, &dat); + inode, unhashed_streams, &dat); } static int @@ -1266,7 +1268,7 @@ again: path, path_num_chars, inode, - params->lookup_table, + params->unhashed_streams, file_size, vol_flags); if (ret) diff --git a/src/write.c b/src/write.c index b8d86e85..a852c199 100644 --- a/src/write.c +++ b/src/write.c @@ -39,6 +39,7 @@ #include "wimlib/error.h" #include "wimlib/file_io.h" #include "wimlib/header.h" +#include "wimlib/inode.h" #include "wimlib/integrity.h" #include "wimlib/lookup_table.h" #include "wimlib/metadata.h" @@ -1842,7 +1843,7 @@ determine_stream_size_uniquity(struct list_head *stream_list, struct stream_size_table tab; struct wim_lookup_table_entry *lte; - ret = init_stream_size_table(&tab, lt->capacity); + ret = init_stream_size_table(&tab, 9001); if (ret) return ret; diff --git a/tests/test-imagex-ntfs b/tests/test-imagex-ntfs index 783c96ac..30633494 100755 --- a/tests/test-imagex-ntfs +++ b/tests/test-imagex-ntfs @@ -272,7 +272,7 @@ do_test 'echo -n 8 > file; setfattr -n user.ads3 anotherfile -v 33; echo -n > emptyfile; setfattr -n user.ads emptyfile -v 8; - setfattr -n user.ads5 emptyfile -v"`cat $srcdir/src/hardlink.c`" + setfattr -n user.ads5 emptyfile -v"`cat $srcdir/src/sha1.c`" mkdir dir/subdir; ln file dir/subdir/file; echo -n 8 > dir/subdir/file2;