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