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