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