From: Eric Biggers Date: Sat, 18 Aug 2012 19:52:05 +0000 (-0500) Subject: Symbolic links (IN PROGRESS) X-Git-Tag: v1.0.0~143 X-Git-Url: https://wimlib.net/git/?p=wimlib;a=commitdiff_plain;h=815ad485b8ce48db48a3137d73e511f6c9af868c Symbolic links (IN PROGRESS) --- diff --git a/Makefile.am b/Makefile.am index a34bc9a2..b694fa1d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,6 +35,7 @@ libwim_la_SOURCES = \ src/sha1.c \ src/sha1.h \ src/split.c \ + src/symlink.c \ src/timestamp.h \ src/util.c \ src/util.h \ diff --git a/doc/imagex-append.1.in b/doc/imagex-append.1.in index 027959f7..b48bfdb9 100644 --- a/doc/imagex-append.1.in +++ b/doc/imagex-append.1.in @@ -30,6 +30,10 @@ is included in the new WIM file, even if there was one before. .TP \fB--flags\fR \fIEDITIONID\fR Specify a string to use in the element of the XML data for the image. +.TP +\fB--dereference\fR +Follow symlinks; archive and dump the files they point to. (The default is to +archive the symlinks themselves) .SH EXAMPLES .IP diff --git a/doc/imagex-capture.1.in b/doc/imagex-capture.1.in index 6e3edd42..5e76499d 100644 --- a/doc/imagex-capture.1.in +++ b/doc/imagex-capture.1.in @@ -37,6 +37,10 @@ Specify a string to use in the element of the XML data for the image. .TP \fB--verbose\fR Print the names of files and directories as they are captured. +.TP +\fB--dereference\fR +Follow symlinks; archive and dump the files they point to. (The default is to +archive the symlinks themselves) .SH EXAMPLES .IP diff --git a/programs/imagex.c b/programs/imagex.c index 11becdd2..ffd0d945 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -75,14 +75,14 @@ static const char *path_basename(const char *path) static const char *usage_strings[] = { [APPEND] = " imagex append DIRECTORY WIMFILE [\"IMAGE_NAME\"] [\"DESCRIPTION\"] [--boot]\n" -" [--check] [--flags EDITIONID]\n", +" [--check] [--flags EDITIONID] [--dereference]\n", [APPLY] = " imagex apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all] DIRECTORY [--check]\n" " [--hardlink] [--symlink] [--verbose]\n", [CAPTURE] = " imagex capture DIRECTORY WIMFILE [\"IMAGE_NAME\"] [\"DESCRIPTION\"]\n" " l [--boot] [--check] [--compress[=TYPE]]\n" -" [--flags \"EditionID\"] [--verbose]\n", +" [--flags \"EditionID\"] [--verbose] [--dereference]\n", [DELETE] = " imagex delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check]\n", [DIR] = @@ -119,6 +119,7 @@ static const struct option append_options[] = { {"boot", no_argument, NULL, 'b'}, {"check", no_argument, NULL, 'c'}, {"flags", required_argument, NULL, 'f'}, + {"dereference", no_argument, NULL, 'L'}, {NULL, 0, NULL, 0}, }; static const struct option apply_options[] = { @@ -136,6 +137,7 @@ static const struct option capture_options[] = { {"flags", required_argument, NULL, 'f'}, {"verbose", no_argument, NULL,'v'}, {"ntfs", no_argument, NULL, 'N'}, + {"dereference", no_argument, NULL, 'L'}, {NULL, 0, NULL, 0}, }; static const struct option delete_options[] = { @@ -312,6 +314,9 @@ static int imagex_append(int argc, const char **argv) case 'f': flags_element = optarg; break; + case 'L': + add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE; + break; default: usage(APPEND); return -1; @@ -455,6 +460,9 @@ static int imagex_capture(int argc, const char **argv) case 'N': add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS; break; + case 'L': + add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE; + break; default: usage(CAPTURE); return -1; diff --git a/src/dentry.c b/src/dentry.c index a25680f9..68446822 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -52,10 +52,14 @@ u64 dentry_total_length(const struct dentry *dentry) /* Transfers file attributes from a `stat' buffer to a struct dentry. */ void stbuf_to_dentry(const struct stat *stbuf, struct dentry *dentry) { - if (S_ISDIR(stbuf->st_mode)) + if (S_ISLNK(stbuf->st_mode)) { + dentry->attributes = FILE_ATTRIBUTE_REPARSE_POINT; + dentry->reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK; + } else if (S_ISDIR(stbuf->st_mode)) { dentry->attributes = FILE_ATTRIBUTE_DIRECTORY; - else + } else { dentry->attributes = FILE_ATTRIBUTE_NORMAL; + } } /* Transfers file attributes from a struct dentry to a `stat' buffer. */ @@ -360,11 +364,10 @@ int print_dentry(struct dentry *dentry, void *lookup_table) printf("Subdir offset = %"PRIu64"\n", dentry->subdir_offset); /*printf("Unused1 = 0x%"PRIu64"\n", dentry->unused1);*/ /*printf("Unused2 = %"PRIu64"\n", dentry->unused2);*/ - printf("Creation Time = %"PRIu64"\n", dentry->creation_time); - printf("Last Access Time = %"PRIu64"\n", dentry->last_access_time); - printf("Last Write Time = %"PRIu64"\n", dentry->last_write_time); printf("Creation Time = 0x%"PRIx64"\n", dentry->creation_time); - printf("Hash = "); + printf("Last Access Time = 0x%"PRIx64"\n", dentry->last_access_time); + printf("Last Write Time = 0x%"PRIx64"\n", dentry->last_write_time); + printf("Hash = 0x"); print_hash(dentry->hash); putchar('\n'); printf("Reparse Tag = 0x%"PRIx32"\n", dentry->reparse_tag); @@ -395,6 +398,9 @@ int print_dentry(struct dentry *dentry, void *lookup_table) printf("Name = \"%s\"\n", dentry->ads_entries[i].stream_name_utf8); printf("Name Length (UTF-16) = %u\n", dentry->ads_entries[i].stream_name_len); + printf("Hash = 0x"); + print_hash(dentry->ads_entries[i].hash); + putchar('\n'); lte = lookup_resource(lookup_table, dentry->ads_entries[i].hash); if (lte) print_lookup_table_entry(lte, NULL); @@ -438,6 +444,17 @@ struct dentry *new_dentry(const char *name) return dentry; } +static void dentry_free_ads_entries(struct dentry *dentry) +{ + for (u16 i = 0; i < dentry->num_ads; i++) { + FREE(dentry->ads_entries[i].stream_name); + FREE(dentry->ads_entries[i].stream_name_utf8); + } + FREE(dentry->ads_entries); + dentry->ads_entries = NULL; + dentry->num_ads = 0; +} + void free_dentry(struct dentry *dentry) { @@ -445,6 +462,7 @@ void free_dentry(struct dentry *dentry) FREE(dentry->file_name_utf8); FREE(dentry->short_name); FREE(dentry->full_path_utf8); + dentry_free_ads_entries(dentry); FREE(dentry); } @@ -932,6 +950,7 @@ out_free_file_name: static u8 *write_dentry(const struct dentry *dentry, u8 *p) { u8 *orig_p = p; + unsigned padding; memset(p, 0, dentry->length); p = put_u64(p, dentry->length); p = put_u32(p, dentry->attributes); @@ -952,6 +971,13 @@ static u8 *write_dentry(const struct dentry *dentry, u8 *p) p = put_bytes(p, dentry->file_name_len, (u8*)dentry->file_name); p = put_u16(p, 0); /* filename padding, 2 bytes. */ p = put_bytes(p, dentry->short_name_len, (u8*)dentry->short_name); + + wimlib_assert(p - orig_p <= dentry->length); + if (p - orig_p < dentry->length) + p = put_zeroes(p, dentry->length - (p - orig_p)); + + p = put_zeroes(p, (8 - (p - orig_p) % 8) % 8); + for (u16 i = 0; i < dentry->num_ads; i++) { p = put_u64(p, ads_entry_length(&dentry->ads_entries[i])); p = put_u64(p, 0); /* Unused */ @@ -959,8 +985,9 @@ static u8 *write_dentry(const struct dentry *dentry, u8 *p) p = put_u16(p, dentry->ads_entries[i].stream_name_len); p = put_bytes(p, dentry->ads_entries[i].stream_name_len, (u8*)dentry->ads_entries[i].stream_name); + p = put_zeroes(p, (8 - (p - orig_p) % 8) % 8); } - return orig_p + dentry->length; + return p; } /* Recursive function that writes a dentry tree rooted at @tree, not including @@ -1089,3 +1116,17 @@ int read_dentry_tree(const u8 metadata_resource[], u64 metadata_resource_len, dentry->children = first_child; return ret; } + +int dentry_set_symlink_buf(struct dentry *dentry, const u8 symlink_buf_hash[]) +{ + struct ads_entry *ads_entries; + + ads_entries = CALLOC(2, sizeof(struct ads_entry)); + if (!ads_entries) + return WIMLIB_ERR_NOMEM; + memcpy(ads_entries[1].hash, symlink_buf_hash, WIM_HASH_SIZE); + dentry_free_ads_entries(dentry); + dentry->num_ads = 2; + dentry->ads_entries = ads_entries; + return 0; +} diff --git a/src/dentry.h b/src/dentry.h index 6c260fc2..2aabf02e 100644 --- a/src/dentry.h +++ b/src/dentry.h @@ -5,16 +5,22 @@ #include "config.h" #include +struct stat; +struct lookup_table; +typedef struct WIMStruct WIMStruct; + /* Size of the struct dentry up to and including the file_name_len. */ #define WIM_DENTRY_DISK_SIZE 102 #define WIM_ADS_ENTRY_DISK_SIZE 38 +#ifndef WIM_HASH_SIZE +#define WIM_HASH_SIZE 20 +#endif + /* * Reparse tags documented at * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx - * - * IO_REPARSE_TAG_SYMLINK is the only one we really care about. */ #define WIM_IO_REPARSE_TAG_RESERVED_ZERO 0x00000000 #define WIM_IO_REPARSE_TAG_RESERVED_ONE 0x00000001 @@ -241,6 +247,9 @@ extern int read_dentry_tree(const u8 metadata_resource[], extern u8 *write_dentry_tree(const struct dentry *tree, u8 *p); +extern int dentry_set_symlink_buf(struct dentry *dentry, + const u8 symlink_buf_hash[]); + /* Inline utility functions for WIMDentries */ /* @@ -275,15 +284,18 @@ static inline bool dentry_is_directory(const struct dentry *dentry) return (dentry->attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } +/* For our purposes, we consider "real" symlinks and "junction points" to both + * be symlinks. */ static inline bool dentry_is_symlink(const struct dentry *dentry) { return (dentry->attributes & FILE_ATTRIBUTE_REPARSE_POINT) - && (dentry->reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK); + && ((dentry->reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK) || + dentry->reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT); } static inline bool dentry_is_regular_file(const struct dentry *dentry) { - return !dentry_is_directory(dentry); + return !dentry_is_directory(dentry) && !dentry_is_symlink(dentry); } static inline bool dentry_is_empty_directory(const struct dentry *dentry) diff --git a/src/extract.c b/src/extract.c index a04e3f2e..49433f08 100644 --- a/src/extract.c +++ b/src/extract.c @@ -197,6 +197,25 @@ done: return ret; } +static int extract_symlink(const struct dentry *dentry, const char *output_path, + const WIMStruct *w) +{ + char target[4096]; + ssize_t ret = dentry_readlink(dentry, target, sizeof(target), w); + if (ret <= 0) { + ERROR("Could not read the symbolic link from dentry `%s'", + dentry->full_path_utf8); + return WIMLIB_ERR_INVALID_DENTRY; + } + ret = symlink(target, output_path); + if (ret != 0) { + ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'", + output_path, target); + return WIMLIB_ERR_LINK; + } + return 0; +} + /* * Extracts a directory from the WIM archive. * @@ -239,13 +258,14 @@ struct extract_args { * @dentry: The dentry to extract. * @arg: A pointer to the WIMStruct for the WIM file. */ -static int extract_regular_file_or_directory(struct dentry *dentry, void *arg) +static int extract_dentry(struct dentry *dentry, void *arg) { struct extract_args *args = arg; WIMStruct *w = args->w; int extract_flags = args->extract_flags; size_t len = strlen(w->output_dir); char output_path[len + dentry->full_path_utf8_len + 1]; + int ret = 0; if (extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE) puts(dentry->full_path_utf8); @@ -254,13 +274,13 @@ static int extract_regular_file_or_directory(struct dentry *dentry, void *arg) memcpy(output_path + len, dentry->full_path_utf8, dentry->full_path_utf8_len); output_path[len + dentry->full_path_utf8_len] = '\0'; - if (dentry_is_regular_file(dentry)) { - return extract_regular_file(w, dentry, output_path, extract_flags); + if (dentry_is_symlink(dentry)) { + ret = extract_symlink(dentry, output_path, w); + } else if (dentry_is_directory(dentry)) { + if (!dentry_is_root(dentry)) /* Root doesn't need to be extracted. */ + ret = extract_directory(dentry, output_path); } else { - if (dentry_is_root(dentry)) /* Root doesn't need to be extracted. */ - return 0; - else - return extract_directory(dentry, output_path); + ret = extract_regular_file(w, dentry, output_path, extract_flags); } } @@ -282,8 +302,7 @@ static int extract_single_image(WIMStruct *w, int image, int extract_flags) #endif }; - return for_dentry_in_tree(wim_root_dentry(w), - extract_regular_file_or_directory, &args); + return for_dentry_in_tree(wim_root_dentry(w), extract_dentry, &args); } diff --git a/src/io.h b/src/io.h index ac5ea506..5ab46d22 100644 --- a/src/io.h +++ b/src/io.h @@ -108,6 +108,12 @@ static inline const u8 *get_bytes(const u8 *p, size_t num_bytes, void *res) return p + num_bytes; } +static inline u8 *put_zeroes(u8 *p, size_t num_bytes) +{ + memset(p, 0, num_bytes); + return p + num_bytes; +} + static inline u8 *put_bytes(u8 *p, size_t num_bytes, const u8 *input) { memcpy(p, input, num_bytes); diff --git a/src/lookup_table.c b/src/lookup_table.c index 93580934..821954eb 100644 --- a/src/lookup_table.c +++ b/src/lookup_table.c @@ -348,3 +348,9 @@ WIMLIBAPI void wimlib_print_lookup_table(WIMStruct *w) for_lookup_table_entry(w->lookup_table, print_lookup_table_entry, NULL); } + +/*struct lookup_table_entry *lookup_resource(const struct lookup_table *table,*/ + /*const char *path, int lookup_flags)*/ +/*{*/ + /*return lookup_resource(w->lookup_table, dentry->hash);*/ +/*}*/ diff --git a/src/lookup_table.h b/src/lookup_table.h index 6deb86fc..a269e512 100644 --- a/src/lookup_table.h +++ b/src/lookup_table.h @@ -7,6 +7,8 @@ /* Size of each lookup table entry in the WIM file. */ #define WIM_LOOKUP_TABLE_ENTRY_DISK_SIZE 50 +#define LOOKUP_FLAG_ADS_OK + /* A lookup table that is used to translate the hash codes of dentries into the * offsets and sizes of uncompressed or compressed file resources. It is @@ -35,6 +37,11 @@ struct lookup_table_entry { /* Number of times this lookup table entry is referenced by dentries. */ u32 refcnt; + /* If %true, this lookup table entry corresponds to a symbolic link + * reparse buffer. @symlink_reparse_data_buf will give the target of + * the symbolic link. */ + bool is_symlink; + union { /* SHA1 hash of the file resource pointed to by this lookup * table entry */ @@ -56,6 +63,7 @@ struct lookup_table_entry { union { char *file_on_disk; char *staging_file_name; + void *symlink_buf; struct lookup_table_entry *next_lte_in_swm; }; @@ -133,6 +141,7 @@ wim_lookup_resource(const WIMStruct *w, const struct dentry *dentry) return lookup_resource(w->lookup_table, dentry->hash); } + extern int zero_out_refcnts(struct lookup_table_entry *entry, void *ignore); extern int print_lookup_table_entry(struct lookup_table_entry *entry, void *ignore); diff --git a/src/modify.c b/src/modify.c index 5694ec20..6b1239da 100644 --- a/src/modify.c +++ b/src/modify.c @@ -36,6 +36,11 @@ #include #include #include +#include + +/** Private flag: Used to mark that we currently adding the root directory of + * the WIM. */ +#define WIMLIB_ADD_IMAGE_FLAG_ROOT 0x80000000 static void destroy_image_metadata(struct image_metadata *imd, struct lookup_table *lt) @@ -68,31 +73,52 @@ static void destroy_image_metadata(struct image_metadata *imd, * to the WIM may still occur later when trying to actually read * the regular files in the tree into the WIM as file resources. */ -static int build_dentry_tree(struct dentry *root, const char *source_path, - struct stat *root_stat, - struct lookup_table* lookup_table) +static int build_dentry_tree(struct dentry *root, const char *root_disk_path, + struct lookup_table* lookup_table, + int add_flags) { - int ret = 0; + DEBUG("`%s'", root_disk_path); + struct stat root_stbuf; + int ret; + int (*stat_fn)(const char *restrict, struct stat *restrict); + + if (add_flags & WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE) + stat_fn = stat; + else + stat_fn = lstat; + + + ret = (*stat_fn)(root_disk_path, &root_stbuf); + if (ret != 0) { + ERROR_WITH_ERRNO("Failed to stat `%s'", root_disk_path); + return WIMLIB_ERR_STAT; + } + + if ((add_flags & WIMLIB_ADD_IMAGE_FLAG_ROOT) && + !S_ISDIR(root_stbuf.st_mode)) { + ERROR("`%s' is not a directory", root_disk_path); + return WIMLIB_ERR_NOTDIR; + } + stbuf_to_dentry(&root_stbuf, root); + add_flags &= ~WIMLIB_ADD_IMAGE_FLAG_ROOT; - stbuf_to_dentry(root_stat, root); if (dentry_is_directory(root)) { /* Open the directory on disk */ DIR *dir; struct dirent *p; - struct stat child_stat; struct dentry *child; - dir = opendir(source_path); + dir = opendir(root_disk_path); if (!dir) { ERROR_WITH_ERRNO("Failed to open the directory `%s'", - source_path); + root_disk_path); return WIMLIB_ERR_OPEN; } /* Buffer for names of files in directory. */ - size_t len = strlen(source_path); + size_t len = strlen(root_disk_path); char name[len + 1 + FILENAME_MAX + 1]; - memcpy(name, source_path, len); + memcpy(name, root_disk_path, len); name[len] = '/'; /* Create a dentry for each entry in the directory on disk, and recurse @@ -102,23 +128,69 @@ static int build_dentry_tree(struct dentry *root, const char *source_path, || (p->d_name[1] == '.' && p->d_name[2] == '\0'))) continue; strcpy(name + len + 1, p->d_name); - if (stat(name, &child_stat) != 0) { - ERROR_WITH_ERRNO("Cannot stat `%s'", name); - ret = WIMLIB_ERR_STAT; - break; - } child = new_dentry(p->d_name); if (!child) { ERROR("No memory to allocate new dentry"); return WIMLIB_ERR_NOMEM; } - ret = build_dentry_tree(child, name, &child_stat, - lookup_table); + ret = build_dentry_tree(child, name, lookup_table, + add_flags); link_dentry(child, root); if (ret != 0) break; } closedir(dir); + } else if (dentry_is_symlink(root)) { + /* Archiving a symbolic link */ + size_t symlink_buf_len; + char deref_name_buf[4096]; + ssize_t ret = readlink(root_disk_path, deref_name_buf, + sizeof(deref_name_buf) - 1); + if (ret == -1) { + ERROR_WITH_ERRNO("Failed to read target of " + "symbolic link `%s'", root_disk_path); + return WIMLIB_ERR_STAT; + } + deref_name_buf[ret] = '\0'; + DEBUG("Read symlink `%s'", deref_name_buf); + void *symlink_buf = make_symlink_reparse_data_buf(deref_name_buf, + &symlink_buf_len); + if (!symlink_buf) + return WIMLIB_ERR_NOMEM; + DEBUG("Made symlink reparse data buf (len = %zu, name len = %zu)", + symlink_buf_len, ret); + + u8 symlink_buf_hash[WIM_HASH_SIZE]; + sha1_buffer(symlink_buf, symlink_buf_len, symlink_buf_hash); + + ret = dentry_set_symlink_buf(root, symlink_buf_hash); + + if (ret != 0) { + FREE(symlink_buf); + return ret; + } + DEBUG("Created symlink buf"); + + struct lookup_table_entry *lte; + struct lookup_table_entry *existing_lte; + + existing_lte = lookup_resource(lookup_table, symlink_buf_hash); + if (existing_lte) { + existing_lte->refcnt++; + } else { + DEBUG("Creating new lookup table entry"); + lte = new_lookup_table_entry(); + if (!lte) { + FREE(symlink_buf); + return WIMLIB_ERR_NOMEM; + } + lte->symlink_buf = symlink_buf; + lte->resource_entry.original_size = symlink_buf_len; + lte->resource_entry.size = symlink_buf_len; + lte->is_symlink = true; + memcpy(lte->hash, symlink_buf_hash, WIM_HASH_SIZE); + lookup_table_insert(lookup_table, lte); + } } else { struct lookup_table_entry *lte; @@ -126,7 +198,7 @@ static int build_dentry_tree(struct dentry *root, const char *source_path, * in the lookup table already; if it is, we increment its * refcnt; otherwise, we create a new lookup table entry and * insert it. */ - ret = sha1sum(source_path, root->hash); + ret = sha1sum(root_disk_path, root->hash); if (ret != 0) return ret; @@ -134,7 +206,7 @@ static int build_dentry_tree(struct dentry *root, const char *source_path, if (lte) { lte->refcnt++; } else { - char *file_on_disk = STRDUP(source_path); + char *file_on_disk = STRDUP(root_disk_path); if (!file_on_disk) { ERROR("Failed to allocate memory for file path"); return WIMLIB_ERR_NOMEM; @@ -145,11 +217,8 @@ static int build_dentry_tree(struct dentry *root, const char *source_path, return WIMLIB_ERR_NOMEM; } lte->file_on_disk = file_on_disk; - lte->resource_entry.flags = 0; - lte->refcnt = 1; - lte->part_number = 1; - lte->resource_entry.original_size = root_stat->st_size; - lte->resource_entry.size = root_stat->st_size; + lte->resource_entry.original_size = root_stbuf.st_size; + lte->resource_entry.size = root_stbuf.st_size; memcpy(lte->hash, root->hash, WIM_HASH_SIZE); lookup_table_insert(lookup_table, lte); } @@ -480,19 +549,9 @@ WIMLIBAPI int wimlib_add_image(WIMStruct *w, const char *dir, root_dentry->attributes |= FILE_ATTRIBUTE_DIRECTORY; - /* Construct the dentry tree from the outside filesystem. */ - if (stat(dir, &root_stat) != 0) { - ERROR_WITH_ERRNO("Failed to stat `%s'", dir); - ret = WIMLIB_ERR_STAT; - goto out_free_dentry_tree; - } - if (!S_ISDIR(root_stat.st_mode)) { - ERROR("`%s' is not a directory", dir); - ret = WIMLIB_ERR_NOTDIR; - goto out_free_dentry_tree; - } DEBUG("Building dentry tree."); - ret = build_dentry_tree(root_dentry, dir, &root_stat, w->lookup_table); + ret = build_dentry_tree(root_dentry, dir, w->lookup_table, + flags | WIMLIB_ADD_IMAGE_FLAG_ROOT); if (ret != 0) { ERROR("Failed to build dentry tree for `%s'", dir); diff --git a/src/mount.c b/src/mount.c index a4359947..640b27c6 100644 --- a/src/mount.c +++ b/src/mount.c @@ -618,7 +618,7 @@ static int wimfs_open(const char *path, struct fuse_file_info *fi) dentry = get_dentry(w, path); if (!dentry) - return -EEXIST; + return -ENOENT; if (dentry_is_directory(dentry)) return -EISDIR; lte = wim_lookup_resource(w, dentry); @@ -780,87 +780,20 @@ static int wimfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, return 0; } -/* - * Find the symlink target of a symbolic link dentry in the WIM. - * - * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx - * Except the first 8 bytes aren't included in the resource (presumably because - * we already know the reparse tag from the dentry, and we already know the - * reparse tag len from the lookup table entry resource length). - */ -static int get_symlink_name(const u8 *resource, size_t resource_len, - char *buf, size_t buf_len) -{ - const u8 *p = resource; - u16 substitute_name_offset; - u16 substitute_name_len; - u16 print_name_offset; - u16 print_name_len; - u32 flags; - char *link_target; - size_t link_target_len; - int ret; - if (resource_len < 12) - return -EIO; - p = get_u16(p, &substitute_name_offset); - p = get_u16(p, &substitute_name_len); - p = get_u16(p, &print_name_offset); - p = get_u16(p, &print_name_len); - p = get_u32(p, &flags); - if (12 + substitute_name_offset + substitute_name_len > resource_len) - return -EIO; - link_target = utf16_to_utf8(p + substitute_name_offset, - substitute_name_len, - &link_target_len); - if (!link_target) - return -EIO; - if (link_target_len + 1 > buf_len) { - ret = -ENAMETOOLONG; - goto out; - } - memcpy(buf, link_target, link_target_len + 1); - ret = 0; -out: - free(link_target); - return ret; -} static int wimfs_readlink(const char *path, char *buf, size_t buf_len) { struct dentry *dentry = get_dentry(w, path); - struct ads_entry *ads; - struct lookup_table_entry *entry; - struct resource_entry *res_entry; + int ret; if (!dentry) return -ENOENT; if (!dentry_is_symlink(dentry)) return -EINVAL; - /* - * This is of course not actually documented, but what I think is going - * on here is that the symlink dentries have 2 alternate data streams; - * one is the default data stream, which is not used and is empty, and - * one is the symlink buffer data stream, which is confusingly also - * unnamed, but isn't empty as it contains the symlink target within the - * resource. - */ - if (dentry->num_ads != 2) - return -EIO; - if ((entry = lookup_resource(w->lookup_table, dentry->ads_entries[0].hash))) - goto do_readlink; - if ((entry = lookup_resource(w->lookup_table, dentry->ads_entries[1].hash))) - goto do_readlink; - return -EIO; -do_readlink: - res_entry = &entry->resource_entry; - char res_buf[res_entry->original_size]; - if (read_full_resource(w->fp, res_entry->size, - res_entry->original_size, - res_entry->offset, - wim_resource_compression_type(w, res_entry), - res_buf) != 0) - return -EIO; - return get_symlink_name(res_buf, res_entry->original_size, buf, buf_len); + ret = dentry_readlink(dentry, buf, buf_len, w); + if (ret > 0) + ret = 0; + return ret; } /* Close a file. */ diff --git a/src/resource.c b/src/resource.c index 5e6707d9..03754e08 100644 --- a/src/resource.c +++ b/src/resource.c @@ -1090,37 +1090,12 @@ int write_metadata_resource(WIMStruct *w) return 0; } -/* - * Writes a file resource to the output file. - * - * @dentry: The dentry for the file resource. - * @wim_p: A pointer to the WIMStruct. The fields of interest to this - * function are the input and output file streams and the lookup - * table. - * - * @return zero on success, nonzero on failure. - */ -int write_file_resource(struct dentry *dentry, void *wim_p) +static int write_file_resource(WIMStruct *w, const u8 hash[]) { - WIMStruct *w; - FILE *out_fp; - FILE *in_fp; - struct lookup_table_entry *lte; - int in_wim_ctype; - int out_wim_ctype; - struct resource_entry *output_res_entry; - u64 len; - int ret; - - w = wim_p; - out_fp = w->out_fp; - - /* Directories don't need file resources. */ - if (dentry_is_directory(dentry)) - return 0; - /* Get the lookup entry for the file resource. */ - lte = wim_lookup_resource(w, dentry); + struct lookup_table_entry *lte; + + lte = lookup_resource(w->lookup_table, hash); if (!lte) return 0; @@ -1129,16 +1104,43 @@ int write_file_resource(struct dentry *dentry, void *wim_p) if (++lte->out_refcnt != 1) return 0; - out_wim_ctype = wimlib_get_compression_type(w); - output_res_entry = <e->output_resource_entry; - /* do not write empty resources */ if (lte->resource_entry.original_size == 0) return 0; + int out_wim_ctype = wimlib_get_compression_type(w); + struct resource_entry *output_res_entry = <e->output_resource_entry; + u64 len; + FILE *in_fp; + FILE *out_fp = w->out_fp; + int ret = 0; + /* Figure out if we can read the resource from the WIM file, or - * if we have to read it from the filesystem outside. */ - if (lte->file_on_disk) { + * if we have to read it from the filesystem outside, or if it's a + * symbolic link with the data already in memory pointed to by a field + * of the lookup table entry. */ + if (lte->is_symlink) { + off_t offset = ftello(w->out_fp); + u64 new_size; + + if (offset == -1) { + ERROR_WITH_ERRNO("Could not get position in output " + "file"); + return WIMLIB_ERR_WRITE; + } + + wimlib_assert(lte->symlink_buf); + + len = lte->resource_entry.original_size; + + recompress_resource(in_fp, lte->symlink_buf, len, len, 0, + 0, out_fp, out_wim_ctype, &new_size); + output_res_entry->size = new_size; + output_res_entry->original_size = len; + output_res_entry->offset = offset; + output_res_entry->flags = (out_wim_ctype == WIM_COMPRESSION_TYPE_NONE) + ? 0 : WIM_RESHDR_FLAG_COMPRESSED; + } else if (lte->file_on_disk) { /* Read from disk (uncompressed) */ @@ -1156,6 +1158,7 @@ int write_file_resource(struct dentry *dentry, void *wim_p) out_wim_ctype, output_res_entry); fclose(in_fp); } else { + int in_wim_ctype; /* Read from input WIM (possibly compressed) */ @@ -1185,3 +1188,34 @@ int write_file_resource(struct dentry *dentry, void *wim_p) } return ret; } + +/* + * Writes a dentry's resources to the output file. + * + * @dentry: The dentry for the file resource. + * @wim_p: A pointer to the WIMStruct. The fields of interest to this + * function are the input and output file streams and the lookup + * table, and the alternate data streams. + * + * @return zero on success, nonzero on failure. + */ +int write_dentry_resources(struct dentry *dentry, void *wim_p) +{ + WIMStruct *w = wim_p; + int ret; + + /* Directories don't need file resources. */ + if (dentry_is_directory(dentry)) + return 0; + + ret = write_file_resource(w, dentry->hash); + if (ret != 0) + return ret; + for (u16 i = 0; i < dentry->num_ads; i++) { + ret = write_file_resource(w, dentry->ads_entries[i].hash); + if (ret != 0) + return ret; + } + return 0; +} + diff --git a/src/symlink.c b/src/symlink.c new file mode 100644 index 00000000..42a771e6 --- /dev/null +++ b/src/symlink.c @@ -0,0 +1,162 @@ +#include "dentry.h" +#include "io.h" +#include "lookup_table.h" + +/* + * Find the symlink target of a symbolic link or junction point in the WIM. + * + * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx + * Except the first 8 bytes aren't included in the resource (presumably because + * we already know the reparse tag from the dentry, and we already know the + * reparse tag len from the lookup table entry resource length). + */ +static ssize_t get_symlink_name(const u8 *resource, size_t resource_len, + char *buf, size_t buf_len, + bool is_junction_point) +{ + const u8 *p = resource; + u16 substitute_name_offset; + u16 substitute_name_len; + u16 print_name_offset; + u16 print_name_len; + char *link_target; + size_t link_target_len; + ssize_t ret; + unsigned header_size; + char *translated_target; + bool is_absolute; + + if (resource_len < 12) + return -EIO; + p = get_u16(p, &substitute_name_offset); + p = get_u16(p, &substitute_name_len); + p = get_u16(p, &print_name_offset); + p = get_u16(p, &print_name_len); + if (is_junction_point) { + header_size = 8; + } else { + u32 flags; + p = get_u32(p, &flags); + is_absolute = (flags & 1) ? false : true; + header_size = 12; + } + if (header_size + substitute_name_offset + substitute_name_len > resource_len) + return -EIO; + link_target = utf16_to_utf8(p + substitute_name_offset, + substitute_name_len, + &link_target_len); + + if (!link_target) + return -EIO; + + if (link_target_len + 1 > buf_len) { + ret = -ENAMETOOLONG; + goto out; + } + + translated_target = link_target; + if (is_junction_point || is_absolute) { + if (link_target_len < 7 + || memcmp(translated_target, "\\??\\", 4) != 0 + || translated_target[4] == '\0' + || translated_target[5] != ':' + || translated_target[6] != '\\') { + ret = -EIO; + goto out; + } + translated_target += 4; + link_target_len -= 4; + } + for (size_t i = 0; i < link_target_len; i++) + if (translated_target[i] == '\\') + translated_target[i] = '/'; + + memcpy(buf, translated_target, link_target_len + 1); + ret = link_target_len; +out: + FREE(link_target); + return ret; +} + +void *make_symlink_reparse_data_buf(const char *symlink_target, size_t *len_ret) +{ + size_t utf8_len = strlen(symlink_target); + size_t utf16_len; + char *name_utf16 = utf8_to_utf16(symlink_target, utf8_len, &utf16_len); + if (!name_utf16) + return NULL; + /*DEBUG("utf16_len = %zu", utf16_len);*/ + for (size_t i = 0; i < utf16_len / 2; i++) + if (((u16*)name_utf16)[i] == to_le16('/')) + ((u16*)name_utf16)[i] = to_le16('\\'); + size_t len = 12 + utf16_len * 2; + void *buf = MALLOC(len); + if (!buf) + goto out; + + u8 *p = buf; + p = put_u16(p, 0); /* Substitute name offset */ + p = put_u16(p, utf16_len); /* Substitute name length */ + p = put_u16(p, utf16_len); /* Print name offset */ + p = put_u16(p, utf16_len); /* Print name length */ + p = put_u32(p, (symlink_target[0] == '/') ? 0 : 1); + p = put_bytes(p, utf16_len, name_utf16); + p = put_bytes(p, utf16_len, name_utf16); + /*DEBUG("utf16_len = %zu, len = %zu", utf16_len, len);*/ + *len_ret = len; +out: + FREE(name_utf16); + return buf; +} + +/* Get the symlink target from a dentry that's already checked to be either a + * "real" symlink or a junction point. */ +ssize_t dentry_readlink(const struct dentry *dentry, char *buf, size_t buf_len, + const WIMStruct *w) +{ + struct ads_entry *ads; + struct lookup_table_entry *entry; + struct resource_entry *res_entry; + bool is_junction_point; + + wimlib_assert(dentry_is_symlink(dentry)); + + if (dentry->reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK) { + is_junction_point = false; + /* + * This is of course not actually documented, but what I think is going + * on here is that the symlink dentries have 2 alternate data streams; + * one is the default data stream, which is not used and is empty, and + * one is the symlink buffer data stream, which is confusingly also + * unnamed, but isn't empty as it contains the symlink target within the + * resource. + */ + if (dentry->num_ads != 2) + return -EIO; + if ((entry = lookup_resource(w->lookup_table, dentry->ads_entries[0].hash))) + goto do_readlink; + if ((entry = lookup_resource(w->lookup_table, dentry->ads_entries[1].hash))) + goto do_readlink; + } else { + wimlib_assert(dentry->reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT); + + is_junction_point = true; + + if ((entry = lookup_resource(w->lookup_table, dentry->hash))) + goto do_readlink; + } + return -EIO; +do_readlink: + res_entry = &entry->resource_entry; + if (res_entry->original_size > 10000) + return -EIO; + char res_buf[res_entry->original_size]; + if (read_full_resource(w->fp, res_entry->size, + res_entry->original_size, + res_entry->offset, + wim_resource_compression_type(w, res_entry), + res_buf) != 0) + return -EIO; + return get_symlink_name(res_buf, res_entry->original_size, buf, + buf_len, is_junction_point); +} diff --git a/src/util.c b/src/util.c index 47b76a82..725b1272 100644 --- a/src/util.c +++ b/src/util.c @@ -295,8 +295,8 @@ static iconv_t cd_utf8_to_utf16 = (iconv_t)(-1); /* Converts a string in the UTF-8 encoding to a newly allocated string in the * UTF-16 encoding. */ -char *utf8_to_utf16(const char *utf8_str, size_t utf8_len, - size_t *utf16_len_ret) +char *utf8_to_utf16(const char *utf8_str, size_t utf8_len, + size_t *utf16_len_ret) { if (cd_utf8_to_utf16 == (iconv_t)(-1)) { cd_utf8_to_utf16 = iconv_open("UTF-16LE", "UTF-8"); diff --git a/src/wimlib.h b/src/wimlib.h index dde93c61..81218635 100644 --- a/src/wimlib.h +++ b/src/wimlib.h @@ -276,6 +276,9 @@ enum wim_compression_type { * mounted with NTFS-3g, and wimlib was compiled with support for NTFS-3g */ #define WIMLIB_ADD_IMAGE_FLAG_NTFS 0x00000004 +/** Follow symlinks; archive and dump the files they point to. */ +#define WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE 0x00000008 + /** See documentation for wimlib_export_image(). */ #define WIMLIB_EXPORT_FLAG_BOOT 0x00000001 diff --git a/src/wimlib_internal.h b/src/wimlib_internal.h index 28edf93f..b59b0549 100644 --- a/src/wimlib_internal.h +++ b/src/wimlib_internal.h @@ -31,7 +31,10 @@ struct stat; +#ifndef WIM_HASH_SIZE #define WIM_HASH_SIZE 20 +#endif + #define WIM_MAGIC_LEN 8 #define WIM_GID_LEN 16 #define WIM_UNUSED_LEN 60 @@ -44,8 +47,8 @@ struct stat; * of this size. */ #define WIM_CHUNK_SIZE 32768 -/* Version of the WIM file. I don't know if there has ever been a different - * version or not. */ +/* Version of the WIM file. There is an older version, but we don't support it + * yet. The differences between the versions are undocumented. */ #define WIM_VERSION 0x10d00 enum wim_integrity_status { @@ -367,7 +370,7 @@ static inline int read_full_resource(FILE *fp, u64 resource_size, resource_original_size, 0, contents_ret); } -extern int write_file_resource(struct dentry *dentry, void *wim_p); +extern int write_dentry_resources(struct dentry *dentry, void *wim_p); extern int copy_resource(struct lookup_table_entry *lte, void *w); extern int copy_between_files(FILE *in, off_t in_offset, FILE *out, size_t len); extern int write_resource_from_memory(const u8 resource[], int out_ctype, @@ -375,6 +378,7 @@ extern int write_resource_from_memory(const u8 resource[], int out_ctype, u64 *resource_size_ret); extern int write_metadata_resource(WIMStruct *w); + /* security.c */ int read_security_data(const u8 metadata_resource[], u64 metadata_resource_len, struct wim_security_data **sd_p); @@ -383,6 +387,12 @@ void print_security_data(const struct wim_security_data *sd); u8 *write_security_data(const struct wim_security_data *sd, u8 *p); void free_security_data(struct wim_security_data *sd); +/* symlink.c */ +ssize_t dentry_readlink(const struct dentry *dentry, char *buf, size_t buf_len, + const WIMStruct *w); +extern void *make_symlink_reparse_data_buf(const char *symlink_target, + size_t *len_ret); + /* wim.c */ extern WIMStruct *new_wim_struct(); extern int wimlib_select_image(WIMStruct *w, int image); diff --git a/src/write.c b/src/write.c index 3866bed6..b4aa2655 100644 --- a/src/write.c +++ b/src/write.c @@ -247,7 +247,7 @@ static int write_file_resources(WIMStruct *w) { DEBUG("Writing file resources for image %u.", w->current_image); - return for_dentry_in_tree(wim_root_dentry(w), write_file_resource, w); + return for_dentry_in_tree(wim_root_dentry(w), write_dentry_resources, w); } /* Write the lookup table, xml data, and integrity table, then overwrite the WIM