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