]> wimlib.net Git - wimlib/blobdiff - programs/imagex.c
wimlib-imagex: print version of library being used
[wimlib] / programs / imagex.c
index 9ea7edb019b520fbd2b5f465a96b69e93a02a277..28f0266356429b91879b6d1e59f421f435bcecf5 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 /*
- * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
+ * Copyright (C) 2012-2017 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
@@ -131,14 +131,31 @@ enum {
 static void usage(int cmd, FILE *fp);
 static void usage_all(FILE *fp);
 static void recommend_man_page(int cmd, FILE *fp);
-static const tchar *get_cmd_string(int cmd, bool nospace);
+static const tchar *get_cmd_string(int cmd, bool only_short_form);
 
-static bool imagex_be_quiet = false;
 static FILE *imagex_info_file;
 
-#define imagex_printf(format, ...) \
+#define imagex_printf(format, ...)     \
+       if (imagex_info_file)           \
                tfprintf(imagex_info_file, format, ##__VA_ARGS__)
 
+static void imagex_suppress_output(void)
+{
+       imagex_info_file = NULL;
+}
+
+static void imagex_output_to_stderr(void)
+{
+       if (imagex_info_file)
+               imagex_info_file = stderr;
+}
+
+static void imagex_flush_output(void)
+{
+       if (imagex_info_file)
+               fflush(imagex_info_file);
+}
+
 enum {
        IMAGEX_ALLOW_OTHER_OPTION,
        IMAGEX_BLOBS_OPTION,
@@ -149,7 +166,6 @@ enum {
        IMAGEX_COMMIT_OPTION,
        IMAGEX_COMPACT_OPTION,
        IMAGEX_COMPRESS_OPTION,
-       IMAGEX_COMPRESS_SLOW_OPTION,
        IMAGEX_CONFIG_OPTION,
        IMAGEX_DEBUG_OPTION,
        IMAGEX_DELTA_FROM_OPTION,
@@ -182,7 +198,6 @@ enum {
        IMAGEX_RECOMPRESS_OPTION,
        IMAGEX_RECURSIVE_OPTION,
        IMAGEX_REF_OPTION,
-       IMAGEX_RESUME_OPTION,
        IMAGEX_RPFIX_OPTION,
        IMAGEX_SNAPSHOT_OPTION,
        IMAGEX_SOFT_OPTION,
@@ -216,9 +231,6 @@ static const struct option apply_options[] = {
        {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},
        {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
        {NULL, 0, NULL, 0},
@@ -230,14 +242,10 @@ static const struct option capture_or_append_options[] = {
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("nocheck"),     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("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
        {T("solid"),       no_argument,      NULL, IMAGEX_SOLID_OPTION},
-       {T("pack-streams"), no_argument,      NULL, IMAGEX_SOLID_OPTION},
        {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
-       {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
        {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
-       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
        {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
        {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
        {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
@@ -285,14 +293,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("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
-       {T("compress-slow"), no_argument,     NULL, IMAGEX_COMPRESS_SLOW_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
        {T("solid"),       no_argument,       NULL, IMAGEX_SOLID_OPTION},
-       {T("pack-streams"),no_argument,       NULL, IMAGEX_SOLID_OPTION},
        {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
-       {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
        {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
-       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
        {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
        {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
@@ -334,7 +338,6 @@ static const struct option info_options[] = {
        {T("header"),       no_argument,       NULL, IMAGEX_HEADER_OPTION},
        {T("lookup-table"), no_argument,       NULL, IMAGEX_BLOBS_OPTION},
        {T("blobs"),        no_argument,       NULL, IMAGEX_BLOBS_OPTION},
-       {T("metadata"),     no_argument,       NULL, IMAGEX_METADATA_OPTION},
        {T("xml"),          no_argument,       NULL, IMAGEX_XML_OPTION},
        {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
        {NULL, 0, NULL, 0},
@@ -345,6 +348,7 @@ static const struct option join_options[] = {
        {NULL, 0, NULL, 0},
 };
 
+#if WIM_MOUNTING_SUPPORTED
 static const struct option mount_options[] = {
        {T("check"),             no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("debug"),             no_argument,       NULL, IMAGEX_DEBUG_OPTION},
@@ -355,6 +359,7 @@ static const struct option mount_options[] = {
        {T("allow-other"),       no_argument,       NULL, IMAGEX_ALLOW_OTHER_OPTION},
        {NULL, 0, NULL, 0},
 };
+#endif
 
 static const struct option optimize_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
@@ -362,15 +367,10 @@ static const struct option optimize_options[] = {
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
-       {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("solid"),       no_argument,       NULL, IMAGEX_SOLID_OPTION},
-       {T("pack-streams"),no_argument,       NULL, IMAGEX_SOLID_OPTION},
        {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
-       {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
        {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
-       {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
        {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
@@ -384,6 +384,7 @@ static const struct option split_options[] = {
        {NULL, 0, NULL, 0},
 };
 
+#if WIM_MOUNTING_SUPPORTED
 static const struct option unmount_options[] = {
        {T("commit"),  no_argument, NULL, IMAGEX_COMMIT_OPTION},
        {T("check"),   no_argument, NULL, IMAGEX_CHECK_OPTION},
@@ -393,6 +394,7 @@ static const struct option unmount_options[] = {
        {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
        {NULL, 0, NULL, 0},
 };
+#endif
 
 static const struct option update_options[] = {
        /* Careful: some of the options here set the defaults for update
@@ -505,7 +507,7 @@ verify_image_exists_and_is_single(int image, const tchar *image_name,
 static void
 print_available_compression_types(FILE *fp)
 {
-       static const tchar *s =
+       static const tchar * const s =
        T(
        "Available compression types:\n"
        "\n"
@@ -605,78 +607,68 @@ set_compact_mode(const tchar *arg, int *extract_flags)
 }
 
 
-static void
-set_compress_slow(void)
-{
-#if 0
-       fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
-                       "         Use the '--compress=TYPE:LEVEL' option instead.\n");
-#endif
-       wimlib_set_default_compression_level(-1, 100);
-}
-
-struct string_set {
+struct string_list {
        tchar **strings;
        unsigned num_strings;
        unsigned num_alloc_strings;
 };
 
-#define STRING_SET_INITIALIZER \
+#define STRING_LIST_INITIALIZER \
        { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
 
-#define STRING_SET(_strings) \
-       struct string_set _strings = STRING_SET_INITIALIZER
+#define STRING_LIST(_strings) \
+       struct string_list _strings = STRING_LIST_INITIALIZER
 
 static int
-string_set_append(struct string_set *set, tchar *glob)
+string_list_append(struct string_list *list, tchar *glob)
 {
-       unsigned num_alloc_strings = set->num_alloc_strings;
+       unsigned num_alloc_strings = list->num_alloc_strings;
 
-       if (set->num_strings == num_alloc_strings) {
+       if (list->num_strings == num_alloc_strings) {
                tchar **new_strings;
 
                num_alloc_strings += 4;
-               new_strings = realloc(set->strings,
-                                     sizeof(set->strings[0]) * num_alloc_strings);
+               new_strings = realloc(list->strings,
+                                     sizeof(list->strings[0]) * num_alloc_strings);
                if (!new_strings) {
                        imagex_error(T("Out of memory!"));
                        return -1;
                }
-               set->strings = new_strings;
-               set->num_alloc_strings = num_alloc_strings;
+               list->strings = new_strings;
+               list->num_alloc_strings = num_alloc_strings;
        }
-       set->strings[set->num_strings++] = glob;
+       list->strings[list->num_strings++] = glob;
        return 0;
 }
 
 static void
-string_set_destroy(struct string_set *set)
+string_list_destroy(struct string_list *list)
 {
-       free(set->strings);
+       free(list->strings);
 }
 
 static int
-wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
+wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
 {
-       return wimlib_reference_resource_files(wim, (const tchar **)set->strings,
-                                              set->num_strings,
+       return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
+                                              list->num_strings,
                                               WIMLIB_REF_FLAG_GLOB_ENABLE,
                                               open_flags);
 }
 
 static int
-append_image_property_argument(struct string_set *image_properties)
+append_image_property_argument(struct string_list *image_properties)
 {
        if (!tstrchr(optarg, '=')) {
                imagex_error(T("'--image-property' argument "
                               "must be in the form NAME=VALUE"));
                return -1;
        }
-       return string_set_append(image_properties, optarg);
+       return string_list_append(image_properties, optarg);
 }
 
 static int
-apply_image_properties(struct string_set *image_properties,
+apply_image_properties(struct string_list *image_properties,
                       WIMStruct *wim, int image, bool *any_changes_ret)
 {
        bool any_changes = false;
@@ -712,7 +704,7 @@ apply_image_properties(struct string_set *image_properties,
 static void
 do_resource_not_found_warning(const tchar *wimfile,
                              const struct wimlib_wim_info *info,
-                             const struct string_set *refglobs)
+                             const struct string_list *refglobs)
 {
        if (info->total_parts > 1) {
                if (refglobs->num_strings == 0) {
@@ -1166,8 +1158,6 @@ imagex_progress_func(enum wimlib_progress_msg msg,
        unsigned unit_shift;
        const tchar *unit_name;
 
-       if (imagex_be_quiet)
-               return WIMLIB_PROGRESS_STATUS_CONTINUE;
        switch (msg) {
        case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
                {
@@ -1406,7 +1396,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
        default:
                break;
        }
-       fflush(imagex_info_file);
+       imagex_flush_output();
        return WIMLIB_PROGRESS_STATUS_CONTINUE;
 }
 
@@ -1683,7 +1673,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
        const tchar *image_num_or_name = NULL;
        int extract_flags = 0;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        for_opt(c, apply_options) {
                switch (c) {
@@ -1694,7 +1684,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        /* No longer does anything.  */
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -1720,9 +1710,6 @@ imagex_apply(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_RESUME_OPTION:
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
-                       break;
                case IMAGEX_WIMBOOT_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
                        break;
@@ -1844,7 +1831,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -1864,7 +1851,8 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        int open_flags = 0;
        int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
                        WIMLIB_ADD_FLAG_WINCONFIG |
-                       WIMLIB_ADD_FLAG_VERBOSE;
+                       WIMLIB_ADD_FLAG_VERBOSE |
+                       WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
        int write_flags = 0;
        int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
        uint32_t chunk_size = UINT32_MAX;
@@ -1873,10 +1861,10 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        const tchar *wimfile;
        int wim_fd;
        const tchar *name;
-       STRING_SET(image_properties);
+       STRING_LIST(image_properties);
 
        WIMStruct *wim;
-       STRING_SET(base_wimfiles);
+       STRING_LIST(base_wimfiles);
        WIMStruct **base_wims;
 
        WIMStruct *template_wim;
@@ -1921,9 +1909,6 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
-               case IMAGEX_COMPRESS_SLOW_OPTION:
-                       set_compress_slow();
-                       break;
                case IMAGEX_CHUNK_SIZE_OPTION:
                        chunk_size = parse_chunk_size(optarg);
                        if (chunk_size == UINT32_MAX)
@@ -1948,7 +1933,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                case IMAGEX_FLAGS_OPTION: {
                        tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
                        tsprintf(p, T("FLAGS=%"TS), optarg);
-                       ret = string_set_append(&image_properties, p);
+                       ret = string_list_append(&image_properties, p);
                        if (ret)
                                goto out;
                        break;
@@ -2016,12 +2001,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        }
                        break;
                case IMAGEX_DELTA_FROM_OPTION:
-                       if (cmd != CMD_CAPTURE) {
-                               imagex_error(T("'--delta-from' is only "
-                                              "valid for capture!"));
-                               goto out_usage;
-                       }
-                       ret = string_set_append(&base_wimfiles, optarg);
+                       ret = string_list_append(&base_wimfiles, optarg);
                        if (ret)
                                goto out;
                        write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
@@ -2092,7 +2072,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                }
                wim_fd = STDOUT_FILENO;
                wimfile = NULL;
-               imagex_info_file = stderr;
+               imagex_output_to_stderr();
                set_fd_to_binary_mode(wim_fd);
        }
 
@@ -2145,7 +2125,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        if (argc >= 4) {
                tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
                tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
-               ret = string_set_append(&image_properties, p);
+               ret = string_list_append(&image_properties, p);
                if (ret)
                        goto out;
        }
@@ -2413,8 +2393,8 @@ out_free_capture_sources:
 out_free_source_list_contents:
        free(source_list_contents);
 out:
-       string_set_destroy(&image_properties);
-       string_set_destroy(&base_wimfiles);
+       string_list_destroy(&image_properties);
+       string_list_destroy(&base_wimfiles);
        return ret;
 
 out_usage:
@@ -2534,21 +2514,21 @@ static const struct {
 #define TIMESTR_MAX 100
 
 static void
-timespec_to_string(const struct timespec *spec, tchar *buf)
+print_time(const tchar *type, const struct wimlib_timespec *wts,
+          int32_t high_part)
 {
-       time_t t = spec->tv_sec;
+       tchar timestr[TIMESTR_MAX];
+       time_t t;
        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];
+       if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
+               t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
+       else
+               t = wts->tv_sec;
 
-       timespec_to_string(spec, timestr);
+       gmtime_r(&t, &tm);
+       tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
+       timestr[TIMESTR_MAX - 1] = '\0';
 
        tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
 }
@@ -2662,6 +2642,7 @@ print_blobs(WIMStruct *wim)
        wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
 }
 
+#ifndef __WIN32__
 static void
 default_print_security_descriptor(const uint8_t *sd, size_t size)
 {
@@ -2669,11 +2650,29 @@ default_print_security_descriptor(const uint8_t *sd, size_t size)
        print_byte_field(sd, size);
        tputchar(T('\n'));
 }
+#endif
+
+static bool
+is_null_guid(const uint8_t *guid)
+{
+       static const uint8_t null_guid[WIMLIB_GUID_LEN];
+
+       return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
+}
 
 static void
-print_dentry_detailed(const struct wimlib_dir_entry *dentry)
+print_guid(const tchar *label, const uint8_t *guid)
 {
+       if (is_null_guid(guid))
+               return;
+       tprintf(T("%-20"TS"= 0x"), label);
+       print_byte_field(guid, WIMLIB_GUID_LEN);
+       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);
@@ -2690,9 +2689,12 @@ print_dentry_detailed(const struct wimlib_dir_entry *dentry)
                                          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);
+       print_time(T("Creation Time"),
+                  &dentry->creation_time, dentry->creation_time_high);
+       print_time(T("Last Write Time"),
+                  &dentry->last_write_time, dentry->last_write_time_high);
+       print_time(T("Last Access Time"),
+                  &dentry->last_access_time, dentry->last_access_time_high);
 
 
        if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
@@ -2708,6 +2710,13 @@ print_dentry_detailed(const struct wimlib_dir_entry *dentry)
                        dentry->unix_mode, dentry->unix_rdev);
        }
 
+       if (!is_null_guid(dentry->object_id.object_id)) {
+               print_guid(T("Object ID"), dentry->object_id.object_id);
+               print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
+               print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
+               print_guid(T("Domain ID"), dentry->object_id.domain_id);
+       }
+
        for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
                if (dentry->streams[i].stream_name) {
                        tprintf(T("\tNamed data stream \"%"TS"\":\n"),
@@ -2749,7 +2758,7 @@ imagex_dir(int argc, tchar **argv, int cmd)
        };
        int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        for_opt(c, dir_options) {
                switch (c) {
@@ -2763,7 +2772,7 @@ imagex_dir(int argc, tchar **argv, int cmd)
                        iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -2828,7 +2837,7 @@ imagex_dir(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -2860,7 +2869,7 @@ imagex_export(int argc, tchar **argv, int cmd)
        int image;
        struct stat stbuf;
        bool wim_is_new;
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
        unsigned num_threads = 0;
        uint32_t chunk_size = UINT32_MAX;
        uint32_t solid_chunk_size = UINT32_MAX;
@@ -2883,10 +2892,6 @@ imagex_export(int argc, tchar **argv, int cmd)
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
-               case IMAGEX_COMPRESS_SLOW_OPTION:
-                       set_compress_slow();
-                       write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
-                       break;
                case IMAGEX_RECOMPRESS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
                        break;
@@ -2912,7 +2917,7 @@ imagex_export(int argc, tchar **argv, int cmd)
                                goto out_err;
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -2976,7 +2981,7 @@ imagex_export(int argc, tchar **argv, int cmd)
        #endif
                dest_wimfile = NULL;
                dest_wim_fd = STDOUT_FILENO;
-               imagex_info_file = stderr;
+               imagex_output_to_stderr();
                set_fd_to_binary_mode(dest_wim_fd);
        }
        errno = ENOENT;
@@ -2984,9 +2989,9 @@ imagex_export(int argc, tchar **argv, int cmd)
                wim_is_new = false;
                /* Destination file exists. */
 
-               if (!S_ISREG(stbuf.st_mode)) {
-                       imagex_error(T("\"%"TS"\" is not a regular file"),
-                                    dest_wimfile);
+               if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
+                       imagex_error(T("\"%"TS"\" is not a regular file "
+                                      "or block device"), dest_wimfile);
                        ret = -1;
                        goto out_free_src_wim;
                }
@@ -3129,7 +3134,7 @@ out_free_dest_wim:
 out_free_src_wim:
        wimlib_free(src_wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -3156,7 +3161,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                            WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
        int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        tchar *root_path = WIMLIB_WIM_ROOT_PATH;
 
@@ -3169,7 +3174,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        /* No longer does anything.  */
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -3190,8 +3195,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_TO_STDOUT_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
-                       imagex_info_file = stderr;
-                       imagex_be_quiet = true;
+                       imagex_suppress_output();
                        set_fd_to_binary_mode(STDOUT_FILENO);
                        break;
                case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
@@ -3287,8 +3291,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
        }
 
        if (ret == 0) {
-               if (!imagex_be_quiet)
-                       imagex_printf(T("Done extracting files.\n"));
+               imagex_printf(T("Done extracting files.\n"));
        } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
                if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
                                      WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
@@ -3317,7 +3320,7 @@ imagex_extract(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -3343,7 +3346,7 @@ imagex_info(int argc, tchar **argv, int cmd)
        const tchar *xml_out_file = NULL;
        const tchar *wimfile;
        const tchar *image_num_or_name;
-       STRING_SET(image_properties);
+       STRING_LIST(image_properties);
        WIMStruct *wim;
        int image;
        int ret;
@@ -3377,10 +3380,6 @@ imagex_info(int argc, tchar **argv, int cmd)
                        xml_out_file = optarg;
                        short_header = false;
                        break;
-               case IMAGEX_METADATA_OPTION:
-                       imagex_error(T("The --metadata option has been removed. "
-                                      "Use 'wimdir --detail' instead."));
-                       goto out_err;
                case IMAGEX_IMAGE_PROPERTY_OPTION:
                        ret = append_image_property_argument(&image_properties);
                        if (ret)
@@ -3403,7 +3402,7 @@ imagex_info(int argc, tchar **argv, int cmd)
                /* NEW_NAME */
                tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
                tsprintf(p, T("NAME=%"TS), argv[2]);
-               ret = string_set_append(&image_properties, p);
+               ret = string_list_append(&image_properties, p);
                if (ret)
                        goto out;
        }
@@ -3412,7 +3411,7 @@ imagex_info(int argc, tchar **argv, int cmd)
                /* NEW_DESC */
                tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
                tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
-               ret = string_set_append(&image_properties, p);
+               ret = string_list_append(&image_properties, p);
                if (ret)
                        goto out;
        }
@@ -3582,7 +3581,7 @@ imagex_info(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out:
-       string_set_destroy(&image_properties);
+       string_list_destroy(&image_properties);
        return ret;
 
 out_usage:
@@ -3654,7 +3653,7 @@ imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
        int image;
        int ret;
 
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
 
        if (cmd == CMD_MOUNTRW) {
                mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
@@ -3686,7 +3685,7 @@ imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
                        }
                        break;
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -3754,7 +3753,7 @@ imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
 out_free_wim:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -3797,10 +3796,6 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
                                goto out_err;
                        break;
-               case IMAGEX_COMPRESS_SLOW_OPTION:
-                       set_compress_slow();
-                       write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
-                       break;
                case IMAGEX_RECOMPRESS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
                        break;
@@ -4277,13 +4272,13 @@ imagex_verify(int argc, tchar **argv, int cmd)
        WIMStruct *wim;
        int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
        int verify_flags = 0;
-       STRING_SET(refglobs);
+       STRING_LIST(refglobs);
        int c;
 
        for_opt(c, verify_options) {
                switch (c) {
                case IMAGEX_REF_OPTION:
-                       ret = string_set_append(&refglobs, optarg);
+                       ret = string_list_append(&refglobs, optarg);
                        if (ret)
                                goto out_free_refglobs;
                        break;
@@ -4339,7 +4334,7 @@ imagex_verify(int argc, tchar **argv, int cmd)
 out_wimlib_free:
        wimlib_free(wim);
 out_free_refglobs:
-       string_set_destroy(&refglobs);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
@@ -4394,14 +4389,15 @@ static const struct imagex_command imagex_commands[] = {
 
 #endif
 
-static const tchar *usage_strings[] = {
+static const tchar * const usage_strings[] = {
 [CMD_APPEND] =
 T(
 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
-"                    [--wimboot] [--unix-data] [--dereference] [--snapshot]\n"
+"                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
+"                    [--dereference] [--snapshot]\n"
 ),
 [CMD_APPLY] =
 T(
@@ -4427,7 +4423,7 @@ T(
 ),
 [CMD_DIR] =
 T(
-"    %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
+"    %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
 ),
 [CMD_EXPORT] =
 T(
@@ -4505,21 +4501,18 @@ T(
 static const tchar *invocation_name;
 static int invocation_cmd = CMD_NONE;
 
-static const tchar *get_cmd_string(int cmd, bool nospace)
+static const tchar *get_cmd_string(int cmd, bool only_short_form)
 {
        static tchar buf[50];
-       if (cmd == CMD_NONE) {
+
+       if (cmd == CMD_NONE)
                return T("wimlib-imagex");
-       } else if (invocation_cmd != CMD_NONE) {
+
+       if (only_short_form || invocation_cmd != CMD_NONE) {
                tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
        } else {
-               const tchar *format;
-
-               if (nospace)
-                       format = T("%"TS"-%"TS"");
-               else
-                       format = T("%"TS" %"TS"");
-               tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
+               tsprintf(buf, T("%"TS" %"TS), invocation_name,
+                        imagex_commands[cmd].name);
        }
        return buf;
 }
@@ -4527,23 +4520,27 @@ static const tchar *get_cmd_string(int cmd, bool nospace)
 static void
 version(void)
 {
-       static const tchar *s =
+       uint32_t vers = wimlib_get_version();
+
+       static const tchar * const fmt =
        T(
-"wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
-"Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
+"wimlib-imagex " PACKAGE_VERSION " (using wimlib %u.%u.%u)\n"
+"Copyright (C) 2012-2017 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"
 "\n"
 "Report bugs to "PACKAGE_BUGREPORT".\n"
        );
-       tfputs(s, stdout);
+       tfprintf(stdout, fmt,
+                vers >> 20, (vers >> 10) & 0x3ff, vers & 0x3ff);
 }
 
 
 static void
-help_or_version(int argc, tchar **argv, int cmd)
+do_common_options(int *argc_p, tchar **argv, int cmd)
 {
+       int argc = *argc_p;
        int i;
        const tchar *p;
 
@@ -4560,9 +4557,18 @@ help_or_version(int argc, tchar **argv, int cmd)
                        } else if (!tstrcmp(p, T("version"))) {
                                version();
                                exit(0);
-                       }
+                       } else if (!tstrcmp(p, T("quiet"))) {
+                               imagex_suppress_output();
+                               memmove(&argv[i], &argv[i + 1],
+                                       (argc - i) * sizeof(argv[i]));
+                               argc--;
+                               i--;
+                       } else if (!*p) /* reached "--", no more options */
+                               break;
                }
        }
+
+       *argc_p = argc;
 }
 
 static void
@@ -4579,8 +4585,7 @@ recommend_man_page(int cmd, FILE *fp)
        format_str = T("Some uncommon options are not listed;\n"
                       "See %"TS".pdf in the doc directory for more details.\n");
 #else
-       format_str = T("Some uncommon options are not listed;\n"
-                      "Try `man %"TS"' for more details.\n");
+       format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
 #endif
        tfprintf(fp, format_str, get_cmd_string(cmd, true));
 }
@@ -4602,7 +4607,7 @@ usage_all(FILE *fp)
                print_usage_string(cmd, fp);
                tfprintf(fp, T("\n"));
        }
-       static const tchar *extra =
+       static const tchar * const extra =
        T(
 "    %"TS" --help\n"
 "    %"TS" --version\n"
@@ -4617,16 +4622,17 @@ usage_all(FILE *fp)
        recommend_man_page(CMD_NONE, fp);
 }
 
+#ifdef __WIN32__
+extern int wmain(int argc, wchar_t **argv);
+#define main wmain
+#endif
+
 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
  * something else), while on Windows the command arguments will be UTF-16LE
  * encoded 'wchar_t' strings. */
 int
-#ifdef __WIN32__
-wmain(int argc, wchar_t **argv, wchar_t **envp)
-#else
-main(int argc, char **argv)
-#endif
+main(int argc, tchar **argv)
 {
        int ret;
        int init_flags = 0;
@@ -4635,31 +4641,6 @@ main(int argc, char **argv)
        imagex_info_file = stdout;
        invocation_name = tbasename(argv[0]);
 
-#ifndef __WIN32__
-       if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
-               init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
-       } else {
-               char *codeset;
-
-               setlocale(LC_ALL, "");
-               codeset = nl_langinfo(CODESET);
-               if (!strstr(codeset, "UTF-8") &&
-                   !strstr(codeset, "UTF8") &&
-                   !strstr(codeset, "utf-8") &&
-                   !strstr(codeset, "utf8"))
-               {
-                       fprintf(stderr,
-"WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
-"         Maybe try: `export LANG=en_US.UTF-8'?\n"
-"         Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
-"         to any value to force wimlib to use UTF-8.\n",
-                       invocation_name);
-
-               }
-       }
-
-#endif /* !__WIN32__ */
-
        {
                tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
                if (igcase != NULL) {
@@ -4712,11 +4693,8 @@ main(int argc, char **argv)
                }
        }
 
-       /* Handle --help and --version.  --help can be either for the program as
-        * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
-        * CMD_NONE).  Note: help_or_version() will not return if a --help or
-        * --version argument was found.  */
-       help_or_version(argc, argv, cmd);
+       /* Handle common options.  May exit early (for --help or --version).  */
+       do_common_options(&argc, argv, cmd);
 
        /* Bail if a valid command was not specified.  */
        if (cmd == CMD_NONE) {