]> wimlib.net Git - wimlib/blobdiff - programs/imagex.c
wimlib-imagex: Add --wimboot options
[wimlib] / programs / imagex.c
index b9fd2d79cea518e883c4b42b4a731abe14e6f773..e80d3e6ce6aecd111d98e216e27c469ae0092a2e 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 /*
- * Copyright (C) 2012, 2013 Eric Biggers
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 #ifdef __WIN32__
 #  include "imagex-win32.h"
-#  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>
 #  include <langinfo.h>
-#  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)
 {
 }
@@ -75,6 +74,34 @@ static inline void set_fd_to_binary_mode(int fd)
 
 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
 
+static inline bool
+is_any_path_separator(tchar c)
+{
+       return c == T('/') || c == T('\\');
+}
+
+/* Like basename(), but handles both forward and backwards slashes.  */
+static tchar *
+tbasename(tchar *path)
+{
+       tchar *p = tstrchr(path, T('\0'));
+
+       for (;;) {
+               if (p == path)
+                       return path;
+               if (!is_any_path_separator(*--p))
+                       break;
+               *p = T('\0');
+       }
+
+       for (;;) {
+               if (p == path)
+                       return path;
+               if (is_any_path_separator(*--p))
+                       return ++p;
+       }
+}
+
 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
                                opts, NULL)) != -1)
 
@@ -130,6 +157,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,
@@ -139,14 +167,20 @@ enum {
        IMAGEX_LAZY_OPTION,
        IMAGEX_LOOKUP_TABLE_OPTION,
        IMAGEX_METADATA_OPTION,
-       IMAGEX_NORPFIX_OPTION,
+       IMAGEX_NEW_IMAGE_OPTION,
        IMAGEX_NOCHECK_OPTION,
-       IMAGEX_NO_ACLS_OPTION,
-       IMAGEX_NO_PACK_STREAMS_OPTION,
+       IMAGEX_NORPFIX_OPTION,
        IMAGEX_NOT_PIPABLE_OPTION,
+       IMAGEX_NO_ACLS_OPTION,
+       IMAGEX_NO_ATTRIBUTES_OPTION,
+       IMAGEX_NO_WILDCARDS_OPTION,
+       IMAGEX_NULLGLOB_OPTION,
+       IMAGEX_ONE_FILE_ONLY_OPTION,
+       IMAGEX_PACK_CHUNK_SIZE_OPTION,
        IMAGEX_PACK_STREAMS_OPTION,
        IMAGEX_PATH_OPTION,
        IMAGEX_PIPABLE_OPTION,
+       IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
        IMAGEX_REBUILD_OPTION,
        IMAGEX_RECOMPRESS_OPTION,
        IMAGEX_RECURSIVE_OPTION,
@@ -164,6 +198,7 @@ enum {
        IMAGEX_UNIX_DATA_OPTION,
        IMAGEX_UPDATE_OF_OPTION,
        IMAGEX_VERBOSE_OPTION,
+       IMAGEX_WIMBOOT_OPTION,
        IMAGEX_XML_OPTION,
 };
 
@@ -177,12 +212,14 @@ 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},
 
        /* --resume is undocumented for now as it needs improvement.  */
        {T("resume"),      no_argument,       NULL, IMAGEX_RESUME_OPTION},
+       {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -194,7 +231,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},
@@ -212,6 +252,7 @@ static const struct option capture_or_append_options[] = {
        {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
        {T("update-of"),   required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
        {T("delta-from"),  required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
+       {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -222,7 +263,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},
 };
 
@@ -232,6 +275,12 @@ static const struct option export_options[] = {
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
+       {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_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},
@@ -248,9 +297,14 @@ 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},
+       {T("no-wildcards"), no_argument,      NULL, IMAGEX_NO_WILDCARDS_OPTION},
+       {T("nullglob"),     no_argument,      NULL, IMAGEX_NULLGLOB_OPTION},
+       {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
+       {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -292,7 +346,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},
@@ -309,6 +366,7 @@ static const struct option unmount_options[] = {
        {T("check"),   no_argument, NULL, IMAGEX_CHECK_OPTION},
        {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
        {T("lazy"),    no_argument, NULL, IMAGEX_LAZY_OPTION},
+       {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -359,6 +417,17 @@ imagex_error(const tchar *format, ...)
        va_end(va);
 }
 
+static void _format_attribute(printf, 1, 2)
+imagex_warning(const tchar *format, ...)
+{
+       va_list va;
+       va_start(va, format);
+       tfputs(T("WARNING: "), stderr);
+       tvfprintf(stderr, format, va);
+       tputc(T('\n'), stderr);
+       va_end(va);
+}
+
 /* Print formatted error message to stderr. */
 static void _format_attribute(printf, 1, 2)
 imagex_error_with_errno(const tchar *format, ...)
@@ -429,17 +498,18 @@ get_compression_type(const tchar *optarg)
        }
 }
 
-static int
+static void
 set_compress_slow(void)
 {
-       int ret;
-       static const struct wimlib_lzx_params slow_params = {
-               .size_of_this = sizeof(struct wimlib_lzx_params),
+       static const struct wimlib_lzx_compressor_params lzx_slow_params = {
+               .hdr = {
+                       .size = sizeof(struct wimlib_lzx_compressor_params),
+               },
                .algorithm = WIMLIB_LZX_ALGORITHM_SLOW,
                .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,
@@ -449,10 +519,24 @@ set_compress_slow(void)
                        },
                },
        };
-       ret = wimlib_lzx_set_default_params(&slow_params);
-       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 {
@@ -645,7 +729,7 @@ static bool
 is_comment_line(const tchar *line, size_t len)
 {
        for (;;) {
-               if (*line == T('#'))
+               if (*line == T('#') || *line == T(';'))
                        return true;
                if (!istspace(*line) && *line != T('\0'))
                        return false;
@@ -787,8 +871,8 @@ check_config_section(tchar *line, size_t len,
                         "of capture config file\n"),
                       stderr);
        } else {
-               imagex_error(T("Invalid capture config file section \"%"TS"\""),
-                            line - 1);
+               imagex_warning(T("Unknown capture config file section \"%"TS"\""),
+                              line - 1);
                return CAPTURE_CONFIG_INVALID_SECTION;
        }
        return CAPTURE_CONFIG_CHANGED_SECTION;
@@ -825,9 +909,8 @@ parse_capture_config_line(tchar *line, size_t len,
        int ret;
 
        ret = check_config_section(line, len, cur_section);
-       if (ret == CAPTURE_CONFIG_INVALID_SECTION)
-               return false;
-       if (ret == CAPTURE_CONFIG_CHANGED_SECTION)
+       if (ret == CAPTURE_CONFIG_CHANGED_SECTION ||
+           ret == CAPTURE_CONFIG_INVALID_SECTION)
                return true;
 
        switch (*cur_section) {
@@ -872,6 +955,10 @@ parse_capture_config(tchar **contents_p, size_t nchars,
                tchar *endp = tmemchr(p, T('\n'), nchars);
                size_t len = endp - p + 1;
                *endp = T('\0');
+               if (p != endp && *(endp - 1) == T('\r')) {
+                       *(endp - 1) = '\0';
+                       len--;
+               }
                if (!is_comment_line(p, len))
                        if (!parse_capture_config_line(p, len, &cur_section, config))
                                return -1;
@@ -1052,6 +1139,34 @@ get_unit(uint64_t total_bytes, const tchar **name_ret)
        }
 }
 
+static struct wimlib_progress_info_scan last_scan_progress;
+
+static void
+report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
+{
+       uint64_t prev_count, cur_count;
+
+       prev_count = last_scan_progress.num_nondirs_scanned +
+                    last_scan_progress.num_dirs_scanned;
+       cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
+
+       if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
+           cur_count % 128 == 0)
+       {
+               unsigned unit_shift;
+               const tchar *unit_name;
+
+               unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
+               imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
+                               "%"PRIu64" directories)    "),
+                             scan->num_bytes_scanned >> unit_shift,
+                             unit_name,
+                             scan->num_nondirs_scanned,
+                             scan->num_dirs_scanned);
+               last_scan_progress = *scan;
+       }
+}
+
 /* Progress callback function passed to various wimlib functions. */
 static int
 imagex_progress_func(enum wimlib_progress_msg msg,
@@ -1060,20 +1175,27 @@ imagex_progress_func(enum wimlib_progress_msg msg,
        unsigned percent_done;
        unsigned unit_shift;
        const tchar *unit_name;
+
        if (imagex_be_quiet)
                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)"),
@@ -1106,23 +1228,36 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                                  "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\")...\n"),
                               info->scan.wim_target_path);
                } else {
-                       imagex_printf(T(" (loading as root of WIM image)...\n"));
+                       imagex_printf(T("\n"));
                }
+               memset(&last_scan_progress, 0, sizeof(last_scan_progress));
                break;
        case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
                switch (info->scan.status) {
                case WIMLIB_SCAN_DENTRY_OK:
-                       imagex_printf(T("Scanning \"%"TS"\"\n"), info->scan.cur_path);
+                       report_scan_progress(&info->scan, false);
                        break;
                case WIMLIB_SCAN_DENTRY_EXCLUDED:
-                       imagex_printf(T("Excluding \"%"TS"\" from capture\n"), info->scan.cur_path);
+                       imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
                        break;
                case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
-                       imagex_printf(T("WARNING: Excluding unsupported file or directory\n"
+                       imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
                                        "         \"%"TS"\" from capture\n"), info->scan.cur_path);
                        break;
+               case WIMLIB_SCAN_DENTRY_EXCLUDED_SYMLINK:
+                       imagex_printf(T("\nWARNING: Ignoring absolute symbolic link "
+                                       "with out-of-tree target:\n"
+                                       "           \"%"TS"\" => \"%"TS"\"\n"
+                                       "           (Use --norpfix to capture "
+                                       "absolute symbolic links as-is)\n"),
+                                       info->scan.cur_path, info->scan.symlink_target);
+                       break;
                }
                break;
+       case WIMLIB_PROGRESS_MSG_SCAN_END:
+               report_scan_progress(&info->scan, true);
+               imagex_printf(T("\n"));
+               break;
        case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
                unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
                percent_done = TO_PERCENT(info->integrity.completed_bytes,
@@ -1163,14 +1298,15 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        info->extract.target);
                break;
        case WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN:
-               imagex_printf(T("Extracting "
-                         "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\" from image %d (\"%"TS"\") "
-                         "in \"%"TS"\" to \"%"TS"\"\n"),
-                       info->extract.extract_root_wim_source_path,
-                       info->extract.image,
-                       info->extract.image_name,
-                       info->extract.wimfile_name,
-                       info->extract.target);
+               if (info->extract.extract_root_wim_source_path[0]) {
+                       imagex_printf(T("Extracting \"%"TS"\" from image %d "
+                                       "(\"%"TS"\") in \"%"TS"\" to \"%"TS"\"\n"),
+                                     info->extract.extract_root_wim_source_path,
+                                     info->extract.image,
+                                     info->extract.image_name,
+                                     info->extract.wimfile_name,
+                                     info->extract.target);
+               }
                break;
        case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
                percent_done = TO_PERCENT(info->extract.completed_bytes,
@@ -1499,7 +1635,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);
 
@@ -1515,7 +1651,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        extract_flags |= WIMLIB_EXTRACT_FLAG_SYMLINK;
                        break;
                case IMAGEX_VERBOSE_OPTION:
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_VERBOSE;
+                       /* No longer does anything.  */
                        break;
                case IMAGEX_REF_OPTION:
                        ret = string_set_append(&refglobs, optarg);
@@ -1531,6 +1667,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;
@@ -1544,6 +1683,9 @@ imagex_apply(int argc, tchar **argv, int cmd)
                case IMAGEX_RESUME_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
                        break;
+               case IMAGEX_WIMBOOT_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -1672,10 +1814,12 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        int c;
        int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
        int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE |
-                             WIMLIB_ADD_IMAGE_FLAG_WINCONFIG;
+                             WIMLIB_ADD_IMAGE_FLAG_WINCONFIG |
+                             WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
        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;
@@ -1697,7 +1841,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        tchar *source;
        tchar *source_copy;
 
-       const tchar *config_file = NULL;
+       tchar *config_file = NULL;
        tchar *config_str;
        struct wimlib_capture_config *config;
 
@@ -1708,6 +1852,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) {
@@ -1731,16 +1876,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;
@@ -1751,7 +1898,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
                        break;
                case IMAGEX_VERBOSE_OPTION:
-                       add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
+                       /* No longer does anything.  */
                        break;
                case IMAGEX_THREADS_OPTION:
                        num_threads = parse_num_threads(optarg);
@@ -1815,6 +1962,9 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                goto out_free_base_wimfiles;
                        write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
                        break;
+               case IMAGEX_WIMBOOT_OPTION:
+                       add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_WIMBOOT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -1828,18 +1978,50 @@ 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_params params;
-               memset(&params, 0, sizeof(params));
-               params.size_of_this = sizeof(params);
-               params.algorithm = WIMLIB_LZX_ALGORITHM_FAST;
-               params.use_defaults = 1;
+               if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) {
+                       compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
+               } else {
+                       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);
+                       }
+               }
 
-               wimlib_lzx_set_default_params(&params);
-               compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
        }
 
+       if (compress_slow)
+               set_compress_slow();
+
+       /* Set default configuration file  */
+#ifdef __WIN32__
+       if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) && !config) {
+               struct stat st;
+
+               config_file = alloca(wcslen(source) * sizeof(wchar_t) + 100);
+               swprintf(config_file, L"%ls\\%ls",
+                        source,  L"Windows\\System32\\WimBootCompress.ini");
+
+               if (tstat(config_file, &st)) {
+                       imagex_printf(L"\"%ls\" does not exist; using "
+                                     "default configuration\n",
+                                     config_file);
+                       config_file = NULL;
+               } else {
+                       add_image_flags &= ~WIMLIB_ADD_IMAGE_FLAG_WINCONFIG;
+               }
+       }
+#endif
+
        if (!tstrcmp(wimfile, T("-"))) {
                /* Writing captured WIM to standard output.  */
        #if 0
@@ -1979,6 +2161,16 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                ret = wimlib_set_output_chunk_size(wim, chunk_size);
                if (ret)
                        goto out_free_wim;
+       } else if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) &&
+                  compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
+               ret = wimlib_set_output_chunk_size(wim, 4096);
+               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__
@@ -2267,11 +2459,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_full_path(const struct wimlib_dir_entry *wdentry, void *_ignore)
+print_resource(const struct wimlib_resource_entry *resource,
+              void *_ignore)
 {
-       int ret = tprintf(T("%"TS"\n"), wdentry->full_path);
-       return (ret >= 0) ? 0 : -1;
+       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_dentry(const struct wimlib_dir_entry *dentry, void *_options)
+{
+       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. */
@@ -2284,12 +2670,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;
                }
@@ -2333,9 +2729,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:
@@ -2372,6 +2767,8 @@ imagex_export(int argc, tchar **argv, int cmd)
        bool wim_is_new;
        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) {
@@ -2390,6 +2787,23 @@ imagex_export(int argc, tchar **argv, int cmd)
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
+               case IMAGEX_COMPRESS_SLOW_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
+                       set_compress_slow();
+                       break;
+               case IMAGEX_PACK_STREAMS_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
+                       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_REF_OPTION:
                        ret = string_set_append(&refglobs, optarg);
                        if (ret)
@@ -2507,7 +2921,22 @@ imagex_export(int argc, tchar **argv, int cmd)
                if (ret)
                        goto out_free_src_wim;
 
-               wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
+               /* Use same chunk size if compression type is the same.  */
+               if (compression_type == src_info.compression_type &&
+                   chunk_size == UINT32_MAX)
+                       wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
+       }
+
+       if (chunk_size != UINT32_MAX) {
+               /* Set destination chunk size.  */
+               ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
+               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);
@@ -2566,67 +2995,6 @@ out_err:
        goto out_free_refglobs;
 }
 
-static bool
-is_root_wim_path(const tchar *path)
-{
-       const tchar *p;
-       for (p = path; *p; p++)
-               if (*p != T('\\') && *p != T('/'))
-                       return false;
-       return true;
-}
-
-static void
-free_extract_commands(struct wimlib_extract_command *cmds, size_t num_cmds,
-                     const tchar *dest_dir)
-{
-       for (size_t i = 0; i < num_cmds; i++)
-               if (cmds[i].fs_dest_path != dest_dir)
-                       free(cmds[i].fs_dest_path);
-       free(cmds);
-}
-
-static struct wimlib_extract_command *
-prepare_extract_commands(tchar **paths, unsigned num_paths,
-                        int extract_flags, tchar *dest_dir,
-                        size_t *num_cmds_ret)
-{
-       struct wimlib_extract_command *cmds;
-       size_t num_cmds;
-       tchar *emptystr = T("");
-
-       if (num_paths == 0) {
-               num_paths = 1;
-               paths = &emptystr;
-       }
-       num_cmds = num_paths;
-       cmds = calloc(num_cmds, sizeof(cmds[0]));
-       if (!cmds) {
-               imagex_error(T("Out of memory!"));
-               return NULL;
-       }
-
-       for (size_t i = 0; i < num_cmds; i++) {
-               cmds[i].extract_flags = extract_flags;
-               cmds[i].wim_source_path = paths[i];
-               if (is_root_wim_path(paths[i])) {
-                       cmds[i].fs_dest_path = dest_dir;
-               } else {
-                       size_t len = tstrlen(dest_dir) + 1 + tstrlen(paths[i]);
-                       cmds[i].fs_dest_path = malloc((len + 1) * sizeof(tchar));
-                       if (!cmds[i].fs_dest_path) {
-                               free_extract_commands(cmds, num_cmds, dest_dir);
-                               return NULL;
-                       }
-                       tsprintf(cmds[i].fs_dest_path,
-                                T("%"TS""OS_PREFERRED_PATH_SEPARATOR_STRING"%"TS),
-                                dest_dir, tbasename(paths[i]));
-               }
-       }
-       *num_cmds_ret = num_cmds;
-       return cmds;
-}
-
 /* Extract files or directories from a WIM image */
 static int
 imagex_extract(int argc, tchar **argv, int cmd)
@@ -2639,12 +3007,14 @@ imagex_extract(int argc, tchar **argv, int cmd)
        const tchar *wimfile;
        const tchar *image_num_or_name;
        tchar *dest_dir = T(".");
-       int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL | WIMLIB_EXTRACT_FLAG_NORPFIX;
+       int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
+                           WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
+                           WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
+       int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
 
        STRING_SET(refglobs);
 
-       struct wimlib_extract_command *cmds;
-       size_t num_cmds;
+       tchar *root_path = T("");
 
        for_opt(c, extract_options) {
                switch (c) {
@@ -2652,7 +3022,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_VERBOSE_OPTION:
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_VERBOSE;
+                       /* No longer does anything.  */
                        break;
                case IMAGEX_REF_OPTION:
                        ret = string_set_append(&refglobs, optarg);
@@ -2668,6 +3038,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;
@@ -2680,6 +3053,18 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
                        extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
                        break;
+               case IMAGEX_NO_WILDCARDS_OPTION:
+                       extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
+                       break;
+               case IMAGEX_NULLGLOB_OPTION:
+                       extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
+                       break;
+               case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
+                       notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
+                       break;
+               case IMAGEX_WIMBOOT_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2690,20 +3075,22 @@ imagex_extract(int argc, tchar **argv, int cmd)
        if (argc < 2)
                goto out_usage;
 
+       if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
+                              WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
+       {
+               imagex_error(T("Can't combine --no-wildcards and --nullglob!"));
+               goto out_err;
+       }
+
        wimfile = argv[0];
        image_num_or_name = argv[1];
 
        argc -= 2;
        argv += 2;
 
-       cmds = prepare_extract_commands(argv, argc, extract_flags, dest_dir,
-                                       &num_cmds);
-       if (!cmds)
-               goto out_err;
-
        ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
        if (ret)
-               goto out_free_cmds;
+               goto out_free_refglobs;
 
        image = wimlib_resolve_image(wim, image_num_or_name);
        ret = verify_image_exists_and_is_single(image,
@@ -2718,8 +3105,38 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        goto out_wimlib_free;
        }
 
-       ret = wimlib_extract_files(wim, image, cmds, num_cmds, 0,
-                                  imagex_progress_func);
+       if (argc == 0) {
+               argv = &root_path;
+               argc = 1;
+               extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
+       }
+
+       while (argc != 0 && ret == 0) {
+               int num_paths;
+
+               for (num_paths = 0;
+                    num_paths < argc && argv[num_paths][0] != T('@');
+                    num_paths++)
+                       ;
+
+               if (num_paths) {
+                       ret = wimlib_extract_paths(wim, image, dest_dir,
+                                                  (const tchar **)argv,
+                                                  num_paths,
+                                                  extract_flags | notlist_extract_flags,
+                                                  imagex_progress_func);
+                       argc -= num_paths;
+                       argv += num_paths;
+               } else {
+                       ret = wimlib_extract_pathlist(wim, image, dest_dir,
+                                                     argv[0] + 1,
+                                                     extract_flags,
+                                                     imagex_progress_func);
+                       argc--;
+                       argv++;
+               }
+       }
+
        if (ret == 0) {
                if (!imagex_be_quiet)
                        imagex_printf(T("Done extracting files.\n"));
@@ -2727,7 +3144,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
                                   "files and directories\n"
                                   "      are in the WIM image.\n"),
-                               get_cmd_string(CMD_INFO, false));
+                               get_cmd_string(CMD_DIR, false));
        } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
                struct wimlib_wim_info info;
 
@@ -2736,8 +3153,6 @@ imagex_extract(int argc, tchar **argv, int cmd)
        }
 out_wimlib_free:
        wimlib_free(wim);
-out_free_cmds:
-       free_extract_commands(cmds, num_cmds, dest_dir);
 out_free_refglobs:
        string_set_destroy(&refglobs);
        return ret;
@@ -2749,91 +3164,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
@@ -2846,7 +3176,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;
@@ -2887,9 +3216,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;
                }
@@ -3010,11 +3339,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 {
 
@@ -3284,6 +3608,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;
@@ -3311,16 +3636,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;
@@ -3365,6 +3692,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);
@@ -3481,6 +3813,9 @@ imagex_unmount(int argc, tchar **argv, int cmd)
                case IMAGEX_LAZY_OPTION:
                        unmount_flags |= WIMLIB_UNMOUNT_FLAG_LAZY;
                        break;
+               case IMAGEX_NEW_IMAGE_OPTION:
+                       unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3490,6 +3825,15 @@ imagex_unmount(int argc, tchar **argv, int cmd)
        if (argc != 1)
                goto out_usage;
 
+       if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
+               if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
+                       imagex_error(T("--new-image is meaningless "
+                                      "without --commit also specified!"));
+                       goto out_err;
+               }
+               imagex_printf(T("Committing changes as new image...\n"));
+       }
+
        ret = wimlib_unmount_image(argv[0], unmount_flags,
                                   imagex_progress_func);
        if (ret)
@@ -3499,6 +3843,7 @@ out:
 
 out_usage:
        usage(CMD_UNMOUNT, stderr);
+out_err:
        ret = -1;
        goto out;
 }
@@ -3517,7 +3862,8 @@ imagex_update(int argc, tchar **argv, int cmd)
        int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
        int write_flags = 0;
        int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
-       int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE;
+       int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
+                               WIMLIB_ADD_FLAG_VERBOSE;
        int default_delete_flags = 0;
        unsigned num_threads = 0;
        int c;
@@ -3576,7 +3922,7 @@ imagex_update(int argc, tchar **argv, int cmd)
 
                /* Default add options */
                case IMAGEX_VERBOSE_OPTION:
-                       default_add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
+                       /* No longer does anything.  */
                        break;
                case IMAGEX_DEREFERENCE_OPTION:
                        default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
@@ -3752,40 +4098,38 @@ static const tchar *usage_strings[] = {
 [CMD_APPEND] =
 T(
 "    %"TS" (DIRECTORY | NTFS_VOLUME) WIMFILE\n"
-"                    [IMAGE_NAME [IMAGE_DESCRIPTION]] [--boot]\n"
-"                    [--check] [--nocheck] [--flags EDITION_ID] [--verbose]\n"
-"                    [--dereference] [--config=FILE] [--threads=NUM_THREADS]\n"
-"                    [--source-list] [--no-acls] [--strict-acls] [--rpfix]\n"
-"                    [--norpfix] [--unix-data] [--pipable]\n"
+"                    [IMAGE_NAME [IMAGE_DESCRIPTION]] [--boot] [--check]\n"
+"                    [--nocheck] [--flags EDITION_ID] [--dereference]\n"
+"                    [--config=FILE] [--threads=NUM_THREADS] [--source-list]\n"
+"                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
 "                    [--update-of=[WIMFILE:]IMAGE]\n"
 ),
 [CMD_APPLY] =
 T(
 "    %"TS" WIMFILE [(IMAGE_NUM | IMAGE_NAME | all)]\n"
-"                    (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n"
-"                    [--symlink] [--verbose] [--ref=\"GLOB\"] [--unix-data]\n"
-"                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
+"                    (DIRECTORY | NTFS_VOLUME) [--check] [--ref=\"GLOB\"]\n"
+"                    [--no-acls] [--strict-acls] [--no-attributes]\n"
+"                    [--rpfix] [--norpfix] [--hardlink] [--symlink]\n"
 "                    [--include-invalid-names]\n"
 ),
 [CMD_CAPTURE] =
 T(
 "    %"TS" (DIRECTORY | NTFS_VOLUME) WIMFILE\n"
-"                   [IMAGE_NAME [IMAGE_DESCRIPTION]] [--boot]\n"
-"                    [--check] [--nocheck] [--compress=TYPE]\n"
-"                    [--flags EDITION_ID] [--verbose] [--dereference]\n"
-"                    [--config=FILE] [--threads=NUM_THREADS] [--source-list]\n"
-"                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
-"                    [--unix-data] [--pipable] [--update-of=[WIMFILE:]IMAGE]\n"
+"                   [IMAGE_NAME [IMAGE_DESCRIPTION]] [--boot] [--check]\n"
+"                    [--nocheck] [--compress=TYPE] [--flags EDITION_ID]\n"
+"                    [--dereference] [--config=FILE] [--threads=NUM_THREADS]\n"
+"                    [--source-list] [--no-acls] [--strict-acls] [--rpfix]\n"
+"                    [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
 "                    [--delta-from=WIMFILE]\n"
 ),
 [CMD_DELETE] =
 T(
-"    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check]\n"
-"                    [--soft]\n"
+"    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME | all)\n"
+"                    [--check] [--soft]\n"
 ),
 [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(
@@ -3793,21 +4137,20 @@ T(
 "                    DEST_WIMFILE [DEST_IMAGE_NAME [DEST_IMAGE_DESCRIPTION]]\n"
 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
-"                    [--pipable] [--not-pipable]\n"
 ),
 [CMD_EXTRACT] =
 T(
-"    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME) [PATH...]\n"
-"                    [--check] [--ref=\"GLOB\"] [--verbose] [--unix-data]\n"
-"                    [--no-acls] [--strict-acls] [--to-stdout]\n"
-"                    [--dest-dir=CMD_DIR] [--include-invalid-names]\n"
+"    %"TS" WIMFILE (IMAGE_NUM | IMAGE_NAME) [(PATH | @LISTFILE)...]\n"
+"                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
+"                    [--to-stdout] [--no-acls] [--strict-acls]\n"
+"                    [--no-attributes] [--include-invalid-names]\n"
+"                    [--no-wildcards] [--nullglob] [--preserve-dir-structure]\n"
 ),
 [CMD_INFO] =
 T(
 "    %"TS" WIMFILE [(IMAGE_NUM | IMAGE_NAME) [NEW_NAME\n"
-"                    [NEW_DESC]]] [--boot] [--check] [--nocheck] [--header]\n"
-"                    [--lookup-table] [--xml] [--extract-xml FILE]\n"
-"                    [--metadata]\n"
+"                    [NEW_DESC]]] [--boot] [--check] [--nocheck] [--xml]\n"
+"                    [--extract-xml FILE] [--header] [--lookup-table]\n"
 ),
 [CMD_JOIN] =
 T(
@@ -3817,21 +4160,21 @@ T(
 [CMD_MOUNT] =
 T(
 "    %"TS" WIMFILE [(IMAGE_NUM | IMAGE_NAME)] DIRECTORY\n"
-"                    [--check] [--debug] [--streams-interface=INTERFACE]\n"
-"                    [--ref=\"GLOB\"] [--unix-data] [--allow-other]\n"
+"                    [--check] [--streams-interface=INTERFACE]\n"
+"                    [--ref=\"GLOB\"] [--allow-other]\n"
 ),
 [CMD_MOUNTRW] =
 T(
 "    %"TS" WIMFILE [(IMAGE_NUM | IMAGE_NAME)] DIRECTORY\n"
-"                    [--check] [--debug] [--streams-interface=INTERFACE]\n"
-"                    [--staging-dir=CMD_DIR] [--unix-data] [--allow-other]\n"
+"                    [--check] [--streams-interface=INTERFACE]\n"
+"                    [--staging-dir=CMD_DIR] [--allow-other]\n"
 ),
 #endif
 [CMD_OPTIMIZE] =
 T(
 "    %"TS" WIMFILE [--check] [--nocheck] [--recompress]\n"
 "                    [--recompress-slow] [--compress=TYPE]\n"
-"                    [--threads=NUM_THREADS] [--pipable] [--not-pipable]\n"
+"                    [--threads=NUM_THREADS]\n"
 ),
 [CMD_SPLIT] =
 T(
@@ -3841,6 +4184,7 @@ T(
 [CMD_UNMOUNT] =
 T(
 "    %"TS" DIRECTORY [--commit] [--check] [--rebuild] [--lazy]\n"
+"                    [--new-image]\n"
 ),
 #endif
 [CMD_UPDATE] =
@@ -3878,8 +4222,8 @@ version(void)
 {
        static const tchar *s =
        T(
-IMAGEX_PROGNAME " (" PACKAGE ") " PACKAGE_VERSION "\n"
-"Copyright (C) 2012, 2013 Eric Biggers\n"
+IMAGEX_PROGNAME " (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
+"Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
 "This is free software: you are free to change and redistribute it.\n"
 "There is NO WARRANTY, to the extent permitted by law.\n"
@@ -3925,9 +4269,11 @@ recommend_man_page(int cmd, FILE *fp)
 {
        const tchar *format_str;
 #ifdef __WIN32__
-       format_str = T("See %"TS".pdf in the doc directory for more details.\n");
+       format_str = T("Uncommon options are not listed;\n"
+                      "See %"TS".pdf in the doc directory for more details.\n");
 #else
-       format_str = T("Try `man %"TS"' for more details.\n");
+       format_str = T("Uncommon options are not listed;\n"
+                      "Try `man %"TS"' for more details.\n");
 #endif
        tfprintf(fp, format_str, get_cmd_string(cmd, true));
 }
@@ -4001,8 +4347,26 @@ main(int argc, char **argv)
 
                }
        }
+
 #endif /* !__WIN32__ */
 
+       {
+               tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
+               if (igcase != NULL) {
+                       if (!tstrcmp(igcase, T("no")) ||
+                           !tstrcmp(igcase, T("0")))
+                               init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
+                       else if (!tstrcmp(igcase, T("yes")) ||
+                                !tstrcmp(igcase, T("1")))
+                               init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
+                       else {
+                               fprintf(stderr,
+                                       "WARNING: Ignoring unknown setting of "
+                                       "WIMLIB_IMAGEX_IGNORE_CASE\n");
+                       }
+               }
+       }
+
        /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
        cmd = CMD_NONE;
        if (!tstrncmp(invocation_name, T("wim"), 3) &&