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