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