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