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