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