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