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