]> wimlib.net Git - wimlib/blob - programs/imagex.c
69d7a6c74e7e5760a04c30d44d2d4f5934d1a3b9
[wimlib] / programs / imagex.c
1 /*
2  * imagex.c
3  *
4  * Use wimlib to create, modify, extract, mount, unmount, or display information
5  * about a WIM file
6  */
7
8 /*
9  * Copyright (C) 2012-2018 Eric Biggers
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h" /* Need for PACKAGE_VERSION, etc. */
27 #endif
28
29 #include "wimlib.h"
30 #include "wimlib_tchar.h"
31
32 #include <ctype.h>
33 #include <errno.h>
34
35 #include <inttypes.h>
36 #include <libgen.h>
37 #include <limits.h>
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <locale.h>
44
45 #ifdef HAVE_ALLOCA_H
46 #  include <alloca.h>
47 #endif
48
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
50
51 #ifdef __WIN32__
52 #  include "imagex-win32.h"
53 #  define print_security_descriptor     win32_print_security_descriptor
54 #else /* __WIN32__ */
55 #  include <getopt.h>
56 #  include <langinfo.h>
57 #  define print_security_descriptor     default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
59 {
60 }
61 #endif /* !__WIN32 */
62
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64  * they will never work.  However on UNIX-like systems we always present them,
65  * even if WITH_FUSE is not defined at this point, as to not tie the build of
66  * wimlib-imagex to a specific build of wimlib.  */
67 #ifdef __WIN32__
68 #  define WIM_MOUNTING_SUPPORTED 0
69 #else
70 #  define WIM_MOUNTING_SUPPORTED 1
71 #endif
72
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
74
75 static inline bool
76 is_any_path_separator(tchar c)
77 {
78         return c == T('/') || c == T('\\');
79 }
80
81 /* Like basename(), but handles both forward and backwards slashes.  */
82 static tchar *
83 tbasename(tchar *path)
84 {
85         tchar *p = tstrchr(path, T('\0'));
86
87         for (;;) {
88                 if (p == path)
89                         return path;
90                 if (!is_any_path_separator(*--p))
91                         break;
92                 *p = T('\0');
93         }
94
95         for (;;) {
96                 if (p == path)
97                         return path;
98                 if (is_any_path_separator(*--p))
99                         return ++p;
100         }
101 }
102
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
104                                 opts, NULL)) != -1)
105
106 enum {
107         CMD_NONE = -1,
108         CMD_APPEND = 0,
109         CMD_APPLY,
110         CMD_CAPTURE,
111         CMD_DELETE,
112         CMD_DIR,
113         CMD_EXPORT,
114         CMD_EXTRACT,
115         CMD_INFO,
116         CMD_JOIN,
117 #if WIM_MOUNTING_SUPPORTED
118         CMD_MOUNT,
119         CMD_MOUNTRW,
120 #endif
121         CMD_OPTIMIZE,
122         CMD_SPLIT,
123 #if WIM_MOUNTING_SUPPORTED
124         CMD_UNMOUNT,
125 #endif
126         CMD_UPDATE,
127         CMD_VERIFY,
128         CMD_MAX,
129 };
130
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool only_short_form);
135
136 static FILE *imagex_info_file;
137
138 #define imagex_printf(format, ...)      \
139         if (imagex_info_file)           \
140                 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
141
142 static void imagex_suppress_output(void)
143 {
144         imagex_info_file = NULL;
145 }
146
147 static void imagex_output_to_stderr(void)
148 {
149         if (imagex_info_file)
150                 imagex_info_file = stderr;
151 }
152
153 static void imagex_flush_output(void)
154 {
155         if (imagex_info_file)
156                 fflush(imagex_info_file);
157 }
158
159 enum {
160         IMAGEX_ALLOW_OTHER_OPTION,
161         IMAGEX_BLOBS_OPTION,
162         IMAGEX_BOOT_OPTION,
163         IMAGEX_CHECK_OPTION,
164         IMAGEX_CHUNK_SIZE_OPTION,
165         IMAGEX_COMMAND_OPTION,
166         IMAGEX_COMMIT_OPTION,
167         IMAGEX_COMPACT_OPTION,
168         IMAGEX_COMPRESS_OPTION,
169         IMAGEX_CONFIG_OPTION,
170         IMAGEX_DEBUG_OPTION,
171         IMAGEX_DELTA_FROM_OPTION,
172         IMAGEX_DEREFERENCE_OPTION,
173         IMAGEX_DEST_DIR_OPTION,
174         IMAGEX_DETAILED_OPTION,
175         IMAGEX_EXTRACT_XML_OPTION,
176         IMAGEX_FLAGS_OPTION,
177         IMAGEX_FORCE_OPTION,
178         IMAGEX_HEADER_OPTION,
179         IMAGEX_IMAGE_PROPERTY_OPTION,
180         IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
181         IMAGEX_LAZY_OPTION,
182         IMAGEX_METADATA_OPTION,
183         IMAGEX_NEW_IMAGE_OPTION,
184         IMAGEX_NOCHECK_OPTION,
185         IMAGEX_NORPFIX_OPTION,
186         IMAGEX_NOT_PIPABLE_OPTION,
187         IMAGEX_NO_ACLS_OPTION,
188         IMAGEX_NO_ATTRIBUTES_OPTION,
189         IMAGEX_NO_GLOBS_OPTION,
190         IMAGEX_NO_REPLACE_OPTION,
191         IMAGEX_NO_SOLID_SORT_OPTION,
192         IMAGEX_NULLGLOB_OPTION,
193         IMAGEX_ONE_FILE_ONLY_OPTION,
194         IMAGEX_PATH_OPTION,
195         IMAGEX_PIPABLE_OPTION,
196         IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
197         IMAGEX_REBUILD_OPTION,
198         IMAGEX_RECOMPRESS_OPTION,
199         IMAGEX_RECURSIVE_OPTION,
200         IMAGEX_REF_OPTION,
201         IMAGEX_RPFIX_OPTION,
202         IMAGEX_SNAPSHOT_OPTION,
203         IMAGEX_SOFT_OPTION,
204         IMAGEX_SOLID_CHUNK_SIZE_OPTION,
205         IMAGEX_SOLID_COMPRESS_OPTION,
206         IMAGEX_SOLID_OPTION,
207         IMAGEX_SOURCE_LIST_OPTION,
208         IMAGEX_STAGING_DIR_OPTION,
209         IMAGEX_STREAMS_INTERFACE_OPTION,
210         IMAGEX_STRICT_ACLS_OPTION,
211         IMAGEX_THREADS_OPTION,
212         IMAGEX_TO_STDOUT_OPTION,
213         IMAGEX_UNIX_DATA_OPTION,
214         IMAGEX_UNSAFE_COMPACT_OPTION,
215         IMAGEX_UPDATE_OF_OPTION,
216         IMAGEX_VERBOSE_OPTION,
217         IMAGEX_WIMBOOT_CONFIG_OPTION,
218         IMAGEX_WIMBOOT_OPTION,
219         IMAGEX_XML_OPTION,
220 };
221
222 static const struct option apply_options[] = {
223         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
224         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
225         {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
226         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
227         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
228         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
229         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
230         {T("no-attributes"), no_argument,     NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
231         {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
232         {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
233         {T("include-invalid-names"), no_argument,       NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
234         {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
235         {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
236         {NULL, 0, NULL, 0},
237 };
238
239 static const struct option capture_or_append_options[] = {
240         {T("boot"),        no_argument,       NULL, IMAGEX_BOOT_OPTION},
241         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
242         {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
243         {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
244         {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
245         {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
246         {T("solid"),       no_argument,      NULL, IMAGEX_SOLID_OPTION},
247         {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
248         {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
249         {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
250         {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
251         {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
252         {T("flags"),       required_argument, NULL, IMAGEX_FLAGS_OPTION},
253         {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
254         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
255         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
256         {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
257         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
258         {T("source-list"), no_argument,       NULL, IMAGEX_SOURCE_LIST_OPTION},
259         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
260         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
261         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
262         {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
263         {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
264         {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
265         {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
266         {T("update-of"),   required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
267         {T("delta-from"),  required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
268         {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
269         {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
270         {T("snapshot"),    no_argument,       NULL, IMAGEX_SNAPSHOT_OPTION},
271         {NULL, 0, NULL, 0},
272 };
273
274 static const struct option delete_options[] = {
275         {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
276         {T("soft"),  no_argument, NULL, IMAGEX_SOFT_OPTION},
277         {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
278         {NULL, 0, NULL, 0},
279 };
280
281 static const struct option dir_options[] = {
282         {T("path"),     required_argument, NULL, IMAGEX_PATH_OPTION},
283         {T("detailed"), no_argument,       NULL, IMAGEX_DETAILED_OPTION},
284         {T("one-file-only"), no_argument,  NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
285         {T("ref"),      required_argument, NULL, IMAGEX_REF_OPTION},
286         {NULL, 0, NULL, 0},
287 };
288
289 static const struct option export_options[] = {
290         {T("boot"),        no_argument,       NULL, IMAGEX_BOOT_OPTION},
291         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
292         {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
293         {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
294         {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
295         {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
296         {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
297         {T("solid"),       no_argument,       NULL, IMAGEX_SOLID_OPTION},
298         {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
299         {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
300         {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
301         {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
302         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
303         {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
304         {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
305         {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
306         {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
307         {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
308         {NULL, 0, NULL, 0},
309 };
310
311 static const struct option extract_options[] = {
312         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
313         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
314         {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
315         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
316         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
317         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
318         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
319         {T("no-attributes"), no_argument,     NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
320         {T("dest-dir"),    required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
321         {T("to-stdout"),   no_argument,       NULL, IMAGEX_TO_STDOUT_OPTION},
322         {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
323         {T("no-wildcards"), no_argument,      NULL, IMAGEX_NO_GLOBS_OPTION},
324         {T("no-globs"),     no_argument,      NULL, IMAGEX_NO_GLOBS_OPTION},
325         {T("nullglob"),     no_argument,      NULL, IMAGEX_NULLGLOB_OPTION},
326         {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
327         {T("wimboot"),     no_argument,       NULL, IMAGEX_WIMBOOT_OPTION},
328         {T("compact"),     required_argument, NULL, IMAGEX_COMPACT_OPTION},
329         {NULL, 0, NULL, 0},
330 };
331
332 static const struct option info_options[] = {
333         {T("boot"),         no_argument,       NULL, IMAGEX_BOOT_OPTION},
334         {T("check"),        no_argument,       NULL, IMAGEX_CHECK_OPTION},
335         {T("nocheck"),      no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
336         {T("no-check"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
337         {T("extract-xml"),  required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
338         {T("header"),       no_argument,       NULL, IMAGEX_HEADER_OPTION},
339         {T("lookup-table"), no_argument,       NULL, IMAGEX_BLOBS_OPTION},
340         {T("blobs"),        no_argument,       NULL, IMAGEX_BLOBS_OPTION},
341         {T("xml"),          no_argument,       NULL, IMAGEX_XML_OPTION},
342         {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
343         {NULL, 0, NULL, 0},
344 };
345
346 static const struct option join_options[] = {
347         {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
348         {NULL, 0, NULL, 0},
349 };
350
351 #if WIM_MOUNTING_SUPPORTED
352 static const struct option mount_options[] = {
353         {T("check"),             no_argument,       NULL, IMAGEX_CHECK_OPTION},
354         {T("debug"),             no_argument,       NULL, IMAGEX_DEBUG_OPTION},
355         {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
356         {T("ref"),               required_argument, NULL, IMAGEX_REF_OPTION},
357         {T("staging-dir"),       required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
358         {T("unix-data"),         no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
359         {T("allow-other"),       no_argument,       NULL, IMAGEX_ALLOW_OTHER_OPTION},
360         {NULL, 0, NULL, 0},
361 };
362 #endif
363
364 static const struct option optimize_options[] = {
365         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
366         {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
367         {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
368         {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
369         {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
370         {T("chunk-size"),  required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
371         {T("solid"),       no_argument,       NULL, IMAGEX_SOLID_OPTION},
372         {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
373         {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
374         {T("no-solid-sort"), no_argument,     NULL, IMAGEX_NO_SOLID_SORT_OPTION},
375         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
376         {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
377         {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
378         {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
379         {NULL, 0, NULL, 0},
380 };
381
382 static const struct option split_options[] = {
383         {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
384         {NULL, 0, NULL, 0},
385 };
386
387 #if WIM_MOUNTING_SUPPORTED
388 static const struct option unmount_options[] = {
389         {T("commit"),  no_argument, NULL, IMAGEX_COMMIT_OPTION},
390         {T("check"),   no_argument, NULL, IMAGEX_CHECK_OPTION},
391         {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
392         {T("lazy"),    no_argument, NULL, IMAGEX_LAZY_OPTION},
393         {T("force"),    no_argument, NULL, IMAGEX_FORCE_OPTION},
394         {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
395         {NULL, 0, NULL, 0},
396 };
397 #endif
398
399 static const struct option update_options[] = {
400         /* Careful: some of the options here set the defaults for update
401          * commands, but the flags given to an actual update command (and not to
402          * `imagex update' itself are also handled in
403          * update_command_add_option().  */
404         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
405         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
406         {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
407         {T("command"),     required_argument, NULL, IMAGEX_COMMAND_OPTION},
408         {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
409
410         /* Default delete options */
411         {T("force"),       no_argument,       NULL, IMAGEX_FORCE_OPTION},
412         {T("recursive"),   no_argument,       NULL, IMAGEX_RECURSIVE_OPTION},
413
414         /* Global add option */
415         {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
416
417         /* Default add options */
418         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
419         {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
420         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
421         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
422         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
423         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
424         {T("no-replace"),  no_argument,       NULL, IMAGEX_NO_REPLACE_OPTION},
425         {T("unsafe-compact"), no_argument,    NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
426
427         {NULL, 0, NULL, 0},
428 };
429
430 static const struct option verify_options[] = {
431         {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
432         {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
433
434         {NULL, 0, NULL, 0},
435 };
436
437 #if 0
438 #       define _format_attribute(type, format_str, args_start) \
439                         __attribute__((format(type, format_str, args_start)))
440 #else
441 #       define _format_attribute(type, format_str, args_start)
442 #endif
443
444 /* Print formatted error message to stderr. */
445 static void _format_attribute(printf, 1, 2)
446 imagex_error(const tchar *format, ...)
447 {
448         va_list va;
449         va_start(va, format);
450         tfputs(T("ERROR: "), stderr);
451         tvfprintf(stderr, format, va);
452         tputc(T('\n'), stderr);
453         va_end(va);
454 }
455
456 /* Print formatted error message to stderr. */
457 static void _format_attribute(printf, 1, 2)
458 imagex_error_with_errno(const tchar *format, ...)
459 {
460         int errno_save = errno;
461         va_list va;
462         va_start(va, format);
463         tfputs(T("ERROR: "), stderr);
464         tvfprintf(stderr, format, va);
465         tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
466         va_end(va);
467 }
468
469 static int
470 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
471 {
472         if (image == WIMLIB_NO_IMAGE) {
473                 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
474                              "       Please specify a 1-based image index or "
475                              "image name.  To list the images\n"
476                              "       contained in the WIM archive, run\n"
477                              "\n"
478                              "           %"TS" \"%"TS"\"\n"),
479                              image_name, wim_name,
480                              get_cmd_string(CMD_INFO, false), wim_name);
481                 return WIMLIB_ERR_INVALID_IMAGE;
482         }
483         return 0;
484 }
485
486 static int
487 verify_image_is_single(int image)
488 {
489         if (image == WIMLIB_ALL_IMAGES) {
490                 imagex_error(T("Cannot specify all images for this action!"));
491                 return WIMLIB_ERR_INVALID_IMAGE;
492         }
493         return 0;
494 }
495
496 static int
497 verify_image_exists_and_is_single(int image, const tchar *image_name,
498                                   const tchar *wim_name)
499 {
500         int ret;
501         ret = verify_image_exists(image, image_name, wim_name);
502         if (ret == 0)
503                 ret = verify_image_is_single(image);
504         return ret;
505 }
506
507 static void
508 print_available_compression_types(FILE *fp)
509 {
510         static const tchar * const s =
511         T(
512         "Available compression types:\n"
513         "\n"
514         "    none\n"
515         "    xpress (alias: \"fast\")\n"
516         "    lzx    (alias: \"maximum\") (default for capture)\n"
517         "    lzms   (alias: \"recovery\")\n"
518         "\n"
519         );
520         tfputs(s, fp);
521 }
522
523 /* Parse the argument to --compress or --solid-compress  */
524 static int
525 get_compression_type(tchar *optarg, bool solid)
526 {
527         int ctype;
528         unsigned int compression_level = 0;
529         tchar *plevel;
530
531         plevel = tstrchr(optarg, T(':'));
532         if (plevel) {
533                 tchar *ptmp;
534                 unsigned long ultmp;
535
536                 *plevel++ = T('\0');
537                 ultmp = tstrtoul(plevel, &ptmp, 10);
538                 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
539                         imagex_error(T("Compression level must be a positive integer! "
540                                        "e.g. --compress=lzx:80"));
541                         return WIMLIB_COMPRESSION_TYPE_INVALID;
542                 }
543                 compression_level = ultmp;
544         }
545
546         if (!tstrcasecmp(optarg, T("maximum")) ||
547             !tstrcasecmp(optarg, T("lzx")) ||
548             !tstrcasecmp(optarg, T("max"))) {
549                 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
550         } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
551                 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
552         } else if (!tstrcasecmp(optarg, T("recovery"))) {
553                 if (!solid) {
554                         tfprintf(stderr,
555 T(
556 "Warning: use of '--compress=recovery' is discouraged because it behaves\n"
557 "   differently from DISM.  Instead, you typically want to use '--solid' to\n"
558 "   create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
559 "   /compress:recovery.  But if you really want *non-solid* LZMS compression,\n"
560 "   then you may suppress this warning by specifying '--compress=lzms' instead\n"
561 "   of '--compress=recovery'.\n"));
562                 }
563                 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
564         } else if (!tstrcasecmp(optarg, T("lzms"))) {
565                 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
566         } else if (!tstrcasecmp(optarg, T("none"))) {
567                 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
568         } else {
569                 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
570                 print_available_compression_types(stderr);
571                 return WIMLIB_COMPRESSION_TYPE_INVALID;
572         }
573
574         if (compression_level != 0)
575                 wimlib_set_default_compression_level(ctype, compression_level);
576         return ctype;
577 }
578
579 /* Parse the argument to --compact */
580 static int
581 set_compact_mode(const tchar *arg, int *extract_flags)
582 {
583         int flag = 0;
584         if (!tstrcasecmp(arg, T("xpress4k")))
585                 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
586         else if (!tstrcasecmp(arg, T("xpress8k")))
587                 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
588         else if (!tstrcasecmp(arg, T("xpress16k")))
589                 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
590         else if (!tstrcasecmp(arg, T("lzx")))
591                 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
592
593         if (flag) {
594                 *extract_flags |= flag;
595                 return 0;
596         }
597
598         imagex_error(T(
599 "\"%"TS"\" is not a recognized System Compression format.  The options are:"
600 "\n"
601 "    --compact=xpress4k\n"
602 "    --compact=xpress8k\n"
603 "    --compact=xpress16k\n"
604 "    --compact=lzx\n"
605         ), arg);
606         return -1;
607 }
608
609
610 struct string_list {
611         tchar **strings;
612         unsigned num_strings;
613         unsigned num_alloc_strings;
614 };
615
616 #define STRING_LIST_INITIALIZER \
617         { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
618
619 #define STRING_LIST(_strings) \
620         struct string_list _strings = STRING_LIST_INITIALIZER
621
622 static int
623 string_list_append(struct string_list *list, tchar *glob)
624 {
625         unsigned num_alloc_strings = list->num_alloc_strings;
626
627         if (list->num_strings == num_alloc_strings) {
628                 tchar **new_strings;
629
630                 num_alloc_strings += 4;
631                 new_strings = realloc(list->strings,
632                                       sizeof(list->strings[0]) * num_alloc_strings);
633                 if (!new_strings) {
634                         imagex_error(T("Out of memory!"));
635                         return -1;
636                 }
637                 list->strings = new_strings;
638                 list->num_alloc_strings = num_alloc_strings;
639         }
640         list->strings[list->num_strings++] = glob;
641         return 0;
642 }
643
644 static void
645 string_list_destroy(struct string_list *list)
646 {
647         free(list->strings);
648 }
649
650 static int
651 wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
652 {
653         return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
654                                                list->num_strings,
655                                                WIMLIB_REF_FLAG_GLOB_ENABLE,
656                                                open_flags);
657 }
658
659 static int
660 append_image_property_argument(struct string_list *image_properties)
661 {
662         if (!tstrchr(optarg, '=')) {
663                 imagex_error(T("'--image-property' argument "
664                                "must be in the form NAME=VALUE"));
665                 return -1;
666         }
667         return string_list_append(image_properties, optarg);
668 }
669
670 static int
671 apply_image_properties(struct string_list *image_properties,
672                        WIMStruct *wim, int image, bool *any_changes_ret)
673 {
674         bool any_changes = false;
675         for (unsigned i = 0; i < image_properties->num_strings; i++) {
676                 tchar *name, *value;
677                 const tchar *current_value;
678                 int ret;
679
680                 name = image_properties->strings[i];
681                 value = tstrchr(name, '=');
682                 *value++ = '\0';
683
684                 current_value = wimlib_get_image_property(wim, image, name);
685                 if (current_value && !tstrcmp(current_value, value)) {
686                         imagex_printf(T("The %"TS" property of image %d "
687                                         "already has value \"%"TS"\".\n"),
688                                       name, image, value);
689                 } else {
690                         imagex_printf(T("Setting the %"TS" property of image "
691                                         "%d to \"%"TS"\".\n"),
692                                       name, image, value);
693                         ret = wimlib_set_image_property(wim, image, name, value);
694                         if (ret)
695                                 return ret;
696                         any_changes = true;
697                 }
698         }
699         if (any_changes_ret)
700                 *any_changes_ret = any_changes;
701         return 0;
702 }
703
704 static void
705 do_resource_not_found_warning(const tchar *wimfile,
706                               const struct wimlib_wim_info *info,
707                               const struct string_list *refglobs)
708 {
709         if (info->total_parts > 1) {
710                 if (refglobs->num_strings == 0) {
711                         imagex_error(T("\"%"TS"\" is part of a split WIM. "
712                                        "Use --ref to specify the other parts."),
713                                      wimfile);
714                 } else {
715                         imagex_error(T("Perhaps the '--ref' argument did not "
716                                        "specify all other parts of the split "
717                                        "WIM?"));
718                 }
719         } else {
720                 imagex_error(T("If this is a delta WIM, use the --ref argument "
721                                "to specify the WIM(s) on which it is based."));
722         }
723 }
724
725 static void
726 do_metadata_not_found_warning(const tchar *wimfile,
727                               const struct wimlib_wim_info *info)
728 {
729         if (info->part_number != 1) {
730                 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
731                                "       You must specify the first part."),
732                                wimfile);
733         }
734 }
735
736 /* Returns the size of a file given its name, or -1 if the file does not exist
737  * or its size cannot be determined.  */
738 static off_t
739 file_get_size(const tchar *filename)
740 {
741         struct stat st;
742         if (tstat(filename, &st) == 0)
743                 return st.st_size;
744         else
745                 return (off_t)-1;
746 }
747
748 enum {
749         PARSE_STRING_SUCCESS = 0,
750         PARSE_STRING_FAILURE = 1,
751         PARSE_STRING_NONE = 2,
752 };
753
754 /*
755  * Parses a string token from an array of characters.
756  *
757  * Tokens are either whitespace-delimited, or double or single-quoted.
758  *
759  * @line_p:  Pointer to the pointer to the line of data.  Will be updated
760  *           to point past the string token iff the return value is
761  *           PARSE_STRING_SUCCESS.  If *len_p > 0, (*line_p)[*len_p - 1] must
762  *           be '\0'.
763  *
764  * @len_p:   @len_p initially stores the length of the line of data, which may
765  *           be 0, and it will be updated to the number of bytes remaining in
766  *           the line iff the return value is PARSE_STRING_SUCCESS.
767  *
768  * @fn_ret:  Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
769  *           parsed string token will be returned here.
770  *
771  * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
772  *          PARSE_STRING_FAILURE if the data was invalid due to a missing
773  *          closing quote; or PARSE_STRING_NONE if the line ended before the
774  *          beginning of a string token was found.
775  */
776 static int
777 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
778 {
779         size_t len = *len_p;
780         tchar *line = *line_p;
781         tchar *fn;
782         tchar quote_char;
783
784         /* Skip leading whitespace */
785         for (;;) {
786                 if (len == 0)
787                         return PARSE_STRING_NONE;
788                 if (!istspace(*line) && *line != T('\0'))
789                         break;
790                 line++;
791                 len--;
792         }
793         quote_char = *line;
794         if (quote_char == T('"') || quote_char == T('\'')) {
795                 /* Quoted string */
796                 line++;
797                 len--;
798                 fn = line;
799                 line = tmemchr(line, quote_char, len);
800                 if (!line) {
801                         imagex_error(T("Missing closing quote: %"TS), fn - 1);
802                         return PARSE_STRING_FAILURE;
803                 }
804         } else {
805                 /* Unquoted string.  Go until whitespace.  Line is terminated
806                  * by '\0', so no need to check 'len'. */
807                 fn = line;
808                 do {
809                         line++;
810                 } while (!istspace(*line) && *line != T('\0'));
811         }
812         *line = T('\0');
813         len -= line - fn;
814         *len_p = len;
815         *line_p = line;
816         *fn_ret = fn;
817         return PARSE_STRING_SUCCESS;
818 }
819
820 /* Parses a line of data (not an empty line or comment) in the source list file
821  * format.  (See the man page for 'wimlib-imagex capture' for details on this
822  * format and the meaning.)
823  *
824  * @line:  Line of data to be parsed.  line[len - 1] must be '\0', unless
825  *         len == 0.  The data in @line will be modified by this function call.
826  *
827  * @len:   Length of the line of data.
828  *
829  * @source:  On success, the capture source and target described by the line is
830  *           written into this destination.  Note that it will contain pointers
831  *           to data in the @line array.
832  *
833  * Returns true if the line was valid; false otherwise.  */
834 static bool
835 parse_source_list_line(tchar *line, size_t len,
836                        struct wimlib_capture_source *source)
837 {
838         /* SOURCE [DEST] */
839         int ret;
840         ret = parse_string(&line, &len, &source->fs_source_path);
841         if (ret != PARSE_STRING_SUCCESS)
842                 return false;
843         ret = parse_string(&line, &len, &source->wim_target_path);
844         if (ret == PARSE_STRING_NONE)
845                 source->wim_target_path = source->fs_source_path;
846         return ret != PARSE_STRING_FAILURE;
847 }
848
849 /* Returns %true if the given line of length @len > 0 is a comment or empty line
850  * in the source list file format. */
851 static bool
852 is_comment_line(const tchar *line, size_t len)
853 {
854         for (;;) {
855                 if (*line == T('#') || *line == T(';'))
856                         return true;
857                 if (!istspace(*line) && *line != T('\0'))
858                         return false;
859                 ++line;
860                 --len;
861                 if (len == 0)
862                         return true;
863         }
864 }
865
866 static ssize_t
867 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
868 {
869         ssize_t nlines = 0;
870         tchar *contents = *contents_p;
871         size_t nchars = *nchars_p;
872         size_t i;
873
874         for (i = 0; i < nchars; i++)
875                 if (contents[i] == T('\n'))
876                         nlines++;
877
878         /* Handle last line not terminated by a newline */
879         if (nchars != 0 && contents[nchars - 1] != T('\n')) {
880                 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
881                 if (!contents) {
882                         imagex_error(T("Out of memory!"));
883                         return -1;
884                 }
885                 contents[nchars] = T('\n');
886                 *contents_p = contents;
887                 nchars++;
888                 nlines++;
889         }
890         *nchars_p = nchars;
891         return nlines;
892 }
893
894 /* Parses a file in the source list format.  (See the man page for
895  * 'wimlib-imagex capture' for details on this format and the meaning.)
896  *
897  * @source_list_contents:  Contents of the source list file.  Note that this
898  *                         buffer will be modified to save memory allocations,
899  *                         and cannot be freed until the returned array of
900  *                         wimlib_capture_source's has also been freed.
901  *
902  * @source_list_nbytes:    Number of bytes of data in the @source_list_contents
903  *                         buffer.
904  *
905  * @nsources_ret:          On success, the length of the returned array is
906  *                         returned here.
907  *
908  * Returns:   An array of `struct wimlib_capture_source's that can be passed to
909  * the wimlib_add_image_multisource() function to specify how a WIM image is to
910  * be created.  */
911 static struct wimlib_capture_source *
912 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
913                   size_t *nsources_ret)
914 {
915         ssize_t nlines;
916         tchar *p;
917         struct wimlib_capture_source *sources;
918         size_t i, j;
919
920         nlines = text_file_count_lines(source_list_contents_p,
921                                        &source_list_nchars);
922         if (nlines < 0)
923                 return NULL;
924
925         /* Always allocate at least 1 slot, just in case the implementation of
926          * calloc() returns NULL if 0 bytes are requested. */
927         sources = calloc(nlines ?: 1, sizeof(*sources));
928         if (!sources) {
929                 imagex_error(T("out of memory"));
930                 return NULL;
931         }
932         p = *source_list_contents_p;
933         j = 0;
934         for (i = 0; i < nlines; i++) {
935                 /* XXX: Could use rawmemchr() here instead, but it may not be
936                  * available on all platforms. */
937                 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
938                 size_t len = endp - p + 1;
939                 *endp = T('\0');
940                 if (!is_comment_line(p, len)) {
941                         if (!parse_source_list_line(p, len, &sources[j++])) {
942                                 free(sources);
943                                 return NULL;
944                         }
945                 }
946                 p = endp + 1;
947
948         }
949         *nsources_ret = j;
950         return sources;
951 }
952
953 /* Reads the contents of a file into memory. */
954 static char *
955 file_get_contents(const tchar *filename, size_t *len_ret)
956 {
957         struct stat stbuf;
958         void *buf = NULL;
959         size_t len;
960         FILE *fp;
961
962         if (tstat(filename, &stbuf) != 0) {
963                 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
964                 goto out;
965         }
966         len = stbuf.st_size;
967
968         fp = tfopen(filename, T("rb"));
969         if (!fp) {
970                 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
971                 goto out;
972         }
973
974         buf = malloc(len ? len : 1);
975         if (!buf) {
976                 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
977                                "contents of file \"%"TS"\""), len, filename);
978                 goto out_fclose;
979         }
980         if (fread(buf, 1, len, fp) != len) {
981                 imagex_error_with_errno(T("Failed to read %zu bytes from the "
982                                           "file \"%"TS"\""), len, filename);
983                 goto out_free_buf;
984         }
985         *len_ret = len;
986         goto out_fclose;
987 out_free_buf:
988         free(buf);
989         buf = NULL;
990 out_fclose:
991         fclose(fp);
992 out:
993         return buf;
994 }
995
996 /* Read standard input until EOF and return the full contents in a malloc()ed
997  * buffer and the number of bytes of data in @len_ret.  Returns NULL on read
998  * error. */
999 static char *
1000 stdin_get_contents(size_t *len_ret)
1001 {
1002         /* stdin can, of course, be a pipe or other non-seekable file, so the
1003          * total length of the data cannot be pre-determined */
1004         char *buf = NULL;
1005         size_t newlen = 1024;
1006         size_t pos = 0;
1007         size_t inc = 1024;
1008         for (;;) {
1009                 char *p = realloc(buf, newlen);
1010                 size_t bytes_read, bytes_to_read;
1011                 if (!p) {
1012                         imagex_error(T("out of memory while reading stdin"));
1013                         break;
1014                 }
1015                 buf = p;
1016                 bytes_to_read = newlen - pos;
1017                 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
1018                 pos += bytes_read;
1019                 if (bytes_read != bytes_to_read) {
1020                         if (feof(stdin)) {
1021                                 *len_ret = pos;
1022                                 return buf;
1023                         } else {
1024                                 imagex_error_with_errno(T("error reading stdin"));
1025                                 break;
1026                         }
1027                 }
1028                 newlen += inc;
1029                 inc *= 3;
1030                 inc /= 2;
1031         }
1032         free(buf);
1033         return NULL;
1034 }
1035
1036
1037 static tchar *
1038 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1039 {
1040 #ifndef __WIN32__
1041         /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1042          * */
1043         *num_tchars_ret = num_bytes;
1044         return text;
1045 #else /* !__WIN32__ */
1046         /* On Windows, translate the text to UTF-16LE */
1047         wchar_t *text_wstr;
1048         size_t num_wchars;
1049
1050         if (num_bytes >= 2 &&
1051             (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1052              ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1053         {
1054                 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1055                  * with something that looks like an ASCII character encoded as
1056                  * a UTF-16LE code unit.  Assume the file is encoded as
1057                  * UTF-16LE.  This is not a 100% reliable check. */
1058                 num_wchars = num_bytes / 2;
1059                 text_wstr = (wchar_t*)text;
1060         } else {
1061                 /* File does not look like UTF-16LE.  Assume it is encoded in
1062                  * the current Windows code page.  I think these are always
1063                  * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1064                  * should work as expected. */
1065                 text_wstr = win32_mbs_to_wcs(text,
1066                                              num_bytes,
1067                                              &num_wchars);
1068                 free(text);
1069         }
1070         *num_tchars_ret = num_wchars;
1071         return text_wstr;
1072 #endif /* __WIN32__ */
1073 }
1074
1075 static tchar *
1076 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1077 {
1078         char *contents;
1079         size_t num_bytes;
1080
1081         contents = file_get_contents(filename, &num_bytes);
1082         if (!contents)
1083                 return NULL;
1084         return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1085 }
1086
1087 static tchar *
1088 stdin_get_text_contents(size_t *num_tchars_ret)
1089 {
1090         char *contents;
1091         size_t num_bytes;
1092
1093         contents = stdin_get_contents(&num_bytes);
1094         if (!contents)
1095                 return NULL;
1096         return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1097 }
1098
1099 #define TO_PERCENT(numerator, denominator) \
1100         (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1101
1102 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1103 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1104 #define KIBIBYTE_MIN_NBYTES 10000ULL
1105
1106 static unsigned
1107 get_unit(uint64_t total_bytes, const tchar **name_ret)
1108 {
1109         if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1110                 *name_ret = T("GiB");
1111                 return 30;
1112         } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1113                 *name_ret = T("MiB");
1114                 return 20;
1115         } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1116                 *name_ret = T("KiB");
1117                 return 10;
1118         } else {
1119                 *name_ret = T("bytes");
1120                 return 0;
1121         }
1122 }
1123
1124 static struct wimlib_progress_info_scan last_scan_progress;
1125
1126 static void
1127 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1128 {
1129         uint64_t prev_count, cur_count;
1130
1131         prev_count = last_scan_progress.num_nondirs_scanned +
1132                      last_scan_progress.num_dirs_scanned;
1133         cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1134
1135         if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1136             cur_count % 128 == 0)
1137         {
1138                 unsigned unit_shift;
1139                 const tchar *unit_name;
1140
1141                 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1142                 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1143                                 "%"PRIu64" directories)    "),
1144                               scan->num_bytes_scanned >> unit_shift,
1145                               unit_name,
1146                               scan->num_nondirs_scanned,
1147                               scan->num_dirs_scanned);
1148                 last_scan_progress = *scan;
1149         }
1150 }
1151 /* Progress callback function passed to various wimlib functions. */
1152 static enum wimlib_progress_status
1153 imagex_progress_func(enum wimlib_progress_msg msg,
1154                      union wimlib_progress_info *info,
1155                      void *_ignored_context)
1156 {
1157         unsigned percent_done;
1158         unsigned unit_shift;
1159         const tchar *unit_name;
1160
1161         switch (msg) {
1162         case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1163                 {
1164                         static bool started;
1165                         if (!started) {
1166                                 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1167                                         imagex_printf(T("Using %"TS" compression "
1168                                                         "with %u thread%"TS"\n"),
1169                                                       wimlib_get_compression_type_string(
1170                                                                 info->write_streams.compression_type),
1171                                                 info->write_streams.num_threads,
1172                                                 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1173                                 }
1174                                 started = true;
1175                         }
1176                 }
1177                 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1178                 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1179                                           info->write_streams.total_bytes);
1180
1181                 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1182                         info->write_streams.completed_bytes >> unit_shift,
1183                         unit_name,
1184                         info->write_streams.total_bytes >> unit_shift,
1185                         unit_name,
1186                         percent_done);
1187                 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1188                         imagex_printf(T("\n"));
1189                 break;
1190         case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1191                 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1192                 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1193                         imagex_printf(T("\n"));
1194                 } else {
1195                         imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1196                                       info->scan.wim_target_path);
1197                 }
1198                 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1199                 break;
1200         case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1201                 switch (info->scan.status) {
1202                 case WIMLIB_SCAN_DENTRY_OK:
1203                         report_scan_progress(&info->scan, false);
1204                         break;
1205                 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1206                         imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1207                         break;
1208                 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1209                         imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1210                                         "         \"%"TS"\" from capture\n"), info->scan.cur_path);
1211                         break;
1212                 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1213                         /* Symlink fixups are enabled by default.  This is
1214                          * mainly intended for Windows, which for some reason
1215                          * uses absolute junctions (with drive letters!) in the
1216                          * default installation.  On UNIX-like systems, warn the
1217                          * user when fixing the target of an absolute symbolic
1218                          * link, so they know to disable this if they want.  */
1219                 #ifndef __WIN32__
1220                         imagex_printf(T("\nWARNING: Adjusted target of "
1221                                         "absolute symbolic link \"%"TS"\"\n"
1222                                         "           (Use --norpfix to capture "
1223                                         "absolute symbolic links as-is)\n"),
1224                                         info->scan.cur_path);
1225                 #endif
1226                         break;
1227                 default:
1228                         break;
1229                 }
1230                 break;
1231         case WIMLIB_PROGRESS_MSG_SCAN_END:
1232                 report_scan_progress(&info->scan, true);
1233                 imagex_printf(T("\n"));
1234                 break;
1235         case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1236                 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1237                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1238                                           info->integrity.total_bytes);
1239                 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1240                         "of %"PRIu64" %"TS" (%u%%) done"),
1241                         info->integrity.filename,
1242                         info->integrity.completed_bytes >> unit_shift,
1243                         unit_name,
1244                         info->integrity.total_bytes >> unit_shift,
1245                         unit_name,
1246                         percent_done);
1247                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1248                         imagex_printf(T("\n"));
1249                 break;
1250         case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1251                 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1252                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1253                                           info->integrity.total_bytes);
1254                 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1255                           "of %"PRIu64" %"TS" (%u%%) done"),
1256                         info->integrity.completed_bytes >> unit_shift,
1257                         unit_name,
1258                         info->integrity.total_bytes >> unit_shift,
1259                         unit_name,
1260                         percent_done);
1261                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1262                         imagex_printf(T("\n"));
1263                 break;
1264         case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1265                 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1266                           "to %"TS" \"%"TS"\"\n"),
1267                         info->extract.image,
1268                         info->extract.image_name,
1269                         info->extract.wimfile_name,
1270                         ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1271                          T("NTFS volume") : T("directory")),
1272                         info->extract.target);
1273                 break;
1274         case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1275                 if (info->extract.end_file_count >= 2000) {
1276                         percent_done = TO_PERCENT(info->extract.current_file_count,
1277                                                   info->extract.end_file_count);
1278                         imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1279                                       info->extract.current_file_count,
1280                                       info->extract.end_file_count, percent_done);
1281                         if (info->extract.current_file_count == info->extract.end_file_count)
1282                                 imagex_printf(T("\n"));
1283                 }
1284                 break;
1285         case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1286                 percent_done = TO_PERCENT(info->extract.completed_bytes,
1287                                           info->extract.total_bytes);
1288                 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1289                 imagex_printf(T("\rExtracting file data: "
1290                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1291                         info->extract.completed_bytes >> unit_shift,
1292                         unit_name,
1293                         info->extract.total_bytes >> unit_shift,
1294                         unit_name,
1295                         percent_done);
1296                 if (info->extract.completed_bytes >= info->extract.total_bytes)
1297                         imagex_printf(T("\n"));
1298                 break;
1299         case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1300                 if (info->extract.end_file_count >= 2000) {
1301                         percent_done = TO_PERCENT(info->extract.current_file_count,
1302                                                   info->extract.end_file_count);
1303                         imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1304                                       info->extract.current_file_count,
1305                                       info->extract.end_file_count, percent_done);
1306                         if (info->extract.current_file_count == info->extract.end_file_count)
1307                                 imagex_printf(T("\n"));
1308                 }
1309                 break;
1310         case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1311                 if (info->extract.total_parts != 1) {
1312                         imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1313                                       info->extract.part_number,
1314                                       info->extract.total_parts);
1315                 }
1316                 break;
1317         case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1318                 percent_done = TO_PERCENT(info->split.completed_bytes,
1319                                           info->split.total_bytes);
1320                 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1321                 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1322                           "%"PRIu64" %"TS" (%u%%) written\n"),
1323                         info->split.part_name,
1324                         info->split.cur_part_number,
1325                         info->split.total_parts,
1326                         info->split.completed_bytes >> unit_shift,
1327                         unit_name,
1328                         info->split.total_bytes >> unit_shift,
1329                         unit_name,
1330                         percent_done);
1331                 break;
1332         case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1333                 if (info->split.completed_bytes == info->split.total_bytes) {
1334                         imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1335                                 info->split.cur_part_number,
1336                                 info->split.total_parts);
1337                 }
1338                 break;
1339         case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1340                 switch (info->update.command->op) {
1341                 case WIMLIB_UPDATE_OP_DELETE:
1342                         imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1343                                 info->update.command->delete_.wim_path);
1344                         break;
1345                 case WIMLIB_UPDATE_OP_RENAME:
1346                         imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1347                                 info->update.command->rename.wim_source_path,
1348                                 info->update.command->rename.wim_target_path);
1349                         break;
1350                 case WIMLIB_UPDATE_OP_ADD:
1351                 default:
1352                         break;
1353                 }
1354                 break;
1355         case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1356                 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1357                               info->replace.path_in_wim);
1358                 break;
1359         case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1360                 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1361                               info->wimboot_exclude.path_in_wim);
1362                 break;
1363         case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1364                 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1365                         if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1366                                 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1367                                               info->unmount.mounted_wim,
1368                                               info->unmount.mounted_image);
1369                         } else {
1370                                 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1371                                               info->unmount.mounted_wim,
1372                                               info->unmount.mounted_image);
1373                                 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1374                         }
1375                 }
1376                 break;
1377         case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1378                 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1379                               info->verify_image.current_image,
1380                               info->verify_image.total_images);
1381                 break;
1382         case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1383                 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1384                                           info->verify_streams.total_bytes);
1385                 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1386                 imagex_printf(T("\rVerifying file data: "
1387                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1388                         info->verify_streams.completed_bytes >> unit_shift,
1389                         unit_name,
1390                         info->verify_streams.total_bytes >> unit_shift,
1391                         unit_name,
1392                         percent_done);
1393                 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1394                         imagex_printf(T("\n"));
1395                 break;
1396         default:
1397                 break;
1398         }
1399         imagex_flush_output();
1400         return WIMLIB_PROGRESS_STATUS_CONTINUE;
1401 }
1402
1403 static unsigned
1404 parse_num_threads(const tchar *optarg)
1405 {
1406         tchar *tmp;
1407         unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1408         if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1409                 imagex_error(T("Number of threads must be a non-negative integer!"));
1410                 return UINT_MAX;
1411         } else {
1412                 return ul_nthreads;
1413         }
1414 }
1415
1416 static uint32_t
1417 parse_chunk_size(const tchar *optarg)
1418 {
1419         tchar *tmp;
1420         uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1421         if (chunk_size == 0) {
1422                 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1423                                "       with optional K, M, or G suffix"));
1424                 return UINT32_MAX;
1425         }
1426         if (*tmp) {
1427                 if (*tmp == T('k') || *tmp == T('K')) {
1428                         chunk_size <<= 10;
1429                         tmp++;
1430                 } else if (*tmp == T('m') || *tmp == T('M')) {
1431                         chunk_size <<= 20;
1432                         tmp++;
1433                 } else if (*tmp == T('g') || *tmp == T('G')) {
1434                         chunk_size <<= 30;
1435                         tmp++;
1436                 }
1437                 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1438                         imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1439                         return UINT32_MAX;
1440                 }
1441         }
1442         if (chunk_size >= UINT32_MAX) {
1443                 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1444                 return UINT32_MAX;
1445         }
1446         return chunk_size;
1447 }
1448
1449
1450 /*
1451  * Parse an option passed to an update command.
1452  *
1453  * @op:         One of WIMLIB_UPDATE_OP_* that indicates the command being
1454  *              parsed.
1455  *
1456  * @option:     Text string for the option (beginning with --)
1457  *
1458  * @cmd:        `struct wimlib_update_command' that is being constructed for
1459  *              this command.
1460  *
1461  * Returns true if the option was recognized; false if not.
1462  */
1463 static bool
1464 update_command_add_option(int op, const tchar *option,
1465                           struct wimlib_update_command *cmd)
1466 {
1467         bool recognized = true;
1468         switch (op) {
1469         case WIMLIB_UPDATE_OP_ADD:
1470                 if (!tstrcmp(option, T("--verbose")))
1471                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1472                 else if (!tstrcmp(option, T("--unix-data")))
1473                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1474                 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1475                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1476                 else if (!tstrcmp(option, T("--strict-acls")))
1477                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1478                 else if (!tstrcmp(option, T("--dereference")))
1479                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1480                 else if (!tstrcmp(option, T("--no-replace")))
1481                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1482                 else
1483                         recognized = false;
1484                 break;
1485         case WIMLIB_UPDATE_OP_DELETE:
1486                 if (!tstrcmp(option, T("--force")))
1487                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1488                 else if (!tstrcmp(option, T("--recursive")))
1489                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1490                 else
1491                         recognized = false;
1492                 break;
1493         default:
1494                 recognized = false;
1495                 break;
1496         }
1497         return recognized;
1498 }
1499
1500 /* How many nonoption arguments each `imagex update' command expects */
1501 static const unsigned update_command_num_nonoptions[] = {
1502         [WIMLIB_UPDATE_OP_ADD] = 2,
1503         [WIMLIB_UPDATE_OP_DELETE] = 1,
1504         [WIMLIB_UPDATE_OP_RENAME] = 2,
1505 };
1506
1507 static void
1508 update_command_add_nonoption(int op, const tchar *nonoption,
1509                              struct wimlib_update_command *cmd,
1510                              unsigned num_nonoptions)
1511 {
1512         switch (op) {
1513         case WIMLIB_UPDATE_OP_ADD:
1514                 if (num_nonoptions == 0)
1515                         cmd->add.fs_source_path = (tchar*)nonoption;
1516                 else
1517                         cmd->add.wim_target_path = (tchar*)nonoption;
1518                 break;
1519         case WIMLIB_UPDATE_OP_DELETE:
1520                 cmd->delete_.wim_path = (tchar*)nonoption;
1521                 break;
1522         case WIMLIB_UPDATE_OP_RENAME:
1523                 if (num_nonoptions == 0)
1524                         cmd->rename.wim_source_path = (tchar*)nonoption;
1525                 else
1526                         cmd->rename.wim_target_path = (tchar*)nonoption;
1527                 break;
1528         }
1529 }
1530
1531 /*
1532  * Parse a command passed on stdin to `imagex update'.
1533  *
1534  * @line:       Text of the command.
1535  * @len:        Length of the line, including a null terminator
1536  *              at line[len - 1].
1537  *
1538  * @command:    A `struct wimlib_update_command' to fill in from the parsed
1539  *              line.
1540  *
1541  * @line_number: Line number of the command, for diagnostics.
1542  *
1543  * Returns true on success; returns false on parse error.
1544  */
1545 static bool
1546 parse_update_command(tchar *line, size_t len,
1547                      struct wimlib_update_command *command,
1548                      size_t line_number)
1549 {
1550         int ret;
1551         tchar *command_name;
1552         int op;
1553         size_t num_nonoptions;
1554
1555         /* Get the command name ("add", "delete", "rename") */
1556         ret = parse_string(&line, &len, &command_name);
1557         if (ret != PARSE_STRING_SUCCESS)
1558                 return false;
1559
1560         if (!tstrcasecmp(command_name, T("add"))) {
1561                 op = WIMLIB_UPDATE_OP_ADD;
1562         } else if (!tstrcasecmp(command_name, T("delete"))) {
1563                 op = WIMLIB_UPDATE_OP_DELETE;
1564         } else if (!tstrcasecmp(command_name, T("rename"))) {
1565                 op = WIMLIB_UPDATE_OP_RENAME;
1566         } else {
1567                 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1568                              command_name, line_number);
1569                 return false;
1570         }
1571         command->op = op;
1572
1573         /* Parse additional options and non-options as needed */
1574         num_nonoptions = 0;
1575         for (;;) {
1576                 tchar *next_string;
1577
1578                 ret = parse_string(&line, &len, &next_string);
1579                 if (ret == PARSE_STRING_NONE) /* End of line */
1580                         break;
1581                 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1582                         return false;
1583                 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1584                         /* Option */
1585                         if (!update_command_add_option(op, next_string, command))
1586                         {
1587                                 imagex_error(T("Unrecognized option \"%"TS"\" to "
1588                                                "update command \"%"TS"\" on line %zu"),
1589                                              next_string, command_name, line_number);
1590
1591                                 return false;
1592                         }
1593                 } else {
1594                         /* Nonoption */
1595                         if (num_nonoptions == update_command_num_nonoptions[op])
1596                         {
1597                                 imagex_error(T("Unexpected argument \"%"TS"\" in "
1598                                                "update command on line %zu\n"
1599                                                "       (The \"%"TS"\" command only "
1600                                                "takes %zu nonoption arguments!)\n"),
1601                                              next_string, line_number,
1602                                              command_name, num_nonoptions);
1603                                 return false;
1604                         }
1605                         update_command_add_nonoption(op, next_string,
1606                                                      command, num_nonoptions);
1607                         num_nonoptions++;
1608                 }
1609         }
1610
1611         if (num_nonoptions != update_command_num_nonoptions[op]) {
1612                 imagex_error(T("Not enough arguments to update command "
1613                                "\"%"TS"\" on line %zu"), command_name, line_number);
1614                 return false;
1615         }
1616         return true;
1617 }
1618
1619 static struct wimlib_update_command *
1620 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1621                           size_t *num_cmds_ret)
1622 {
1623         ssize_t nlines;
1624         tchar *p;
1625         struct wimlib_update_command *cmds;
1626         size_t i, j;
1627
1628         nlines = text_file_count_lines(cmd_file_contents_p,
1629                                        &cmd_file_nchars);
1630         if (nlines < 0)
1631                 return NULL;
1632
1633         /* Always allocate at least 1 slot, just in case the implementation of
1634          * calloc() returns NULL if 0 bytes are requested. */
1635         cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1636         if (!cmds) {
1637                 imagex_error(T("out of memory"));
1638                 return NULL;
1639         }
1640         p = *cmd_file_contents_p;
1641         j = 0;
1642         for (i = 0; i < nlines; i++) {
1643                 /* XXX: Could use rawmemchr() here instead, but it may not be
1644                  * available on all platforms. */
1645                 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1646                 size_t len = endp - p + 1;
1647                 *endp = T('\0');
1648                 if (!is_comment_line(p, len)) {
1649                         if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1650                                 free(cmds);
1651                                 return NULL;
1652                         }
1653                 }
1654                 p = endp + 1;
1655         }
1656         *num_cmds_ret = j;
1657         return cmds;
1658 }
1659
1660 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1661  * one image from a WIM file to an NTFS volume.  */
1662 static int
1663 imagex_apply(int argc, tchar **argv, int cmd)
1664 {
1665         int c;
1666         int open_flags = 0;
1667         int image = WIMLIB_NO_IMAGE;
1668         WIMStruct *wim;
1669         struct wimlib_wim_info info;
1670         int ret;
1671         const tchar *wimfile;
1672         const tchar *target;
1673         const tchar *image_num_or_name = NULL;
1674         int extract_flags = 0;
1675
1676         STRING_LIST(refglobs);
1677
1678         for_opt(c, apply_options) {
1679                 switch (c) {
1680                 case IMAGEX_CHECK_OPTION:
1681                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1682                         break;
1683                 case IMAGEX_VERBOSE_OPTION:
1684                         /* No longer does anything.  */
1685                         break;
1686                 case IMAGEX_REF_OPTION:
1687                         ret = string_list_append(&refglobs, optarg);
1688                         if (ret)
1689                                 goto out_free_refglobs;
1690                         break;
1691                 case IMAGEX_UNIX_DATA_OPTION:
1692                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1693                         break;
1694                 case IMAGEX_NO_ACLS_OPTION:
1695                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1696                         break;
1697                 case IMAGEX_STRICT_ACLS_OPTION:
1698                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1699                         break;
1700                 case IMAGEX_NO_ATTRIBUTES_OPTION:
1701                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1702                         break;
1703                 case IMAGEX_NORPFIX_OPTION:
1704                         extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1705                         break;
1706                 case IMAGEX_RPFIX_OPTION:
1707                         extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1708                         break;
1709                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1710                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1711                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1712                         break;
1713                 case IMAGEX_WIMBOOT_OPTION:
1714                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1715                         break;
1716                 case IMAGEX_COMPACT_OPTION:
1717                         ret = set_compact_mode(optarg, &extract_flags);
1718                         if (ret)
1719                                 goto out_free_refglobs;
1720                         break;
1721                 default:
1722                         goto out_usage;
1723                 }
1724         }
1725         argc -= optind;
1726         argv += optind;
1727         if (argc != 2 && argc != 3)
1728                 goto out_usage;
1729
1730         wimfile = argv[0];
1731
1732         if (!tstrcmp(wimfile, T("-"))) {
1733                 /* Attempt to apply pipable WIM from standard input.  */
1734                 if (argc == 2) {
1735                         image_num_or_name = NULL;
1736                         target = argv[1];
1737                 } else {
1738                         image_num_or_name = argv[1];
1739                         target = argv[2];
1740                 }
1741                 wim = NULL;
1742         } else {
1743                 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1744                                                     imagex_progress_func, NULL);
1745                 if (ret)
1746                         goto out_free_refglobs;
1747
1748                 wimlib_get_wim_info(wim, &info);
1749
1750                 if (argc >= 3) {
1751                         /* Image explicitly specified.  */
1752                         image_num_or_name = argv[1];
1753                         image = wimlib_resolve_image(wim, image_num_or_name);
1754                         ret = verify_image_exists(image, image_num_or_name, wimfile);
1755                         if (ret)
1756                                 goto out_wimlib_free;
1757                         target = argv[2];
1758                 } else {
1759                         /* No image specified; default to image 1, but only if the WIM
1760                          * contains exactly one image.  */
1761
1762                         if (info.image_count != 1) {
1763                                 imagex_error(T("\"%"TS"\" contains %d images; "
1764                                                "Please select one (or all)."),
1765                                              wimfile, info.image_count);
1766                                 wimlib_free(wim);
1767                                 goto out_usage;
1768                         }
1769                         image = 1;
1770                         target = argv[1];
1771                 }
1772         }
1773
1774         if (refglobs.num_strings) {
1775                 if (wim == NULL) {
1776                         imagex_error(T("Can't specify --ref when applying from stdin!"));
1777                         ret = -1;
1778                         goto out_wimlib_free;
1779                 }
1780                 ret = wim_reference_globs(wim, &refglobs, open_flags);
1781                 if (ret)
1782                         goto out_wimlib_free;
1783         }
1784
1785 #ifndef __WIN32__
1786         {
1787                 /* Interpret a regular file or block device target as an NTFS
1788                  * volume.  */
1789                 struct stat stbuf;
1790
1791                 if (tstat(target, &stbuf)) {
1792                         if (errno != ENOENT) {
1793                                 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1794                                                         target);
1795                                 ret = -1;
1796                                 goto out_wimlib_free;
1797                         }
1798                 } else {
1799                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1800                                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1801                 }
1802         }
1803 #endif
1804
1805         if (wim) {
1806                 ret = wimlib_extract_image(wim, image, target, extract_flags);
1807         } else {
1808                 set_fd_to_binary_mode(STDIN_FILENO);
1809                 ret = wimlib_extract_image_from_pipe_with_progress(
1810                                            STDIN_FILENO,
1811                                            image_num_or_name,
1812                                            target,
1813                                            extract_flags,
1814                                            imagex_progress_func,
1815                                            NULL);
1816         }
1817         if (ret == 0) {
1818                 imagex_printf(T("Done applying WIM image.\n"));
1819         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1820                 if (wim) {
1821                         do_resource_not_found_warning(wimfile, &info, &refglobs);
1822                 } else {
1823                         imagex_error(T(        "If you are applying an image "
1824                                                "from a split pipable WIM,\n"
1825                                        "       make sure you have "
1826                                        "concatenated together all parts."));
1827                 }
1828         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1829                 do_metadata_not_found_warning(wimfile, &info);
1830         }
1831 out_wimlib_free:
1832         wimlib_free(wim);
1833 out_free_refglobs:
1834         string_list_destroy(&refglobs);
1835         return ret;
1836
1837 out_usage:
1838         usage(CMD_APPLY, stderr);
1839         ret = -1;
1840         goto out_free_refglobs;
1841 }
1842
1843 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1844  * directory trees.  'wimlib-imagex capture': create a new WIM file containing
1845  * the desired image.  'wimlib-imagex append': add a new image to an existing
1846  * WIM file. */
1847 static int
1848 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1849 {
1850         int c;
1851         int open_flags = 0;
1852         int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1853                         WIMLIB_ADD_FLAG_WINCONFIG |
1854                         WIMLIB_ADD_FLAG_VERBOSE |
1855                         WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1856         int write_flags = 0;
1857         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1858         uint32_t chunk_size = UINT32_MAX;
1859         uint32_t solid_chunk_size = UINT32_MAX;
1860         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1861         const tchar *wimfile;
1862         int wim_fd;
1863         const tchar *name;
1864         STRING_LIST(image_properties);
1865
1866         WIMStruct *wim;
1867         STRING_LIST(base_wimfiles);
1868         WIMStruct **base_wims;
1869
1870         WIMStruct *template_wim = NULL;
1871         const tchar *template_wimfile = NULL;
1872         const tchar *template_image_name_or_num = NULL;
1873         int template_image = WIMLIB_NO_IMAGE;
1874
1875         int ret;
1876         unsigned num_threads = 0;
1877
1878         tchar *source;
1879         tchar *source_copy;
1880
1881         tchar *config_file = NULL;
1882
1883         bool source_list = false;
1884         size_t source_list_nchars = 0;
1885         tchar *source_list_contents;
1886         bool capture_sources_malloced;
1887         struct wimlib_capture_source *capture_sources;
1888         size_t num_sources;
1889         bool name_defaulted;
1890
1891         for_opt(c, capture_or_append_options) {
1892                 switch (c) {
1893                 case IMAGEX_BOOT_OPTION:
1894                         add_flags |= WIMLIB_ADD_FLAG_BOOT;
1895                         break;
1896                 case IMAGEX_CHECK_OPTION:
1897                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1898                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1899                         break;
1900                 case IMAGEX_NOCHECK_OPTION:
1901                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1902                         break;
1903                 case IMAGEX_CONFIG_OPTION:
1904                         config_file = optarg;
1905                         add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1906                         break;
1907                 case IMAGEX_COMPRESS_OPTION:
1908                         compression_type = get_compression_type(optarg, false);
1909                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1910                                 goto out_err;
1911                         break;
1912                 case IMAGEX_CHUNK_SIZE_OPTION:
1913                         chunk_size = parse_chunk_size(optarg);
1914                         if (chunk_size == UINT32_MAX)
1915                                 goto out_err;
1916                         break;
1917                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1918                         solid_chunk_size = parse_chunk_size(optarg);
1919                         if (solid_chunk_size == UINT32_MAX)
1920                                 goto out_err;
1921                         break;
1922                 case IMAGEX_SOLID_COMPRESS_OPTION:
1923                         solid_ctype = get_compression_type(optarg, true);
1924                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1925                                 goto out_err;
1926                         break;
1927                 case IMAGEX_SOLID_OPTION:
1928                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1929                         break;
1930                 case IMAGEX_NO_SOLID_SORT_OPTION:
1931                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1932                         break;
1933                 case IMAGEX_FLAGS_OPTION: {
1934                         tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1935                         tsprintf(p, T("FLAGS=%"TS), optarg);
1936                         ret = string_list_append(&image_properties, p);
1937                         if (ret)
1938                                 goto out;
1939                         break;
1940                 }
1941                 case IMAGEX_IMAGE_PROPERTY_OPTION:
1942                         ret = append_image_property_argument(&image_properties);
1943                         if (ret)
1944                                 goto out;
1945                         break;
1946                 case IMAGEX_DEREFERENCE_OPTION:
1947                         add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1948                         break;
1949                 case IMAGEX_VERBOSE_OPTION:
1950                         /* No longer does anything.  */
1951                         break;
1952                 case IMAGEX_THREADS_OPTION:
1953                         num_threads = parse_num_threads(optarg);
1954                         if (num_threads == UINT_MAX)
1955                                 goto out_err;
1956                         break;
1957                 case IMAGEX_REBUILD_OPTION:
1958                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1959                         break;
1960                 case IMAGEX_UNIX_DATA_OPTION:
1961                         add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1962                         break;
1963                 case IMAGEX_SOURCE_LIST_OPTION:
1964                         source_list = true;
1965                         break;
1966                 case IMAGEX_NO_ACLS_OPTION:
1967                         add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1968                         break;
1969                 case IMAGEX_STRICT_ACLS_OPTION:
1970                         add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1971                         break;
1972                 case IMAGEX_RPFIX_OPTION:
1973                         add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1974                         break;
1975                 case IMAGEX_NORPFIX_OPTION:
1976                         add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1977                         break;
1978                 case IMAGEX_PIPABLE_OPTION:
1979                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1980                         break;
1981                 case IMAGEX_NOT_PIPABLE_OPTION:
1982                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1983                         break;
1984                 case IMAGEX_UPDATE_OF_OPTION:
1985                         if (template_image_name_or_num) {
1986                                 imagex_error(T("'--update-of' can only be "
1987                                                "specified one time!"));
1988                                 goto out_err;
1989                         } else {
1990                                 tchar *colon;
1991                                 colon = tstrrchr(optarg, T(':'));
1992
1993                                 if (colon) {
1994                                         template_wimfile = optarg;
1995                                         *colon = T('\0');
1996                                         template_image_name_or_num = colon + 1;
1997                                 } else {
1998                                         template_wimfile = NULL;
1999                                         template_image_name_or_num = optarg;
2000                                 }
2001                         }
2002                         break;
2003                 case IMAGEX_DELTA_FROM_OPTION:
2004                         ret = string_list_append(&base_wimfiles, optarg);
2005                         if (ret)
2006                                 goto out;
2007                         write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2008                         break;
2009                 case IMAGEX_WIMBOOT_OPTION:
2010                         add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2011                         break;
2012                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2013                         if (cmd != CMD_APPEND) {
2014                                 imagex_error(T("'--unsafe-compact' is only "
2015                                                "valid for append!"));
2016                                 goto out_err;
2017                         }
2018                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2019                         break;
2020                 case IMAGEX_SNAPSHOT_OPTION:
2021                         add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2022                         break;
2023                 default:
2024                         goto out_usage;
2025                 }
2026         }
2027         argc -= optind;
2028         argv += optind;
2029
2030         if (argc < 2 || argc > 4)
2031                 goto out_usage;
2032
2033         source = argv[0];
2034         wimfile = argv[1];
2035
2036         /* Set default compression type and parameters.  */
2037
2038
2039         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2040                 /* No compression type specified.  Use the default.  */
2041
2042                 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2043                         /* With --wimboot, default to XPRESS compression.  */
2044                         compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2045                 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2046                         /* With --solid, default to LZMS compression.  (However,
2047                          * this will not affect solid resources!)  */
2048                         compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2049                 } else {
2050                         /* Otherwise, default to LZX compression.  */
2051                         compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2052                 }
2053         }
2054
2055         if (!tstrcmp(wimfile, T("-"))) {
2056                 /* Writing captured WIM to standard output.  */
2057         #if 0
2058                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2059                         imagex_error("Can't write a non-pipable WIM to "
2060                                      "standard output!  Specify --pipable\n"
2061                                      "       if you want to create a pipable WIM "
2062                                      "(but read the docs first).");
2063                         goto out_err;
2064                 }
2065         #else
2066                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2067         #endif
2068                 if (cmd == CMD_APPEND) {
2069                         imagex_error(T("Using standard output for append does "
2070                                        "not make sense."));
2071                         goto out_err;
2072                 }
2073                 wim_fd = STDOUT_FILENO;
2074                 wimfile = NULL;
2075                 imagex_output_to_stderr();
2076                 set_fd_to_binary_mode(wim_fd);
2077         }
2078
2079         /* If template image was specified using --update-of=IMAGE rather
2080          * than --update-of=WIMFILE:IMAGE, set the default WIMFILE.  */
2081         if (template_image_name_or_num && !template_wimfile) {
2082                 if (base_wimfiles.num_strings == 1) {
2083                         /* Capturing delta WIM based on single WIM:  default to
2084                          * base WIM.  */
2085                         template_wimfile = base_wimfiles.strings[0];
2086                 } else if (cmd == CMD_APPEND) {
2087                         /* Appending to WIM:  default to WIM being appended to.
2088                          */
2089                         template_wimfile = wimfile;
2090                 } else {
2091                         /* Capturing a normal (non-delta) WIM, so the WIM file
2092                          * *must* be explicitly specified.  */
2093                         if (base_wimfiles.num_strings > 1) {
2094                                 imagex_error(T("For capture of delta WIM "
2095                                                "based on multiple existing "
2096                                                "WIMs,\n"
2097                                                "      '--update-of' must "
2098                                                "specify WIMFILE:IMAGE!"));
2099                         } else {
2100                                 imagex_error(T("For capture of non-delta WIM, "
2101                                                "'--update-of' must specify "
2102                                                "WIMFILE:IMAGE!"));
2103                         }
2104                         goto out_usage;
2105                 }
2106         }
2107
2108         if (argc >= 3) {
2109                 name = argv[2];
2110                 name_defaulted = false;
2111         } else {
2112                 /* Set default name to SOURCE argument, omitting any directory
2113                  * prefixes and trailing slashes.  This requires making a copy
2114                  * of @source.  Leave some free characters at the end in case we
2115                  * append a number to keep the name unique. */
2116                 size_t source_name_len;
2117
2118                 source_name_len = tstrlen(source);
2119                 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2120                 name = tbasename(tstrcpy(source_copy, source));
2121                 name_defaulted = true;
2122         }
2123
2124         /* Image description (if given). */
2125         if (argc >= 4) {
2126                 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2127                 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2128                 ret = string_list_append(&image_properties, p);
2129                 if (ret)
2130                         goto out;
2131         }
2132
2133         if (source_list) {
2134                 /* Set up capture sources in source list mode */
2135                 if (source[0] == T('-') && source[1] == T('\0')) {
2136                         source_list_contents = stdin_get_text_contents(&source_list_nchars);
2137                 } else {
2138                         source_list_contents = file_get_text_contents(source,
2139                                                                       &source_list_nchars);
2140                 }
2141                 if (!source_list_contents)
2142                         goto out_err;
2143
2144                 capture_sources = parse_source_list(&source_list_contents,
2145                                                     source_list_nchars,
2146                                                     &num_sources);
2147                 if (!capture_sources) {
2148                         ret = -1;
2149                         goto out_free_source_list_contents;
2150                 }
2151                 capture_sources_malloced = true;
2152         } else {
2153                 /* Set up capture source in non-source-list mode.  */
2154                 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2155                 capture_sources[0].fs_source_path = source;
2156                 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2157                 capture_sources[0].reserved = 0;
2158                 num_sources = 1;
2159                 capture_sources_malloced = false;
2160                 source_list_contents = NULL;
2161         }
2162
2163         /* Open the existing WIM, or create a new one.  */
2164         if (cmd == CMD_APPEND) {
2165                 ret = wimlib_open_wim_with_progress(wimfile,
2166                                                     open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2167                                                     &wim,
2168                                                     imagex_progress_func,
2169                                                     NULL);
2170                 if (ret)
2171                         goto out_free_capture_sources;
2172         } else {
2173                 ret = wimlib_create_new_wim(compression_type, &wim);
2174                 if (ret)
2175                         goto out_free_capture_sources;
2176                 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2177         }
2178
2179         /* Set chunk size if non-default.  */
2180         if (chunk_size != UINT32_MAX) {
2181                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2182                 if (ret)
2183                         goto out_free_wim;
2184         } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2185
2186                 int ctype = compression_type;
2187
2188                 if (cmd == CMD_APPEND) {
2189                         struct wimlib_wim_info info;
2190                         wimlib_get_wim_info(wim, &info);
2191                         ctype = info.compression_type;
2192                 }
2193
2194                 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2195                         ret = wimlib_set_output_chunk_size(wim, 4096);
2196                         if (ret)
2197                                 goto out_free_wim;
2198                 }
2199         }
2200         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2201                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2202                 if (ret)
2203                         goto out_free_wim;
2204         }
2205         if (solid_chunk_size != UINT32_MAX) {
2206                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2207                 if (ret)
2208                         goto out_free_wim;
2209         }
2210
2211 #ifndef __WIN32__
2212         /* Detect if source is regular file or block device and set NTFS volume
2213          * capture mode.  */
2214         if (!source_list) {
2215                 struct stat stbuf;
2216
2217                 if (tstat(source, &stbuf) == 0) {
2218                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2219                                 imagex_printf(T("Capturing WIM image from NTFS "
2220                                           "filesystem on \"%"TS"\"\n"), source);
2221                                 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2222                         }
2223                 } else {
2224                         if (errno != ENOENT) {
2225                                 imagex_error_with_errno(T("Failed to stat "
2226                                                           "\"%"TS"\""), source);
2227                                 ret = -1;
2228                                 goto out_free_wim;
2229                         }
2230                 }
2231         }
2232 #endif
2233
2234         /* If the user did not specify an image name, and the basename of the
2235          * source already exists as an image name in the WIM file, append a
2236          * suffix to make it unique. */
2237         if (cmd == CMD_APPEND && name_defaulted) {
2238                 unsigned long conflict_idx;
2239                 tchar *name_end = tstrchr(name, T('\0'));
2240                 for (conflict_idx = 1;
2241                      wimlib_image_name_in_use(wim, name);
2242                      conflict_idx++)
2243                 {
2244                         tsprintf(name_end, T(" (%lu)"), conflict_idx);
2245                 }
2246         }
2247
2248         /* If capturing a delta WIM, reference resources from the base WIMs
2249          * before adding the new image.  */
2250         if (base_wimfiles.num_strings) {
2251                 base_wims = calloc(base_wimfiles.num_strings,
2252                                    sizeof(base_wims[0]));
2253                 if (base_wims == NULL) {
2254                         imagex_error(T("Out of memory!"));
2255                         ret = -1;
2256                         goto out_free_wim;
2257                 }
2258
2259                 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2260                         ret = wimlib_open_wim_with_progress(
2261                                     base_wimfiles.strings[i], open_flags,
2262                                     &base_wims[i], imagex_progress_func, NULL);
2263                         if (ret)
2264                                 goto out_free_base_wims;
2265
2266                 }
2267
2268                 ret = wimlib_reference_resources(wim, base_wims,
2269                                                  base_wimfiles.num_strings, 0);
2270                 if (ret)
2271                         goto out_free_base_wims;
2272
2273                 if (base_wimfiles.num_strings == 1) {
2274                         imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2275                                       base_wimfiles.strings[0]);
2276                 } else {
2277                         imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2278                                       base_wimfiles.num_strings);
2279                 }
2280
2281         } else {
2282                 base_wims = NULL;
2283         }
2284
2285         /* If capturing or appending as an update of an existing (template) image,
2286          * open the WIM if needed and parse the image index.  */
2287         if (template_image_name_or_num) {
2288
2289                 if (cmd == CMD_APPEND && !tstrcmp(template_wimfile, wimfile)) {
2290                         template_wim = wim;
2291                 } else {
2292                         for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2293                                 if (!tstrcmp(template_wimfile,
2294                                              base_wimfiles.strings[i])) {
2295                                         template_wim = base_wims[i];
2296                                         break;
2297                                 }
2298                         }
2299                 }
2300
2301                 if (!template_wim) {
2302                         ret = wimlib_open_wim_with_progress(template_wimfile,
2303                                                             open_flags,
2304                                                             &template_wim,
2305                                                             imagex_progress_func,
2306                                                             NULL);
2307                         if (ret)
2308                                 goto out_free_base_wims;
2309                 }
2310
2311                 template_image = wimlib_resolve_image(template_wim,
2312                                                       template_image_name_or_num);
2313
2314                 if (template_image_name_or_num[0] == T('-')) {
2315                         tchar *tmp;
2316                         unsigned long n;
2317                         struct wimlib_wim_info info;
2318
2319                         wimlib_get_wim_info(template_wim, &info);
2320                         n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2321                         if (n >= 1 && n <= info.image_count &&
2322                             *tmp == T('\0') &&
2323                             tmp != template_image_name_or_num + 1)
2324                         {
2325                                 template_image = info.image_count - (n - 1);
2326                         }
2327                 }
2328                 ret = verify_image_exists_and_is_single(template_image,
2329                                                         template_image_name_or_num,
2330                                                         template_wimfile);
2331                 if (ret)
2332                         goto out_free_template_wim;
2333         }
2334
2335         ret = wimlib_add_image_multisource(wim,
2336                                            capture_sources,
2337                                            num_sources,
2338                                            name,
2339                                            config_file,
2340                                            add_flags);
2341         if (ret)
2342                 goto out_free_template_wim;
2343
2344         if (image_properties.num_strings || template_image_name_or_num) {
2345                 /* User asked to set additional image properties, or an image on
2346                  * which the added one is to be based has been specified with
2347                  * --update-of.  */
2348                 struct wimlib_wim_info info;
2349
2350                 wimlib_get_wim_info(wim, &info);
2351
2352                 ret = apply_image_properties(&image_properties, wim,
2353                                              info.image_count, NULL);
2354                 if (ret)
2355                         goto out_free_template_wim;
2356
2357                 /* Reference template image if the user provided one.  */
2358                 if (template_image_name_or_num) {
2359                         imagex_printf(T("Using image %d "
2360                                         "from \"%"TS"\" as template\n"),
2361                                         template_image, template_wimfile);
2362                         ret = wimlib_reference_template_image(wim,
2363                                                               info.image_count,
2364                                                               template_wim,
2365                                                               template_image,
2366                                                               0);
2367                         if (ret)
2368                                 goto out_free_template_wim;
2369                 }
2370         }
2371
2372         /* Write the new WIM or overwrite the existing WIM with the new image
2373          * appended.  */
2374         if (cmd == CMD_APPEND) {
2375                 ret = wimlib_overwrite(wim, write_flags, num_threads);
2376         } else if (wimfile) {
2377                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2378                                    write_flags, num_threads);
2379         } else {
2380                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2381                                          write_flags, num_threads);
2382         }
2383 out_free_template_wim:
2384         /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
2385         if (template_wim == wim)
2386                 goto out_free_base_wims;
2387         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2388                 if (template_wim == base_wims[i])
2389                         goto out_free_base_wims;
2390         wimlib_free(template_wim);
2391 out_free_base_wims:
2392         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2393                 wimlib_free(base_wims[i]);
2394         free(base_wims);
2395 out_free_wim:
2396         wimlib_free(wim);
2397 out_free_capture_sources:
2398         if (capture_sources_malloced)
2399                 free(capture_sources);
2400 out_free_source_list_contents:
2401         free(source_list_contents);
2402 out:
2403         string_list_destroy(&image_properties);
2404         string_list_destroy(&base_wimfiles);
2405         return ret;
2406
2407 out_usage:
2408         usage(cmd, stderr);
2409 out_err:
2410         ret = -1;
2411         goto out;
2412 }
2413
2414 /* Remove image(s) from a WIM. */
2415 static int
2416 imagex_delete(int argc, tchar **argv, int cmd)
2417 {
2418         int c;
2419         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2420         int write_flags = 0;
2421         const tchar *wimfile;
2422         const tchar *image_num_or_name;
2423         WIMStruct *wim;
2424         int image;
2425         int ret;
2426
2427         for_opt(c, delete_options) {
2428                 switch (c) {
2429                 case IMAGEX_CHECK_OPTION:
2430                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2431                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2432                         break;
2433                 case IMAGEX_SOFT_OPTION:
2434                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2435                         break;
2436                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2437                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2438                         break;
2439                 default:
2440                         goto out_usage;
2441                 }
2442         }
2443         argc -= optind;
2444         argv += optind;
2445
2446         if (argc != 2) {
2447                 if (argc < 1)
2448                         imagex_error(T("Must specify a WIM file"));
2449                 if (argc < 2)
2450                         imagex_error(T("Must specify an image"));
2451                 goto out_usage;
2452         }
2453         wimfile = argv[0];
2454         image_num_or_name = argv[1];
2455
2456         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2457                                             imagex_progress_func, NULL);
2458         if (ret)
2459                 goto out;
2460
2461         image = wimlib_resolve_image(wim, image_num_or_name);
2462
2463         ret = verify_image_exists(image, image_num_or_name, wimfile);
2464         if (ret)
2465                 goto out_wimlib_free;
2466
2467         ret = wimlib_delete_image(wim, image);
2468         if (ret) {
2469                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2470                              wimfile);
2471                 goto out_wimlib_free;
2472         }
2473
2474         ret = wimlib_overwrite(wim, write_flags, 0);
2475         if (ret) {
2476                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2477                                "deleted"), wimfile);
2478         }
2479 out_wimlib_free:
2480         wimlib_free(wim);
2481 out:
2482         return ret;
2483
2484 out_usage:
2485         usage(CMD_DELETE, stderr);
2486         ret = -1;
2487         goto out;
2488 }
2489
2490 struct print_dentry_options {
2491         bool detailed;
2492 };
2493
2494 static void
2495 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2496 {
2497         tprintf(T("%"TS"\n"), dentry->full_path);
2498 }
2499
2500 static const struct {
2501         uint32_t flag;
2502         const tchar *name;
2503 } file_attr_flags[] = {
2504         {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
2505         {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
2506         {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
2507         {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
2508         {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
2509         {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
2510         {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
2511         {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
2512         {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
2513         {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
2514         {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
2515         {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
2516         {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2517         {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
2518         {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
2519 };
2520
2521 #define TIMESTR_MAX 100
2522
2523 static void
2524 print_time(const tchar *type, const struct wimlib_timespec *wts,
2525            int32_t high_part)
2526 {
2527         tchar timestr[TIMESTR_MAX];
2528         time_t t;
2529         struct tm tm;
2530
2531         if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2532                 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2533         else
2534                 t = wts->tv_sec;
2535
2536         gmtime_r(&t, &tm);
2537         tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2538         timestr[TIMESTR_MAX - 1] = '\0';
2539
2540         tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2541 }
2542
2543 static void print_byte_field(const uint8_t field[], size_t len)
2544 {
2545         while (len--)
2546                 tprintf(T("%02hhx"), *field++);
2547 }
2548
2549 static void
2550 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2551 {
2552         tchar attr_string[256];
2553         tchar *p;
2554
2555         tputs(T("WIM Information:"));
2556         tputs(T("----------------"));
2557         tprintf(T("Path:           %"TS"\n"), wimfile);
2558         tprintf(T("GUID:           0x"));
2559         print_byte_field(info->guid, sizeof(info->guid));
2560         tputchar(T('\n'));
2561         tprintf(T("Version:        %u\n"), info->wim_version);
2562         tprintf(T("Image Count:    %d\n"), info->image_count);
2563         tprintf(T("Compression:    %"TS"\n"),
2564                 wimlib_get_compression_type_string(info->compression_type));
2565         tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
2566                 info->chunk_size);
2567         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2568         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2569         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2570
2571         attr_string[0] = T('\0');
2572
2573         if (info->pipable)
2574                 tstrcat(attr_string, T("Pipable, "));
2575
2576         if (info->has_integrity_table)
2577                 tstrcat(attr_string, T("Integrity info, "));
2578
2579         if (info->has_rpfix)
2580                 tstrcat(attr_string, T("Relative path junction, "));
2581
2582         if (info->resource_only)
2583                 tstrcat(attr_string, T("Resource only, "));
2584
2585         if (info->metadata_only)
2586                 tstrcat(attr_string, T("Metadata only, "));
2587
2588         if (info->is_marked_readonly)
2589                 tstrcat(attr_string, T("Readonly, "));
2590
2591         p = tstrchr(attr_string, T('\0'));
2592         if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2593                 p[-2] = T('\0');
2594
2595         tprintf(T("Attributes:     %"TS"\n\n"), attr_string);
2596 }
2597
2598 static int
2599 print_resource(const struct wimlib_resource_entry *resource,
2600                void *_ignore)
2601 {
2602         tprintf(T("Hash              = 0x"));
2603         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2604         tputchar(T('\n'));
2605
2606         if (!resource->is_missing) {
2607                 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2608                         resource->uncompressed_size);
2609                 if (resource->packed) {
2610                         tprintf(T("Solid resource    = %"PRIu64" => %"PRIu64" "
2611                                   "bytes @ offset %"PRIu64"\n"),
2612                                 resource->raw_resource_uncompressed_size,
2613                                 resource->raw_resource_compressed_size,
2614                                 resource->raw_resource_offset_in_wim);
2615
2616                         tprintf(T("Solid offset      = %"PRIu64" bytes\n"),
2617                                 resource->offset);
2618                 } else {
2619                         tprintf(T("Compressed size   = %"PRIu64" bytes\n"),
2620                                 resource->compressed_size);
2621
2622                         tprintf(T("Offset in WIM     = %"PRIu64" bytes\n"),
2623                                 resource->offset);
2624                 }
2625
2626                 tprintf(T("Part Number       = %u\n"), resource->part_number);
2627                 tprintf(T("Reference Count   = %u\n"), resource->reference_count);
2628
2629                 tprintf(T("Flags             = "));
2630                 if (resource->is_compressed)
2631                         tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2632                 if (resource->is_metadata)
2633                         tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2634                 if (resource->is_free)
2635                         tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2636                 if (resource->is_spanned)
2637                         tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2638                 if (resource->packed)
2639                         tprintf(T("WIM_RESHDR_FLAG_SOLID  "));
2640                 tputchar(T('\n'));
2641         }
2642         tputchar(T('\n'));
2643         return 0;
2644 }
2645
2646 static void
2647 print_blobs(WIMStruct *wim)
2648 {
2649         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2650 }
2651
2652 #ifndef __WIN32__
2653 static void
2654 default_print_security_descriptor(const uint8_t *sd, size_t size)
2655 {
2656         tprintf(T("Security Descriptor = "));
2657         print_byte_field(sd, size);
2658         tputchar(T('\n'));
2659 }
2660 #endif
2661
2662 static bool
2663 is_null_guid(const uint8_t *guid)
2664 {
2665         static const uint8_t null_guid[WIMLIB_GUID_LEN];
2666
2667         return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2668 }
2669
2670 static void
2671 print_guid(const tchar *label, const uint8_t *guid)
2672 {
2673         if (is_null_guid(guid))
2674                 return;
2675         tprintf(T("%-20"TS"= 0x"), label);
2676         print_byte_field(guid, WIMLIB_GUID_LEN);
2677         tputchar(T('\n'));
2678 }
2679
2680 static void
2681 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2682 {
2683         tprintf(T(
2684 "----------------------------------------------------------------------------\n"));
2685         tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
2686         if (dentry->dos_name)
2687                 tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
2688         tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
2689         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2690                 if (file_attr_flags[i].flag & dentry->attributes)
2691                         tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
2692                                 file_attr_flags[i].name);
2693
2694         if (dentry->security_descriptor) {
2695                 print_security_descriptor(dentry->security_descriptor,
2696                                           dentry->security_descriptor_size);
2697         }
2698
2699         print_time(T("Creation Time"),
2700                    &dentry->creation_time, dentry->creation_time_high);
2701         print_time(T("Last Write Time"),
2702                    &dentry->last_write_time, dentry->last_write_time_high);
2703         print_time(T("Last Access Time"),
2704                    &dentry->last_access_time, dentry->last_access_time_high);
2705
2706
2707         if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2708                 tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
2709
2710         tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2711         tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
2712
2713         if (dentry->unix_mode != 0) {
2714                 tprintf(T("UNIX Data           = uid:%"PRIu32" gid:%"PRIu32" "
2715                           "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2716                         dentry->unix_uid, dentry->unix_gid,
2717                         dentry->unix_mode, dentry->unix_rdev);
2718         }
2719
2720         if (!is_null_guid(dentry->object_id.object_id)) {
2721                 print_guid(T("Object ID"), dentry->object_id.object_id);
2722                 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2723                 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2724                 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2725         }
2726
2727         for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2728                 if (dentry->streams[i].stream_name) {
2729                         tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2730                                 dentry->streams[i].stream_name);
2731                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2732                         tprintf(T("\tRaw encrypted data stream:\n"));
2733                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2734                         tprintf(T("\tReparse point stream:\n"));
2735                 } else {
2736                         tprintf(T("\tUnnamed data stream:\n"));
2737                 }
2738                 print_resource(&dentry->streams[i].resource, NULL);
2739         }
2740 }
2741
2742 static int
2743 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2744 {
2745         const struct print_dentry_options *options = _options;
2746         if (!options->detailed)
2747                 print_dentry_full_path(dentry);
2748         else
2749                 print_dentry_detailed(dentry);
2750         return 0;
2751 }
2752
2753 /* Print the files contained in an image(s) in a WIM file. */
2754 static int
2755 imagex_dir(int argc, tchar **argv, int cmd)
2756 {
2757         const tchar *wimfile;
2758         WIMStruct *wim = NULL;
2759         int image;
2760         int ret;
2761         const tchar *path = WIMLIB_WIM_ROOT_PATH;
2762         int c;
2763         struct print_dentry_options options = {
2764                 .detailed = false,
2765         };
2766         int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2767
2768         STRING_LIST(refglobs);
2769
2770         for_opt(c, dir_options) {
2771                 switch (c) {
2772                 case IMAGEX_PATH_OPTION:
2773                         path = optarg;
2774                         break;
2775                 case IMAGEX_DETAILED_OPTION:
2776                         options.detailed = true;
2777                         break;
2778                 case IMAGEX_ONE_FILE_ONLY_OPTION:
2779                         iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2780                         break;
2781                 case IMAGEX_REF_OPTION:
2782                         ret = string_list_append(&refglobs, optarg);
2783                         if (ret)
2784                                 goto out_free_refglobs;
2785                         break;
2786                 default:
2787                         goto out_usage;
2788                 }
2789         }
2790         argc -= optind;
2791         argv += optind;
2792
2793         if (argc < 1) {
2794                 imagex_error(T("Must specify a WIM file"));
2795                 goto out_usage;
2796         }
2797         if (argc > 2) {
2798                 imagex_error(T("Too many arguments"));
2799                 goto out_usage;
2800         }
2801
2802         wimfile = argv[0];
2803         ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2804                                             imagex_progress_func, NULL);
2805         if (ret)
2806                 goto out_free_refglobs;
2807
2808         if (argc >= 2) {
2809                 image = wimlib_resolve_image(wim, argv[1]);
2810                 ret = verify_image_exists(image, argv[1], wimfile);
2811                 if (ret)
2812                         goto out_wimlib_free;
2813         } else {
2814                 /* No image specified; default to image 1, but only if the WIM
2815                  * contains exactly one image.  */
2816
2817                 struct wimlib_wim_info info;
2818
2819                 wimlib_get_wim_info(wim, &info);
2820                 if (info.image_count != 1) {
2821                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2822                                        "select one (or all)."),
2823                                      wimfile, info.image_count);
2824                         wimlib_free(wim);
2825                         goto out_usage;
2826                 }
2827                 image = 1;
2828         }
2829
2830         if (refglobs.num_strings) {
2831                 ret = wim_reference_globs(wim, &refglobs, 0);
2832                 if (ret)
2833                         goto out_wimlib_free;
2834         }
2835
2836         ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2837                                       print_dentry, &options);
2838         if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2839                 struct wimlib_wim_info info;
2840
2841                 wimlib_get_wim_info(wim, &info);
2842                 do_metadata_not_found_warning(wimfile, &info);
2843         }
2844 out_wimlib_free:
2845         wimlib_free(wim);
2846 out_free_refglobs:
2847         string_list_destroy(&refglobs);
2848         return ret;
2849
2850 out_usage:
2851         usage(CMD_DIR, stderr);
2852         ret = -1;
2853         goto out_free_refglobs;
2854 }
2855
2856 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2857  * WIM file. */
2858 static int
2859 imagex_export(int argc, tchar **argv, int cmd)
2860 {
2861         int c;
2862         int open_flags = 0;
2863         int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2864         int write_flags = 0;
2865         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2866         const tchar *src_wimfile;
2867         const tchar *src_image_num_or_name;
2868         const tchar *dest_wimfile;
2869         int dest_wim_fd;
2870         const tchar *dest_name;
2871         const tchar *dest_desc;
2872         WIMStruct *src_wim;
2873         struct wimlib_wim_info src_info;
2874         WIMStruct *dest_wim;
2875         int ret;
2876         int image;
2877         struct stat stbuf;
2878         bool wim_is_new;
2879         STRING_LIST(refglobs);
2880         unsigned num_threads = 0;
2881         uint32_t chunk_size = UINT32_MAX;
2882         uint32_t solid_chunk_size = UINT32_MAX;
2883         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2884
2885         for_opt(c, export_options) {
2886                 switch (c) {
2887                 case IMAGEX_BOOT_OPTION:
2888                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2889                         break;
2890                 case IMAGEX_CHECK_OPTION:
2891                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2892                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2893                         break;
2894                 case IMAGEX_NOCHECK_OPTION:
2895                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2896                         break;
2897                 case IMAGEX_COMPRESS_OPTION:
2898                         compression_type = get_compression_type(optarg, false);
2899                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2900                                 goto out_err;
2901                         break;
2902                 case IMAGEX_RECOMPRESS_OPTION:
2903                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2904                         break;
2905                 case IMAGEX_SOLID_OPTION:
2906                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2907                         break;
2908                 case IMAGEX_NO_SOLID_SORT_OPTION:
2909                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2910                         break;
2911                 case IMAGEX_CHUNK_SIZE_OPTION:
2912                         chunk_size = parse_chunk_size(optarg);
2913                         if (chunk_size == UINT32_MAX)
2914                                 goto out_err;
2915                         break;
2916                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2917                         solid_chunk_size = parse_chunk_size(optarg);
2918                         if (solid_chunk_size == UINT32_MAX)
2919                                 goto out_err;
2920                         break;
2921                 case IMAGEX_SOLID_COMPRESS_OPTION:
2922                         solid_ctype = get_compression_type(optarg, true);
2923                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2924                                 goto out_err;
2925                         break;
2926                 case IMAGEX_REF_OPTION:
2927                         ret = string_list_append(&refglobs, optarg);
2928                         if (ret)
2929                                 goto out_free_refglobs;
2930                         break;
2931                 case IMAGEX_THREADS_OPTION:
2932                         num_threads = parse_num_threads(optarg);
2933                         if (num_threads == UINT_MAX)
2934                                 goto out_err;
2935                         break;
2936                 case IMAGEX_REBUILD_OPTION:
2937                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2938                         break;
2939                 case IMAGEX_PIPABLE_OPTION:
2940                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2941                         break;
2942                 case IMAGEX_NOT_PIPABLE_OPTION:
2943                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2944                         break;
2945                 case IMAGEX_WIMBOOT_OPTION:
2946                         export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2947                         break;
2948                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2949                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2950                         break;
2951                 default:
2952                         goto out_usage;
2953                 }
2954         }
2955         argc -= optind;
2956         argv += optind;
2957         if (argc < 3 || argc > 5)
2958                 goto out_usage;
2959         src_wimfile           = argv[0];
2960         src_image_num_or_name = argv[1];
2961         dest_wimfile          = argv[2];
2962         dest_name             = (argc >= 4) ? argv[3] : NULL;
2963         dest_desc             = (argc >= 5) ? argv[4] : NULL;
2964         ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2965                                             imagex_progress_func, NULL);
2966         if (ret)
2967                 goto out_free_refglobs;
2968
2969         wimlib_get_wim_info(src_wim, &src_info);
2970
2971         /* Determine if the destination is an existing file or not.  If so, we
2972          * try to append the exported image(s) to it; otherwise, we create a new
2973          * WIM containing the exported image(s).  Furthermore, determine if we
2974          * need to write a pipable WIM directly to standard output.  */
2975
2976         if (tstrcmp(dest_wimfile, T("-")) == 0) {
2977         #if 0
2978                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2979                         imagex_error("Can't write a non-pipable WIM to "
2980                                      "standard output!  Specify --pipable\n"
2981                                      "       if you want to create a pipable WIM "
2982                                      "(but read the docs first).");
2983                         ret = -1;
2984                         goto out_free_src_wim;
2985                 }
2986         #else
2987                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2988         #endif
2989                 dest_wimfile = NULL;
2990                 dest_wim_fd = STDOUT_FILENO;
2991                 imagex_output_to_stderr();
2992                 set_fd_to_binary_mode(dest_wim_fd);
2993         }
2994         errno = ENOENT;
2995         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2996                 wim_is_new = false;
2997                 /* Destination file exists. */
2998
2999                 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
3000                         imagex_error(T("\"%"TS"\" is not a regular file "
3001                                        "or block device"), dest_wimfile);
3002                         ret = -1;
3003                         goto out_free_src_wim;
3004                 }
3005                 ret = wimlib_open_wim_with_progress(dest_wimfile,
3006                                                     open_flags |
3007                                                         WIMLIB_OPEN_FLAG_WRITE_ACCESS,
3008                                                     &dest_wim,
3009                                                     imagex_progress_func,
3010                                                     NULL);
3011                 if (ret)
3012                         goto out_free_src_wim;
3013
3014                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3015                         /* The user specified a compression type, but we're
3016                          * exporting to an existing WIM.  Make sure the
3017                          * specified compression type is the same as the
3018                          * compression type of the existing destination WIM. */
3019                         struct wimlib_wim_info dest_info;
3020
3021                         wimlib_get_wim_info(dest_wim, &dest_info);
3022                         if (compression_type != dest_info.compression_type) {
3023                                 imagex_error(T("Cannot specify a compression type that is "
3024                                                "not the same as that used in the "
3025                                                "destination WIM"));
3026                                 ret = -1;
3027                                 goto out_free_dest_wim;
3028                         }
3029                 }
3030         } else {
3031                 wim_is_new = true;
3032
3033                 if (errno != ENOENT) {
3034                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3035                                                 dest_wimfile);
3036                         ret = -1;
3037                         goto out_free_src_wim;
3038                 }
3039
3040                 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3041                         imagex_error(T("'--unsafe-compact' is only valid when "
3042                                        "exporting to an existing WIM file!"));
3043                         ret = -1;
3044                         goto out_free_src_wim;
3045                 }
3046
3047                 /* dest_wimfile is not an existing file, so create a new WIM. */
3048
3049                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3050                         /* The user did not specify a compression type; default
3051                          * to that of the source WIM, unless --solid or
3052                          * --wimboot was specified.   */
3053
3054                         if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3055                                 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3056                         else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3057                                 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3058                         else
3059                                 compression_type = src_info.compression_type;
3060                 }
3061                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3062                 if (ret)
3063                         goto out_free_src_wim;
3064
3065                 wimlib_register_progress_function(dest_wim,
3066                                                   imagex_progress_func, NULL);
3067
3068                 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3069                     && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3070                 {
3071                         /* For --wimboot export, use small XPRESS chunks.  */
3072                         wimlib_set_output_chunk_size(dest_wim, 4096);
3073                 } else if (compression_type == src_info.compression_type &&
3074                            chunk_size == UINT32_MAX)
3075                 {
3076                         /* Use same chunk size if compression type is the same.  */
3077                         wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3078                 }
3079         }
3080
3081         if (chunk_size != UINT32_MAX) {
3082                 /* Set destination chunk size.  */
3083                 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3084                 if (ret)
3085                         goto out_free_dest_wim;
3086         }
3087         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3088                 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3089                 if (ret)
3090                         goto out_free_dest_wim;
3091         }
3092         if (solid_chunk_size != UINT32_MAX) {
3093                 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3094                 if (ret)
3095                         goto out_free_dest_wim;
3096         }
3097
3098         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3099         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3100         if (ret)
3101                 goto out_free_dest_wim;
3102
3103         if (refglobs.num_strings) {
3104                 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3105                 if (ret)
3106                         goto out_free_dest_wim;
3107         }
3108
3109         if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3110             image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3111         {
3112                 imagex_error(T("--boot specified for all-images export, but source WIM "
3113                                "has no bootable image."));
3114                 ret = -1;
3115                 goto out_free_dest_wim;
3116         }
3117
3118         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3119                                   dest_desc, export_flags);
3120         if (ret) {
3121                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3122                         do_resource_not_found_warning(src_wimfile,
3123                                                       &src_info, &refglobs);
3124                 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3125                         do_metadata_not_found_warning(src_wimfile, &src_info);
3126                 }
3127                 goto out_free_dest_wim;
3128         }
3129
3130         if (!wim_is_new)
3131                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3132         else if (dest_wimfile)
3133                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3134                                    write_flags, num_threads);
3135         else
3136                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3137                                          WIMLIB_ALL_IMAGES, write_flags,
3138                                          num_threads);
3139 out_free_dest_wim:
3140         wimlib_free(dest_wim);
3141 out_free_src_wim:
3142         wimlib_free(src_wim);
3143 out_free_refglobs:
3144         string_list_destroy(&refglobs);
3145         return ret;
3146
3147 out_usage:
3148         usage(CMD_EXPORT, stderr);
3149 out_err:
3150         ret = -1;
3151         goto out_free_refglobs;
3152 }
3153
3154 /* Extract files or directories from a WIM image */
3155 static int
3156 imagex_extract(int argc, tchar **argv, int cmd)
3157 {
3158         int c;
3159         int open_flags = 0;
3160         int image;
3161         WIMStruct *wim;
3162         int ret;
3163         const tchar *wimfile;
3164         const tchar *image_num_or_name;
3165         tchar *dest_dir = T(".");
3166         int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3167                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3168                             WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3169         int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3170
3171         STRING_LIST(refglobs);
3172
3173         tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3174
3175         for_opt(c, extract_options) {
3176                 switch (c) {
3177                 case IMAGEX_CHECK_OPTION:
3178                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3179                         break;
3180                 case IMAGEX_VERBOSE_OPTION:
3181                         /* No longer does anything.  */
3182                         break;
3183                 case IMAGEX_REF_OPTION:
3184                         ret = string_list_append(&refglobs, optarg);
3185                         if (ret)
3186                                 goto out_free_refglobs;
3187                         break;
3188                 case IMAGEX_UNIX_DATA_OPTION:
3189                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3190                         break;
3191                 case IMAGEX_NO_ACLS_OPTION:
3192                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3193                         break;
3194                 case IMAGEX_STRICT_ACLS_OPTION:
3195                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3196                         break;
3197                 case IMAGEX_NO_ATTRIBUTES_OPTION:
3198                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3199                         break;
3200                 case IMAGEX_DEST_DIR_OPTION:
3201                         dest_dir = optarg;
3202                         break;
3203                 case IMAGEX_TO_STDOUT_OPTION:
3204                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3205                         imagex_suppress_output();
3206                         set_fd_to_binary_mode(STDOUT_FILENO);
3207                         break;
3208                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3209                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3210                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3211                         break;
3212                 case IMAGEX_NO_GLOBS_OPTION:
3213                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3214                         break;
3215                 case IMAGEX_NULLGLOB_OPTION:
3216                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3217                         break;
3218                 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3219                         notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3220                         break;
3221                 case IMAGEX_WIMBOOT_OPTION:
3222                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3223                         break;
3224                 case IMAGEX_COMPACT_OPTION:
3225                         ret = set_compact_mode(optarg, &extract_flags);
3226                         if (ret)
3227                                 goto out_free_refglobs;
3228                         break;
3229                 default:
3230                         goto out_usage;
3231                 }
3232         }
3233         argc -= optind;
3234         argv += optind;
3235
3236         if (argc < 2)
3237                 goto out_usage;
3238
3239         if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3240                                WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3241         {
3242                 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3243                 goto out_err;
3244         }
3245
3246         wimfile = argv[0];
3247         image_num_or_name = argv[1];
3248
3249         argc -= 2;
3250         argv += 2;
3251
3252         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3253                                             imagex_progress_func, NULL);
3254         if (ret)
3255                 goto out_free_refglobs;
3256
3257         image = wimlib_resolve_image(wim, image_num_or_name);
3258         ret = verify_image_exists_and_is_single(image,
3259                                                 image_num_or_name,
3260                                                 wimfile);
3261         if (ret)
3262                 goto out_wimlib_free;
3263
3264         if (refglobs.num_strings) {
3265                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3266                 if (ret)
3267                         goto out_wimlib_free;
3268         }
3269
3270         if (argc == 0) {
3271                 argv = &root_path;
3272                 argc = 1;
3273                 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3274         }
3275
3276         while (argc != 0 && ret == 0) {
3277                 int num_paths;
3278
3279                 for (num_paths = 0;
3280                      num_paths < argc && argv[num_paths][0] != T('@');
3281                      num_paths++)
3282                         ;
3283
3284                 if (num_paths) {
3285                         ret = wimlib_extract_paths(wim, image, dest_dir,
3286                                                    (const tchar **)argv,
3287                                                    num_paths,
3288                                                    extract_flags | notlist_extract_flags);
3289                         argc -= num_paths;
3290                         argv += num_paths;
3291                 } else {
3292                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3293                                                       argv[0] + 1,
3294                                                       extract_flags);
3295                         argc--;
3296                         argv++;
3297                 }
3298         }
3299
3300         if (ret == 0) {
3301                 imagex_printf(T("Done extracting files.\n"));
3302         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3303                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3304                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3305                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3306                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3307                 {
3308                         tfprintf(stderr,
3309                                  T("Note: You can use the '--nullglob' "
3310                                    "option to ignore missing files.\n"));
3311                 }
3312                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3313                                    "files and directories\n"
3314                                    "      are in the WIM image.\n"),
3315                                 get_cmd_string(CMD_DIR, false));
3316         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3317                 struct wimlib_wim_info info;
3318
3319                 wimlib_get_wim_info(wim, &info);
3320                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3321         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3322                 struct wimlib_wim_info info;
3323
3324                 wimlib_get_wim_info(wim, &info);
3325                 do_metadata_not_found_warning(wimfile, &info);
3326         }
3327 out_wimlib_free:
3328         wimlib_free(wim);
3329 out_free_refglobs:
3330         string_list_destroy(&refglobs);
3331         return ret;
3332
3333 out_usage:
3334         usage(CMD_EXTRACT, stderr);
3335 out_err:
3336         ret = -1;
3337         goto out_free_refglobs;
3338 }
3339
3340 /* Prints information about a WIM file; also can mark an image as bootable,
3341  * change the name of an image, or change the description of an image. */
3342 static int
3343 imagex_info(int argc, tchar **argv, int cmd)
3344 {
3345         int c;
3346         bool boot         = false;
3347         bool check        = false;
3348         bool nocheck      = false;
3349         bool header       = false;
3350         bool blobs        = false;
3351         bool xml          = false;
3352         bool short_header = true;
3353         const tchar *xml_out_file = NULL;
3354         const tchar *wimfile;
3355         const tchar *image_num_or_name;
3356         STRING_LIST(image_properties);
3357         WIMStruct *wim;
3358         int image;
3359         int ret;
3360         int open_flags = 0;
3361         struct wimlib_wim_info info;
3362
3363         for_opt(c, info_options) {
3364                 switch (c) {
3365                 case IMAGEX_BOOT_OPTION:
3366                         boot = true;
3367                         break;
3368                 case IMAGEX_CHECK_OPTION:
3369                         check = true;
3370                         break;
3371                 case IMAGEX_NOCHECK_OPTION:
3372                         nocheck = true;
3373                         break;
3374                 case IMAGEX_HEADER_OPTION:
3375                         header = true;
3376                         short_header = false;
3377                         break;
3378                 case IMAGEX_BLOBS_OPTION:
3379                         blobs = true;
3380                         short_header = false;
3381                         break;
3382                 case IMAGEX_XML_OPTION:
3383                         xml = true;
3384                         short_header = false;
3385                         break;
3386                 case IMAGEX_EXTRACT_XML_OPTION:
3387                         xml_out_file = optarg;
3388                         short_header = false;
3389                         break;
3390                 case IMAGEX_IMAGE_PROPERTY_OPTION:
3391                         ret = append_image_property_argument(&image_properties);
3392                         if (ret)
3393                                 goto out;
3394                         break;
3395                 default:
3396                         goto out_usage;
3397                 }
3398         }
3399
3400         argc -= optind;
3401         argv += optind;
3402         if (argc < 1 || argc > 4)
3403                 goto out_usage;
3404
3405         wimfile           = argv[0];
3406         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3407
3408         if (argc >= 3) {
3409                 /* NEW_NAME */
3410                 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3411                 tsprintf(p, T("NAME=%"TS), argv[2]);
3412                 ret = string_list_append(&image_properties, p);
3413                 if (ret)
3414                         goto out;
3415         }
3416
3417         if (argc >= 4) {
3418                 /* NEW_DESC */
3419                 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3420                 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3421                 ret = string_list_append(&image_properties, p);
3422                 if (ret)
3423                         goto out;
3424         }
3425
3426         if (check && nocheck) {
3427                 imagex_error(T("Can't specify both --check and --nocheck"));
3428                 goto out_err;
3429         }
3430
3431         if (check)
3432                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3433
3434         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3435                                             imagex_progress_func, NULL);
3436         if (ret)
3437                 goto out;
3438
3439         wimlib_get_wim_info(wim, &info);
3440
3441         image = wimlib_resolve_image(wim, image_num_or_name);
3442         ret = WIMLIB_ERR_INVALID_IMAGE;
3443         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3444                 verify_image_exists(image, image_num_or_name, wimfile);
3445                 if (boot) {
3446                         imagex_error(T("If you would like to set the boot "
3447                                        "index to 0, specify image \"0\" with "
3448                                        "the --boot flag."));
3449                 }
3450                 goto out_wimlib_free;
3451         }
3452
3453         if (boot && info.image_count == 0) {
3454                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3455                 goto out_wimlib_free;
3456         }
3457
3458         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3459                 if (boot) {
3460                         imagex_error(T("Cannot specify the --boot flag "
3461                                        "without specifying a specific "
3462                                        "image in a multi-image WIM"));
3463                         goto out_wimlib_free;
3464                 }
3465                 if (image_properties.num_strings) {
3466                         imagex_error(T("Can't change image properties without "
3467                                        "specifying a specific image in a "
3468                                        "multi-image WIM"));
3469                         goto out_wimlib_free;
3470                 }
3471         }
3472
3473         /* Operations that print information are separated from operations that
3474          * recreate the WIM file. */
3475         if (!image_properties.num_strings && !boot) {
3476
3477                 /* Read-only operations */
3478
3479                 if (image == WIMLIB_NO_IMAGE) {
3480                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3481                                      image_num_or_name, wimfile);
3482                         goto out_wimlib_free;
3483                 }
3484
3485                 if (image == WIMLIB_ALL_IMAGES && short_header)
3486                         print_wim_information(wimfile, &info);
3487
3488                 if (header)
3489                         wimlib_print_header(wim);
3490
3491                 if (blobs) {
3492                         if (info.total_parts != 1) {
3493                                 tfprintf(stderr, T("Warning: Only showing the blobs "
3494                                                    "for part %d of a %d-part WIM.\n"),
3495                                          info.part_number, info.total_parts);
3496                         }
3497                         print_blobs(wim);
3498                 }
3499
3500                 if (xml) {
3501                         ret = wimlib_extract_xml_data(wim, stdout);
3502                         if (ret)
3503                                 goto out_wimlib_free;
3504                 }
3505
3506                 if (xml_out_file) {
3507                         FILE *fp;
3508
3509                         fp = tfopen(xml_out_file, T("wb"));
3510                         if (!fp) {
3511                                 imagex_error_with_errno(T("Failed to open the "
3512                                                           "file \"%"TS"\" for "
3513                                                           "writing"),
3514                                                         xml_out_file);
3515                                 ret = -1;
3516                                 goto out_wimlib_free;
3517                         }
3518                         ret = wimlib_extract_xml_data(wim, fp);
3519                         if (fclose(fp)) {
3520                                 imagex_error(T("Failed to close the file "
3521                                                "\"%"TS"\""),
3522                                              xml_out_file);
3523                                 ret = -1;
3524                         }
3525                         if (ret)
3526                                 goto out_wimlib_free;
3527                 }
3528
3529                 if (short_header)
3530                         wimlib_print_available_images(wim, image);
3531
3532                 ret = 0;
3533         } else {
3534                 /* Modification operations */
3535                 bool any_property_changes;
3536
3537                 if (image == WIMLIB_ALL_IMAGES)
3538                         image = 1;
3539
3540                 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3541                         imagex_error(T("Cannot change image properties "
3542                                        "when using image 0"));
3543                         ret = -1;
3544                         goto out_wimlib_free;
3545                 }
3546
3547                 if (boot) {
3548                         if (image == info.boot_index) {
3549                                 imagex_printf(T("Image %d is already marked as "
3550                                           "bootable.\n"), image);
3551                                 boot = false;
3552                         } else {
3553                                 imagex_printf(T("Marking image %d as bootable.\n"),
3554                                         image);
3555                                 info.boot_index = image;
3556                                 ret = wimlib_set_wim_info(wim, &info,
3557                                                           WIMLIB_CHANGE_BOOT_INDEX);
3558                                 if (ret)
3559                                         goto out_wimlib_free;
3560                         }
3561                 }
3562
3563                 ret = apply_image_properties(&image_properties, wim, image,
3564                                              &any_property_changes);
3565                 if (ret)
3566                         goto out_wimlib_free;
3567
3568                 /* Only call wimlib_overwrite() if something actually needs to
3569                  * be changed.  */
3570                 if (boot || any_property_changes ||
3571                     (check && !info.has_integrity_table) ||
3572                     (nocheck && info.has_integrity_table))
3573                 {
3574                         int write_flags = 0;
3575
3576                         if (check)
3577                                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3578                         if (nocheck)
3579                                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3580                         ret = wimlib_overwrite(wim, write_flags, 1);
3581                 } else {
3582                         imagex_printf(T("The file \"%"TS"\" was not modified "
3583                                         "because nothing needed to be done.\n"),
3584                                       wimfile);
3585                         ret = 0;
3586                 }
3587         }
3588 out_wimlib_free:
3589         wimlib_free(wim);
3590 out:
3591         string_list_destroy(&image_properties);
3592         return ret;
3593
3594 out_usage:
3595         usage(CMD_INFO, stderr);
3596 out_err:
3597         ret = -1;
3598         goto out;
3599 }
3600
3601 /* Join split WIMs into one part WIM */
3602 static int
3603 imagex_join(int argc, tchar **argv, int cmd)
3604 {
3605         int c;
3606         int swm_open_flags = 0;
3607         int wim_write_flags = 0;
3608         const tchar *output_path;
3609         int ret;
3610
3611         for_opt(c, join_options) {
3612                 switch (c) {
3613                 case IMAGEX_CHECK_OPTION:
3614                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3615                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3616                         break;
3617                 default:
3618                         goto out_usage;
3619                 }
3620         }
3621         argc -= optind;
3622         argv += optind;
3623
3624         if (argc < 2) {
3625                 imagex_error(T("Must specify one or more split WIM (.swm) "
3626                                "parts to join"));
3627                 goto out_usage;
3628         }
3629         output_path = argv[0];
3630         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3631                                         --argc,
3632                                         output_path,
3633                                         swm_open_flags,
3634                                         wim_write_flags,
3635                                         imagex_progress_func,
3636                                         NULL);
3637 out:
3638         return ret;
3639
3640 out_usage:
3641         usage(CMD_JOIN, stderr);
3642         ret = -1;
3643         goto out;
3644 }
3645
3646 #if WIM_MOUNTING_SUPPORTED
3647
3648 /* Mounts a WIM image.  */
3649 static int
3650 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3651 {
3652         int c;
3653         int mount_flags = 0;
3654         int open_flags = 0;
3655         const tchar *staging_dir = NULL;
3656         const tchar *wimfile;
3657         const tchar *dir;
3658         WIMStruct *wim;
3659         struct wimlib_wim_info info;
3660         int image;
3661         int ret;
3662
3663         STRING_LIST(refglobs);
3664
3665         if (cmd == CMD_MOUNTRW) {
3666                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3667                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3668         }
3669
3670         for_opt(c, mount_options) {
3671                 switch (c) {
3672                 case IMAGEX_ALLOW_OTHER_OPTION:
3673                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3674                         break;
3675                 case IMAGEX_CHECK_OPTION:
3676                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3677                         break;
3678                 case IMAGEX_DEBUG_OPTION:
3679                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3680                         break;
3681                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3682                         if (!tstrcasecmp(optarg, T("none")))
3683                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3684                         else if (!tstrcasecmp(optarg, T("xattr")))
3685                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3686                         else if (!tstrcasecmp(optarg, T("windows")))
3687                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3688                         else {
3689                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3690                                              optarg);
3691                                 goto out_usage;
3692                         }
3693                         break;
3694                 case IMAGEX_REF_OPTION:
3695                         ret = string_list_append(&refglobs, optarg);
3696                         if (ret)
3697                                 goto out_free_refglobs;
3698                         break;
3699                 case IMAGEX_STAGING_DIR_OPTION:
3700                         staging_dir = optarg;
3701                         break;
3702                 case IMAGEX_UNIX_DATA_OPTION:
3703                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3704                         break;
3705                 default:
3706                         goto out_usage;
3707                 }
3708         }
3709         argc -= optind;
3710         argv += optind;
3711         if (argc != 2 && argc != 3)
3712                 goto out_usage;
3713
3714         wimfile = argv[0];
3715
3716         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3717                                             imagex_progress_func, NULL);
3718         if (ret)
3719                 goto out_free_refglobs;
3720
3721         wimlib_get_wim_info(wim, &info);
3722
3723         if (argc >= 3) {
3724                 /* Image explicitly specified.  */
3725                 image = wimlib_resolve_image(wim, argv[1]);
3726                 dir = argv[2];
3727                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3728                 if (ret)
3729                         goto out_free_wim;
3730         } else {
3731                 /* No image specified; default to image 1, but only if the WIM
3732                  * contains exactly one image.  */
3733
3734                 if (info.image_count != 1) {
3735                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3736                                        "select one."), wimfile, info.image_count);
3737                         wimlib_free(wim);
3738                         goto out_usage;
3739                 }
3740                 image = 1;
3741                 dir = argv[1];
3742         }
3743
3744         if (refglobs.num_strings) {
3745                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3746                 if (ret)
3747                         goto out_free_wim;
3748         }
3749
3750         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3751         if (ret) {
3752                 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3753                         do_metadata_not_found_warning(wimfile, &info);
3754                 } else {
3755                         imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3756                                        "on \"%"TS"\""),
3757                                      image, wimfile, dir);
3758                 }
3759         }
3760 out_free_wim:
3761         wimlib_free(wim);
3762 out_free_refglobs:
3763         string_list_destroy(&refglobs);
3764         return ret;
3765
3766 out_usage:
3767         usage(cmd, stderr);
3768         ret = -1;
3769         goto out_free_refglobs;
3770 }
3771 #endif /* WIM_MOUNTING_SUPPORTED */
3772
3773 /* Rebuild a WIM file */
3774 static int
3775 imagex_optimize(int argc, tchar **argv, int cmd)
3776 {
3777         int c;
3778         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3779         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3780         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3781         uint32_t chunk_size = UINT32_MAX;
3782         uint32_t solid_chunk_size = UINT32_MAX;
3783         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3784         int ret;
3785         WIMStruct *wim;
3786         const tchar *wimfile;
3787         off_t old_size;
3788         off_t new_size;
3789         unsigned num_threads = 0;
3790
3791         for_opt(c, optimize_options) {
3792                 switch (c) {
3793                 case IMAGEX_CHECK_OPTION:
3794                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3795                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3796                         break;
3797                 case IMAGEX_NOCHECK_OPTION:
3798                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3799                         break;
3800                 case IMAGEX_COMPRESS_OPTION:
3801                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3802                         compression_type = get_compression_type(optarg, false);
3803                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3804                                 goto out_err;
3805                         break;
3806                 case IMAGEX_RECOMPRESS_OPTION:
3807                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3808                         break;
3809                 case IMAGEX_CHUNK_SIZE_OPTION:
3810                         chunk_size = parse_chunk_size(optarg);
3811                         if (chunk_size == UINT32_MAX)
3812                                 goto out_err;
3813                         break;
3814                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3815                         solid_chunk_size = parse_chunk_size(optarg);
3816                         if (solid_chunk_size == UINT32_MAX)
3817                                 goto out_err;
3818                         break;
3819                 case IMAGEX_SOLID_COMPRESS_OPTION:
3820                         solid_ctype = get_compression_type(optarg, true);
3821                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3822                                 goto out_err;
3823                         break;
3824                 case IMAGEX_SOLID_OPTION:
3825                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3826                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3827                         break;
3828                 case IMAGEX_NO_SOLID_SORT_OPTION:
3829                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3830                         break;
3831                 case IMAGEX_THREADS_OPTION:
3832                         num_threads = parse_num_threads(optarg);
3833                         if (num_threads == UINT_MAX)
3834                                 goto out_err;
3835                         break;
3836                 case IMAGEX_PIPABLE_OPTION:
3837                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3838                         break;
3839                 case IMAGEX_NOT_PIPABLE_OPTION:
3840                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3841                         break;
3842                 case IMAGEX_UNSAFE_COMPACT_OPTION:
3843                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3844                         break;
3845                 default:
3846                         goto out_usage;
3847                 }
3848         }
3849         argc -= optind;
3850         argv += optind;
3851
3852         if (argc != 1)
3853                 goto out_usage;
3854
3855         wimfile = argv[0];
3856
3857         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3858                                             imagex_progress_func, NULL);
3859         if (ret)
3860                 goto out;
3861
3862         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3863                 /* Change compression type.  */
3864                 ret = wimlib_set_output_compression_type(wim, compression_type);
3865                 if (ret)
3866                         goto out_wimlib_free;
3867         }
3868
3869         if (chunk_size != UINT32_MAX) {
3870                 /* Change chunk size.  */
3871                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3872                 if (ret)
3873                         goto out_wimlib_free;
3874         }
3875         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3876                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3877                 if (ret)
3878                         goto out_wimlib_free;
3879         }
3880         if (solid_chunk_size != UINT32_MAX) {
3881                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3882                 if (ret)
3883                         goto out_wimlib_free;
3884         }
3885
3886         old_size = file_get_size(wimfile);
3887         tprintf(T("\"%"TS"\" original size: "), wimfile);
3888         if (old_size == -1)
3889                 tputs(T("Unknown"));
3890         else
3891                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3892
3893         ret = wimlib_overwrite(wim, write_flags, num_threads);
3894         if (ret) {
3895                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3896                 goto out_wimlib_free;
3897         }
3898
3899         new_size = file_get_size(wimfile);
3900         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3901         if (new_size == -1)
3902                 tputs(T("Unknown"));
3903         else
3904                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3905
3906         tfputs(T("Space saved: "), stdout);
3907         if (new_size != -1 && old_size != -1) {
3908                 tprintf(T("%lld KiB\n"),
3909                        ((long long)old_size - (long long)new_size) >> 10);
3910         } else {
3911                 tputs(T("Unknown"));
3912         }
3913         ret = 0;
3914 out_wimlib_free:
3915         wimlib_free(wim);
3916 out:
3917         return ret;
3918
3919 out_usage:
3920         usage(CMD_OPTIMIZE, stderr);
3921 out_err:
3922         ret = -1;
3923         goto out;
3924 }
3925
3926 /* Split a WIM into a spanned set */
3927 static int
3928 imagex_split(int argc, tchar **argv, int cmd)
3929 {
3930         int c;
3931         int open_flags = 0;
3932         int write_flags = 0;
3933         unsigned long part_size;
3934         tchar *tmp;
3935         int ret;
3936         WIMStruct *wim;
3937
3938         for_opt(c, split_options) {
3939                 switch (c) {
3940                 case IMAGEX_CHECK_OPTION:
3941                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3942                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3943                         break;
3944                 default:
3945                         goto out_usage;
3946                 }
3947         }
3948         argc -= optind;
3949         argv += optind;
3950
3951         if (argc != 3)
3952                 goto out_usage;
3953
3954         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3955         if (tmp == argv[2] || *tmp) {
3956                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3957                 imagex_error(T("The part size must be an integer or "
3958                                "floating-point number of megabytes."));
3959                 goto out_err;
3960         }
3961         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3962                                             imagex_progress_func, NULL);
3963         if (ret)
3964                 goto out;
3965
3966         ret = wimlib_split(wim, argv[1], part_size, write_flags);
3967         wimlib_free(wim);
3968 out:
3969         return ret;
3970
3971 out_usage:
3972         usage(CMD_SPLIT, stderr);
3973 out_err:
3974         ret = -1;
3975         goto out;
3976 }
3977
3978 #if WIM_MOUNTING_SUPPORTED
3979 /* Unmounts a mounted WIM image. */
3980 static int
3981 imagex_unmount(int argc, tchar **argv, int cmd)
3982 {
3983         int c;
3984         int unmount_flags = 0;
3985         int ret;
3986
3987         for_opt(c, unmount_options) {
3988                 switch (c) {
3989                 case IMAGEX_COMMIT_OPTION:
3990                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3991                         break;
3992                 case IMAGEX_CHECK_OPTION:
3993                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3994                         break;
3995                 case IMAGEX_REBUILD_OPTION:
3996                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3997                         break;
3998                 case IMAGEX_LAZY_OPTION:
3999                 case IMAGEX_FORCE_OPTION:
4000                         /* Now, unmount is lazy by default.  However, committing
4001                          * the image will fail with
4002                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
4003                          * file descriptors on the WIM image.  The
4004                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
4005                          * descriptors to be closed.  */
4006                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4007                         break;
4008                 case IMAGEX_NEW_IMAGE_OPTION:
4009                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4010                         break;
4011                 default:
4012                         goto out_usage;
4013                 }
4014         }
4015         argc -= optind;
4016         argv += optind;
4017         if (argc != 1)
4018                 goto out_usage;
4019
4020         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4021                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4022                         imagex_error(T("--new-image is meaningless "
4023                                        "without --commit also specified!"));
4024                         goto out_err;
4025                 }
4026         }
4027
4028         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4029                                                  imagex_progress_func, NULL);
4030         if (ret) {
4031                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4032                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4033                         imagex_printf(T(
4034                                 "\tNote: Use --commit --force to force changes "
4035                                         "to be committed, regardless\n"
4036                                 "\t      of open files.\n"));
4037                 }
4038         }
4039 out:
4040         return ret;
4041
4042 out_usage:
4043         usage(CMD_UNMOUNT, stderr);
4044 out_err:
4045         ret = -1;
4046         goto out;
4047 }
4048 #endif /* WIM_MOUNTING_SUPPORTED */
4049
4050 /*
4051  * Add, delete, or rename files in a WIM image.
4052  */
4053 static int
4054 imagex_update(int argc, tchar **argv, int cmd)
4055 {
4056         const tchar *wimfile;
4057         int image;
4058         WIMStruct *wim;
4059         int ret;
4060         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4061         int write_flags = 0;
4062         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4063         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4064                                 WIMLIB_ADD_FLAG_VERBOSE |
4065                                 WIMLIB_ADD_FLAG_WINCONFIG;
4066         int default_delete_flags = 0;
4067         unsigned num_threads = 0;
4068         int c;
4069         tchar *cmd_file_contents;
4070         size_t cmd_file_nchars;
4071         struct wimlib_update_command *cmds;
4072         size_t num_cmds;
4073         tchar *command_str = NULL;
4074         tchar *config_file = NULL;
4075         tchar *wimboot_config = NULL;
4076
4077         for_opt(c, update_options) {
4078                 switch (c) {
4079                 /* Generic or write options */
4080                 case IMAGEX_THREADS_OPTION:
4081                         num_threads = parse_num_threads(optarg);
4082                         if (num_threads == UINT_MAX)
4083                                 goto out_err;
4084                         break;
4085                 case IMAGEX_CHECK_OPTION:
4086                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4087                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4088                         break;
4089                 case IMAGEX_REBUILD_OPTION:
4090                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4091                         break;
4092                 case IMAGEX_COMMAND_OPTION:
4093                         if (command_str) {
4094                                 imagex_error(T("--command may only be specified "
4095                                                "one time.  Please provide\n"
4096                                                "       the update commands "
4097                                                "on standard input instead."));
4098                                 goto out_err;
4099                         }
4100                         command_str = tstrdup(optarg);
4101                         if (!command_str) {
4102                                 imagex_error(T("Out of memory!"));
4103                                 goto out_err;
4104                         }
4105                         break;
4106                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4107                         wimboot_config = optarg;
4108                         break;
4109                 /* Default delete options */
4110                 case IMAGEX_FORCE_OPTION:
4111                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4112                         break;
4113                 case IMAGEX_RECURSIVE_OPTION:
4114                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4115                         break;
4116
4117                 /* Global add option */
4118                 case IMAGEX_CONFIG_OPTION:
4119                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4120                         config_file = optarg;
4121                         break;
4122
4123                 /* Default add options */
4124                 case IMAGEX_VERBOSE_OPTION:
4125                         /* No longer does anything.  */
4126                         break;
4127                 case IMAGEX_DEREFERENCE_OPTION:
4128                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4129                         break;
4130                 case IMAGEX_UNIX_DATA_OPTION:
4131                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4132                         break;
4133                 case IMAGEX_NO_ACLS_OPTION:
4134                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4135                         break;
4136                 case IMAGEX_STRICT_ACLS_OPTION:
4137                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4138                         break;
4139                 case IMAGEX_NO_REPLACE_OPTION:
4140                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4141                         break;
4142                 case IMAGEX_UNSAFE_COMPACT_OPTION:
4143                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4144                         break;
4145                 default:
4146                         goto out_usage;
4147                 }
4148         }
4149         argv += optind;
4150         argc -= optind;
4151
4152         if (argc != 1 && argc != 2)
4153                 goto out_usage;
4154         wimfile = argv[0];
4155
4156         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4157                                             imagex_progress_func, NULL);
4158         if (ret)
4159                 goto out_free_command_str;
4160
4161         if (argc >= 2) {
4162                 /* Image explicitly specified.  */
4163                 image = wimlib_resolve_image(wim, argv[1]);
4164                 ret = verify_image_exists_and_is_single(image, argv[1],
4165                                                         wimfile);
4166                 if (ret)
4167                         goto out_wimlib_free;
4168         } else {
4169                 /* No image specified; default to image 1, but only if the WIM
4170                  * contains exactly one image.  */
4171                 struct wimlib_wim_info info;
4172
4173                 wimlib_get_wim_info(wim, &info);
4174                 if (info.image_count != 1) {
4175                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4176                                      wimfile, info.image_count);
4177                         wimlib_free(wim);
4178                         goto out_usage;
4179                 }
4180                 image = 1;
4181         }
4182
4183         /* Read update commands from standard input, or the command string if
4184          * specified.  */
4185         if (command_str) {
4186                 cmd_file_contents = NULL;
4187                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4188                                                  &num_cmds);
4189                 if (!cmds) {
4190                         ret = -1;
4191                         goto out_free_cmd_file_contents;
4192                 }
4193         } else if (!wimboot_config) {
4194                 if (isatty(STDIN_FILENO)) {
4195                         tputs(T("Reading update commands from standard input..."));
4196                         recommend_man_page(CMD_UPDATE, stdout);
4197                 }
4198                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4199                 if (!cmd_file_contents) {
4200                         ret = -1;
4201                         goto out_wimlib_free;
4202                 }
4203
4204                 /* Parse the update commands */
4205                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4206                                                  &num_cmds);
4207                 if (!cmds) {
4208                         ret = -1;
4209                         goto out_free_cmd_file_contents;
4210                 }
4211         } else {
4212                 cmd_file_contents = NULL;
4213                 cmds = NULL;
4214                 num_cmds = 0;
4215         }
4216
4217         /* Set default flags and capture config on the update commands */
4218         for (size_t i = 0; i < num_cmds; i++) {
4219                 switch (cmds[i].op) {
4220                 case WIMLIB_UPDATE_OP_ADD:
4221                         cmds[i].add.add_flags |= default_add_flags;
4222                         cmds[i].add.config_file = config_file;
4223                         break;
4224                 case WIMLIB_UPDATE_OP_DELETE:
4225                         cmds[i].delete_.delete_flags |= default_delete_flags;
4226                         break;
4227                 default:
4228                         break;
4229                 }
4230         }
4231
4232         /* Execute the update commands */
4233         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4234         if (ret)
4235                 goto out_free_cmds;
4236
4237         if (wimboot_config) {
4238                 /* --wimboot-config=FILE is short for an
4239                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
4240                  */
4241                 struct wimlib_update_command cmd;
4242
4243                 cmd.op = WIMLIB_UPDATE_OP_ADD;
4244                 cmd.add.fs_source_path = wimboot_config;
4245                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4246                 cmd.add.config_file = NULL;
4247                 cmd.add.add_flags = 0;
4248
4249                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4250                 if (ret)
4251                         goto out_free_cmds;
4252         }
4253
4254         /* Overwrite the updated WIM */
4255         ret = wimlib_overwrite(wim, write_flags, num_threads);
4256 out_free_cmds:
4257         free(cmds);
4258 out_free_cmd_file_contents:
4259         free(cmd_file_contents);
4260 out_wimlib_free:
4261         wimlib_free(wim);
4262 out_free_command_str:
4263         free(command_str);
4264         return ret;
4265
4266 out_usage:
4267         usage(CMD_UPDATE, stderr);
4268 out_err:
4269         ret = -1;
4270         goto out_free_command_str;
4271 }
4272
4273 /* Verify a WIM file.  */
4274 static int
4275 imagex_verify(int argc, tchar **argv, int cmd)
4276 {
4277         int ret;
4278         const tchar *wimfile;
4279         WIMStruct *wim;
4280         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4281         int verify_flags = 0;
4282         STRING_LIST(refglobs);
4283         int c;
4284
4285         for_opt(c, verify_options) {
4286                 switch (c) {
4287                 case IMAGEX_REF_OPTION:
4288                         ret = string_list_append(&refglobs, optarg);
4289                         if (ret)
4290                                 goto out_free_refglobs;
4291                         break;
4292                 case IMAGEX_NOCHECK_OPTION:
4293                         open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4294                         break;
4295                 default:
4296                         goto out_usage;
4297                 }
4298         }
4299
4300         argv += optind;
4301         argc -= optind;
4302
4303         if (argc != 1) {
4304                 if (argc == 0)
4305                         imagex_error(T("Must specify a WIM file!"));
4306                 else
4307                         imagex_error(T("At most one WIM file can be specified!"));
4308                 goto out_usage;
4309         }
4310
4311         wimfile = argv[0];
4312
4313         ret = wimlib_open_wim_with_progress(wimfile,
4314                                             open_flags,
4315                                             &wim,
4316                                             imagex_progress_func,
4317                                             NULL);
4318         if (ret)
4319                 goto out_free_refglobs;
4320
4321         ret = wim_reference_globs(wim, &refglobs, open_flags);
4322         if (ret)
4323                 goto out_wimlib_free;
4324
4325         ret = wimlib_verify_wim(wim, verify_flags);
4326         if (ret) {
4327                 tputc(T('\n'), stderr);
4328                 imagex_error(T("\"%"TS"\" failed verification!"),
4329                              wimfile);
4330                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4331                     refglobs.num_strings == 0)
4332                 {
4333                         imagex_printf(T("Note: if this WIM file is not standalone, "
4334                                         "use the --ref option to specify the other parts.\n"));
4335                 }
4336         } else {
4337                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4338                               wimfile);
4339         }
4340
4341 out_wimlib_free:
4342         wimlib_free(wim);
4343 out_free_refglobs:
4344         string_list_destroy(&refglobs);
4345         return ret;
4346
4347 out_usage:
4348         usage(CMD_VERIFY, stderr);
4349         ret = -1;
4350         goto out_free_refglobs;
4351 }
4352
4353 struct imagex_command {
4354         const tchar *name;
4355         int (*func)(int argc, tchar **argv, int cmd);
4356 };
4357
4358 static const struct imagex_command imagex_commands[] = {
4359         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4360         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4361         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4362         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4363         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4364         [CMD_EXPORT]   = {T("export"),   imagex_export},
4365         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4366         [CMD_INFO]     = {T("info"),     imagex_info},
4367         [CMD_JOIN]     = {T("join"),     imagex_join},
4368 #if WIM_MOUNTING_SUPPORTED
4369         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4370         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4371 #endif
4372         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4373         [CMD_SPLIT]    = {T("split"),    imagex_split},
4374 #if WIM_MOUNTING_SUPPORTED
4375         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4376 #endif
4377         [CMD_UPDATE]   = {T("update"),   imagex_update},
4378         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4379 };
4380
4381 #ifdef __WIN32__
4382
4383    /* Can be a directory or source list file.  But source list file is probably
4384     * a rare use case, so just say directory.  */
4385 #  define SOURCE_STR T("DIRECTORY")
4386
4387    /* Can only be a directory  */
4388 #  define TARGET_STR T("DIRECTORY")
4389
4390 #else
4391    /* Can be a directory, NTFS volume, or source list file. */
4392 #  define SOURCE_STR T("SOURCE")
4393
4394    /* Can be a directory or NTFS volume.  */
4395 #  define TARGET_STR T("TARGET")
4396
4397 #endif
4398
4399 static const tchar * const usage_strings[] = {
4400 [CMD_APPEND] =
4401 T(
4402 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4403 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4404 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4405 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4406 "                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4407 "                    [--dereference] [--snapshot]\n"
4408 ),
4409 [CMD_APPLY] =
4410 T(
4411 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4412 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4413 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4414 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4415 "                    [--compact=FORMAT]\n"
4416 ),
4417 [CMD_CAPTURE] =
4418 T(
4419 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4420 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4421 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4422 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4423 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4424 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4425 "                    [--snapshot]\n"
4426 ),
4427 [CMD_DELETE] =
4428 T(
4429 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4430 ),
4431 [CMD_DIR] =
4432 T(
4433 "    %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4434 ),
4435 [CMD_EXPORT] =
4436 T(
4437 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4438 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4439 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4440 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4441 "                    [--wimboot] [--solid]\n"
4442 ),
4443 [CMD_EXTRACT] =
4444 T(
4445 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4446 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4447 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4448 "                    [--no-attributes] [--include-invalid-names]\n"
4449 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4450 ),
4451 [CMD_INFO] =
4452 T(
4453 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4454 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4455 "                    [--extract-xml FILE] [--header] [--blobs]\n"
4456 "                    [--image-property NAME=VALUE]\n"
4457 ),
4458 [CMD_JOIN] =
4459 T(
4460 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4461 ),
4462 #if WIM_MOUNTING_SUPPORTED
4463 [CMD_MOUNT] =
4464 T(
4465 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4466 "                    [--check] [--streams-interface=INTERFACE]\n"
4467 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4468 ),
4469 [CMD_MOUNTRW] =
4470 T(
4471 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4472 "                    [--check] [--streams-interface=INTERFACE]\n"
4473 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4474 ),
4475 #endif
4476 [CMD_OPTIMIZE] =
4477 T(
4478 "    %"TS" WIMFILE\n"
4479 "                    [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4480 "                    [--check] [--nocheck] [--solid]\n"
4481 "\n"
4482 ),
4483 [CMD_SPLIT] =
4484 T(
4485 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4486 ),
4487 #if WIM_MOUNTING_SUPPORTED
4488 [CMD_UNMOUNT] =
4489 T(
4490 "    %"TS" DIRECTORY\n"
4491 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4492 ),
4493 #endif
4494 [CMD_UPDATE] =
4495 T(
4496 "    %"TS" WIMFILE [IMAGE]\n"
4497 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4498 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4499 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4500 "                    [< CMDFILE]\n"
4501 ),
4502 [CMD_VERIFY] =
4503 T(
4504 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4505 ),
4506 };
4507
4508 static const tchar *invocation_name;
4509 static int invocation_cmd = CMD_NONE;
4510
4511 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4512 {
4513         static tchar buf[50];
4514
4515         if (cmd == CMD_NONE)
4516                 return T("wimlib-imagex");
4517
4518         if (only_short_form || invocation_cmd != CMD_NONE) {
4519                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4520         } else {
4521                 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4522                          imagex_commands[cmd].name);
4523         }
4524         return buf;
4525 }
4526
4527 static void
4528 version(void)
4529 {
4530         static const tchar * const fmt =
4531         T(
4532 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4533 "Copyright (C) 2012-2018 Eric Biggers\n"
4534 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4535 "This is free software: you are free to change and redistribute it.\n"
4536 "There is NO WARRANTY, to the extent permitted by law.\n"
4537 "\n"
4538 "Report bugs to "PACKAGE_BUGREPORT".\n"
4539         );
4540         tfprintf(stdout, fmt, wimlib_get_version_string());
4541 }
4542
4543 static void
4544 do_common_options(int *argc_p, tchar **argv, int cmd)
4545 {
4546         int argc = *argc_p;
4547         int i;
4548         const tchar *p;
4549
4550         for (i = 1; i < argc; i++) {
4551                 p = argv[i];
4552                 if (p[0] == T('-') && p[1] == T('-')) {
4553                         p += 2;
4554                         if (!tstrcmp(p, T("help"))) {
4555                                 if (cmd == CMD_NONE)
4556                                         usage_all(stdout);
4557                                 else
4558                                         usage(cmd, stdout);
4559                                 exit(0);
4560                         } else if (!tstrcmp(p, T("version"))) {
4561                                 version();
4562                                 exit(0);
4563                         } else if (!tstrcmp(p, T("quiet"))) {
4564                                 imagex_suppress_output();
4565                                 memmove(&argv[i], &argv[i + 1],
4566                                         (argc - i) * sizeof(argv[i]));
4567                                 argc--;
4568                                 i--;
4569                         } else if (!*p) /* reached "--", no more options */
4570                                 break;
4571                 }
4572         }
4573
4574         *argc_p = argc;
4575 }
4576
4577 static void
4578 print_usage_string(int cmd, FILE *fp)
4579 {
4580         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4581 }
4582
4583 static void
4584 recommend_man_page(int cmd, FILE *fp)
4585 {
4586         const tchar *format_str;
4587 #ifdef __WIN32__
4588         format_str = T("Some uncommon options are not listed;\n"
4589                        "See %"TS".pdf in the doc directory for more details.\n");
4590 #else
4591         format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4592 #endif
4593         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4594 }
4595
4596 static void
4597 usage(int cmd, FILE *fp)
4598 {
4599         tfprintf(fp, T("Usage:\n"));
4600         print_usage_string(cmd, fp);
4601         tfprintf(fp, T("\n"));
4602         recommend_man_page(cmd, fp);
4603 }
4604
4605 static void
4606 usage_all(FILE *fp)
4607 {
4608         tfprintf(fp, T("Usage:\n"));
4609         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4610                 print_usage_string(cmd, fp);
4611                 tfprintf(fp, T("\n"));
4612         }
4613         static const tchar * const extra =
4614         T(
4615 "    %"TS" --help\n"
4616 "    %"TS" --version\n"
4617 "\n"
4618         );
4619         tfprintf(fp, extra, invocation_name, invocation_name);
4620         tfprintf(fp,
4621                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4622                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4623                    "For some commands IMAGE may be \"all\".\n"
4624                    "\n"));
4625         recommend_man_page(CMD_NONE, fp);
4626 }
4627
4628 #ifdef __WIN32__
4629 extern int wmain(int argc, wchar_t **argv);
4630 #define main wmain
4631 #endif
4632
4633 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4634  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4635  * something else), while on Windows the command arguments will be UTF-16LE
4636  * encoded 'wchar_t' strings. */
4637 int
4638 main(int argc, tchar **argv)
4639 {
4640         int ret;
4641         int init_flags = 0;
4642         int cmd;
4643
4644         imagex_info_file = stdout;
4645         invocation_name = tbasename(argv[0]);
4646
4647         {
4648                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4649                 if (igcase != NULL) {
4650                         if (!tstrcmp(igcase, T("no")) ||
4651                             !tstrcmp(igcase, T("0")))
4652                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4653                         else if (!tstrcmp(igcase, T("yes")) ||
4654                                  !tstrcmp(igcase, T("1")))
4655                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4656                         else {
4657                                 fprintf(stderr,
4658                                         "WARNING: Ignoring unknown setting of "
4659                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4660                         }
4661                 }
4662         }
4663
4664         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4665         cmd = CMD_NONE;
4666         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4667             tstrcmp(invocation_name, T("wimlib-imagex"))) {
4668                 for (int i = 0; i < CMD_MAX; i++) {
4669                         if (!tstrcmp(invocation_name + 3,
4670                                      imagex_commands[i].name))
4671                         {
4672                                 invocation_cmd = i;
4673                                 cmd = i;
4674                                 break;
4675                         }
4676                 }
4677         }
4678
4679         /* Unless already known from the invocation name, determine which
4680          * command was specified.  */
4681         if (cmd == CMD_NONE) {
4682                 if (argc < 2) {
4683                         imagex_error(T("No command specified!\n"));
4684                         usage_all(stderr);
4685                         exit(2);
4686                 }
4687                 for (int i = 0; i < CMD_MAX; i++) {
4688                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4689                                 cmd = i;
4690                                 break;
4691                         }
4692                 }
4693                 if (cmd != CMD_NONE) {
4694                         argc--;
4695                         argv++;
4696                 }
4697         }
4698
4699         /* Handle common options.  May exit early (for --help or --version).  */
4700         do_common_options(&argc, argv, cmd);
4701
4702         /* Bail if a valid command was not specified.  */
4703         if (cmd == CMD_NONE) {
4704                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4705                 usage_all(stderr);
4706                 exit(2);
4707         }
4708
4709         /* Enable warning and error messages in wimlib to be more user-friendly.
4710          * */
4711         wimlib_set_print_errors(true);
4712
4713         /* Initialize wimlib.  */
4714         ret = wimlib_global_init(init_flags);
4715         if (ret)
4716                 goto out_check_status;
4717
4718         /* Call the command handler function.  */
4719         ret = imagex_commands[cmd].func(argc, argv, cmd);
4720
4721         /* Check for error writing to standard output, especially since for some
4722          * commands, writing to standard output is part of the program's actual
4723          * behavior and not just for informational purposes.  */
4724         if (ferror(stdout) || fclose(stdout)) {
4725                 imagex_error_with_errno(T("error writing to standard output"));
4726                 if (ret == 0)
4727                         ret = -1;
4728         }
4729 out_check_status:
4730         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4731          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4732          * error code from which an error message can be printed.  */
4733         if (ret > 0) {
4734                 imagex_error(T("Exiting with error code %d:\n"
4735                                "       %"TS"."), ret,
4736                              wimlib_get_error_string(ret));
4737                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4738                         imagex_error_with_errno(T("errno"));
4739         }
4740         /* Make wimlib free any resources it's holding (although this is not
4741          * strictly necessary because the process is ending anyway).  */
4742         wimlib_global_cleanup();
4743         return ret;
4744 }