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