]> wimlib.net Git - wimlib/blob - programs/imagex.c
Allow retrieving PACKAGE_VERSION from the library
[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;
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
2290                 if (base_wimfiles.num_strings == 1 &&
2291                     template_wimfile == base_wimfiles.strings[0]) {
2292                         template_wim = base_wims[0];
2293                 } else if (template_wimfile == wimfile) {
2294                         template_wim = wim;
2295                 } else {
2296                         ret = wimlib_open_wim_with_progress(template_wimfile,
2297                                                             open_flags,
2298                                                             &template_wim,
2299                                                             imagex_progress_func,
2300                                                             NULL);
2301                         if (ret)
2302                                 goto out_free_base_wims;
2303                 }
2304
2305                 template_image = wimlib_resolve_image(template_wim,
2306                                                       template_image_name_or_num);
2307
2308                 if (template_image_name_or_num[0] == T('-')) {
2309                         tchar *tmp;
2310                         unsigned long n;
2311                         struct wimlib_wim_info info;
2312
2313                         wimlib_get_wim_info(template_wim, &info);
2314                         n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2315                         if (n >= 1 && n <= info.image_count &&
2316                             *tmp == T('\0') &&
2317                             tmp != template_image_name_or_num + 1)
2318                         {
2319                                 template_image = info.image_count - (n - 1);
2320                         }
2321                 }
2322                 ret = verify_image_exists_and_is_single(template_image,
2323                                                         template_image_name_or_num,
2324                                                         template_wimfile);
2325                 if (ret)
2326                         goto out_free_template_wim;
2327         } else {
2328                 template_wim = NULL;
2329         }
2330
2331         ret = wimlib_add_image_multisource(wim,
2332                                            capture_sources,
2333                                            num_sources,
2334                                            name,
2335                                            config_file,
2336                                            add_flags);
2337         if (ret)
2338                 goto out_free_template_wim;
2339
2340         if (image_properties.num_strings || template_image_name_or_num) {
2341                 /* User asked to set additional image properties, or an image on
2342                  * which the added one is to be based has been specified with
2343                  * --update-of.  */
2344                 struct wimlib_wim_info info;
2345
2346                 wimlib_get_wim_info(wim, &info);
2347
2348                 ret = apply_image_properties(&image_properties, wim,
2349                                              info.image_count, NULL);
2350                 if (ret)
2351                         goto out_free_template_wim;
2352
2353                 /* Reference template image if the user provided one.  */
2354                 if (template_image_name_or_num) {
2355                         imagex_printf(T("Using image %d "
2356                                         "from \"%"TS"\" as template\n"),
2357                                         template_image, template_wimfile);
2358                         ret = wimlib_reference_template_image(wim,
2359                                                               info.image_count,
2360                                                               template_wim,
2361                                                               template_image,
2362                                                               0);
2363                         if (ret)
2364                                 goto out_free_template_wim;
2365                 }
2366         }
2367
2368         /* Write the new WIM or overwrite the existing WIM with the new image
2369          * appended.  */
2370         if (cmd == CMD_APPEND) {
2371                 ret = wimlib_overwrite(wim, write_flags, num_threads);
2372         } else if (wimfile) {
2373                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2374                                    write_flags, num_threads);
2375         } else {
2376                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2377                                          write_flags, num_threads);
2378         }
2379 out_free_template_wim:
2380         /* template_wim may alias base_wims[0] or wim.  */
2381         if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2382             template_wim != wim)
2383                 wimlib_free(template_wim);
2384 out_free_base_wims:
2385         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2386                 wimlib_free(base_wims[i]);
2387         free(base_wims);
2388 out_free_wim:
2389         wimlib_free(wim);
2390 out_free_capture_sources:
2391         if (capture_sources_malloced)
2392                 free(capture_sources);
2393 out_free_source_list_contents:
2394         free(source_list_contents);
2395 out:
2396         string_list_destroy(&image_properties);
2397         string_list_destroy(&base_wimfiles);
2398         return ret;
2399
2400 out_usage:
2401         usage(cmd, stderr);
2402 out_err:
2403         ret = -1;
2404         goto out;
2405 }
2406
2407 /* Remove image(s) from a WIM. */
2408 static int
2409 imagex_delete(int argc, tchar **argv, int cmd)
2410 {
2411         int c;
2412         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2413         int write_flags = 0;
2414         const tchar *wimfile;
2415         const tchar *image_num_or_name;
2416         WIMStruct *wim;
2417         int image;
2418         int ret;
2419
2420         for_opt(c, delete_options) {
2421                 switch (c) {
2422                 case IMAGEX_CHECK_OPTION:
2423                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2424                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2425                         break;
2426                 case IMAGEX_SOFT_OPTION:
2427                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2428                         break;
2429                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2430                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2431                         break;
2432                 default:
2433                         goto out_usage;
2434                 }
2435         }
2436         argc -= optind;
2437         argv += optind;
2438
2439         if (argc != 2) {
2440                 if (argc < 1)
2441                         imagex_error(T("Must specify a WIM file"));
2442                 if (argc < 2)
2443                         imagex_error(T("Must specify an image"));
2444                 goto out_usage;
2445         }
2446         wimfile = argv[0];
2447         image_num_or_name = argv[1];
2448
2449         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2450                                             imagex_progress_func, NULL);
2451         if (ret)
2452                 goto out;
2453
2454         image = wimlib_resolve_image(wim, image_num_or_name);
2455
2456         ret = verify_image_exists(image, image_num_or_name, wimfile);
2457         if (ret)
2458                 goto out_wimlib_free;
2459
2460         ret = wimlib_delete_image(wim, image);
2461         if (ret) {
2462                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2463                              wimfile);
2464                 goto out_wimlib_free;
2465         }
2466
2467         ret = wimlib_overwrite(wim, write_flags, 0);
2468         if (ret) {
2469                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2470                                "deleted"), wimfile);
2471         }
2472 out_wimlib_free:
2473         wimlib_free(wim);
2474 out:
2475         return ret;
2476
2477 out_usage:
2478         usage(CMD_DELETE, stderr);
2479         ret = -1;
2480         goto out;
2481 }
2482
2483 struct print_dentry_options {
2484         bool detailed;
2485 };
2486
2487 static void
2488 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2489 {
2490         tprintf(T("%"TS"\n"), dentry->full_path);
2491 }
2492
2493 static const struct {
2494         uint32_t flag;
2495         const tchar *name;
2496 } file_attr_flags[] = {
2497         {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
2498         {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
2499         {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
2500         {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
2501         {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
2502         {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
2503         {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
2504         {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
2505         {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
2506         {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
2507         {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
2508         {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
2509         {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2510         {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
2511         {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
2512 };
2513
2514 #define TIMESTR_MAX 100
2515
2516 static void
2517 print_time(const tchar *type, const struct wimlib_timespec *wts,
2518            int32_t high_part)
2519 {
2520         tchar timestr[TIMESTR_MAX];
2521         time_t t;
2522         struct tm tm;
2523
2524         if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2525                 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2526         else
2527                 t = wts->tv_sec;
2528
2529         gmtime_r(&t, &tm);
2530         tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2531         timestr[TIMESTR_MAX - 1] = '\0';
2532
2533         tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2534 }
2535
2536 static void print_byte_field(const uint8_t field[], size_t len)
2537 {
2538         while (len--)
2539                 tprintf(T("%02hhx"), *field++);
2540 }
2541
2542 static void
2543 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2544 {
2545         tchar attr_string[256];
2546         tchar *p;
2547
2548         tputs(T("WIM Information:"));
2549         tputs(T("----------------"));
2550         tprintf(T("Path:           %"TS"\n"), wimfile);
2551         tprintf(T("GUID:           0x"));
2552         print_byte_field(info->guid, sizeof(info->guid));
2553         tputchar(T('\n'));
2554         tprintf(T("Version:        %u\n"), info->wim_version);
2555         tprintf(T("Image Count:    %d\n"), info->image_count);
2556         tprintf(T("Compression:    %"TS"\n"),
2557                 wimlib_get_compression_type_string(info->compression_type));
2558         tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
2559                 info->chunk_size);
2560         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2561         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2562         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2563
2564         attr_string[0] = T('\0');
2565
2566         if (info->pipable)
2567                 tstrcat(attr_string, T("Pipable, "));
2568
2569         if (info->has_integrity_table)
2570                 tstrcat(attr_string, T("Integrity info, "));
2571
2572         if (info->has_rpfix)
2573                 tstrcat(attr_string, T("Relative path junction, "));
2574
2575         if (info->resource_only)
2576                 tstrcat(attr_string, T("Resource only, "));
2577
2578         if (info->metadata_only)
2579                 tstrcat(attr_string, T("Metadata only, "));
2580
2581         if (info->is_marked_readonly)
2582                 tstrcat(attr_string, T("Readonly, "));
2583
2584         p = tstrchr(attr_string, T('\0'));
2585         if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2586                 p[-2] = T('\0');
2587
2588         tprintf(T("Attributes:     %"TS"\n\n"), attr_string);
2589 }
2590
2591 static int
2592 print_resource(const struct wimlib_resource_entry *resource,
2593                void *_ignore)
2594 {
2595         tprintf(T("Hash              = 0x"));
2596         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2597         tputchar(T('\n'));
2598
2599         if (!resource->is_missing) {
2600                 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2601                         resource->uncompressed_size);
2602                 if (resource->packed) {
2603                         tprintf(T("Solid resource    = %"PRIu64" => %"PRIu64" "
2604                                   "bytes @ offset %"PRIu64"\n"),
2605                                 resource->raw_resource_uncompressed_size,
2606                                 resource->raw_resource_compressed_size,
2607                                 resource->raw_resource_offset_in_wim);
2608
2609                         tprintf(T("Solid offset      = %"PRIu64" bytes\n"),
2610                                 resource->offset);
2611                 } else {
2612                         tprintf(T("Compressed size   = %"PRIu64" bytes\n"),
2613                                 resource->compressed_size);
2614
2615                         tprintf(T("Offset in WIM     = %"PRIu64" bytes\n"),
2616                                 resource->offset);
2617                 }
2618
2619                 tprintf(T("Part Number       = %u\n"), resource->part_number);
2620                 tprintf(T("Reference Count   = %u\n"), resource->reference_count);
2621
2622                 tprintf(T("Flags             = "));
2623                 if (resource->is_compressed)
2624                         tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2625                 if (resource->is_metadata)
2626                         tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2627                 if (resource->is_free)
2628                         tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2629                 if (resource->is_spanned)
2630                         tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2631                 if (resource->packed)
2632                         tprintf(T("WIM_RESHDR_FLAG_SOLID  "));
2633                 tputchar(T('\n'));
2634         }
2635         tputchar(T('\n'));
2636         return 0;
2637 }
2638
2639 static void
2640 print_blobs(WIMStruct *wim)
2641 {
2642         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2643 }
2644
2645 #ifndef __WIN32__
2646 static void
2647 default_print_security_descriptor(const uint8_t *sd, size_t size)
2648 {
2649         tprintf(T("Security Descriptor = "));
2650         print_byte_field(sd, size);
2651         tputchar(T('\n'));
2652 }
2653 #endif
2654
2655 static bool
2656 is_null_guid(const uint8_t *guid)
2657 {
2658         static const uint8_t null_guid[WIMLIB_GUID_LEN];
2659
2660         return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2661 }
2662
2663 static void
2664 print_guid(const tchar *label, const uint8_t *guid)
2665 {
2666         if (is_null_guid(guid))
2667                 return;
2668         tprintf(T("%-20"TS"= 0x"), label);
2669         print_byte_field(guid, WIMLIB_GUID_LEN);
2670         tputchar(T('\n'));
2671 }
2672
2673 static void
2674 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2675 {
2676         tprintf(T(
2677 "----------------------------------------------------------------------------\n"));
2678         tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
2679         if (dentry->dos_name)
2680                 tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
2681         tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
2682         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2683                 if (file_attr_flags[i].flag & dentry->attributes)
2684                         tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
2685                                 file_attr_flags[i].name);
2686
2687         if (dentry->security_descriptor) {
2688                 print_security_descriptor(dentry->security_descriptor,
2689                                           dentry->security_descriptor_size);
2690         }
2691
2692         print_time(T("Creation Time"),
2693                    &dentry->creation_time, dentry->creation_time_high);
2694         print_time(T("Last Write Time"),
2695                    &dentry->last_write_time, dentry->last_write_time_high);
2696         print_time(T("Last Access Time"),
2697                    &dentry->last_access_time, dentry->last_access_time_high);
2698
2699
2700         if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2701                 tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
2702
2703         tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2704         tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
2705
2706         if (dentry->unix_mode != 0) {
2707                 tprintf(T("UNIX Data           = uid:%"PRIu32" gid:%"PRIu32" "
2708                           "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2709                         dentry->unix_uid, dentry->unix_gid,
2710                         dentry->unix_mode, dentry->unix_rdev);
2711         }
2712
2713         if (!is_null_guid(dentry->object_id.object_id)) {
2714                 print_guid(T("Object ID"), dentry->object_id.object_id);
2715                 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2716                 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2717                 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2718         }
2719
2720         for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2721                 if (dentry->streams[i].stream_name) {
2722                         tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2723                                 dentry->streams[i].stream_name);
2724                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2725                         tprintf(T("\tRaw encrypted data stream:\n"));
2726                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2727                         tprintf(T("\tReparse point stream:\n"));
2728                 } else {
2729                         tprintf(T("\tUnnamed data stream:\n"));
2730                 }
2731                 print_resource(&dentry->streams[i].resource, NULL);
2732         }
2733 }
2734
2735 static int
2736 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2737 {
2738         const struct print_dentry_options *options = _options;
2739         if (!options->detailed)
2740                 print_dentry_full_path(dentry);
2741         else
2742                 print_dentry_detailed(dentry);
2743         return 0;
2744 }
2745
2746 /* Print the files contained in an image(s) in a WIM file. */
2747 static int
2748 imagex_dir(int argc, tchar **argv, int cmd)
2749 {
2750         const tchar *wimfile;
2751         WIMStruct *wim = NULL;
2752         int image;
2753         int ret;
2754         const tchar *path = WIMLIB_WIM_ROOT_PATH;
2755         int c;
2756         struct print_dentry_options options = {
2757                 .detailed = false,
2758         };
2759         int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2760
2761         STRING_LIST(refglobs);
2762
2763         for_opt(c, dir_options) {
2764                 switch (c) {
2765                 case IMAGEX_PATH_OPTION:
2766                         path = optarg;
2767                         break;
2768                 case IMAGEX_DETAILED_OPTION:
2769                         options.detailed = true;
2770                         break;
2771                 case IMAGEX_ONE_FILE_ONLY_OPTION:
2772                         iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2773                         break;
2774                 case IMAGEX_REF_OPTION:
2775                         ret = string_list_append(&refglobs, optarg);
2776                         if (ret)
2777                                 goto out_free_refglobs;
2778                         break;
2779                 default:
2780                         goto out_usage;
2781                 }
2782         }
2783         argc -= optind;
2784         argv += optind;
2785
2786         if (argc < 1) {
2787                 imagex_error(T("Must specify a WIM file"));
2788                 goto out_usage;
2789         }
2790         if (argc > 2) {
2791                 imagex_error(T("Too many arguments"));
2792                 goto out_usage;
2793         }
2794
2795         wimfile = argv[0];
2796         ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2797                                             imagex_progress_func, NULL);
2798         if (ret)
2799                 goto out_free_refglobs;
2800
2801         if (argc >= 2) {
2802                 image = wimlib_resolve_image(wim, argv[1]);
2803                 ret = verify_image_exists(image, argv[1], wimfile);
2804                 if (ret)
2805                         goto out_wimlib_free;
2806         } else {
2807                 /* No image specified; default to image 1, but only if the WIM
2808                  * contains exactly one image.  */
2809
2810                 struct wimlib_wim_info info;
2811
2812                 wimlib_get_wim_info(wim, &info);
2813                 if (info.image_count != 1) {
2814                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2815                                        "select one (or all)."),
2816                                      wimfile, info.image_count);
2817                         wimlib_free(wim);
2818                         goto out_usage;
2819                 }
2820                 image = 1;
2821         }
2822
2823         if (refglobs.num_strings) {
2824                 ret = wim_reference_globs(wim, &refglobs, 0);
2825                 if (ret)
2826                         goto out_wimlib_free;
2827         }
2828
2829         ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2830                                       print_dentry, &options);
2831         if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2832                 struct wimlib_wim_info info;
2833
2834                 wimlib_get_wim_info(wim, &info);
2835                 do_metadata_not_found_warning(wimfile, &info);
2836         }
2837 out_wimlib_free:
2838         wimlib_free(wim);
2839 out_free_refglobs:
2840         string_list_destroy(&refglobs);
2841         return ret;
2842
2843 out_usage:
2844         usage(CMD_DIR, stderr);
2845         ret = -1;
2846         goto out_free_refglobs;
2847 }
2848
2849 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2850  * WIM file. */
2851 static int
2852 imagex_export(int argc, tchar **argv, int cmd)
2853 {
2854         int c;
2855         int open_flags = 0;
2856         int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2857         int write_flags = 0;
2858         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2859         const tchar *src_wimfile;
2860         const tchar *src_image_num_or_name;
2861         const tchar *dest_wimfile;
2862         int dest_wim_fd;
2863         const tchar *dest_name;
2864         const tchar *dest_desc;
2865         WIMStruct *src_wim;
2866         struct wimlib_wim_info src_info;
2867         WIMStruct *dest_wim;
2868         int ret;
2869         int image;
2870         struct stat stbuf;
2871         bool wim_is_new;
2872         STRING_LIST(refglobs);
2873         unsigned num_threads = 0;
2874         uint32_t chunk_size = UINT32_MAX;
2875         uint32_t solid_chunk_size = UINT32_MAX;
2876         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2877
2878         for_opt(c, export_options) {
2879                 switch (c) {
2880                 case IMAGEX_BOOT_OPTION:
2881                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2882                         break;
2883                 case IMAGEX_CHECK_OPTION:
2884                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2885                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2886                         break;
2887                 case IMAGEX_NOCHECK_OPTION:
2888                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2889                         break;
2890                 case IMAGEX_COMPRESS_OPTION:
2891                         compression_type = get_compression_type(optarg, false);
2892                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2893                                 goto out_err;
2894                         break;
2895                 case IMAGEX_RECOMPRESS_OPTION:
2896                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2897                         break;
2898                 case IMAGEX_SOLID_OPTION:
2899                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2900                         break;
2901                 case IMAGEX_NO_SOLID_SORT_OPTION:
2902                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2903                         break;
2904                 case IMAGEX_CHUNK_SIZE_OPTION:
2905                         chunk_size = parse_chunk_size(optarg);
2906                         if (chunk_size == UINT32_MAX)
2907                                 goto out_err;
2908                         break;
2909                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2910                         solid_chunk_size = parse_chunk_size(optarg);
2911                         if (solid_chunk_size == UINT32_MAX)
2912                                 goto out_err;
2913                         break;
2914                 case IMAGEX_SOLID_COMPRESS_OPTION:
2915                         solid_ctype = get_compression_type(optarg, true);
2916                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2917                                 goto out_err;
2918                         break;
2919                 case IMAGEX_REF_OPTION:
2920                         ret = string_list_append(&refglobs, optarg);
2921                         if (ret)
2922                                 goto out_free_refglobs;
2923                         break;
2924                 case IMAGEX_THREADS_OPTION:
2925                         num_threads = parse_num_threads(optarg);
2926                         if (num_threads == UINT_MAX)
2927                                 goto out_err;
2928                         break;
2929                 case IMAGEX_REBUILD_OPTION:
2930                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2931                         break;
2932                 case IMAGEX_PIPABLE_OPTION:
2933                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2934                         break;
2935                 case IMAGEX_NOT_PIPABLE_OPTION:
2936                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2937                         break;
2938                 case IMAGEX_WIMBOOT_OPTION:
2939                         export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2940                         break;
2941                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2942                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2943                         break;
2944                 default:
2945                         goto out_usage;
2946                 }
2947         }
2948         argc -= optind;
2949         argv += optind;
2950         if (argc < 3 || argc > 5)
2951                 goto out_usage;
2952         src_wimfile           = argv[0];
2953         src_image_num_or_name = argv[1];
2954         dest_wimfile          = argv[2];
2955         dest_name             = (argc >= 4) ? argv[3] : NULL;
2956         dest_desc             = (argc >= 5) ? argv[4] : NULL;
2957         ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2958                                             imagex_progress_func, NULL);
2959         if (ret)
2960                 goto out_free_refglobs;
2961
2962         wimlib_get_wim_info(src_wim, &src_info);
2963
2964         /* Determine if the destination is an existing file or not.  If so, we
2965          * try to append the exported image(s) to it; otherwise, we create a new
2966          * WIM containing the exported image(s).  Furthermore, determine if we
2967          * need to write a pipable WIM directly to standard output.  */
2968
2969         if (tstrcmp(dest_wimfile, T("-")) == 0) {
2970         #if 0
2971                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2972                         imagex_error("Can't write a non-pipable WIM to "
2973                                      "standard output!  Specify --pipable\n"
2974                                      "       if you want to create a pipable WIM "
2975                                      "(but read the docs first).");
2976                         ret = -1;
2977                         goto out_free_src_wim;
2978                 }
2979         #else
2980                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2981         #endif
2982                 dest_wimfile = NULL;
2983                 dest_wim_fd = STDOUT_FILENO;
2984                 imagex_output_to_stderr();
2985                 set_fd_to_binary_mode(dest_wim_fd);
2986         }
2987         errno = ENOENT;
2988         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2989                 wim_is_new = false;
2990                 /* Destination file exists. */
2991
2992                 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
2993                         imagex_error(T("\"%"TS"\" is not a regular file "
2994                                        "or block device"), dest_wimfile);
2995                         ret = -1;
2996                         goto out_free_src_wim;
2997                 }
2998                 ret = wimlib_open_wim_with_progress(dest_wimfile,
2999                                                     open_flags |
3000                                                         WIMLIB_OPEN_FLAG_WRITE_ACCESS,
3001                                                     &dest_wim,
3002                                                     imagex_progress_func,
3003                                                     NULL);
3004                 if (ret)
3005                         goto out_free_src_wim;
3006
3007                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3008                         /* The user specified a compression type, but we're
3009                          * exporting to an existing WIM.  Make sure the
3010                          * specified compression type is the same as the
3011                          * compression type of the existing destination WIM. */
3012                         struct wimlib_wim_info dest_info;
3013
3014                         wimlib_get_wim_info(dest_wim, &dest_info);
3015                         if (compression_type != dest_info.compression_type) {
3016                                 imagex_error(T("Cannot specify a compression type that is "
3017                                                "not the same as that used in the "
3018                                                "destination WIM"));
3019                                 ret = -1;
3020                                 goto out_free_dest_wim;
3021                         }
3022                 }
3023         } else {
3024                 wim_is_new = true;
3025
3026                 if (errno != ENOENT) {
3027                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3028                                                 dest_wimfile);
3029                         ret = -1;
3030                         goto out_free_src_wim;
3031                 }
3032
3033                 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3034                         imagex_error(T("'--unsafe-compact' is only valid when "
3035                                        "exporting to an existing WIM file!"));
3036                         ret = -1;
3037                         goto out_free_src_wim;
3038                 }
3039
3040                 /* dest_wimfile is not an existing file, so create a new WIM. */
3041
3042                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3043                         /* The user did not specify a compression type; default
3044                          * to that of the source WIM, unless --solid or
3045                          * --wimboot was specified.   */
3046
3047                         if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3048                                 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3049                         else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3050                                 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3051                         else
3052                                 compression_type = src_info.compression_type;
3053                 }
3054                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3055                 if (ret)
3056                         goto out_free_src_wim;
3057
3058                 wimlib_register_progress_function(dest_wim,
3059                                                   imagex_progress_func, NULL);
3060
3061                 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3062                     && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3063                 {
3064                         /* For --wimboot export, use small XPRESS chunks.  */
3065                         wimlib_set_output_chunk_size(dest_wim, 4096);
3066                 } else if (compression_type == src_info.compression_type &&
3067                            chunk_size == UINT32_MAX)
3068                 {
3069                         /* Use same chunk size if compression type is the same.  */
3070                         wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3071                 }
3072         }
3073
3074         if (chunk_size != UINT32_MAX) {
3075                 /* Set destination chunk size.  */
3076                 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3077                 if (ret)
3078                         goto out_free_dest_wim;
3079         }
3080         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3081                 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3082                 if (ret)
3083                         goto out_free_dest_wim;
3084         }
3085         if (solid_chunk_size != UINT32_MAX) {
3086                 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3087                 if (ret)
3088                         goto out_free_dest_wim;
3089         }
3090
3091         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3092         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3093         if (ret)
3094                 goto out_free_dest_wim;
3095
3096         if (refglobs.num_strings) {
3097                 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3098                 if (ret)
3099                         goto out_free_dest_wim;
3100         }
3101
3102         if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3103             image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3104         {
3105                 imagex_error(T("--boot specified for all-images export, but source WIM "
3106                                "has no bootable image."));
3107                 ret = -1;
3108                 goto out_free_dest_wim;
3109         }
3110
3111         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3112                                   dest_desc, export_flags);
3113         if (ret) {
3114                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3115                         do_resource_not_found_warning(src_wimfile,
3116                                                       &src_info, &refglobs);
3117                 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3118                         do_metadata_not_found_warning(src_wimfile, &src_info);
3119                 }
3120                 goto out_free_dest_wim;
3121         }
3122
3123         if (!wim_is_new)
3124                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3125         else if (dest_wimfile)
3126                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3127                                    write_flags, num_threads);
3128         else
3129                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3130                                          WIMLIB_ALL_IMAGES, write_flags,
3131                                          num_threads);
3132 out_free_dest_wim:
3133         wimlib_free(dest_wim);
3134 out_free_src_wim:
3135         wimlib_free(src_wim);
3136 out_free_refglobs:
3137         string_list_destroy(&refglobs);
3138         return ret;
3139
3140 out_usage:
3141         usage(CMD_EXPORT, stderr);
3142 out_err:
3143         ret = -1;
3144         goto out_free_refglobs;
3145 }
3146
3147 /* Extract files or directories from a WIM image */
3148 static int
3149 imagex_extract(int argc, tchar **argv, int cmd)
3150 {
3151         int c;
3152         int open_flags = 0;
3153         int image;
3154         WIMStruct *wim;
3155         int ret;
3156         const tchar *wimfile;
3157         const tchar *image_num_or_name;
3158         tchar *dest_dir = T(".");
3159         int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3160                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3161                             WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3162         int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3163
3164         STRING_LIST(refglobs);
3165
3166         tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3167
3168         for_opt(c, extract_options) {
3169                 switch (c) {
3170                 case IMAGEX_CHECK_OPTION:
3171                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3172                         break;
3173                 case IMAGEX_VERBOSE_OPTION:
3174                         /* No longer does anything.  */
3175                         break;
3176                 case IMAGEX_REF_OPTION:
3177                         ret = string_list_append(&refglobs, optarg);
3178                         if (ret)
3179                                 goto out_free_refglobs;
3180                         break;
3181                 case IMAGEX_UNIX_DATA_OPTION:
3182                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3183                         break;
3184                 case IMAGEX_NO_ACLS_OPTION:
3185                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3186                         break;
3187                 case IMAGEX_STRICT_ACLS_OPTION:
3188                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3189                         break;
3190                 case IMAGEX_NO_ATTRIBUTES_OPTION:
3191                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3192                         break;
3193                 case IMAGEX_DEST_DIR_OPTION:
3194                         dest_dir = optarg;
3195                         break;
3196                 case IMAGEX_TO_STDOUT_OPTION:
3197                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3198                         imagex_suppress_output();
3199                         set_fd_to_binary_mode(STDOUT_FILENO);
3200                         break;
3201                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3202                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3203                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3204                         break;
3205                 case IMAGEX_NO_GLOBS_OPTION:
3206                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3207                         break;
3208                 case IMAGEX_NULLGLOB_OPTION:
3209                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3210                         break;
3211                 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3212                         notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3213                         break;
3214                 case IMAGEX_WIMBOOT_OPTION:
3215                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3216                         break;
3217                 case IMAGEX_COMPACT_OPTION:
3218                         ret = set_compact_mode(optarg, &extract_flags);
3219                         if (ret)
3220                                 goto out_free_refglobs;
3221                         break;
3222                 default:
3223                         goto out_usage;
3224                 }
3225         }
3226         argc -= optind;
3227         argv += optind;
3228
3229         if (argc < 2)
3230                 goto out_usage;
3231
3232         if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3233                                WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3234         {
3235                 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3236                 goto out_err;
3237         }
3238
3239         wimfile = argv[0];
3240         image_num_or_name = argv[1];
3241
3242         argc -= 2;
3243         argv += 2;
3244
3245         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3246                                             imagex_progress_func, NULL);
3247         if (ret)
3248                 goto out_free_refglobs;
3249
3250         image = wimlib_resolve_image(wim, image_num_or_name);
3251         ret = verify_image_exists_and_is_single(image,
3252                                                 image_num_or_name,
3253                                                 wimfile);
3254         if (ret)
3255                 goto out_wimlib_free;
3256
3257         if (refglobs.num_strings) {
3258                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3259                 if (ret)
3260                         goto out_wimlib_free;
3261         }
3262
3263         if (argc == 0) {
3264                 argv = &root_path;
3265                 argc = 1;
3266                 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3267         }
3268
3269         while (argc != 0 && ret == 0) {
3270                 int num_paths;
3271
3272                 for (num_paths = 0;
3273                      num_paths < argc && argv[num_paths][0] != T('@');
3274                      num_paths++)
3275                         ;
3276
3277                 if (num_paths) {
3278                         ret = wimlib_extract_paths(wim, image, dest_dir,
3279                                                    (const tchar **)argv,
3280                                                    num_paths,
3281                                                    extract_flags | notlist_extract_flags);
3282                         argc -= num_paths;
3283                         argv += num_paths;
3284                 } else {
3285                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3286                                                       argv[0] + 1,
3287                                                       extract_flags);
3288                         argc--;
3289                         argv++;
3290                 }
3291         }
3292
3293         if (ret == 0) {
3294                 imagex_printf(T("Done extracting files.\n"));
3295         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3296                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3297                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3298                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3299                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3300                 {
3301                         tfprintf(stderr,
3302                                  T("Note: You can use the '--nullglob' "
3303                                    "option to ignore missing files.\n"));
3304                 }
3305                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3306                                    "files and directories\n"
3307                                    "      are in the WIM image.\n"),
3308                                 get_cmd_string(CMD_DIR, false));
3309         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3310                 struct wimlib_wim_info info;
3311
3312                 wimlib_get_wim_info(wim, &info);
3313                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3314         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3315                 struct wimlib_wim_info info;
3316
3317                 wimlib_get_wim_info(wim, &info);
3318                 do_metadata_not_found_warning(wimfile, &info);
3319         }
3320 out_wimlib_free:
3321         wimlib_free(wim);
3322 out_free_refglobs:
3323         string_list_destroy(&refglobs);
3324         return ret;
3325
3326 out_usage:
3327         usage(CMD_EXTRACT, stderr);
3328 out_err:
3329         ret = -1;
3330         goto out_free_refglobs;
3331 }
3332
3333 /* Prints information about a WIM file; also can mark an image as bootable,
3334  * change the name of an image, or change the description of an image. */
3335 static int
3336 imagex_info(int argc, tchar **argv, int cmd)
3337 {
3338         int c;
3339         bool boot         = false;
3340         bool check        = false;
3341         bool nocheck      = false;
3342         bool header       = false;
3343         bool blobs        = false;
3344         bool xml          = false;
3345         bool short_header = true;
3346         const tchar *xml_out_file = NULL;
3347         const tchar *wimfile;
3348         const tchar *image_num_or_name;
3349         STRING_LIST(image_properties);
3350         WIMStruct *wim;
3351         int image;
3352         int ret;
3353         int open_flags = 0;
3354         struct wimlib_wim_info info;
3355
3356         for_opt(c, info_options) {
3357                 switch (c) {
3358                 case IMAGEX_BOOT_OPTION:
3359                         boot = true;
3360                         break;
3361                 case IMAGEX_CHECK_OPTION:
3362                         check = true;
3363                         break;
3364                 case IMAGEX_NOCHECK_OPTION:
3365                         nocheck = true;
3366                         break;
3367                 case IMAGEX_HEADER_OPTION:
3368                         header = true;
3369                         short_header = false;
3370                         break;
3371                 case IMAGEX_BLOBS_OPTION:
3372                         blobs = true;
3373                         short_header = false;
3374                         break;
3375                 case IMAGEX_XML_OPTION:
3376                         xml = true;
3377                         short_header = false;
3378                         break;
3379                 case IMAGEX_EXTRACT_XML_OPTION:
3380                         xml_out_file = optarg;
3381                         short_header = false;
3382                         break;
3383                 case IMAGEX_IMAGE_PROPERTY_OPTION:
3384                         ret = append_image_property_argument(&image_properties);
3385                         if (ret)
3386                                 goto out;
3387                         break;
3388                 default:
3389                         goto out_usage;
3390                 }
3391         }
3392
3393         argc -= optind;
3394         argv += optind;
3395         if (argc < 1 || argc > 4)
3396                 goto out_usage;
3397
3398         wimfile           = argv[0];
3399         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3400
3401         if (argc >= 3) {
3402                 /* NEW_NAME */
3403                 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3404                 tsprintf(p, T("NAME=%"TS), argv[2]);
3405                 ret = string_list_append(&image_properties, p);
3406                 if (ret)
3407                         goto out;
3408         }
3409
3410         if (argc >= 4) {
3411                 /* NEW_DESC */
3412                 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3413                 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3414                 ret = string_list_append(&image_properties, p);
3415                 if (ret)
3416                         goto out;
3417         }
3418
3419         if (check && nocheck) {
3420                 imagex_error(T("Can't specify both --check and --nocheck"));
3421                 goto out_err;
3422         }
3423
3424         if (check)
3425                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3426
3427         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3428                                             imagex_progress_func, NULL);
3429         if (ret)
3430                 goto out;
3431
3432         wimlib_get_wim_info(wim, &info);
3433
3434         image = wimlib_resolve_image(wim, image_num_or_name);
3435         ret = WIMLIB_ERR_INVALID_IMAGE;
3436         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3437                 verify_image_exists(image, image_num_or_name, wimfile);
3438                 if (boot) {
3439                         imagex_error(T("If you would like to set the boot "
3440                                        "index to 0, specify image \"0\" with "
3441                                        "the --boot flag."));
3442                 }
3443                 goto out_wimlib_free;
3444         }
3445
3446         if (boot && info.image_count == 0) {
3447                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3448                 goto out_wimlib_free;
3449         }
3450
3451         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3452                 if (boot) {
3453                         imagex_error(T("Cannot specify the --boot flag "
3454                                        "without specifying a specific "
3455                                        "image in a multi-image WIM"));
3456                         goto out_wimlib_free;
3457                 }
3458                 if (image_properties.num_strings) {
3459                         imagex_error(T("Can't change image properties without "
3460                                        "specifying a specific image in a "
3461                                        "multi-image WIM"));
3462                         goto out_wimlib_free;
3463                 }
3464         }
3465
3466         /* Operations that print information are separated from operations that
3467          * recreate the WIM file. */
3468         if (!image_properties.num_strings && !boot) {
3469
3470                 /* Read-only operations */
3471
3472                 if (image == WIMLIB_NO_IMAGE) {
3473                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3474                                      image_num_or_name, wimfile);
3475                         goto out_wimlib_free;
3476                 }
3477
3478                 if (image == WIMLIB_ALL_IMAGES && short_header)
3479                         print_wim_information(wimfile, &info);
3480
3481                 if (header)
3482                         wimlib_print_header(wim);
3483
3484                 if (blobs) {
3485                         if (info.total_parts != 1) {
3486                                 tfprintf(stderr, T("Warning: Only showing the blobs "
3487                                                    "for part %d of a %d-part WIM.\n"),
3488                                          info.part_number, info.total_parts);
3489                         }
3490                         print_blobs(wim);
3491                 }
3492
3493                 if (xml) {
3494                         ret = wimlib_extract_xml_data(wim, stdout);
3495                         if (ret)
3496                                 goto out_wimlib_free;
3497                 }
3498
3499                 if (xml_out_file) {
3500                         FILE *fp;
3501
3502                         fp = tfopen(xml_out_file, T("wb"));
3503                         if (!fp) {
3504                                 imagex_error_with_errno(T("Failed to open the "
3505                                                           "file \"%"TS"\" for "
3506                                                           "writing"),
3507                                                         xml_out_file);
3508                                 ret = -1;
3509                                 goto out_wimlib_free;
3510                         }
3511                         ret = wimlib_extract_xml_data(wim, fp);
3512                         if (fclose(fp)) {
3513                                 imagex_error(T("Failed to close the file "
3514                                                "\"%"TS"\""),
3515                                              xml_out_file);
3516                                 ret = -1;
3517                         }
3518                         if (ret)
3519                                 goto out_wimlib_free;
3520                 }
3521
3522                 if (short_header)
3523                         wimlib_print_available_images(wim, image);
3524
3525                 ret = 0;
3526         } else {
3527                 /* Modification operations */
3528                 bool any_property_changes;
3529
3530                 if (image == WIMLIB_ALL_IMAGES)
3531                         image = 1;
3532
3533                 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3534                         imagex_error(T("Cannot change image properties "
3535                                        "when using image 0"));
3536                         ret = -1;
3537                         goto out_wimlib_free;
3538                 }
3539
3540                 if (boot) {
3541                         if (image == info.boot_index) {
3542                                 imagex_printf(T("Image %d is already marked as "
3543                                           "bootable.\n"), image);
3544                                 boot = false;
3545                         } else {
3546                                 imagex_printf(T("Marking image %d as bootable.\n"),
3547                                         image);
3548                                 info.boot_index = image;
3549                                 ret = wimlib_set_wim_info(wim, &info,
3550                                                           WIMLIB_CHANGE_BOOT_INDEX);
3551                                 if (ret)
3552                                         goto out_wimlib_free;
3553                         }
3554                 }
3555
3556                 ret = apply_image_properties(&image_properties, wim, image,
3557                                              &any_property_changes);
3558                 if (ret)
3559                         goto out_wimlib_free;
3560
3561                 /* Only call wimlib_overwrite() if something actually needs to
3562                  * be changed.  */
3563                 if (boot || any_property_changes ||
3564                     (check && !info.has_integrity_table) ||
3565                     (nocheck && info.has_integrity_table))
3566                 {
3567                         int write_flags = 0;
3568
3569                         if (check)
3570                                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3571                         if (nocheck)
3572                                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3573                         ret = wimlib_overwrite(wim, write_flags, 1);
3574                 } else {
3575                         imagex_printf(T("The file \"%"TS"\" was not modified "
3576                                         "because nothing needed to be done.\n"),
3577                                       wimfile);
3578                         ret = 0;
3579                 }
3580         }
3581 out_wimlib_free:
3582         wimlib_free(wim);
3583 out:
3584         string_list_destroy(&image_properties);
3585         return ret;
3586
3587 out_usage:
3588         usage(CMD_INFO, stderr);
3589 out_err:
3590         ret = -1;
3591         goto out;
3592 }
3593
3594 /* Join split WIMs into one part WIM */
3595 static int
3596 imagex_join(int argc, tchar **argv, int cmd)
3597 {
3598         int c;
3599         int swm_open_flags = 0;
3600         int wim_write_flags = 0;
3601         const tchar *output_path;
3602         int ret;
3603
3604         for_opt(c, join_options) {
3605                 switch (c) {
3606                 case IMAGEX_CHECK_OPTION:
3607                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3608                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3609                         break;
3610                 default:
3611                         goto out_usage;
3612                 }
3613         }
3614         argc -= optind;
3615         argv += optind;
3616
3617         if (argc < 2) {
3618                 imagex_error(T("Must specify one or more split WIM (.swm) "
3619                                "parts to join"));
3620                 goto out_usage;
3621         }
3622         output_path = argv[0];
3623         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3624                                         --argc,
3625                                         output_path,
3626                                         swm_open_flags,
3627                                         wim_write_flags,
3628                                         imagex_progress_func,
3629                                         NULL);
3630 out:
3631         return ret;
3632
3633 out_usage:
3634         usage(CMD_JOIN, stderr);
3635         ret = -1;
3636         goto out;
3637 }
3638
3639 #if WIM_MOUNTING_SUPPORTED
3640
3641 /* Mounts a WIM image.  */
3642 static int
3643 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3644 {
3645         int c;
3646         int mount_flags = 0;
3647         int open_flags = 0;
3648         const tchar *staging_dir = NULL;
3649         const tchar *wimfile;
3650         const tchar *dir;
3651         WIMStruct *wim;
3652         struct wimlib_wim_info info;
3653         int image;
3654         int ret;
3655
3656         STRING_LIST(refglobs);
3657
3658         if (cmd == CMD_MOUNTRW) {
3659                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3660                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3661         }
3662
3663         for_opt(c, mount_options) {
3664                 switch (c) {
3665                 case IMAGEX_ALLOW_OTHER_OPTION:
3666                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3667                         break;
3668                 case IMAGEX_CHECK_OPTION:
3669                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3670                         break;
3671                 case IMAGEX_DEBUG_OPTION:
3672                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3673                         break;
3674                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3675                         if (!tstrcasecmp(optarg, T("none")))
3676                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3677                         else if (!tstrcasecmp(optarg, T("xattr")))
3678                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3679                         else if (!tstrcasecmp(optarg, T("windows")))
3680                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3681                         else {
3682                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3683                                              optarg);
3684                                 goto out_usage;
3685                         }
3686                         break;
3687                 case IMAGEX_REF_OPTION:
3688                         ret = string_list_append(&refglobs, optarg);
3689                         if (ret)
3690                                 goto out_free_refglobs;
3691                         break;
3692                 case IMAGEX_STAGING_DIR_OPTION:
3693                         staging_dir = optarg;
3694                         break;
3695                 case IMAGEX_UNIX_DATA_OPTION:
3696                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3697                         break;
3698                 default:
3699                         goto out_usage;
3700                 }
3701         }
3702         argc -= optind;
3703         argv += optind;
3704         if (argc != 2 && argc != 3)
3705                 goto out_usage;
3706
3707         wimfile = argv[0];
3708
3709         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3710                                             imagex_progress_func, NULL);
3711         if (ret)
3712                 goto out_free_refglobs;
3713
3714         wimlib_get_wim_info(wim, &info);
3715
3716         if (argc >= 3) {
3717                 /* Image explicitly specified.  */
3718                 image = wimlib_resolve_image(wim, argv[1]);
3719                 dir = argv[2];
3720                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3721                 if (ret)
3722                         goto out_free_wim;
3723         } else {
3724                 /* No image specified; default to image 1, but only if the WIM
3725                  * contains exactly one image.  */
3726
3727                 if (info.image_count != 1) {
3728                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3729                                        "select one."), wimfile, info.image_count);
3730                         wimlib_free(wim);
3731                         goto out_usage;
3732                 }
3733                 image = 1;
3734                 dir = argv[1];
3735         }
3736
3737         if (refglobs.num_strings) {
3738                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3739                 if (ret)
3740                         goto out_free_wim;
3741         }
3742
3743         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3744         if (ret) {
3745                 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3746                         do_metadata_not_found_warning(wimfile, &info);
3747                 } else {
3748                         imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3749                                        "on \"%"TS"\""),
3750                                      image, wimfile, dir);
3751                 }
3752         }
3753 out_free_wim:
3754         wimlib_free(wim);
3755 out_free_refglobs:
3756         string_list_destroy(&refglobs);
3757         return ret;
3758
3759 out_usage:
3760         usage(cmd, stderr);
3761         ret = -1;
3762         goto out_free_refglobs;
3763 }
3764 #endif /* WIM_MOUNTING_SUPPORTED */
3765
3766 /* Rebuild a WIM file */
3767 static int
3768 imagex_optimize(int argc, tchar **argv, int cmd)
3769 {
3770         int c;
3771         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3772         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3773         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3774         uint32_t chunk_size = UINT32_MAX;
3775         uint32_t solid_chunk_size = UINT32_MAX;
3776         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3777         int ret;
3778         WIMStruct *wim;
3779         const tchar *wimfile;
3780         off_t old_size;
3781         off_t new_size;
3782         unsigned num_threads = 0;
3783
3784         for_opt(c, optimize_options) {
3785                 switch (c) {
3786                 case IMAGEX_CHECK_OPTION:
3787                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3788                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3789                         break;
3790                 case IMAGEX_NOCHECK_OPTION:
3791                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3792                         break;
3793                 case IMAGEX_COMPRESS_OPTION:
3794                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3795                         compression_type = get_compression_type(optarg, false);
3796                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3797                                 goto out_err;
3798                         break;
3799                 case IMAGEX_RECOMPRESS_OPTION:
3800                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3801                         break;
3802                 case IMAGEX_CHUNK_SIZE_OPTION:
3803                         chunk_size = parse_chunk_size(optarg);
3804                         if (chunk_size == UINT32_MAX)
3805                                 goto out_err;
3806                         break;
3807                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3808                         solid_chunk_size = parse_chunk_size(optarg);
3809                         if (solid_chunk_size == UINT32_MAX)
3810                                 goto out_err;
3811                         break;
3812                 case IMAGEX_SOLID_COMPRESS_OPTION:
3813                         solid_ctype = get_compression_type(optarg, true);
3814                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3815                                 goto out_err;
3816                         break;
3817                 case IMAGEX_SOLID_OPTION:
3818                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3819                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3820                         break;
3821                 case IMAGEX_NO_SOLID_SORT_OPTION:
3822                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3823                         break;
3824                 case IMAGEX_THREADS_OPTION:
3825                         num_threads = parse_num_threads(optarg);
3826                         if (num_threads == UINT_MAX)
3827                                 goto out_err;
3828                         break;
3829                 case IMAGEX_PIPABLE_OPTION:
3830                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3831                         break;
3832                 case IMAGEX_NOT_PIPABLE_OPTION:
3833                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3834                         break;
3835                 case IMAGEX_UNSAFE_COMPACT_OPTION:
3836                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3837                         break;
3838                 default:
3839                         goto out_usage;
3840                 }
3841         }
3842         argc -= optind;
3843         argv += optind;
3844
3845         if (argc != 1)
3846                 goto out_usage;
3847
3848         wimfile = argv[0];
3849
3850         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3851                                             imagex_progress_func, NULL);
3852         if (ret)
3853                 goto out;
3854
3855         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3856                 /* Change compression type.  */
3857                 ret = wimlib_set_output_compression_type(wim, compression_type);
3858                 if (ret)
3859                         goto out_wimlib_free;
3860         }
3861
3862         if (chunk_size != UINT32_MAX) {
3863                 /* Change chunk size.  */
3864                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3865                 if (ret)
3866                         goto out_wimlib_free;
3867         }
3868         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3869                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3870                 if (ret)
3871                         goto out_wimlib_free;
3872         }
3873         if (solid_chunk_size != UINT32_MAX) {
3874                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3875                 if (ret)
3876                         goto out_wimlib_free;
3877         }
3878
3879         old_size = file_get_size(wimfile);
3880         tprintf(T("\"%"TS"\" original size: "), wimfile);
3881         if (old_size == -1)
3882                 tputs(T("Unknown"));
3883         else
3884                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3885
3886         ret = wimlib_overwrite(wim, write_flags, num_threads);
3887         if (ret) {
3888                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3889                 goto out_wimlib_free;
3890         }
3891
3892         new_size = file_get_size(wimfile);
3893         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3894         if (new_size == -1)
3895                 tputs(T("Unknown"));
3896         else
3897                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3898
3899         tfputs(T("Space saved: "), stdout);
3900         if (new_size != -1 && old_size != -1) {
3901                 tprintf(T("%lld KiB\n"),
3902                        ((long long)old_size - (long long)new_size) >> 10);
3903         } else {
3904                 tputs(T("Unknown"));
3905         }
3906         ret = 0;
3907 out_wimlib_free:
3908         wimlib_free(wim);
3909 out:
3910         return ret;
3911
3912 out_usage:
3913         usage(CMD_OPTIMIZE, stderr);
3914 out_err:
3915         ret = -1;
3916         goto out;
3917 }
3918
3919 /* Split a WIM into a spanned set */
3920 static int
3921 imagex_split(int argc, tchar **argv, int cmd)
3922 {
3923         int c;
3924         int open_flags = 0;
3925         int write_flags = 0;
3926         unsigned long part_size;
3927         tchar *tmp;
3928         int ret;
3929         WIMStruct *wim;
3930
3931         for_opt(c, split_options) {
3932                 switch (c) {
3933                 case IMAGEX_CHECK_OPTION:
3934                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3935                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3936                         break;
3937                 default:
3938                         goto out_usage;
3939                 }
3940         }
3941         argc -= optind;
3942         argv += optind;
3943
3944         if (argc != 3)
3945                 goto out_usage;
3946
3947         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3948         if (tmp == argv[2] || *tmp) {
3949                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3950                 imagex_error(T("The part size must be an integer or "
3951                                "floating-point number of megabytes."));
3952                 goto out_err;
3953         }
3954         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3955                                             imagex_progress_func, NULL);
3956         if (ret)
3957                 goto out;
3958
3959         ret = wimlib_split(wim, argv[1], part_size, write_flags);
3960         wimlib_free(wim);
3961 out:
3962         return ret;
3963
3964 out_usage:
3965         usage(CMD_SPLIT, stderr);
3966 out_err:
3967         ret = -1;
3968         goto out;
3969 }
3970
3971 #if WIM_MOUNTING_SUPPORTED
3972 /* Unmounts a mounted WIM image. */
3973 static int
3974 imagex_unmount(int argc, tchar **argv, int cmd)
3975 {
3976         int c;
3977         int unmount_flags = 0;
3978         int ret;
3979
3980         for_opt(c, unmount_options) {
3981                 switch (c) {
3982                 case IMAGEX_COMMIT_OPTION:
3983                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3984                         break;
3985                 case IMAGEX_CHECK_OPTION:
3986                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3987                         break;
3988                 case IMAGEX_REBUILD_OPTION:
3989                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3990                         break;
3991                 case IMAGEX_LAZY_OPTION:
3992                 case IMAGEX_FORCE_OPTION:
3993                         /* Now, unmount is lazy by default.  However, committing
3994                          * the image will fail with
3995                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3996                          * file descriptors on the WIM image.  The
3997                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3998                          * descriptors to be closed.  */
3999                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4000                         break;
4001                 case IMAGEX_NEW_IMAGE_OPTION:
4002                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4003                         break;
4004                 default:
4005                         goto out_usage;
4006                 }
4007         }
4008         argc -= optind;
4009         argv += optind;
4010         if (argc != 1)
4011                 goto out_usage;
4012
4013         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4014                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4015                         imagex_error(T("--new-image is meaningless "
4016                                        "without --commit also specified!"));
4017                         goto out_err;
4018                 }
4019         }
4020
4021         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4022                                                  imagex_progress_func, NULL);
4023         if (ret) {
4024                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4025                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4026                         imagex_printf(T(
4027                                 "\tNote: Use --commit --force to force changes "
4028                                         "to be committed, regardless\n"
4029                                 "\t      of open files.\n"));
4030                 }
4031         }
4032 out:
4033         return ret;
4034
4035 out_usage:
4036         usage(CMD_UNMOUNT, stderr);
4037 out_err:
4038         ret = -1;
4039         goto out;
4040 }
4041 #endif /* WIM_MOUNTING_SUPPORTED */
4042
4043 /*
4044  * Add, delete, or rename files in a WIM image.
4045  */
4046 static int
4047 imagex_update(int argc, tchar **argv, int cmd)
4048 {
4049         const tchar *wimfile;
4050         int image;
4051         WIMStruct *wim;
4052         int ret;
4053         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4054         int write_flags = 0;
4055         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4056         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4057                                 WIMLIB_ADD_FLAG_VERBOSE |
4058                                 WIMLIB_ADD_FLAG_WINCONFIG;
4059         int default_delete_flags = 0;
4060         unsigned num_threads = 0;
4061         int c;
4062         tchar *cmd_file_contents;
4063         size_t cmd_file_nchars;
4064         struct wimlib_update_command *cmds;
4065         size_t num_cmds;
4066         tchar *command_str = NULL;
4067         tchar *config_file = NULL;
4068         tchar *wimboot_config = NULL;
4069
4070         for_opt(c, update_options) {
4071                 switch (c) {
4072                 /* Generic or write options */
4073                 case IMAGEX_THREADS_OPTION:
4074                         num_threads = parse_num_threads(optarg);
4075                         if (num_threads == UINT_MAX)
4076                                 goto out_err;
4077                         break;
4078                 case IMAGEX_CHECK_OPTION:
4079                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4080                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4081                         break;
4082                 case IMAGEX_REBUILD_OPTION:
4083                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4084                         break;
4085                 case IMAGEX_COMMAND_OPTION:
4086                         if (command_str) {
4087                                 imagex_error(T("--command may only be specified "
4088                                                "one time.  Please provide\n"
4089                                                "       the update commands "
4090                                                "on standard input instead."));
4091                                 goto out_err;
4092                         }
4093                         command_str = tstrdup(optarg);
4094                         if (!command_str) {
4095                                 imagex_error(T("Out of memory!"));
4096                                 goto out_err;
4097                         }
4098                         break;
4099                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4100                         wimboot_config = optarg;
4101                         break;
4102                 /* Default delete options */
4103                 case IMAGEX_FORCE_OPTION:
4104                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4105                         break;
4106                 case IMAGEX_RECURSIVE_OPTION:
4107                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4108                         break;
4109
4110                 /* Global add option */
4111                 case IMAGEX_CONFIG_OPTION:
4112                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4113                         config_file = optarg;
4114                         break;
4115
4116                 /* Default add options */
4117                 case IMAGEX_VERBOSE_OPTION:
4118                         /* No longer does anything.  */
4119                         break;
4120                 case IMAGEX_DEREFERENCE_OPTION:
4121                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4122                         break;
4123                 case IMAGEX_UNIX_DATA_OPTION:
4124                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4125                         break;
4126                 case IMAGEX_NO_ACLS_OPTION:
4127                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4128                         break;
4129                 case IMAGEX_STRICT_ACLS_OPTION:
4130                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4131                         break;
4132                 case IMAGEX_NO_REPLACE_OPTION:
4133                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4134                         break;
4135                 case IMAGEX_UNSAFE_COMPACT_OPTION:
4136                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4137                         break;
4138                 default:
4139                         goto out_usage;
4140                 }
4141         }
4142         argv += optind;
4143         argc -= optind;
4144
4145         if (argc != 1 && argc != 2)
4146                 goto out_usage;
4147         wimfile = argv[0];
4148
4149         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4150                                             imagex_progress_func, NULL);
4151         if (ret)
4152                 goto out_free_command_str;
4153
4154         if (argc >= 2) {
4155                 /* Image explicitly specified.  */
4156                 image = wimlib_resolve_image(wim, argv[1]);
4157                 ret = verify_image_exists_and_is_single(image, argv[1],
4158                                                         wimfile);
4159                 if (ret)
4160                         goto out_wimlib_free;
4161         } else {
4162                 /* No image specified; default to image 1, but only if the WIM
4163                  * contains exactly one image.  */
4164                 struct wimlib_wim_info info;
4165
4166                 wimlib_get_wim_info(wim, &info);
4167                 if (info.image_count != 1) {
4168                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4169                                      wimfile, info.image_count);
4170                         wimlib_free(wim);
4171                         goto out_usage;
4172                 }
4173                 image = 1;
4174         }
4175
4176         /* Read update commands from standard input, or the command string if
4177          * specified.  */
4178         if (command_str) {
4179                 cmd_file_contents = NULL;
4180                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4181                                                  &num_cmds);
4182                 if (!cmds) {
4183                         ret = -1;
4184                         goto out_free_cmd_file_contents;
4185                 }
4186         } else if (!wimboot_config) {
4187                 if (isatty(STDIN_FILENO)) {
4188                         tputs(T("Reading update commands from standard input..."));
4189                         recommend_man_page(CMD_UPDATE, stdout);
4190                 }
4191                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4192                 if (!cmd_file_contents) {
4193                         ret = -1;
4194                         goto out_wimlib_free;
4195                 }
4196
4197                 /* Parse the update commands */
4198                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4199                                                  &num_cmds);
4200                 if (!cmds) {
4201                         ret = -1;
4202                         goto out_free_cmd_file_contents;
4203                 }
4204         } else {
4205                 cmd_file_contents = NULL;
4206                 cmds = NULL;
4207                 num_cmds = 0;
4208         }
4209
4210         /* Set default flags and capture config on the update commands */
4211         for (size_t i = 0; i < num_cmds; i++) {
4212                 switch (cmds[i].op) {
4213                 case WIMLIB_UPDATE_OP_ADD:
4214                         cmds[i].add.add_flags |= default_add_flags;
4215                         cmds[i].add.config_file = config_file;
4216                         break;
4217                 case WIMLIB_UPDATE_OP_DELETE:
4218                         cmds[i].delete_.delete_flags |= default_delete_flags;
4219                         break;
4220                 default:
4221                         break;
4222                 }
4223         }
4224
4225         /* Execute the update commands */
4226         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4227         if (ret)
4228                 goto out_free_cmds;
4229
4230         if (wimboot_config) {
4231                 /* --wimboot-config=FILE is short for an
4232                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
4233                  */
4234                 struct wimlib_update_command cmd;
4235
4236                 cmd.op = WIMLIB_UPDATE_OP_ADD;
4237                 cmd.add.fs_source_path = wimboot_config;
4238                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4239                 cmd.add.config_file = NULL;
4240                 cmd.add.add_flags = 0;
4241
4242                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4243                 if (ret)
4244                         goto out_free_cmds;
4245         }
4246
4247         /* Overwrite the updated WIM */
4248         ret = wimlib_overwrite(wim, write_flags, num_threads);
4249 out_free_cmds:
4250         free(cmds);
4251 out_free_cmd_file_contents:
4252         free(cmd_file_contents);
4253 out_wimlib_free:
4254         wimlib_free(wim);
4255 out_free_command_str:
4256         free(command_str);
4257         return ret;
4258
4259 out_usage:
4260         usage(CMD_UPDATE, stderr);
4261 out_err:
4262         ret = -1;
4263         goto out_free_command_str;
4264 }
4265
4266 /* Verify a WIM file.  */
4267 static int
4268 imagex_verify(int argc, tchar **argv, int cmd)
4269 {
4270         int ret;
4271         const tchar *wimfile;
4272         WIMStruct *wim;
4273         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4274         int verify_flags = 0;
4275         STRING_LIST(refglobs);
4276         int c;
4277
4278         for_opt(c, verify_options) {
4279                 switch (c) {
4280                 case IMAGEX_REF_OPTION:
4281                         ret = string_list_append(&refglobs, optarg);
4282                         if (ret)
4283                                 goto out_free_refglobs;
4284                         break;
4285                 case IMAGEX_NOCHECK_OPTION:
4286                         open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4287                         break;
4288                 default:
4289                         goto out_usage;
4290                 }
4291         }
4292
4293         argv += optind;
4294         argc -= optind;
4295
4296         if (argc != 1) {
4297                 if (argc == 0)
4298                         imagex_error(T("Must specify a WIM file!"));
4299                 else
4300                         imagex_error(T("At most one WIM file can be specified!"));
4301                 goto out_usage;
4302         }
4303
4304         wimfile = argv[0];
4305
4306         ret = wimlib_open_wim_with_progress(wimfile,
4307                                             open_flags,
4308                                             &wim,
4309                                             imagex_progress_func,
4310                                             NULL);
4311         if (ret)
4312                 goto out_free_refglobs;
4313
4314         ret = wim_reference_globs(wim, &refglobs, open_flags);
4315         if (ret)
4316                 goto out_wimlib_free;
4317
4318         ret = wimlib_verify_wim(wim, verify_flags);
4319         if (ret) {
4320                 tputc(T('\n'), stderr);
4321                 imagex_error(T("\"%"TS"\" failed verification!"),
4322                              wimfile);
4323                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4324                     refglobs.num_strings == 0)
4325                 {
4326                         imagex_printf(T("Note: if this WIM file is not standalone, "
4327                                         "use the --ref option to specify the other parts.\n"));
4328                 }
4329         } else {
4330                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4331                               wimfile);
4332         }
4333
4334 out_wimlib_free:
4335         wimlib_free(wim);
4336 out_free_refglobs:
4337         string_list_destroy(&refglobs);
4338         return ret;
4339
4340 out_usage:
4341         usage(CMD_VERIFY, stderr);
4342         ret = -1;
4343         goto out_free_refglobs;
4344 }
4345
4346 struct imagex_command {
4347         const tchar *name;
4348         int (*func)(int argc, tchar **argv, int cmd);
4349 };
4350
4351 static const struct imagex_command imagex_commands[] = {
4352         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4353         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4354         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4355         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4356         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4357         [CMD_EXPORT]   = {T("export"),   imagex_export},
4358         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4359         [CMD_INFO]     = {T("info"),     imagex_info},
4360         [CMD_JOIN]     = {T("join"),     imagex_join},
4361 #if WIM_MOUNTING_SUPPORTED
4362         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4363         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4364 #endif
4365         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4366         [CMD_SPLIT]    = {T("split"),    imagex_split},
4367 #if WIM_MOUNTING_SUPPORTED
4368         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4369 #endif
4370         [CMD_UPDATE]   = {T("update"),   imagex_update},
4371         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4372 };
4373
4374 #ifdef __WIN32__
4375
4376    /* Can be a directory or source list file.  But source list file is probably
4377     * a rare use case, so just say directory.  */
4378 #  define SOURCE_STR T("DIRECTORY")
4379
4380    /* Can only be a directory  */
4381 #  define TARGET_STR T("DIRECTORY")
4382
4383 #else
4384    /* Can be a directory, NTFS volume, or source list file. */
4385 #  define SOURCE_STR T("SOURCE")
4386
4387    /* Can be a directory or NTFS volume.  */
4388 #  define TARGET_STR T("TARGET")
4389
4390 #endif
4391
4392 static const tchar * const usage_strings[] = {
4393 [CMD_APPEND] =
4394 T(
4395 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4396 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4397 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4398 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4399 "                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4400 "                    [--dereference] [--snapshot]\n"
4401 ),
4402 [CMD_APPLY] =
4403 T(
4404 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4405 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4406 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4407 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4408 "                    [--compact=FORMAT]\n"
4409 ),
4410 [CMD_CAPTURE] =
4411 T(
4412 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4413 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4414 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4415 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4416 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4417 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4418 "                    [--snapshot]\n"
4419 ),
4420 [CMD_DELETE] =
4421 T(
4422 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4423 ),
4424 [CMD_DIR] =
4425 T(
4426 "    %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4427 ),
4428 [CMD_EXPORT] =
4429 T(
4430 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4431 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4432 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4433 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4434 "                    [--wimboot] [--solid]\n"
4435 ),
4436 [CMD_EXTRACT] =
4437 T(
4438 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4439 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4440 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4441 "                    [--no-attributes] [--include-invalid-names]\n"
4442 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4443 ),
4444 [CMD_INFO] =
4445 T(
4446 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4447 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4448 "                    [--extract-xml FILE] [--header] [--blobs]\n"
4449 "                    [--image-property NAME=VALUE]\n"
4450 ),
4451 [CMD_JOIN] =
4452 T(
4453 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4454 ),
4455 #if WIM_MOUNTING_SUPPORTED
4456 [CMD_MOUNT] =
4457 T(
4458 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4459 "                    [--check] [--streams-interface=INTERFACE]\n"
4460 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4461 ),
4462 [CMD_MOUNTRW] =
4463 T(
4464 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4465 "                    [--check] [--streams-interface=INTERFACE]\n"
4466 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4467 ),
4468 #endif
4469 [CMD_OPTIMIZE] =
4470 T(
4471 "    %"TS" WIMFILE\n"
4472 "                    [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4473 "                    [--check] [--nocheck] [--solid]\n"
4474 "\n"
4475 ),
4476 [CMD_SPLIT] =
4477 T(
4478 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4479 ),
4480 #if WIM_MOUNTING_SUPPORTED
4481 [CMD_UNMOUNT] =
4482 T(
4483 "    %"TS" DIRECTORY\n"
4484 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4485 ),
4486 #endif
4487 [CMD_UPDATE] =
4488 T(
4489 "    %"TS" WIMFILE [IMAGE]\n"
4490 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4491 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4492 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4493 "                    [< CMDFILE]\n"
4494 ),
4495 [CMD_VERIFY] =
4496 T(
4497 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4498 ),
4499 };
4500
4501 static const tchar *invocation_name;
4502 static int invocation_cmd = CMD_NONE;
4503
4504 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4505 {
4506         static tchar buf[50];
4507
4508         if (cmd == CMD_NONE)
4509                 return T("wimlib-imagex");
4510
4511         if (only_short_form || invocation_cmd != CMD_NONE) {
4512                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4513         } else {
4514                 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4515                          imagex_commands[cmd].name);
4516         }
4517         return buf;
4518 }
4519
4520 static void
4521 version(void)
4522 {
4523         static const tchar * const fmt =
4524         T(
4525 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4526 "Copyright (C) 2012-2018 Eric Biggers\n"
4527 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4528 "This is free software: you are free to change and redistribute it.\n"
4529 "There is NO WARRANTY, to the extent permitted by law.\n"
4530 "\n"
4531 "Report bugs to "PACKAGE_BUGREPORT".\n"
4532         );
4533         tfprintf(stdout, fmt, wimlib_get_version_string());
4534 }
4535
4536 static void
4537 do_common_options(int *argc_p, tchar **argv, int cmd)
4538 {
4539         int argc = *argc_p;
4540         int i;
4541         const tchar *p;
4542
4543         for (i = 1; i < argc; i++) {
4544                 p = argv[i];
4545                 if (p[0] == T('-') && p[1] == T('-')) {
4546                         p += 2;
4547                         if (!tstrcmp(p, T("help"))) {
4548                                 if (cmd == CMD_NONE)
4549                                         usage_all(stdout);
4550                                 else
4551                                         usage(cmd, stdout);
4552                                 exit(0);
4553                         } else if (!tstrcmp(p, T("version"))) {
4554                                 version();
4555                                 exit(0);
4556                         } else if (!tstrcmp(p, T("quiet"))) {
4557                                 imagex_suppress_output();
4558                                 memmove(&argv[i], &argv[i + 1],
4559                                         (argc - i) * sizeof(argv[i]));
4560                                 argc--;
4561                                 i--;
4562                         } else if (!*p) /* reached "--", no more options */
4563                                 break;
4564                 }
4565         }
4566
4567         *argc_p = argc;
4568 }
4569
4570 static void
4571 print_usage_string(int cmd, FILE *fp)
4572 {
4573         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4574 }
4575
4576 static void
4577 recommend_man_page(int cmd, FILE *fp)
4578 {
4579         const tchar *format_str;
4580 #ifdef __WIN32__
4581         format_str = T("Some uncommon options are not listed;\n"
4582                        "See %"TS".pdf in the doc directory for more details.\n");
4583 #else
4584         format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4585 #endif
4586         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4587 }
4588
4589 static void
4590 usage(int cmd, FILE *fp)
4591 {
4592         tfprintf(fp, T("Usage:\n"));
4593         print_usage_string(cmd, fp);
4594         tfprintf(fp, T("\n"));
4595         recommend_man_page(cmd, fp);
4596 }
4597
4598 static void
4599 usage_all(FILE *fp)
4600 {
4601         tfprintf(fp, T("Usage:\n"));
4602         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4603                 print_usage_string(cmd, fp);
4604                 tfprintf(fp, T("\n"));
4605         }
4606         static const tchar * const extra =
4607         T(
4608 "    %"TS" --help\n"
4609 "    %"TS" --version\n"
4610 "\n"
4611         );
4612         tfprintf(fp, extra, invocation_name, invocation_name);
4613         tfprintf(fp,
4614                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4615                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4616                    "For some commands IMAGE may be \"all\".\n"
4617                    "\n"));
4618         recommend_man_page(CMD_NONE, fp);
4619 }
4620
4621 #ifdef __WIN32__
4622 extern int wmain(int argc, wchar_t **argv);
4623 #define main wmain
4624 #endif
4625
4626 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4627  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4628  * something else), while on Windows the command arguments will be UTF-16LE
4629  * encoded 'wchar_t' strings. */
4630 int
4631 main(int argc, tchar **argv)
4632 {
4633         int ret;
4634         int init_flags = 0;
4635         int cmd;
4636
4637         imagex_info_file = stdout;
4638         invocation_name = tbasename(argv[0]);
4639
4640         {
4641                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4642                 if (igcase != NULL) {
4643                         if (!tstrcmp(igcase, T("no")) ||
4644                             !tstrcmp(igcase, T("0")))
4645                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4646                         else if (!tstrcmp(igcase, T("yes")) ||
4647                                  !tstrcmp(igcase, T("1")))
4648                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4649                         else {
4650                                 fprintf(stderr,
4651                                         "WARNING: Ignoring unknown setting of "
4652                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4653                         }
4654                 }
4655         }
4656
4657         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4658         cmd = CMD_NONE;
4659         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4660             tstrcmp(invocation_name, T("wimlib-imagex"))) {
4661                 for (int i = 0; i < CMD_MAX; i++) {
4662                         if (!tstrcmp(invocation_name + 3,
4663                                      imagex_commands[i].name))
4664                         {
4665                                 invocation_cmd = i;
4666                                 cmd = i;
4667                                 break;
4668                         }
4669                 }
4670         }
4671
4672         /* Unless already known from the invocation name, determine which
4673          * command was specified.  */
4674         if (cmd == CMD_NONE) {
4675                 if (argc < 2) {
4676                         imagex_error(T("No command specified!\n"));
4677                         usage_all(stderr);
4678                         exit(2);
4679                 }
4680                 for (int i = 0; i < CMD_MAX; i++) {
4681                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4682                                 cmd = i;
4683                                 break;
4684                         }
4685                 }
4686                 if (cmd != CMD_NONE) {
4687                         argc--;
4688                         argv++;
4689                 }
4690         }
4691
4692         /* Handle common options.  May exit early (for --help or --version).  */
4693         do_common_options(&argc, argv, cmd);
4694
4695         /* Bail if a valid command was not specified.  */
4696         if (cmd == CMD_NONE) {
4697                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4698                 usage_all(stderr);
4699                 exit(2);
4700         }
4701
4702         /* Enable warning and error messages in wimlib to be more user-friendly.
4703          * */
4704         wimlib_set_print_errors(true);
4705
4706         /* Initialize wimlib.  */
4707         ret = wimlib_global_init(init_flags);
4708         if (ret)
4709                 goto out_check_status;
4710
4711         /* Call the command handler function.  */
4712         ret = imagex_commands[cmd].func(argc, argv, cmd);
4713
4714         /* Check for error writing to standard output, especially since for some
4715          * commands, writing to standard output is part of the program's actual
4716          * behavior and not just for informational purposes.  */
4717         if (ferror(stdout) || fclose(stdout)) {
4718                 imagex_error_with_errno(T("error writing to standard output"));
4719                 if (ret == 0)
4720                         ret = -1;
4721         }
4722 out_check_status:
4723         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4724          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4725          * error code from which an error message can be printed.  */
4726         if (ret > 0) {
4727                 imagex_error(T("Exiting with error code %d:\n"
4728                                "       %"TS"."), ret,
4729                              wimlib_get_error_string(ret));
4730                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4731                         imagex_error_with_errno(T("errno"));
4732         }
4733         /* Make wimlib free any resources it's holding (although this is not
4734          * strictly necessary because the process is ending anyway).  */
4735         wimlib_global_cleanup();
4736         return ret;
4737 }