]> wimlib.net Git - wimlib/blobdiff - programs/imagex.c
Add support for not extracting file attributes
[wimlib] / programs / imagex.c
index 142d809163af8ea019bd22fee93a41ddc3056099..6044a93c0c23d9e01a013d89ae88607fb05a4a21 100644 (file)
@@ -51,6 +51,7 @@
 #  define tbasename    win32_wbasename
 #  define OS_PREFERRED_PATH_SEPARATOR L'\\'
 #  define OS_PREFERRED_PATH_SEPARATOR_STRING L"\\"
+#  define print_security_descriptor     win32_print_security_descriptor
 #else /* __WIN32__ */
 #  include <glob.h>
 #  include <getopt.h>
@@ -58,6 +59,7 @@
 #  define tbasename    basename
 #  define OS_PREFERRED_PATH_SEPARATOR '/'
 #  define OS_PREFERRED_PATH_SEPARATOR_STRING "/"
+#  define print_security_descriptor    default_print_security_descriptor
 static inline void set_fd_to_binary_mode(int fd)
 {
 }
@@ -130,6 +132,7 @@ enum {
        IMAGEX_DELTA_FROM_OPTION,
        IMAGEX_DEREFERENCE_OPTION,
        IMAGEX_DEST_DIR_OPTION,
+       IMAGEX_DETAILED_OPTION,
        IMAGEX_EXTRACT_XML_OPTION,
        IMAGEX_FLAGS_OPTION,
        IMAGEX_FORCE_OPTION,
@@ -142,8 +145,11 @@ enum {
        IMAGEX_NORPFIX_OPTION,
        IMAGEX_NOCHECK_OPTION,
        IMAGEX_NO_ACLS_OPTION,
+       IMAGEX_NO_ATTRIBUTES_OPTION,
        IMAGEX_NO_WILDCARDS_OPTION,
+       IMAGEX_ONE_FILE_ONLY_OPTION,
        IMAGEX_NOT_PIPABLE_OPTION,
+       IMAGEX_PACK_CHUNK_SIZE_OPTION,
        IMAGEX_PACK_STREAMS_OPTION,
        IMAGEX_PATH_OPTION,
        IMAGEX_PIPABLE_OPTION,
@@ -178,6 +184,7 @@ static const struct option apply_options[] = {
        {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
+       {T("no-attributes"), no_argument,     NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
        {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
        {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
        {T("include-invalid-names"), no_argument,       NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
@@ -195,7 +202,10 @@ static const struct option capture_or_append_options[] = {
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
+       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_PACK_CHUNK_SIZE_OPTION},
+       {T("solid-chunk-size"),required_argument, NULL, IMAGEX_PACK_CHUNK_SIZE_OPTION},
        {T("pack-streams"), no_argument,      NULL, IMAGEX_PACK_STREAMS_OPTION},
+       {T("solid"),       no_argument,      NULL, IMAGEX_PACK_STREAMS_OPTION},
        {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
        {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
        {T("flags"),       required_argument, NULL, IMAGEX_FLAGS_OPTION},
@@ -223,7 +233,9 @@ static const struct option delete_options[] = {
 };
 
 static const struct option dir_options[] = {
-       {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
+       {T("path"),     required_argument, NULL, IMAGEX_PATH_OPTION},
+       {T("detailed"), no_argument,       NULL, IMAGEX_DETAILED_OPTION},
+       {T("one-file-only"), no_argument,  NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -234,7 +246,10 @@ static const struct option export_options[] = {
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("pack-streams"),no_argument,       NULL, IMAGEX_PACK_STREAMS_OPTION},
+       {T("solid"),       no_argument,       NULL, IMAGEX_PACK_STREAMS_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
+       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_PACK_CHUNK_SIZE_OPTION},
+       {T("solid-chunk-size"),required_argument, NULL, IMAGEX_PACK_CHUNK_SIZE_OPTION},
        {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
@@ -251,6 +266,7 @@ static const struct option extract_options[] = {
        {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
+       {T("no-attributes"), no_argument,     NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
        {T("dest-dir"),    required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
        {T("to-stdout"),   no_argument,       NULL, IMAGEX_TO_STDOUT_OPTION},
        {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
@@ -297,7 +313,10 @@ static const struct option optimize_options[] = {
        {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("recompress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
+       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_PACK_CHUNK_SIZE_OPTION},
+       {T("solid-chunk-size"),required_argument, NULL, IMAGEX_PACK_CHUNK_SIZE_OPTION},
        {T("pack-streams"),no_argument,       NULL, IMAGEX_PACK_STREAMS_OPTION},
+       {T("solid"),       no_argument,       NULL, IMAGEX_PACK_STREAMS_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
        {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
@@ -434,11 +453,10 @@ get_compression_type(const tchar *optarg)
        }
 }
 
-static int
+static void
 set_compress_slow(void)
 {
-       int ret;
-       static const struct wimlib_lzx_compressor_params slow_params = {
+       static const struct wimlib_lzx_compressor_params lzx_slow_params = {
                .hdr = {
                        .size = sizeof(struct wimlib_lzx_compressor_params),
                },
@@ -446,7 +464,7 @@ set_compress_slow(void)
                .alg_params = {
                        .slow = {
                                .use_len2_matches = 1,
-                               .num_fast_bytes = 96,
+                               .nice_match_length = 96,
                                .num_optim_passes = 4,
                                .max_search_depth = 100,
                                .max_matches_per_pos = 10,
@@ -456,11 +474,24 @@ set_compress_slow(void)
                        },
                },
        };
-       ret = wimlib_set_default_compressor_params(WIMLIB_COMPRESSION_TYPE_LZX,
-                                                  &slow_params.hdr);
-       if (ret)
-               imagex_error(T("Couldn't set slow compression parameters.!"));
-       return ret;
+
+       static const struct wimlib_lzms_compressor_params lzms_slow_params = {
+               .hdr = {
+                       .size = sizeof(struct wimlib_lzms_compressor_params),
+               },
+               .min_match_length = 2,
+               .max_match_length = UINT32_MAX,
+               .nice_match_length = 96,
+               .max_search_depth = 100,
+               .max_matches_per_pos = 10,
+               .optim_array_length = 1024,
+       };
+
+       wimlib_set_default_compressor_params(WIMLIB_COMPRESSION_TYPE_LZX,
+                                            &lzx_slow_params.hdr);
+
+       wimlib_set_default_compressor_params(WIMLIB_COMPRESSION_TYPE_LZMS,
+                                            &lzms_slow_params.hdr);
 }
 
 struct string_set {
@@ -1101,16 +1132,22 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                return 0;
        switch (msg) {
        case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
+               {
+                       static bool first = true;
+                       if (first) {
+                               imagex_printf(T("Writing %"TS"-compressed data "
+                                               "using %u thread%"TS"\n"),
+                                             wimlib_get_compression_type_string(
+                                                       info->write_streams.compression_type),
+                                       info->write_streams.num_threads,
+                                       (info->write_streams.num_threads == 1) ? T("") : T("s"));
+                               first = false;
+                       }
+               }
                unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
                percent_done = TO_PERCENT(info->write_streams.completed_bytes,
                                          info->write_streams.total_bytes);
 
-               if (info->write_streams.completed_streams == 0) {
-                       imagex_printf(T("Writing %"TS"-compressed data using %u thread%"TS"\n"),
-                               wimlib_get_compression_type_string(info->write_streams.compression_type),
-                               info->write_streams.num_threads,
-                               (info->write_streams.num_threads == 1) ? T("") : T("s"));
-               }
                if (info->write_streams.total_parts <= 1) {
                        imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
                                "written (%u%% done)"),
@@ -1552,7 +1589,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
        const tchar *wimfile;
        const tchar *target;
        const tchar *image_num_or_name = NULL;
-       int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
+       int extract_flags = 0;
 
        STRING_SET(refglobs);
 
@@ -1584,6 +1621,9 @@ imagex_apply(int argc, tchar **argv, int cmd)
                case IMAGEX_STRICT_ACLS_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
                        break;
+               case IMAGEX_NO_ATTRIBUTES_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
+                       break;
                case IMAGEX_NORPFIX_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
                        break;
@@ -1730,6 +1770,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        int write_flags = 0;
        int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
        uint32_t chunk_size = UINT32_MAX;
+       uint32_t pack_chunk_size = UINT32_MAX;
        const tchar *wimfile;
        int wim_fd;
        const tchar *name;
@@ -1762,6 +1803,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        struct wimlib_capture_source *capture_sources;
        size_t num_sources;
        bool name_defaulted;
+       bool compress_slow = false;
 
        for_opt(c, capture_or_append_options) {
                switch (c) {
@@ -1785,16 +1827,18 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                goto out_err;
                        break;
                case IMAGEX_COMPRESS_SLOW_OPTION:
-                       ret = set_compress_slow();
-                       if (ret)
-                               goto out_err;
-                       compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
+                       compress_slow = true;
                        break;
                case IMAGEX_CHUNK_SIZE_OPTION:
                        chunk_size = parse_chunk_size(optarg);
                        if (chunk_size == UINT32_MAX)
                                goto out_err;
                        break;
+               case IMAGEX_PACK_CHUNK_SIZE_OPTION:
+                       pack_chunk_size = parse_chunk_size(optarg);
+                       if (pack_chunk_size == UINT32_MAX)
+                               goto out_err;
+                       break;
                case IMAGEX_PACK_STREAMS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
                        break;
@@ -1882,19 +1926,26 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        source = argv[0];
        wimfile = argv[1];
 
-       /* Set default compression type.  */
+       /* Set default compression type and parameters.  */
+
+
        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
-               struct wimlib_lzx_compressor_params params;
-               memset(&params, 0, sizeof(params));
-               params.hdr.size = sizeof(params);
-               params.algorithm = WIMLIB_LZX_ALGORITHM_FAST;
-               params.use_defaults = 1;
-
-               wimlib_set_default_compressor_params(WIMLIB_COMPRESSION_TYPE_LZX,
-                                                    &params.hdr);
                compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
+
+               if (!compress_slow) {
+                       struct wimlib_lzx_compressor_params params = {
+                               .hdr.size = sizeof(params),
+                               .algorithm = WIMLIB_LZX_ALGORITHM_FAST,
+                               .use_defaults = 1,
+                       };
+                       wimlib_set_default_compressor_params(WIMLIB_COMPRESSION_TYPE_LZX,
+                                                            &params.hdr);
+               }
        }
 
+       if (compress_slow)
+               set_compress_slow();
+
        if (!tstrcmp(wimfile, T("-"))) {
                /* Writing captured WIM to standard output.  */
        #if 0
@@ -2035,6 +2086,11 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                if (ret)
                        goto out_free_wim;
        }
+       if (pack_chunk_size != UINT32_MAX) {
+               ret = wimlib_set_output_pack_chunk_size(wim, pack_chunk_size);
+               if (ret)
+                       goto out_free_wim;
+       }
 
 #ifndef __WIN32__
        /* Detect if source is regular file or block device and set NTFS volume
@@ -2322,11 +2378,205 @@ out_usage:
        goto out;
 }
 
+struct print_dentry_options {
+       bool detailed;
+};
+
+static void
+print_dentry_full_path(const struct wimlib_dir_entry *dentry)
+{
+       tprintf(T("%"TS"\n"), dentry->full_path);
+}
+
+static const struct {
+       uint32_t flag;
+       const tchar *name;
+} file_attr_flags[] = {
+       {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
+       {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
+       {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
+       {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
+       {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
+       {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
+       {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
+       {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
+       {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
+       {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
+       {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
+       {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
+       {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
+       {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
+       {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
+};
+
+#define TIMESTR_MAX 100
+
+static void
+timespec_to_string(const struct timespec *spec, tchar *buf)
+{
+       time_t t = spec->tv_sec;
+       struct tm tm;
+       gmtime_r(&t, &tm);
+       tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
+       buf[TIMESTR_MAX - 1] = '\0';
+}
+
+static void
+print_time(const tchar *type, const struct timespec *spec)
+{
+       tchar timestr[TIMESTR_MAX];
+
+       timespec_to_string(spec, timestr);
+
+       tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
+}
+
+static void print_byte_field(const uint8_t field[], size_t len)
+{
+       while (len--)
+               tprintf(T("%02hhx"), *field++);
+}
+
+static void
+print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
+{
+       tputs(T("WIM Information:"));
+       tputs(T("----------------"));
+       tprintf(T("Path:           %"TS"\n"), wimfile);
+       tprintf(T("GUID:           0x"));
+       print_byte_field(info->guid, sizeof(info->guid));
+       tputchar(T('\n'));
+       tprintf(T("Version:        %u\n"), info->wim_version);
+       tprintf(T("Image Count:    %d\n"), info->image_count);
+       tprintf(T("Compression:    %"TS"\n"),
+               wimlib_get_compression_type_string(info->compression_type));
+       tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
+               info->chunk_size);
+       tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
+       tprintf(T("Boot Index:     %d\n"), info->boot_index);
+       tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
+       tprintf(T("Integrity Info: %"TS"\n"),
+               info->has_integrity_table ? T("yes") : T("no"));
+       tprintf(T("Relative path junction: %"TS"\n"),
+               info->has_rpfix ? T("yes") : T("no"));
+       tprintf(T("Pipable:        %"TS"\n"),
+               info->pipable ? T("yes") : T("no"));
+       tputchar(T('\n'));
+}
+
+static int
+print_resource(const struct wimlib_resource_entry *resource,
+              void *_ignore)
+{
+       tprintf(T("Hash                = 0x"));
+       print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
+       tputchar(T('\n'));
+
+       if (!resource->is_missing) {
+               tprintf(T("Uncompressed size   = %"PRIu64" bytes\n"),
+                       resource->uncompressed_size);
+               if (resource->packed) {
+                       tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
+                               resource->raw_resource_compressed_size);
+
+                       tprintf(T("Raw offset in WIM   = %"PRIu64" bytes\n"),
+                               resource->raw_resource_offset_in_wim);
+
+                       tprintf(T("Offset in raw       = %"PRIu64" bytes\n"),
+                               resource->offset);
+               } else {
+                       tprintf(T("Compressed size     = %"PRIu64" bytes\n"),
+                               resource->compressed_size);
+
+                       tprintf(T("Offset in WIM       = %"PRIu64" bytes\n"),
+                               resource->offset);
+               }
+
+               tprintf(T("Part Number         = %u\n"), resource->part_number);
+               tprintf(T("Reference Count     = %u\n"), resource->reference_count);
+
+               tprintf(T("Flags               = "));
+               if (resource->is_compressed)
+                       tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
+               if (resource->is_metadata)
+                       tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
+               if (resource->is_free)
+                       tprintf(T("WIM_RESHDR_FLAG_FREE  "));
+               if (resource->is_spanned)
+                       tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
+               if (resource->packed)
+                       tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS  "));
+               tputchar(T('\n'));
+       }
+       tputchar(T('\n'));
+       return 0;
+}
+
+static void
+print_lookup_table(WIMStruct *wim)
+{
+       wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
+}
+
+static void
+default_print_security_descriptor(const uint8_t *sd, size_t size)
+{
+       tprintf(T("Security Descriptor = "));
+       print_byte_field(sd, size);
+       tputchar(T('\n'));
+}
+
+static void
+print_dentry_detailed(const struct wimlib_dir_entry *dentry)
+{
+
+       tprintf(T(
+"----------------------------------------------------------------------------\n"));
+       tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
+       if (dentry->dos_name)
+               tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
+       tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
+       for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
+               if (file_attr_flags[i].flag & dentry->attributes)
+                       tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
+                               file_attr_flags[i].name);
+
+       if (dentry->security_descriptor) {
+               print_security_descriptor(dentry->security_descriptor,
+                                         dentry->security_descriptor_size);
+       }
+
+       print_time(T("Creation Time"), &dentry->creation_time);
+       print_time(T("Last Write Time"), &dentry->last_write_time);
+       print_time(T("Last Access Time"), &dentry->last_access_time);
+
+
+       if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
+               tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
+
+       tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
+       tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
+
+       for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
+               if (dentry->streams[i].stream_name) {
+                       tprintf(T("\tData stream \"%"TS"\":\n"),
+                               dentry->streams[i].stream_name);
+               } else {
+                       tprintf(T("\tUnnamed data stream:\n"));
+               }
+               print_resource(&dentry->streams[i].resource, NULL);
+       }
+}
+
 static int
-print_full_path(const struct wimlib_dir_entry *wdentry, void *_ignore)
+print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
 {
-       int ret = tprintf(T("%"TS"\n"), wdentry->full_path);
-       return (ret >= 0) ? 0 : -1;
+       const struct print_dentry_options *options = _options;
+       if (!options->detailed)
+               print_dentry_full_path(dentry);
+       else
+               print_dentry_detailed(dentry);
+       return 0;
 }
 
 /* Print the files contained in an image(s) in a WIM file. */
@@ -2339,12 +2589,22 @@ imagex_dir(int argc, tchar **argv, int cmd)
        int ret;
        const tchar *path = T("");
        int c;
+       struct print_dentry_options options = {
+               .detailed = false,
+       };
+       int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
 
        for_opt(c, dir_options) {
                switch (c) {
                case IMAGEX_PATH_OPTION:
                        path = optarg;
                        break;
+               case IMAGEX_DETAILED_OPTION:
+                       options.detailed = true;
+                       break;
+               case IMAGEX_ONE_FILE_ONLY_OPTION:
+                       iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2388,9 +2648,8 @@ imagex_dir(int argc, tchar **argv, int cmd)
                image = 1;
        }
 
-       ret = wimlib_iterate_dir_tree(wim, image, path,
-                                     WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE,
-                                     print_full_path, NULL);
+       ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
+                                     print_dentry, &options);
 out_wimlib_free:
        wimlib_free(wim);
 out:
@@ -2428,6 +2687,7 @@ imagex_export(int argc, tchar **argv, int cmd)
        STRING_SET(refglobs);
        unsigned num_threads = 0;
        uint32_t chunk_size = UINT32_MAX;
+       uint32_t pack_chunk_size = UINT32_MAX;
 
        for_opt(c, export_options) {
                switch (c) {
@@ -2454,6 +2714,11 @@ imagex_export(int argc, tchar **argv, int cmd)
                        if (chunk_size == UINT32_MAX)
                                goto out_err;
                        break;
+               case IMAGEX_PACK_CHUNK_SIZE_OPTION:
+                       pack_chunk_size = parse_chunk_size(optarg);
+                       if (pack_chunk_size == UINT32_MAX)
+                               goto out_err;
+                       break;
                case IMAGEX_REF_OPTION:
                        ret = string_set_append(&refglobs, optarg);
                        if (ret)
@@ -2583,6 +2848,11 @@ imagex_export(int argc, tchar **argv, int cmd)
                if (ret)
                        goto out_free_dest_wim;
        }
+       if (pack_chunk_size != UINT32_MAX) {
+               ret = wimlib_set_output_pack_chunk_size(dest_wim, pack_chunk_size);
+               if (ret)
+                       goto out_free_dest_wim;
+       }
 
        image = wimlib_resolve_image(src_wim, src_image_num_or_name);
        ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
@@ -2714,7 +2984,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
        const tchar *image_num_or_name;
        const tchar *pathlist;
        tchar *dest_dir = T(".");
-       int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL | WIMLIB_EXTRACT_FLAG_NORPFIX;
+       int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX;
        int listfile_extract_flags = WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
 
        STRING_SET(refglobs);
@@ -2744,6 +3014,9 @@ imagex_extract(int argc, tchar **argv, int cmd)
                case IMAGEX_STRICT_ACLS_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
                        break;
+               case IMAGEX_NO_ATTRIBUTES_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
+                       break;
                case IMAGEX_DEST_DIR_OPTION:
                        dest_dir = optarg;
                        break;
@@ -2847,91 +3120,6 @@ out_err:
        goto out_free_refglobs;
 }
 
-static void print_byte_field(const uint8_t field[], size_t len)
-{
-       while (len--)
-               tprintf(T("%02hhx"), *field++);
-}
-
-static void
-print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
-{
-       tputs(T("WIM Information:"));
-       tputs(T("----------------"));
-       tprintf(T("Path:           %"TS"\n"), wimfile);
-       tprintf(T("GUID:           0x"));
-       print_byte_field(info->guid, sizeof(info->guid));
-       tputchar(T('\n'));
-       tprintf(T("Version:        %u\n"), info->wim_version);
-       tprintf(T("Image Count:    %d\n"), info->image_count);
-       tprintf(T("Compression:    %"TS"\n"),
-               wimlib_get_compression_type_string(info->compression_type));
-       tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
-               info->chunk_size);
-       tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
-       tprintf(T("Boot Index:     %d\n"), info->boot_index);
-       tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
-       tprintf(T("Integrity Info: %"TS"\n"),
-               info->has_integrity_table ? T("yes") : T("no"));
-       tprintf(T("Relative path junction: %"TS"\n"),
-               info->has_rpfix ? T("yes") : T("no"));
-       tprintf(T("Pipable:        %"TS"\n"),
-               info->pipable ? T("yes") : T("no"));
-       tputchar(T('\n'));
-}
-
-static int
-print_resource(const struct wimlib_resource_entry *resource,
-              void *_ignore)
-{
-       tprintf(T("Uncompressed size     = %"PRIu64" bytes\n"),
-               resource->uncompressed_size);
-       if (resource->packed) {
-               tprintf(T("Raw compressed size   = %"PRIu64" bytes\n"),
-                       resource->raw_resource_compressed_size);
-
-               tprintf(T("Raw offset in WIM     = %"PRIu64" bytes\n"),
-                       resource->raw_resource_offset_in_wim);
-
-               tprintf(T("Offset in raw         = %"PRIu64" bytes\n"),
-                       resource->offset);
-       } else {
-               tprintf(T("Compressed size       = %"PRIu64" bytes\n"),
-                       resource->compressed_size);
-
-               tprintf(T("Offset in WIM         = %"PRIu64" bytes\n"),
-                       resource->offset);
-       }
-
-       tprintf(T("Part Number           = %u\n"), resource->part_number);
-       tprintf(T("Reference Count       = %u\n"), resource->reference_count);
-
-       tprintf(T("Hash                  = 0x"));
-       print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
-       tputchar(T('\n'));
-
-       tprintf(T("Flags                 = "));
-       if (resource->is_compressed)
-               tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
-       if (resource->is_metadata)
-               tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
-       if (resource->is_free)
-               tprintf(T("WIM_RESHDR_FLAG_FREE  "));
-       if (resource->is_spanned)
-               tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
-       if (resource->packed)
-               tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS  "));
-       tputchar(T('\n'));
-       tputchar(T('\n'));
-       return 0;
-}
-
-static void
-print_lookup_table(WIMStruct *wim)
-{
-       wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
-}
-
 /* Prints information about a WIM file; also can mark an image as bootable,
  * change the name of an image, or change the description of an image. */
 static int
@@ -2944,7 +3132,6 @@ imagex_info(int argc, tchar **argv, int cmd)
        bool header       = false;
        bool lookup_table = false;
        bool xml          = false;
-       bool metadata     = false;
        bool short_header = true;
        const tchar *xml_out_file = NULL;
        const tchar *wimfile;
@@ -2985,9 +3172,9 @@ imagex_info(int argc, tchar **argv, int cmd)
                        short_header = false;
                        break;
                case IMAGEX_METADATA_OPTION:
-                       metadata = true;
-                       short_header = false;
-                       break;
+                       imagex_error(T("The --metadata option has been removed. "
+                                      "Use 'wimdir --detail' instead."));
+                       goto out_err;
                default:
                        goto out_usage;
                }
@@ -3108,11 +3295,6 @@ imagex_info(int argc, tchar **argv, int cmd)
                if (short_header)
                        wimlib_print_available_images(wim, image);
 
-               if (metadata) {
-                       ret = wimlib_print_metadata(wim, image);
-                       if (ret)
-                               goto out_wimlib_free;
-               }
                ret = 0;
        } else {
 
@@ -3382,6 +3564,7 @@ imagex_optimize(int argc, tchar **argv, int cmd)
        int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
        int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
        uint32_t chunk_size = UINT32_MAX;
+       uint32_t pack_chunk_size = UINT32_MAX;
        int ret;
        WIMStruct *wim;
        const tchar *wimfile;
@@ -3409,16 +3592,18 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_COMPRESS_SLOW_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
-                       compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
-                       ret = set_compress_slow();
-                       if (ret)
-                               goto out_err;
+                       set_compress_slow();
                        break;
                case IMAGEX_CHUNK_SIZE_OPTION:
                        chunk_size = parse_chunk_size(optarg);
                        if (chunk_size == UINT32_MAX)
                                goto out_err;
                        break;
+               case IMAGEX_PACK_CHUNK_SIZE_OPTION:
+                       pack_chunk_size = parse_chunk_size(optarg);
+                       if (pack_chunk_size == UINT32_MAX)
+                               goto out_err;
+                       break;
                case IMAGEX_PACK_STREAMS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
@@ -3463,6 +3648,11 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                if (ret)
                        goto out_wimlib_free;
        }
+       if (pack_chunk_size != UINT32_MAX) {
+               ret = wimlib_set_output_pack_chunk_size(wim, pack_chunk_size);
+               if (ret)
+                       goto out_wimlib_free;
+       }
 
        old_size = file_get_size(wimfile);
        tprintf(T("\"%"TS"\" original size: "), wimfile);
@@ -3861,8 +4051,9 @@ T(
 T(
 "    %"TS" WIMFILE [(IMAGE_NUM | IMAGE_NAME | all)]\n"
 "                    (DIRECTORY | NTFS_VOLUME) [--check] [--ref=\"GLOB\"]\n"
-"                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
-"                    [--hardlink] [--symlink] [--include-invalid-names]\n"
+"                    [--no-acls] [--strict-acls] [--no-attributes]\n"
+"                    [--rpfix] [--norpfix] [--hardlink] [--symlink]\n"
+"                    [--include-invalid-names]\n"
 ),
 [CMD_CAPTURE] =
 T(
@@ -3881,7 +4072,7 @@ T(
 ),
 [CMD_DIR] =
 T(
-"    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--path=PATH]\n"
+"    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--path=PATH] [--detailed]\n"
 ),
 [CMD_EXPORT] =
 T(
@@ -3894,7 +4085,7 @@ T(
 T(
 "    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME) ([PATH...] | @LISTFILE)\n"
 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
-"                    [--to-stdout] [--dest-dir=CMD_DIR]\n"
+"                    [--no-attributes] [--to-stdout] [--dest-dir=CMD_DIR]\n"
 "                    [--include-invalid-names]\n"
 ),
 [CMD_INFO] =