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