]> wimlib.net Git - wimlib/blobdiff - programs/imagex.c
mount_image.c: add fallback definitions of RENAME_* constants
[wimlib] / programs / imagex.c
index 69d7a6c74e7e5760a04c30d44d2d4f5934d1a3b9..54e50bfcdd06fe68015b8a23c2ac81c36be80097 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 /*
- * Copyright (C) 2012-2018 Eric Biggers
+ * Copyright 2012-2023 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
@@ -19,7 +19,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 
 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
 
-#ifdef __WIN32__
+#ifdef _WIN32
 #  include "imagex-win32.h"
 #  define print_security_descriptor     win32_print_security_descriptor
-#else /* __WIN32__ */
+#else /* _WIN32 */
 #  include <getopt.h>
 #  include <langinfo.h>
 #  define print_security_descriptor    default_print_security_descriptor
 static inline void set_fd_to_binary_mode(int fd)
 {
 }
-#endif /* !__WIN32 */
+/* NetBSD is missing getopt_long_only() but has getopt_long() */
+#ifndef HAVE_GETOPT_LONG_ONLY
+#  define getopt_long_only getopt_long
+#endif
+#endif /* !_WIN32 */
 
 /* Don't confuse the user by presenting the mounting commands on Windows when
  * they will never work.  However on UNIX-like systems we always present them,
  * even if WITH_FUSE is not defined at this point, as to not tie the build of
  * wimlib-imagex to a specific build of wimlib.  */
-#ifdef __WIN32__
+#ifdef _WIN32
 #  define WIM_MOUNTING_SUPPORTED 0
 #else
 #  define WIM_MOUNTING_SUPPORTED 1
@@ -167,6 +171,7 @@ enum {
        IMAGEX_COMPACT_OPTION,
        IMAGEX_COMPRESS_OPTION,
        IMAGEX_CONFIG_OPTION,
+       IMAGEX_CREATE_OPTION,
        IMAGEX_DEBUG_OPTION,
        IMAGEX_DELTA_FROM_OPTION,
        IMAGEX_DEREFERENCE_OPTION,
@@ -177,6 +182,7 @@ enum {
        IMAGEX_FORCE_OPTION,
        IMAGEX_HEADER_OPTION,
        IMAGEX_IMAGE_PROPERTY_OPTION,
+       IMAGEX_INCLUDE_INTEGRITY_OPTION,
        IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
        IMAGEX_LAZY_OPTION,
        IMAGEX_METADATA_OPTION,
@@ -196,6 +202,7 @@ enum {
        IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
        IMAGEX_REBUILD_OPTION,
        IMAGEX_RECOMPRESS_OPTION,
+       IMAGEX_RECOVER_DATA_OPTION,
        IMAGEX_RECURSIVE_OPTION,
        IMAGEX_REF_OPTION,
        IMAGEX_RPFIX_OPTION,
@@ -233,6 +240,7 @@ static const struct option apply_options[] = {
        {T("include-invalid-names"), no_argument,       NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
        {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
+       {T("recover-data"), no_argument,      NULL, IMAGEX_RECOVER_DATA_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -241,6 +249,7 @@ static const struct option capture_or_append_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
        {T("solid"),       no_argument,      NULL, IMAGEX_SOLID_OPTION},
@@ -268,11 +277,13 @@ static const struct option capture_or_append_options[] = {
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
        {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
        {T("snapshot"),    no_argument,       NULL, IMAGEX_SNAPSHOT_OPTION},
+       {T("create"),      no_argument,       NULL, IMAGEX_CREATE_OPTION},
        {NULL, 0, NULL, 0},
 };
 
 static const struct option delete_options[] = {
        {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("soft"),  no_argument, NULL, IMAGEX_SOFT_OPTION},
        {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
        {NULL, 0, NULL, 0},
@@ -291,6 +302,7 @@ static const struct option export_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
@@ -326,6 +338,7 @@ static const struct option extract_options[] = {
        {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
        {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
        {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
+       {T("recover-data"), no_argument,      NULL, IMAGEX_RECOVER_DATA_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -334,6 +347,7 @@ static const struct option info_options[] = {
        {T("check"),        no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("nocheck"),      no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument,  NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("extract-xml"),  required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
        {T("header"),       no_argument,       NULL, IMAGEX_HEADER_OPTION},
        {T("lookup-table"), no_argument,       NULL, IMAGEX_BLOBS_OPTION},
@@ -345,6 +359,7 @@ static const struct option info_options[] = {
 
 static const struct option join_options[] = {
        {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -365,6 +380,7 @@ static const struct option optimize_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
        {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
        {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
@@ -381,6 +397,7 @@ static const struct option optimize_options[] = {
 
 static const struct option split_options[] = {
        {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -399,13 +416,14 @@ static const struct option unmount_options[] = {
 static const struct option update_options[] = {
        /* Careful: some of the options here set the defaults for update
         * commands, but the flags given to an actual update command (and not to
-        * `imagex update' itself are also handled in
-        * update_command_add_option().  */
+        * wimupdate itself) are also handled in update_command_add_option(). */
        {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
+       {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
        {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
        {T("command"),     required_argument, NULL, IMAGEX_COMMAND_OPTION},
        {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
+       {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
 
        /* Default delete options */
        {T("force"),       no_argument,       NULL, IMAGEX_FORCE_OPTION},
@@ -950,152 +968,6 @@ parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
        return sources;
 }
 
-/* Reads the contents of a file into memory. */
-static char *
-file_get_contents(const tchar *filename, size_t *len_ret)
-{
-       struct stat stbuf;
-       void *buf = NULL;
-       size_t len;
-       FILE *fp;
-
-       if (tstat(filename, &stbuf) != 0) {
-               imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
-               goto out;
-       }
-       len = stbuf.st_size;
-
-       fp = tfopen(filename, T("rb"));
-       if (!fp) {
-               imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
-               goto out;
-       }
-
-       buf = malloc(len ? len : 1);
-       if (!buf) {
-               imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
-                              "contents of file \"%"TS"\""), len, filename);
-               goto out_fclose;
-       }
-       if (fread(buf, 1, len, fp) != len) {
-               imagex_error_with_errno(T("Failed to read %zu bytes from the "
-                                         "file \"%"TS"\""), len, filename);
-               goto out_free_buf;
-       }
-       *len_ret = len;
-       goto out_fclose;
-out_free_buf:
-       free(buf);
-       buf = NULL;
-out_fclose:
-       fclose(fp);
-out:
-       return buf;
-}
-
-/* Read standard input until EOF and return the full contents in a malloc()ed
- * buffer and the number of bytes of data in @len_ret.  Returns NULL on read
- * error. */
-static char *
-stdin_get_contents(size_t *len_ret)
-{
-       /* stdin can, of course, be a pipe or other non-seekable file, so the
-        * total length of the data cannot be pre-determined */
-       char *buf = NULL;
-       size_t newlen = 1024;
-       size_t pos = 0;
-       size_t inc = 1024;
-       for (;;) {
-               char *p = realloc(buf, newlen);
-               size_t bytes_read, bytes_to_read;
-               if (!p) {
-                       imagex_error(T("out of memory while reading stdin"));
-                       break;
-               }
-               buf = p;
-               bytes_to_read = newlen - pos;
-               bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
-               pos += bytes_read;
-               if (bytes_read != bytes_to_read) {
-                       if (feof(stdin)) {
-                               *len_ret = pos;
-                               return buf;
-                       } else {
-                               imagex_error_with_errno(T("error reading stdin"));
-                               break;
-                       }
-               }
-               newlen += inc;
-               inc *= 3;
-               inc /= 2;
-       }
-       free(buf);
-       return NULL;
-}
-
-
-static tchar *
-translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
-{
-#ifndef __WIN32__
-       /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
-        * */
-       *num_tchars_ret = num_bytes;
-       return text;
-#else /* !__WIN32__ */
-       /* On Windows, translate the text to UTF-16LE */
-       wchar_t *text_wstr;
-       size_t num_wchars;
-
-       if (num_bytes >= 2 &&
-           (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
-            ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
-       {
-               /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
-                * with something that looks like an ASCII character encoded as
-                * a UTF-16LE code unit.  Assume the file is encoded as
-                * UTF-16LE.  This is not a 100% reliable check. */
-               num_wchars = num_bytes / 2;
-               text_wstr = (wchar_t*)text;
-       } else {
-               /* File does not look like UTF-16LE.  Assume it is encoded in
-                * the current Windows code page.  I think these are always
-                * ASCII-compatible, so any so-called "plain-text" (ASCII) files
-                * should work as expected. */
-               text_wstr = win32_mbs_to_wcs(text,
-                                            num_bytes,
-                                            &num_wchars);
-               free(text);
-       }
-       *num_tchars_ret = num_wchars;
-       return text_wstr;
-#endif /* __WIN32__ */
-}
-
-static tchar *
-file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
-{
-       char *contents;
-       size_t num_bytes;
-
-       contents = file_get_contents(filename, &num_bytes);
-       if (!contents)
-               return NULL;
-       return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
-}
-
-static tchar *
-stdin_get_text_contents(size_t *num_tchars_ret)
-{
-       char *contents;
-       size_t num_bytes;
-
-       contents = stdin_get_contents(&num_bytes);
-       if (!contents)
-               return NULL;
-       return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
-}
-
 #define TO_PERCENT(numerator, denominator) \
        (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
 
@@ -1148,6 +1020,31 @@ report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
                last_scan_progress = *scan;
        }
 }
+
+static struct wimlib_progress_info_split last_split_progress;
+
+static void
+report_split_progress(uint64_t bytes_completed_in_part)
+{
+       uint64_t completed_bytes = last_split_progress.completed_bytes +
+                                  bytes_completed_in_part;
+       unsigned percent_done = TO_PERCENT(completed_bytes,
+                                          last_split_progress.total_bytes);
+       unsigned unit_shift;
+       const tchar *unit_name;
+
+       unit_shift = get_unit(last_split_progress.total_bytes, &unit_name);
+       imagex_printf(T("\rSplitting WIM: %"PRIu64" %"TS" of "
+                       "%"PRIu64" %"TS" (%u%%) written, part %u of %u"),
+                     completed_bytes >> unit_shift,
+                     unit_name,
+                     last_split_progress.total_bytes >> unit_shift,
+                     unit_name,
+                     percent_done,
+                     last_split_progress.cur_part_number,
+                     last_split_progress.total_parts);
+}
+
 /* Progress callback function passed to various wimlib functions. */
 static enum wimlib_progress_status
 imagex_progress_func(enum wimlib_progress_msg msg,
@@ -1160,6 +1057,12 @@ imagex_progress_func(enum wimlib_progress_msg msg,
 
        switch (msg) {
        case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
+               if (last_split_progress.total_bytes != 0) {
+                       /* wimlib_split() in progress; use the split-specific
+                        * progress message.  */
+                       report_split_progress(info->write_streams.completed_compressed_bytes);
+                       break;
+               }
                {
                        static bool started;
                        if (!started) {
@@ -1216,7 +1119,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                         * default installation.  On UNIX-like systems, warn the
                         * user when fixing the target of an absolute symbolic
                         * link, so they know to disable this if they want.  */
-               #ifndef __WIN32__
+               #ifndef _WIN32
                        imagex_printf(T("\nWARNING: Adjusted target of "
                                        "absolute symbolic link \"%"TS"\"\n"
                                        "           (Use --norpfix to capture "
@@ -1315,26 +1218,9 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                }
                break;
        case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
-               percent_done = TO_PERCENT(info->split.completed_bytes,
-                                         info->split.total_bytes);
-               unit_shift = get_unit(info->split.total_bytes, &unit_name);
-               imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
-                         "%"PRIu64" %"TS" (%u%%) written\n"),
-                       info->split.part_name,
-                       info->split.cur_part_number,
-                       info->split.total_parts,
-                       info->split.completed_bytes >> unit_shift,
-                       unit_name,
-                       info->split.total_bytes >> unit_shift,
-                       unit_name,
-                       percent_done);
-               break;
        case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
-               if (info->split.completed_bytes == info->split.total_bytes) {
-                       imagex_printf(T("Finished writing split WIM part %u of %u\n"),
-                               info->split.cur_part_number,
-                               info->split.total_parts);
-               }
+               last_split_progress = info->split;
+               report_split_progress(0);
                break;
        case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
                switch (info->update.command->op) {
@@ -1497,7 +1383,7 @@ update_command_add_option(int op, const tchar *option,
        return recognized;
 }
 
-/* How many nonoption arguments each `imagex update' command expects */
+/* How many nonoption arguments each wimupdate command expects */
 static const unsigned update_command_num_nonoptions[] = {
        [WIMLIB_UPDATE_OP_ADD] = 2,
        [WIMLIB_UPDATE_OP_DELETE] = 1,
@@ -1529,7 +1415,7 @@ update_command_add_nonoption(int op, const tchar *nonoption,
 }
 
 /*
- * Parse a command passed on stdin to `imagex update'.
+ * Parse a command passed on stdin to wimupdate.
  *
  * @line:      Text of the command.
  * @len:       Length of the line, including a null terminator
@@ -1718,6 +1604,9 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        if (ret)
                                goto out_free_refglobs;
                        break;
+               case IMAGEX_RECOVER_DATA_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
+                       break;
                default:
                        goto out_usage;
                }
@@ -1782,7 +1671,7 @@ imagex_apply(int argc, tchar **argv, int cmd)
                        goto out_wimlib_free;
        }
 
-#ifndef __WIN32__
+#ifndef _WIN32
        {
                /* Interpret a regular file or block device target as an NTFS
                 * volume.  */
@@ -1840,14 +1729,18 @@ out_usage:
        goto out_free_refglobs;
 }
 
-/* Create a WIM image from a directory tree, NTFS volume, or multiple files or
- * directory trees.  'wimlib-imagex capture': create a new WIM file containing
- * the desired image.  'wimlib-imagex append': add a new image to an existing
- * WIM file. */
+/*
+ * Create a WIM image from a directory tree, NTFS volume, or multiple files or
+ * directory trees.  'wimcapture': create a new WIM file containing the desired
+ * image.  'wimappend': add a new image to an existing WIM file; or, with
+ * '--create' behave like 'wimcapture' if the WIM file doesn't exist.
+ */
 static int
 imagex_capture_or_append(int argc, tchar **argv, int cmd)
 {
        int c;
+       bool create = false;
+       bool appending = (cmd == CMD_APPEND);
        int open_flags = 0;
        int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
                        WIMLIB_ADD_FLAG_WINCONFIG |
@@ -1895,6 +1788,8 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
@@ -1999,6 +1894,9 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                                        template_image_name_or_num = optarg;
                                }
                        }
+               #ifdef _WIN32
+                       imagex_printf(T("[WARNING] '--update-of' is unreliable on Windows!\n"));
+               #endif
                        break;
                case IMAGEX_DELTA_FROM_OPTION:
                        ret = string_list_append(&base_wimfiles, optarg);
@@ -2010,16 +1908,18 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
                        break;
                case IMAGEX_UNSAFE_COMPACT_OPTION:
-                       if (cmd != CMD_APPEND) {
-                               imagex_error(T("'--unsafe-compact' is only "
-                                              "valid for append!"));
-                               goto out_err;
-                       }
                        write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
                        break;
                case IMAGEX_SNAPSHOT_OPTION:
                        add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
                        break;
+               case IMAGEX_CREATE_OPTION:
+                       if (cmd == CMD_CAPTURE) {
+                               imagex_error(T("'--create' is only valid for 'wimappend', not 'wimcapture'"));
+                               goto out_err;
+                       }
+                       create = true;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2054,6 +1954,8 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
 
        if (!tstrcmp(wimfile, T("-"))) {
                /* Writing captured WIM to standard output.  */
+               if (create)
+                       appending = false;
        #if 0
                if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
                        imagex_error("Can't write a non-pipable WIM to "
@@ -2065,7 +1967,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        #else
                write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
        #endif
-               if (cmd == CMD_APPEND) {
+               if (appending) {
                        imagex_error(T("Using standard output for append does "
                                       "not make sense."));
                        goto out_err;
@@ -2074,6 +1976,28 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                wimfile = NULL;
                imagex_output_to_stderr();
                set_fd_to_binary_mode(wim_fd);
+       } else {
+               struct stat stbuf;
+
+               /* Check for 'wimappend --create' acting as wimcapture */
+               if (create && tstat(wimfile, &stbuf) != 0 && errno == ENOENT) {
+
+                       appending = false;
+
+                       /* Ignore '--update-of' for the target WIMFILE */
+                       if (template_image_name_or_num &&
+                           (!template_wimfile ||
+                            !tstrcmp(template_wimfile, wimfile)))
+                       {
+                               template_image_name_or_num = NULL;
+                               template_wimfile = NULL;
+                       }
+               }
+       }
+
+       if ((write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) && !appending) {
+               imagex_error(T("'--unsafe-compact' is only valid for append!"));
+               goto out_err;
        }
 
        /* If template image was specified using --update-of=IMAGE rather
@@ -2083,7 +2007,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        /* Capturing delta WIM based on single WIM:  default to
                         * base WIM.  */
                        template_wimfile = base_wimfiles.strings[0];
-               } else if (cmd == CMD_APPEND) {
+               } else if (appending) {
                        /* Appending to WIM:  default to WIM being appended to.
                         */
                        template_wimfile = wimfile;
@@ -2132,13 +2056,8 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
 
        if (source_list) {
                /* Set up capture sources in source list mode */
-               if (source[0] == T('-') && source[1] == T('\0')) {
-                       source_list_contents = stdin_get_text_contents(&source_list_nchars);
-               } else {
-                       source_list_contents = file_get_text_contents(source,
-                                                                     &source_list_nchars);
-               }
-               if (!source_list_contents)
+               if (wimlib_load_text_file(source, &source_list_contents,
+                                         &source_list_nchars) != 0)
                        goto out_err;
 
                capture_sources = parse_source_list(&source_list_contents,
@@ -2161,7 +2080,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        }
 
        /* Open the existing WIM, or create a new one.  */
-       if (cmd == CMD_APPEND) {
+       if (appending) {
                ret = wimlib_open_wim_with_progress(wimfile,
                                                    open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
                                                    &wim,
@@ -2185,7 +2104,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
 
                int ctype = compression_type;
 
-               if (cmd == CMD_APPEND) {
+               if (appending) {
                        struct wimlib_wim_info info;
                        wimlib_get_wim_info(wim, &info);
                        ctype = info.compression_type;
@@ -2208,7 +2127,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
                        goto out_free_wim;
        }
 
-#ifndef __WIN32__
+#ifndef _WIN32
        /* Detect if source is regular file or block device and set NTFS volume
         * capture mode.  */
        if (!source_list) {
@@ -2234,7 +2153,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
        /* If the user did not specify an image name, and the basename of the
         * source already exists as an image name in the WIM file, append a
         * suffix to make it unique. */
-       if (cmd == CMD_APPEND && name_defaulted) {
+       if (appending && name_defaulted) {
                unsigned long conflict_idx;
                tchar *name_end = tstrchr(name, T('\0'));
                for (conflict_idx = 1;
@@ -2286,7 +2205,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
         * open the WIM if needed and parse the image index.  */
        if (template_image_name_or_num) {
 
-               if (cmd == CMD_APPEND && !tstrcmp(template_wimfile, wimfile)) {
+               if (appending && !tstrcmp(template_wimfile, wimfile)) {
                        template_wim = wim;
                } else {
                        for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
@@ -2371,7 +2290,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd)
 
        /* Write the new WIM or overwrite the existing WIM with the new image
         * appended.  */
-       if (cmd == CMD_APPEND) {
+       if (appending) {
                ret = wimlib_overwrite(wim, write_flags, num_threads);
        } else if (wimfile) {
                ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
@@ -2428,6 +2347,8 @@ imagex_delete(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_SOFT_OPTION:
@@ -2649,7 +2570,7 @@ print_blobs(WIMStruct *wim)
        wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
 }
 
-#ifndef __WIN32__
+#ifndef _WIN32
 static void
 default_print_security_descriptor(const uint8_t *sd, size_t size)
 {
@@ -2889,6 +2810,8 @@ imagex_export(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
@@ -3226,6 +3149,9 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        if (ret)
                                goto out_free_refglobs;
                        break;
+               case IMAGEX_RECOVER_DATA_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3289,9 +3215,15 @@ imagex_extract(int argc, tchar **argv, int cmd)
                        argc -= num_paths;
                        argv += num_paths;
                } else {
+                       const tchar *listfile = argv[0] + 1;
+
+                       if (!tstrcmp(listfile, T("-"))) {
+                               tputs(T("Reading pathlist file from standard input..."));
+                               listfile = NULL;
+                       }
+
                        ret = wimlib_extract_pathlist(wim, image, dest_dir,
-                                                     argv[0] + 1,
-                                                     extract_flags);
+                                                     listfile, extract_flags);
                        argc--;
                        argv++;
                }
@@ -3344,8 +3276,6 @@ imagex_info(int argc, tchar **argv, int cmd)
 {
        int c;
        bool boot         = false;
-       bool check        = false;
-       bool nocheck      = false;
        bool header       = false;
        bool blobs        = false;
        bool xml          = false;
@@ -3358,6 +3288,7 @@ imagex_info(int argc, tchar **argv, int cmd)
        int image;
        int ret;
        int open_flags = 0;
+       int write_flags = 0;
        struct wimlib_wim_info info;
 
        for_opt(c, info_options) {
@@ -3366,10 +3297,13 @@ imagex_info(int argc, tchar **argv, int cmd)
                        boot = true;
                        break;
                case IMAGEX_CHECK_OPTION:
-                       check = true;
+                       open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
-                       nocheck = true;
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
                        break;
                case IMAGEX_HEADER_OPTION:
                        header = true;
@@ -3423,14 +3357,6 @@ imagex_info(int argc, tchar **argv, int cmd)
                        goto out;
        }
 
-       if (check && nocheck) {
-               imagex_error(T("Can't specify both --check and --nocheck"));
-               goto out_err;
-       }
-
-       if (check)
-               open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
-
        ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
                                            imagex_progress_func, NULL);
        if (ret)
@@ -3568,15 +3494,11 @@ imagex_info(int argc, tchar **argv, int cmd)
                /* Only call wimlib_overwrite() if something actually needs to
                 * be changed.  */
                if (boot || any_property_changes ||
-                   (check && !info.has_integrity_table) ||
-                   (nocheck && info.has_integrity_table))
+                   ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
+                    !info.has_integrity_table) ||
+                   ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
+                    info.has_integrity_table))
                {
-                       int write_flags = 0;
-
-                       if (check)
-                               write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
-                       if (nocheck)
-                               write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
                        ret = wimlib_overwrite(wim, write_flags, 1);
                } else {
                        imagex_printf(T("The file \"%"TS"\" was not modified "
@@ -3593,7 +3515,6 @@ out:
 
 out_usage:
        usage(CMD_INFO, stderr);
-out_err:
        ret = -1;
        goto out;
 }
@@ -3612,6 +3533,8 @@ imagex_join(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                default:
@@ -3783,6 +3706,7 @@ imagex_optimize(int argc, tchar **argv, int cmd)
        int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
        int ret;
        WIMStruct *wim;
+       struct wimlib_wim_info info;
        const tchar *wimfile;
        off_t old_size;
        off_t new_size;
@@ -3792,6 +3716,8 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_NOCHECK_OPTION:
@@ -3824,6 +3750,9 @@ imagex_optimize(int argc, tchar **argv, int cmd)
                case IMAGEX_SOLID_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_SOLID;
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
+                       /* Reset the non-solid compression type to LZMS. */
+                       if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
+                               compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
                        break;
                case IMAGEX_NO_SOLID_SORT_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
@@ -3859,11 +3788,18 @@ imagex_optimize(int argc, tchar **argv, int cmd)
        if (ret)
                goto out;
 
-       if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
+       wimlib_get_wim_info(wim, &info);
+
+       if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID &&
+           compression_type != info.compression_type) {
                /* Change compression type.  */
                ret = wimlib_set_output_compression_type(wim, compression_type);
                if (ret)
                        goto out_wimlib_free;
+
+               /* Reset the chunk size. */
+               if (chunk_size == UINT32_MAX)
+                       chunk_size = 0;
        }
 
        if (chunk_size != UINT32_MAX) {
@@ -3930,7 +3866,7 @@ imagex_split(int argc, tchar **argv, int cmd)
        int c;
        int open_flags = 0;
        int write_flags = 0;
-       unsigned long part_size;
+       uint64_t part_size;
        tchar *tmp;
        int ret;
        WIMStruct *wim;
@@ -3939,6 +3875,8 @@ imagex_split(int argc, tchar **argv, int cmd)
                switch (c) {
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                default:
@@ -3964,6 +3902,8 @@ imagex_split(int argc, tchar **argv, int cmd)
                goto out;
 
        ret = wimlib_split(wim, argv[1], part_size, write_flags);
+       if (ret == 0)
+               tprintf(T("\nFinished splitting \"%"TS"\"\n"), argv[0]);
        wimlib_free(wim);
 out:
        return ret;
@@ -4065,6 +4005,7 @@ imagex_update(int argc, tchar **argv, int cmd)
                                WIMLIB_ADD_FLAG_WINCONFIG;
        int default_delete_flags = 0;
        unsigned num_threads = 0;
+       STRING_LIST(refglobs);
        int c;
        tchar *cmd_file_contents;
        size_t cmd_file_nchars;
@@ -4084,6 +4025,8 @@ imagex_update(int argc, tchar **argv, int cmd)
                        break;
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+                       /* fall-through */
+               case IMAGEX_INCLUDE_INTEGRITY_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
                case IMAGEX_REBUILD_OPTION:
@@ -4106,6 +4049,13 @@ imagex_update(int argc, tchar **argv, int cmd)
                case IMAGEX_WIMBOOT_CONFIG_OPTION:
                        wimboot_config = optarg;
                        break;
+               case IMAGEX_REF_OPTION:
+                       ret = string_list_append(&refglobs, optarg);
+                       if (ret)
+                               goto out;
+                       /* assume delta WIM */
+                       write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
+                       break;
                /* Default delete options */
                case IMAGEX_FORCE_OPTION:
                        default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
@@ -4156,7 +4106,7 @@ imagex_update(int argc, tchar **argv, int cmd)
        ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
                                            imagex_progress_func, NULL);
        if (ret)
-               goto out_free_command_str;
+               goto out;
 
        if (argc >= 2) {
                /* Image explicitly specified.  */
@@ -4180,6 +4130,10 @@ imagex_update(int argc, tchar **argv, int cmd)
                image = 1;
        }
 
+       ret = wim_reference_globs(wim, &refglobs, open_flags);
+       if (ret)
+               goto out_wimlib_free;
+
        /* Read update commands from standard input, or the command string if
         * specified.  */
        if (command_str) {
@@ -4195,8 +4149,8 @@ imagex_update(int argc, tchar **argv, int cmd)
                        tputs(T("Reading update commands from standard input..."));
                        recommend_man_page(CMD_UPDATE, stdout);
                }
-               cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
-               if (!cmd_file_contents) {
+               if (wimlib_load_text_file(NULL, &cmd_file_contents,
+                                         &cmd_file_nchars) != 0) {
                        ret = -1;
                        goto out_wimlib_free;
                }
@@ -4259,15 +4213,16 @@ out_free_cmd_file_contents:
        free(cmd_file_contents);
 out_wimlib_free:
        wimlib_free(wim);
-out_free_command_str:
+out:
        free(command_str);
+       string_list_destroy(&refglobs);
        return ret;
 
 out_usage:
        usage(CMD_UPDATE, stderr);
 out_err:
        ret = -1;
-       goto out_free_command_str;
+       goto out;
 }
 
 /* Verify a WIM file.  */
@@ -4378,7 +4333,7 @@ static const struct imagex_command imagex_commands[] = {
        [CMD_VERIFY]   = {T("verify"),   imagex_verify},
 };
 
-#ifdef __WIN32__
+#ifdef _WIN32
 
    /* Can be a directory or source list file.  But source list file is probably
     * a rare use case, so just say directory.  */
@@ -4404,7 +4359,7 @@ T(
 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
 "                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
-"                    [--dereference] [--snapshot]\n"
+"                    [--dereference] [--snapshot] [--create]\n"
 ),
 [CMD_APPLY] =
 T(
@@ -4412,7 +4367,7 @@ T(
 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
-"                    [--compact=FORMAT]\n"
+"                    [--compact=FORMAT] [--recover-data]\n"
 ),
 [CMD_CAPTURE] =
 T(
@@ -4445,8 +4400,8 @@ T(
 "    %"TS" WIMFILE IMAGE [(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-globs] [--nullglob] [--preserve-dir-structure]\n"
+"                    [--no-attributes] [--include-invalid-names] [--no-globs]\n"
+"                    [--nullglob] [--preserve-dir-structure] [--recover-data]\n"
 ),
 [CMD_INFO] =
 T(
@@ -4530,8 +4485,8 @@ version(void)
        static const tchar * const fmt =
        T(
 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
-"Copyright (C) 2012-2018 Eric Biggers\n"
-"License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
+"Copyright 2012-2023 Eric Biggers\n"
+"License GPLv3+; GNU GPL version 3 or later <https://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"
@@ -4584,7 +4539,7 @@ static void
 recommend_man_page(int cmd, FILE *fp)
 {
        const tchar *format_str;
-#ifdef __WIN32__
+#ifdef _WIN32
        format_str = T("Some uncommon options are not listed;\n"
                       "See %"TS".pdf in the doc directory for more details.\n");
 #else
@@ -4625,8 +4580,8 @@ usage_all(FILE *fp)
        recommend_man_page(CMD_NONE, fp);
 }
 
-#ifdef __WIN32__
-extern int wmain(int argc, wchar_t **argv);
+#ifdef _WIN32
+int wmain(int argc, wchar_t **argv);
 #define main wmain
 #endif