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