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