]> wimlib.net Git - wimlib/blob - programs/imagex.c
Forbid modifying multi-referenced images
[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                    compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2127                 ret = wimlib_set_output_chunk_size(wim, 4096);
2128                 if (ret)
2129                         goto out_free_wim;
2130         }
2131         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2132                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2133                 if (ret)
2134                         goto out_free_wim;
2135         }
2136         if (solid_chunk_size != UINT32_MAX) {
2137                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2138                 if (ret)
2139                         goto out_free_wim;
2140         }
2141
2142 #ifndef __WIN32__
2143         /* Detect if source is regular file or block device and set NTFS volume
2144          * capture mode.  */
2145         if (!source_list) {
2146                 struct stat stbuf;
2147
2148                 if (tstat(source, &stbuf) == 0) {
2149                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2150                                 imagex_printf(T("Capturing WIM image from NTFS "
2151                                           "filesystem on \"%"TS"\"\n"), source);
2152                                 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2153                         }
2154                 } else {
2155                         if (errno != ENOENT) {
2156                                 imagex_error_with_errno(T("Failed to stat "
2157                                                           "\"%"TS"\""), source);
2158                                 ret = -1;
2159                                 goto out_free_wim;
2160                         }
2161                 }
2162         }
2163 #endif
2164
2165         /* If the user did not specify an image name, and the basename of the
2166          * source already exists as an image name in the WIM file, append a
2167          * suffix to make it unique. */
2168         if (cmd == CMD_APPEND && name_defaulted) {
2169                 unsigned long conflict_idx;
2170                 tchar *name_end = tstrchr(name, T('\0'));
2171                 for (conflict_idx = 1;
2172                      wimlib_image_name_in_use(wim, name);
2173                      conflict_idx++)
2174                 {
2175                         tsprintf(name_end, T(" (%lu)"), conflict_idx);
2176                 }
2177         }
2178
2179         /* If capturing a delta WIM, reference resources from the base WIMs
2180          * before adding the new image.  */
2181         if (base_wimfiles.num_strings) {
2182                 base_wims = calloc(base_wimfiles.num_strings,
2183                                    sizeof(base_wims[0]));
2184                 if (base_wims == NULL) {
2185                         imagex_error(T("Out of memory!"));
2186                         ret = -1;
2187                         goto out_free_wim;
2188                 }
2189
2190                 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2191                         ret = wimlib_open_wim_with_progress(
2192                                     base_wimfiles.strings[i], open_flags,
2193                                     &base_wims[i], imagex_progress_func, NULL);
2194                         if (ret)
2195                                 goto out_free_base_wims;
2196
2197                 }
2198
2199                 ret = wimlib_reference_resources(wim, base_wims,
2200                                                  base_wimfiles.num_strings, 0);
2201                 if (ret)
2202                         goto out_free_base_wims;
2203
2204                 if (base_wimfiles.num_strings == 1) {
2205                         imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2206                                       base_wimfiles.strings[0]);
2207                 } else {
2208                         imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2209                                       base_wimfiles.num_strings);
2210                 }
2211
2212         } else {
2213                 base_wims = NULL;
2214         }
2215
2216         /* If capturing or appending as an update of an existing (template) image,
2217          * open the WIM if needed and parse the image index.  */
2218         if (template_image_name_or_num) {
2219
2220
2221                 if (base_wimfiles.num_strings == 1 &&
2222                     template_wimfile == base_wimfiles.strings[0]) {
2223                         template_wim = base_wims[0];
2224                 } else if (template_wimfile == wimfile) {
2225                         template_wim = wim;
2226                 } else {
2227                         ret = wimlib_open_wim_with_progress(template_wimfile,
2228                                                             open_flags,
2229                                                             &template_wim,
2230                                                             imagex_progress_func,
2231                                                             NULL);
2232                         if (ret)
2233                                 goto out_free_base_wims;
2234                 }
2235
2236                 template_image = wimlib_resolve_image(template_wim,
2237                                                       template_image_name_or_num);
2238
2239                 if (template_image_name_or_num[0] == T('-')) {
2240                         tchar *tmp;
2241                         unsigned long n;
2242                         struct wimlib_wim_info info;
2243
2244                         wimlib_get_wim_info(template_wim, &info);
2245                         n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2246                         if (n >= 1 && n <= info.image_count &&
2247                             *tmp == T('\0') &&
2248                             tmp != template_image_name_or_num + 1)
2249                         {
2250                                 template_image = info.image_count - (n - 1);
2251                         }
2252                 }
2253                 ret = verify_image_exists_and_is_single(template_image,
2254                                                         template_image_name_or_num,
2255                                                         template_wimfile);
2256                 if (ret)
2257                         goto out_free_template_wim;
2258         } else {
2259                 template_wim = NULL;
2260         }
2261
2262         ret = wimlib_add_image_multisource(wim,
2263                                            capture_sources,
2264                                            num_sources,
2265                                            name,
2266                                            config_file,
2267                                            add_flags);
2268         if (ret)
2269                 goto out_free_template_wim;
2270
2271         if (desc || flags_element || template_image_name_or_num) {
2272                 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2273                  * on which the added one is to be based has been specified with
2274                  * --update-of.  Get the index of the image we just
2275                  *  added, then use it to call the appropriate functions.  */
2276                 struct wimlib_wim_info info;
2277
2278                 wimlib_get_wim_info(wim, &info);
2279
2280                 if (desc) {
2281                         ret = wimlib_set_image_descripton(wim,
2282                                                           info.image_count,
2283                                                           desc);
2284                         if (ret)
2285                                 goto out_free_template_wim;
2286                 }
2287
2288                 if (flags_element) {
2289                         ret = wimlib_set_image_flags(wim, info.image_count,
2290                                                      flags_element);
2291                         if (ret)
2292                                 goto out_free_template_wim;
2293                 }
2294
2295                 /* Reference template image if the user provided one.  */
2296                 if (template_image_name_or_num) {
2297                         imagex_printf(T("Using image %d "
2298                                         "from \"%"TS"\" as template\n"),
2299                                         template_image, template_wimfile);
2300                         ret = wimlib_reference_template_image(wim,
2301                                                               info.image_count,
2302                                                               template_wim,
2303                                                               template_image,
2304                                                               0);
2305                         if (ret)
2306                                 goto out_free_template_wim;
2307                 }
2308         }
2309
2310         /* Write the new WIM or overwrite the existing WIM with the new image
2311          * appended.  */
2312         if (cmd == CMD_APPEND) {
2313                 ret = wimlib_overwrite(wim, write_flags, num_threads);
2314         } else if (wimfile) {
2315                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2316                                    write_flags, num_threads);
2317         } else {
2318                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2319                                          write_flags, num_threads);
2320         }
2321 out_free_template_wim:
2322         /* template_wim may alias base_wims[0] or wim.  */
2323         if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2324             template_wim != wim)
2325                 wimlib_free(template_wim);
2326 out_free_base_wims:
2327         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2328                 wimlib_free(base_wims[i]);
2329         free(base_wims);
2330 out_free_wim:
2331         wimlib_free(wim);
2332 out_free_capture_sources:
2333         if (capture_sources_malloced)
2334                 free(capture_sources);
2335 out_free_source_list_contents:
2336         free(source_list_contents);
2337 out_free_base_wimfiles:
2338         string_set_destroy(&base_wimfiles);
2339         return ret;
2340
2341 out_usage:
2342         usage(cmd, stderr);
2343 out_err:
2344         ret = -1;
2345         goto out_free_base_wimfiles;
2346 }
2347
2348 /* Remove image(s) from a WIM. */
2349 static int
2350 imagex_delete(int argc, tchar **argv, int cmd)
2351 {
2352         int c;
2353         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2354         int write_flags = 0;
2355         const tchar *wimfile;
2356         const tchar *image_num_or_name;
2357         WIMStruct *wim;
2358         int image;
2359         int ret;
2360
2361         for_opt(c, delete_options) {
2362                 switch (c) {
2363                 case IMAGEX_CHECK_OPTION:
2364                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2365                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2366                         break;
2367                 case IMAGEX_SOFT_OPTION:
2368                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2369                         break;
2370                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2371                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2372                         break;
2373                 default:
2374                         goto out_usage;
2375                 }
2376         }
2377         argc -= optind;
2378         argv += optind;
2379
2380         if (argc != 2) {
2381                 if (argc < 1)
2382                         imagex_error(T("Must specify a WIM file"));
2383                 if (argc < 2)
2384                         imagex_error(T("Must specify an image"));
2385                 goto out_usage;
2386         }
2387         wimfile = argv[0];
2388         image_num_or_name = argv[1];
2389
2390         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2391                                             imagex_progress_func, NULL);
2392         if (ret)
2393                 goto out;
2394
2395         image = wimlib_resolve_image(wim, image_num_or_name);
2396
2397         ret = verify_image_exists(image, image_num_or_name, wimfile);
2398         if (ret)
2399                 goto out_wimlib_free;
2400
2401         ret = wimlib_delete_image(wim, image);
2402         if (ret) {
2403                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2404                              wimfile);
2405                 goto out_wimlib_free;
2406         }
2407
2408         ret = wimlib_overwrite(wim, write_flags, 0);
2409         if (ret) {
2410                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2411                                "deleted"), wimfile);
2412         }
2413 out_wimlib_free:
2414         wimlib_free(wim);
2415 out:
2416         return ret;
2417
2418 out_usage:
2419         usage(CMD_DELETE, stderr);
2420         ret = -1;
2421         goto out;
2422 }
2423
2424 struct print_dentry_options {
2425         bool detailed;
2426 };
2427
2428 static void
2429 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2430 {
2431         tprintf(T("%"TS"\n"), dentry->full_path);
2432 }
2433
2434 static const struct {
2435         uint32_t flag;
2436         const tchar *name;
2437 } file_attr_flags[] = {
2438         {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
2439         {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
2440         {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
2441         {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
2442         {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
2443         {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
2444         {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
2445         {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
2446         {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
2447         {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
2448         {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
2449         {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
2450         {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2451         {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
2452         {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
2453 };
2454
2455 #define TIMESTR_MAX 100
2456
2457 static void
2458 timespec_to_string(const struct timespec *spec, tchar *buf)
2459 {
2460         time_t t = spec->tv_sec;
2461         struct tm tm;
2462         gmtime_r(&t, &tm);
2463         tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2464         buf[TIMESTR_MAX - 1] = '\0';
2465 }
2466
2467 static void
2468 print_time(const tchar *type, const struct timespec *spec)
2469 {
2470         tchar timestr[TIMESTR_MAX];
2471
2472         timespec_to_string(spec, timestr);
2473
2474         tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2475 }
2476
2477 static void print_byte_field(const uint8_t field[], size_t len)
2478 {
2479         while (len--)
2480                 tprintf(T("%02hhx"), *field++);
2481 }
2482
2483 static void
2484 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2485 {
2486         tchar attr_string[256];
2487         tchar *p;
2488
2489         tputs(T("WIM Information:"));
2490         tputs(T("----------------"));
2491         tprintf(T("Path:           %"TS"\n"), wimfile);
2492         tprintf(T("GUID:           0x"));
2493         print_byte_field(info->guid, sizeof(info->guid));
2494         tputchar(T('\n'));
2495         tprintf(T("Version:        %u\n"), info->wim_version);
2496         tprintf(T("Image Count:    %d\n"), info->image_count);
2497         tprintf(T("Compression:    %"TS"\n"),
2498                 wimlib_get_compression_type_string(info->compression_type));
2499         tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
2500                 info->chunk_size);
2501         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2502         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2503         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2504
2505         attr_string[0] = T('\0');
2506
2507         if (info->pipable)
2508                 tstrcat(attr_string, T("Pipable, "));
2509
2510         if (info->has_integrity_table)
2511                 tstrcat(attr_string, T("Integrity info, "));
2512
2513         if (info->has_rpfix)
2514                 tstrcat(attr_string, T("Relative path junction, "));
2515
2516         if (info->resource_only)
2517                 tstrcat(attr_string, T("Resource only, "));
2518
2519         if (info->metadata_only)
2520                 tstrcat(attr_string, T("Metadata only, "));
2521
2522         if (info->is_marked_readonly)
2523                 tstrcat(attr_string, T("Readonly, "));
2524
2525         p = tstrchr(attr_string, T('\0'));
2526         if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2527                 p[-2] = T('\0');
2528
2529         tprintf(T("Attributes:     %"TS"\n\n"), attr_string);
2530 }
2531
2532 static int
2533 print_resource(const struct wimlib_resource_entry *resource,
2534                void *_ignore)
2535 {
2536         tprintf(T("Hash              = 0x"));
2537         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2538         tputchar(T('\n'));
2539
2540         if (!resource->is_missing) {
2541                 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2542                         resource->uncompressed_size);
2543                 if (resource->packed) {
2544                         tprintf(T("Solid resource    = %"PRIu64" => %"PRIu64" "
2545                                   "bytes @ offset %"PRIu64"\n"),
2546                                 resource->raw_resource_uncompressed_size,
2547                                 resource->raw_resource_compressed_size,
2548                                 resource->raw_resource_offset_in_wim);
2549
2550                         tprintf(T("Solid offset      = %"PRIu64" bytes\n"),
2551                                 resource->offset);
2552                 } else {
2553                         tprintf(T("Compressed size   = %"PRIu64" bytes\n"),
2554                                 resource->compressed_size);
2555
2556                         tprintf(T("Offset in WIM     = %"PRIu64" bytes\n"),
2557                                 resource->offset);
2558                 }
2559
2560                 tprintf(T("Part Number       = %u\n"), resource->part_number);
2561                 tprintf(T("Reference Count   = %u\n"), resource->reference_count);
2562
2563                 tprintf(T("Flags             = "));
2564                 if (resource->is_compressed)
2565                         tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2566                 if (resource->is_metadata)
2567                         tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2568                 if (resource->is_free)
2569                         tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2570                 if (resource->is_spanned)
2571                         tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2572                 if (resource->packed)
2573                         tprintf(T("WIM_RESHDR_FLAG_SOLID  "));
2574                 tputchar(T('\n'));
2575         }
2576         tputchar(T('\n'));
2577         return 0;
2578 }
2579
2580 static void
2581 print_blobs(WIMStruct *wim)
2582 {
2583         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2584 }
2585
2586 static void
2587 default_print_security_descriptor(const uint8_t *sd, size_t size)
2588 {
2589         tprintf(T("Security Descriptor = "));
2590         print_byte_field(sd, size);
2591         tputchar(T('\n'));
2592 }
2593
2594 static void
2595 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2596 {
2597
2598         tprintf(T(
2599 "----------------------------------------------------------------------------\n"));
2600         tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
2601         if (dentry->dos_name)
2602                 tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
2603         tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
2604         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2605                 if (file_attr_flags[i].flag & dentry->attributes)
2606                         tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
2607                                 file_attr_flags[i].name);
2608
2609         if (dentry->security_descriptor) {
2610                 print_security_descriptor(dentry->security_descriptor,
2611                                           dentry->security_descriptor_size);
2612         }
2613
2614         print_time(T("Creation Time"), &dentry->creation_time);
2615         print_time(T("Last Write Time"), &dentry->last_write_time);
2616         print_time(T("Last Access Time"), &dentry->last_access_time);
2617
2618
2619         if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2620                 tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
2621
2622         tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2623         tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
2624
2625         if (dentry->unix_mode != 0) {
2626                 tprintf(T("UNIX Data           = uid:%"PRIu32" gid:%"PRIu32" "
2627                           "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2628                         dentry->unix_uid, dentry->unix_gid,
2629                         dentry->unix_mode, dentry->unix_rdev);
2630         }
2631
2632         for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2633                 if (dentry->streams[i].stream_name) {
2634                         tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2635                                 dentry->streams[i].stream_name);
2636                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2637                         tprintf(T("\tRaw encrypted data stream:\n"));
2638                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2639                         tprintf(T("\tReparse point stream:\n"));
2640                 } else {
2641                         tprintf(T("\tUnnamed data stream:\n"));
2642                 }
2643                 print_resource(&dentry->streams[i].resource, NULL);
2644         }
2645 }
2646
2647 static int
2648 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2649 {
2650         const struct print_dentry_options *options = _options;
2651         if (!options->detailed)
2652                 print_dentry_full_path(dentry);
2653         else
2654                 print_dentry_detailed(dentry);
2655         return 0;
2656 }
2657
2658 /* Print the files contained in an image(s) in a WIM file. */
2659 static int
2660 imagex_dir(int argc, tchar **argv, int cmd)
2661 {
2662         const tchar *wimfile;
2663         WIMStruct *wim = NULL;
2664         int image;
2665         int ret;
2666         const tchar *path = WIMLIB_WIM_ROOT_PATH;
2667         int c;
2668         struct print_dentry_options options = {
2669                 .detailed = false,
2670         };
2671         int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2672
2673         STRING_SET(refglobs);
2674
2675         for_opt(c, dir_options) {
2676                 switch (c) {
2677                 case IMAGEX_PATH_OPTION:
2678                         path = optarg;
2679                         break;
2680                 case IMAGEX_DETAILED_OPTION:
2681                         options.detailed = true;
2682                         break;
2683                 case IMAGEX_ONE_FILE_ONLY_OPTION:
2684                         iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2685                         break;
2686                 case IMAGEX_REF_OPTION:
2687                         ret = string_set_append(&refglobs, optarg);
2688                         if (ret)
2689                                 goto out_free_refglobs;
2690                         break;
2691                 default:
2692                         goto out_usage;
2693                 }
2694         }
2695         argc -= optind;
2696         argv += optind;
2697
2698         if (argc < 1) {
2699                 imagex_error(T("Must specify a WIM file"));
2700                 goto out_usage;
2701         }
2702         if (argc > 2) {
2703                 imagex_error(T("Too many arguments"));
2704                 goto out_usage;
2705         }
2706
2707         wimfile = argv[0];
2708         ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2709                                             imagex_progress_func, NULL);
2710         if (ret)
2711                 goto out_free_refglobs;
2712
2713         if (argc >= 2) {
2714                 image = wimlib_resolve_image(wim, argv[1]);
2715                 ret = verify_image_exists(image, argv[1], wimfile);
2716                 if (ret)
2717                         goto out_wimlib_free;
2718         } else {
2719                 /* No image specified; default to image 1, but only if the WIM
2720                  * contains exactly one image.  */
2721
2722                 struct wimlib_wim_info info;
2723
2724                 wimlib_get_wim_info(wim, &info);
2725                 if (info.image_count != 1) {
2726                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2727                                        "select one (or all)."),
2728                                      wimfile, info.image_count);
2729                         wimlib_free(wim);
2730                         goto out_usage;
2731                 }
2732                 image = 1;
2733         }
2734
2735         if (refglobs.num_strings) {
2736                 ret = wim_reference_globs(wim, &refglobs, 0);
2737                 if (ret)
2738                         goto out_wimlib_free;
2739         }
2740
2741         ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2742                                       print_dentry, &options);
2743         if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2744                 struct wimlib_wim_info info;
2745
2746                 wimlib_get_wim_info(wim, &info);
2747                 do_metadata_not_found_warning(wimfile, &info);
2748         }
2749 out_wimlib_free:
2750         wimlib_free(wim);
2751 out_free_refglobs:
2752         string_set_destroy(&refglobs);
2753         return ret;
2754
2755 out_usage:
2756         usage(CMD_DIR, stderr);
2757         ret = -1;
2758         goto out_free_refglobs;
2759 }
2760
2761 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2762  * WIM file. */
2763 static int
2764 imagex_export(int argc, tchar **argv, int cmd)
2765 {
2766         int c;
2767         int open_flags = 0;
2768         int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2769         int write_flags = 0;
2770         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2771         const tchar *src_wimfile;
2772         const tchar *src_image_num_or_name;
2773         const tchar *dest_wimfile;
2774         int dest_wim_fd;
2775         const tchar *dest_name;
2776         const tchar *dest_desc;
2777         WIMStruct *src_wim;
2778         struct wimlib_wim_info src_info;
2779         WIMStruct *dest_wim;
2780         int ret;
2781         int image;
2782         struct stat stbuf;
2783         bool wim_is_new;
2784         STRING_SET(refglobs);
2785         unsigned num_threads = 0;
2786         uint32_t chunk_size = UINT32_MAX;
2787         uint32_t solid_chunk_size = UINT32_MAX;
2788         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2789
2790         for_opt(c, export_options) {
2791                 switch (c) {
2792                 case IMAGEX_BOOT_OPTION:
2793                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2794                         break;
2795                 case IMAGEX_CHECK_OPTION:
2796                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2797                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2798                         break;
2799                 case IMAGEX_NOCHECK_OPTION:
2800                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2801                         break;
2802                 case IMAGEX_COMPRESS_OPTION:
2803                         compression_type = get_compression_type(optarg);
2804                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2805                                 goto out_err;
2806                         break;
2807                 case IMAGEX_COMPRESS_SLOW_OPTION:
2808                         set_compress_slow();
2809                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2810                         break;
2811                 case IMAGEX_RECOMPRESS_OPTION:
2812                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2813                         break;
2814                 case IMAGEX_SOLID_OPTION:
2815                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2816                         break;
2817                 case IMAGEX_NO_SOLID_SORT_OPTION:
2818                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2819                         break;
2820                 case IMAGEX_CHUNK_SIZE_OPTION:
2821                         chunk_size = parse_chunk_size(optarg);
2822                         if (chunk_size == UINT32_MAX)
2823                                 goto out_err;
2824                         break;
2825                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2826                         solid_chunk_size = parse_chunk_size(optarg);
2827                         if (solid_chunk_size == UINT32_MAX)
2828                                 goto out_err;
2829                         break;
2830                 case IMAGEX_SOLID_COMPRESS_OPTION:
2831                         solid_ctype = get_compression_type(optarg);
2832                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2833                                 goto out_err;
2834                         break;
2835                 case IMAGEX_REF_OPTION:
2836                         ret = string_set_append(&refglobs, optarg);
2837                         if (ret)
2838                                 goto out_free_refglobs;
2839                         break;
2840                 case IMAGEX_THREADS_OPTION:
2841                         num_threads = parse_num_threads(optarg);
2842                         if (num_threads == UINT_MAX)
2843                                 goto out_err;
2844                         break;
2845                 case IMAGEX_REBUILD_OPTION:
2846                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2847                         break;
2848                 case IMAGEX_PIPABLE_OPTION:
2849                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2850                         break;
2851                 case IMAGEX_NOT_PIPABLE_OPTION:
2852                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2853                         break;
2854                 case IMAGEX_WIMBOOT_OPTION:
2855                         export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2856                         break;
2857                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2858                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2859                         break;
2860                 default:
2861                         goto out_usage;
2862                 }
2863         }
2864         argc -= optind;
2865         argv += optind;
2866         if (argc < 3 || argc > 5)
2867                 goto out_usage;
2868         src_wimfile           = argv[0];
2869         src_image_num_or_name = argv[1];
2870         dest_wimfile          = argv[2];
2871         dest_name             = (argc >= 4) ? argv[3] : NULL;
2872         dest_desc             = (argc >= 5) ? argv[4] : NULL;
2873         ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2874                                             imagex_progress_func, NULL);
2875         if (ret)
2876                 goto out_free_refglobs;
2877
2878         wimlib_get_wim_info(src_wim, &src_info);
2879
2880         /* Determine if the destination is an existing file or not.  If so, we
2881          * try to append the exported image(s) to it; otherwise, we create a new
2882          * WIM containing the exported image(s).  Furthermore, determine if we
2883          * need to write a pipable WIM directly to standard output.  */
2884
2885         if (tstrcmp(dest_wimfile, T("-")) == 0) {
2886         #if 0
2887                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2888                         imagex_error("Can't write a non-pipable WIM to "
2889                                      "standard output!  Specify --pipable\n"
2890                                      "       if you want to create a pipable WIM "
2891                                      "(but read the docs first).");
2892                         ret = -1;
2893                         goto out_free_src_wim;
2894                 }
2895         #else
2896                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2897         #endif
2898                 dest_wimfile = NULL;
2899                 dest_wim_fd = STDOUT_FILENO;
2900                 imagex_info_file = stderr;
2901                 set_fd_to_binary_mode(dest_wim_fd);
2902         }
2903         errno = ENOENT;
2904         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2905                 wim_is_new = false;
2906                 /* Destination file exists. */
2907
2908                 if (!S_ISREG(stbuf.st_mode)) {
2909                         imagex_error(T("\"%"TS"\" is not a regular file"),
2910                                      dest_wimfile);
2911                         ret = -1;
2912                         goto out_free_src_wim;
2913                 }
2914                 ret = wimlib_open_wim_with_progress(dest_wimfile,
2915                                                     open_flags |
2916                                                         WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2917                                                     &dest_wim,
2918                                                     imagex_progress_func,
2919                                                     NULL);
2920                 if (ret)
2921                         goto out_free_src_wim;
2922
2923                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2924                         /* The user specified a compression type, but we're
2925                          * exporting to an existing WIM.  Make sure the
2926                          * specified compression type is the same as the
2927                          * compression type of the existing destination WIM. */
2928                         struct wimlib_wim_info dest_info;
2929
2930                         wimlib_get_wim_info(dest_wim, &dest_info);
2931                         if (compression_type != dest_info.compression_type) {
2932                                 imagex_error(T("Cannot specify a compression type that is "
2933                                                "not the same as that used in the "
2934                                                "destination WIM"));
2935                                 ret = -1;
2936                                 goto out_free_dest_wim;
2937                         }
2938                 }
2939         } else {
2940                 wim_is_new = true;
2941
2942                 if (errno != ENOENT) {
2943                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2944                                                 dest_wimfile);
2945                         ret = -1;
2946                         goto out_free_src_wim;
2947                 }
2948
2949                 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
2950                         imagex_error(T("'--unsafe-compact' is only valid when "
2951                                        "exporting to an existing WIM file!"));
2952                         ret = -1;
2953                         goto out_free_src_wim;
2954                 }
2955
2956                 /* dest_wimfile is not an existing file, so create a new WIM. */
2957
2958                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2959                         /* The user did not specify a compression type; default
2960                          * to that of the source WIM, unless --solid or
2961                          * --wimboot was specified.   */
2962
2963                         if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2964                                 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2965                         else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2966                                 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2967                         else
2968                                 compression_type = src_info.compression_type;
2969                 }
2970                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2971                 if (ret)
2972                         goto out_free_src_wim;
2973
2974                 wimlib_register_progress_function(dest_wim,
2975                                                   imagex_progress_func, NULL);
2976
2977                 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2978                     && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2979                 {
2980                         /* For --wimboot export, use small XPRESS chunks.  */
2981                         wimlib_set_output_chunk_size(dest_wim, 4096);
2982                 } else if (compression_type == src_info.compression_type &&
2983                            chunk_size == UINT32_MAX)
2984                 {
2985                         /* Use same chunk size if compression type is the same.  */
2986                         wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2987                 }
2988         }
2989
2990         if (chunk_size != UINT32_MAX) {
2991                 /* Set destination chunk size.  */
2992                 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2993                 if (ret)
2994                         goto out_free_dest_wim;
2995         }
2996         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2997                 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2998                 if (ret)
2999                         goto out_free_dest_wim;
3000         }
3001         if (solid_chunk_size != UINT32_MAX) {
3002                 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3003                 if (ret)
3004                         goto out_free_dest_wim;
3005         }
3006
3007         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3008         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3009         if (ret)
3010                 goto out_free_dest_wim;
3011
3012         if (refglobs.num_strings) {
3013                 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3014                 if (ret)
3015                         goto out_free_dest_wim;
3016         }
3017
3018         if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3019             image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3020         {
3021                 imagex_error(T("--boot specified for all-images export, but source WIM "
3022                                "has no bootable image."));
3023                 ret = -1;
3024                 goto out_free_dest_wim;
3025         }
3026
3027         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3028                                   dest_desc, export_flags);
3029         if (ret) {
3030                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3031                         do_resource_not_found_warning(src_wimfile,
3032                                                       &src_info, &refglobs);
3033                 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3034                         do_metadata_not_found_warning(src_wimfile, &src_info);
3035                 }
3036                 goto out_free_dest_wim;
3037         }
3038
3039         if (!wim_is_new)
3040                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3041         else if (dest_wimfile)
3042                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3043                                    write_flags, num_threads);
3044         else
3045                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3046                                          WIMLIB_ALL_IMAGES, write_flags,
3047                                          num_threads);
3048 out_free_dest_wim:
3049         wimlib_free(dest_wim);
3050 out_free_src_wim:
3051         wimlib_free(src_wim);
3052 out_free_refglobs:
3053         string_set_destroy(&refglobs);
3054         return ret;
3055
3056 out_usage:
3057         usage(CMD_EXPORT, stderr);
3058 out_err:
3059         ret = -1;
3060         goto out_free_refglobs;
3061 }
3062
3063 /* Extract files or directories from a WIM image */
3064 static int
3065 imagex_extract(int argc, tchar **argv, int cmd)
3066 {
3067         int c;
3068         int open_flags = 0;
3069         int image;
3070         WIMStruct *wim;
3071         int ret;
3072         const tchar *wimfile;
3073         const tchar *image_num_or_name;
3074         tchar *dest_dir = T(".");
3075         int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3076                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3077                             WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3078         int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3079
3080         STRING_SET(refglobs);
3081
3082         tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3083
3084         for_opt(c, extract_options) {
3085                 switch (c) {
3086                 case IMAGEX_CHECK_OPTION:
3087                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3088                         break;
3089                 case IMAGEX_VERBOSE_OPTION:
3090                         /* No longer does anything.  */
3091                         break;
3092                 case IMAGEX_REF_OPTION:
3093                         ret = string_set_append(&refglobs, optarg);
3094                         if (ret)
3095                                 goto out_free_refglobs;
3096                         break;
3097                 case IMAGEX_UNIX_DATA_OPTION:
3098                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3099                         break;
3100                 case IMAGEX_NO_ACLS_OPTION:
3101                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3102                         break;
3103                 case IMAGEX_STRICT_ACLS_OPTION:
3104                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3105                         break;
3106                 case IMAGEX_NO_ATTRIBUTES_OPTION:
3107                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3108                         break;
3109                 case IMAGEX_DEST_DIR_OPTION:
3110                         dest_dir = optarg;
3111                         break;
3112                 case IMAGEX_TO_STDOUT_OPTION:
3113                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3114                         imagex_info_file = stderr;
3115                         imagex_be_quiet = true;
3116                         set_fd_to_binary_mode(STDOUT_FILENO);
3117                         break;
3118                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3119                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3120                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3121                         break;
3122                 case IMAGEX_NO_GLOBS_OPTION:
3123                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3124                         break;
3125                 case IMAGEX_NULLGLOB_OPTION:
3126                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3127                         break;
3128                 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3129                         notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3130                         break;
3131                 case IMAGEX_WIMBOOT_OPTION:
3132                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3133                         break;
3134                 case IMAGEX_COMPACT_OPTION:
3135                         ret = set_compact_mode(optarg, &extract_flags);
3136                         if (ret)
3137                                 goto out_free_refglobs;
3138                         break;
3139                 default:
3140                         goto out_usage;
3141                 }
3142         }
3143         argc -= optind;
3144         argv += optind;
3145
3146         if (argc < 2)
3147                 goto out_usage;
3148
3149         if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3150                                WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3151         {
3152                 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3153                 goto out_err;
3154         }
3155
3156         wimfile = argv[0];
3157         image_num_or_name = argv[1];
3158
3159         argc -= 2;
3160         argv += 2;
3161
3162         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3163                                             imagex_progress_func, NULL);
3164         if (ret)
3165                 goto out_free_refglobs;
3166
3167         image = wimlib_resolve_image(wim, image_num_or_name);
3168         ret = verify_image_exists_and_is_single(image,
3169                                                 image_num_or_name,
3170                                                 wimfile);
3171         if (ret)
3172                 goto out_wimlib_free;
3173
3174         if (refglobs.num_strings) {
3175                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3176                 if (ret)
3177                         goto out_wimlib_free;
3178         }
3179
3180         if (argc == 0) {
3181                 argv = &root_path;
3182                 argc = 1;
3183                 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3184         }
3185
3186         while (argc != 0 && ret == 0) {
3187                 int num_paths;
3188
3189                 for (num_paths = 0;
3190                      num_paths < argc && argv[num_paths][0] != T('@');
3191                      num_paths++)
3192                         ;
3193
3194                 if (num_paths) {
3195                         ret = wimlib_extract_paths(wim, image, dest_dir,
3196                                                    (const tchar **)argv,
3197                                                    num_paths,
3198                                                    extract_flags | notlist_extract_flags);
3199                         argc -= num_paths;
3200                         argv += num_paths;
3201                 } else {
3202                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3203                                                       argv[0] + 1,
3204                                                       extract_flags);
3205                         argc--;
3206                         argv++;
3207                 }
3208         }
3209
3210         if (ret == 0) {
3211                 if (!imagex_be_quiet)
3212                         imagex_printf(T("Done extracting files.\n"));
3213         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3214                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3215                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3216                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3217                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3218                 {
3219                         tfprintf(stderr,
3220                                  T("Note: You can use the '--nullglob' "
3221                                    "option to ignore missing files.\n"));
3222                 }
3223                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3224                                    "files and directories\n"
3225                                    "      are in the WIM image.\n"),
3226                                 get_cmd_string(CMD_DIR, false));
3227         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3228                 struct wimlib_wim_info info;
3229
3230                 wimlib_get_wim_info(wim, &info);
3231                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3232         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3233                 struct wimlib_wim_info info;
3234
3235                 wimlib_get_wim_info(wim, &info);
3236                 do_metadata_not_found_warning(wimfile, &info);
3237         }
3238 out_wimlib_free:
3239         wimlib_free(wim);
3240 out_free_refglobs:
3241         string_set_destroy(&refglobs);
3242         return ret;
3243
3244 out_usage:
3245         usage(CMD_EXTRACT, stderr);
3246 out_err:
3247         ret = -1;
3248         goto out_free_refglobs;
3249 }
3250
3251 /* Prints information about a WIM file; also can mark an image as bootable,
3252  * change the name of an image, or change the description of an image. */
3253 static int
3254 imagex_info(int argc, tchar **argv, int cmd)
3255 {
3256         int c;
3257         bool boot         = false;
3258         bool check        = false;
3259         bool nocheck      = false;
3260         bool header       = false;
3261         bool blobs        = false;
3262         bool xml          = false;
3263         bool short_header = true;
3264         const tchar *xml_out_file = NULL;
3265         const tchar *wimfile;
3266         const tchar *image_num_or_name;
3267         const tchar *new_name;
3268         const tchar *new_desc;
3269         WIMStruct *wim;
3270         int image;
3271         int ret;
3272         int open_flags = 0;
3273         struct wimlib_wim_info info;
3274
3275         for_opt(c, info_options) {
3276                 switch (c) {
3277                 case IMAGEX_BOOT_OPTION:
3278                         boot = true;
3279                         break;
3280                 case IMAGEX_CHECK_OPTION:
3281                         check = true;
3282                         break;
3283                 case IMAGEX_NOCHECK_OPTION:
3284                         nocheck = true;
3285                         break;
3286                 case IMAGEX_HEADER_OPTION:
3287                         header = true;
3288                         short_header = false;
3289                         break;
3290                 case IMAGEX_BLOBS_OPTION:
3291                         blobs = true;
3292                         short_header = false;
3293                         break;
3294                 case IMAGEX_XML_OPTION:
3295                         xml = true;
3296                         short_header = false;
3297                         break;
3298                 case IMAGEX_EXTRACT_XML_OPTION:
3299                         xml_out_file = optarg;
3300                         short_header = false;
3301                         break;
3302                 case IMAGEX_METADATA_OPTION:
3303                         imagex_error(T("The --metadata option has been removed. "
3304                                        "Use 'wimdir --detail' instead."));
3305                         goto out_err;
3306                 default:
3307                         goto out_usage;
3308                 }
3309         }
3310
3311         argc -= optind;
3312         argv += optind;
3313         if (argc < 1 || argc > 4)
3314                 goto out_usage;
3315
3316         wimfile           = argv[0];
3317         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3318         new_name          = (argc >= 3) ? argv[2] : NULL;
3319         new_desc          = (argc >= 4) ? argv[3] : NULL;
3320
3321         if (check && nocheck) {
3322                 imagex_error(T("Can't specify both --check and --nocheck"));
3323                 goto out_err;
3324         }
3325
3326         if (check)
3327                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3328
3329         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3330                                             imagex_progress_func, NULL);
3331         if (ret)
3332                 goto out;
3333
3334         wimlib_get_wim_info(wim, &info);
3335
3336         image = wimlib_resolve_image(wim, image_num_or_name);
3337         ret = WIMLIB_ERR_INVALID_IMAGE;
3338         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3339                 verify_image_exists(image, image_num_or_name, wimfile);
3340                 if (boot) {
3341                         imagex_error(T("If you would like to set the boot "
3342                                        "index to 0, specify image \"0\" with "
3343                                        "the --boot flag."));
3344                 }
3345                 goto out_wimlib_free;
3346         }
3347
3348         if (boot && info.image_count == 0) {
3349                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3350                 goto out_wimlib_free;
3351         }
3352
3353         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3354                 if (boot) {
3355                         imagex_error(T("Cannot specify the --boot flag "
3356                                        "without specifying a specific "
3357                                        "image in a multi-image WIM"));
3358                         goto out_wimlib_free;
3359                 }
3360                 if (new_name) {
3361                         imagex_error(T("Cannot specify the NEW_NAME "
3362                                        "without specifying a specific "
3363                                        "image in a multi-image WIM"));
3364                         goto out_wimlib_free;
3365                 }
3366         }
3367
3368         /* Operations that print information are separated from operations that
3369          * recreate the WIM file. */
3370         if (!new_name && !boot) {
3371
3372                 /* Read-only operations */
3373
3374                 if (image == WIMLIB_NO_IMAGE) {
3375                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3376                                      image_num_or_name, wimfile);
3377                         goto out_wimlib_free;
3378                 }
3379
3380                 if (image == WIMLIB_ALL_IMAGES && short_header)
3381                         print_wim_information(wimfile, &info);
3382
3383                 if (header)
3384                         wimlib_print_header(wim);
3385
3386                 if (blobs) {
3387                         if (info.total_parts != 1) {
3388                                 tfprintf(stderr, T("Warning: Only showing the blobs "
3389                                                    "for part %d of a %d-part WIM.\n"),
3390                                          info.part_number, info.total_parts);
3391                         }
3392                         print_blobs(wim);
3393                 }
3394
3395                 if (xml) {
3396                         ret = wimlib_extract_xml_data(wim, stdout);
3397                         if (ret)
3398                                 goto out_wimlib_free;
3399                 }
3400
3401                 if (xml_out_file) {
3402                         FILE *fp;
3403
3404                         fp = tfopen(xml_out_file, T("wb"));
3405                         if (!fp) {
3406                                 imagex_error_with_errno(T("Failed to open the "
3407                                                           "file \"%"TS"\" for "
3408                                                           "writing"),
3409                                                         xml_out_file);
3410                                 ret = -1;
3411                                 goto out_wimlib_free;
3412                         }
3413                         ret = wimlib_extract_xml_data(wim, fp);
3414                         if (fclose(fp)) {
3415                                 imagex_error(T("Failed to close the file "
3416                                                "\"%"TS"\""),
3417                                              xml_out_file);
3418                                 ret = -1;
3419                         }
3420                         if (ret)
3421                                 goto out_wimlib_free;
3422                 }
3423
3424                 if (short_header)
3425                         wimlib_print_available_images(wim, image);
3426
3427                 ret = 0;
3428         } else {
3429
3430                 /* Modification operations */
3431
3432                 if (image == WIMLIB_ALL_IMAGES)
3433                         image = 1;
3434
3435                 if (image == WIMLIB_NO_IMAGE && new_name) {
3436                         imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3437                                        "when using image 0"), new_name);
3438                         ret = -1;
3439                         goto out_wimlib_free;
3440                 }
3441
3442                 if (boot) {
3443                         if (image == info.boot_index) {
3444                                 imagex_printf(T("Image %d is already marked as "
3445                                           "bootable.\n"), image);
3446                                 boot = false;
3447                         } else {
3448                                 imagex_printf(T("Marking image %d as bootable.\n"),
3449                                         image);
3450                                 info.boot_index = image;
3451                                 ret = wimlib_set_wim_info(wim, &info,
3452                                                           WIMLIB_CHANGE_BOOT_INDEX);
3453                                 if (ret)
3454                                         goto out_wimlib_free;
3455                         }
3456                 }
3457                 if (new_name) {
3458                         if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3459                         {
3460                                 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3461                                         image, new_name);
3462                                 new_name = NULL;
3463                         } else {
3464                                 imagex_printf(T("Changing the name of image %d to "
3465                                           "\"%"TS"\".\n"), image, new_name);
3466                                 ret = wimlib_set_image_name(wim, image, new_name);
3467                                 if (ret)
3468                                         goto out_wimlib_free;
3469                         }
3470                 }
3471                 if (new_desc) {
3472                         const tchar *old_desc;
3473                         old_desc = wimlib_get_image_description(wim, image);
3474                         if (old_desc && !tstrcmp(old_desc, new_desc)) {
3475                                 imagex_printf(T("The description of image %d is already "
3476                                           "\"%"TS"\".\n"), image, new_desc);
3477                                 new_desc = NULL;
3478                         } else {
3479                                 imagex_printf(T("Changing the description of image %d "
3480                                           "to \"%"TS"\".\n"), image, new_desc);
3481                                 ret = wimlib_set_image_descripton(wim, image,
3482                                                                   new_desc);
3483                                 if (ret)
3484                                         goto out_wimlib_free;
3485                         }
3486                 }
3487
3488                 /* Only call wimlib_overwrite() if something actually needs to
3489                  * be changed.  */
3490                 if (boot || new_name || new_desc ||
3491                     (check && !info.has_integrity_table) ||
3492                     (nocheck && info.has_integrity_table))
3493                 {
3494                         int write_flags = 0;
3495
3496                         if (check)
3497                                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3498                         if (nocheck)
3499                                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3500                         ret = wimlib_overwrite(wim, write_flags, 1);
3501                 } else {
3502                         imagex_printf(T("The file \"%"TS"\" was not modified "
3503                                         "because nothing needed to be done.\n"),
3504                                       wimfile);
3505                         ret = 0;
3506                 }
3507         }
3508 out_wimlib_free:
3509         wimlib_free(wim);
3510 out:
3511         return ret;
3512
3513 out_usage:
3514         usage(CMD_INFO, stderr);
3515 out_err:
3516         ret = -1;
3517         goto out;
3518 }
3519
3520 /* Join split WIMs into one part WIM */
3521 static int
3522 imagex_join(int argc, tchar **argv, int cmd)
3523 {
3524         int c;
3525         int swm_open_flags = 0;
3526         int wim_write_flags = 0;
3527         const tchar *output_path;
3528         int ret;
3529
3530         for_opt(c, join_options) {
3531                 switch (c) {
3532                 case IMAGEX_CHECK_OPTION:
3533                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3534                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3535                         break;
3536                 default:
3537                         goto out_usage;
3538                 }
3539         }
3540         argc -= optind;
3541         argv += optind;
3542
3543         if (argc < 2) {
3544                 imagex_error(T("Must specify one or more split WIM (.swm) "
3545                                "parts to join"));
3546                 goto out_usage;
3547         }
3548         output_path = argv[0];
3549         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3550                                         --argc,
3551                                         output_path,
3552                                         swm_open_flags,
3553                                         wim_write_flags,
3554                                         imagex_progress_func,
3555                                         NULL);
3556 out:
3557         return ret;
3558
3559 out_usage:
3560         usage(CMD_JOIN, stderr);
3561         ret = -1;
3562         goto out;
3563 }
3564
3565 #if WIM_MOUNTING_SUPPORTED
3566
3567 /* Mounts a WIM image.  */
3568 static int
3569 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3570 {
3571         int c;
3572         int mount_flags = 0;
3573         int open_flags = 0;
3574         const tchar *staging_dir = NULL;
3575         const tchar *wimfile;
3576         const tchar *dir;
3577         WIMStruct *wim;
3578         struct wimlib_wim_info info;
3579         int image;
3580         int ret;
3581
3582         STRING_SET(refglobs);
3583
3584         if (cmd == CMD_MOUNTRW) {
3585                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3586                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3587         }
3588
3589         for_opt(c, mount_options) {
3590                 switch (c) {
3591                 case IMAGEX_ALLOW_OTHER_OPTION:
3592                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3593                         break;
3594                 case IMAGEX_CHECK_OPTION:
3595                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3596                         break;
3597                 case IMAGEX_DEBUG_OPTION:
3598                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3599                         break;
3600                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3601                         if (!tstrcasecmp(optarg, T("none")))
3602                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3603                         else if (!tstrcasecmp(optarg, T("xattr")))
3604                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3605                         else if (!tstrcasecmp(optarg, T("windows")))
3606                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3607                         else {
3608                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3609                                              optarg);
3610                                 goto out_usage;
3611                         }
3612                         break;
3613                 case IMAGEX_REF_OPTION:
3614                         ret = string_set_append(&refglobs, optarg);
3615                         if (ret)
3616                                 goto out_free_refglobs;
3617                         break;
3618                 case IMAGEX_STAGING_DIR_OPTION:
3619                         staging_dir = optarg;
3620                         break;
3621                 case IMAGEX_UNIX_DATA_OPTION:
3622                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3623                         break;
3624                 default:
3625                         goto out_usage;
3626                 }
3627         }
3628         argc -= optind;
3629         argv += optind;
3630         if (argc != 2 && argc != 3)
3631                 goto out_usage;
3632
3633         wimfile = argv[0];
3634
3635         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3636                                             imagex_progress_func, NULL);
3637         if (ret)
3638                 goto out_free_refglobs;
3639
3640         wimlib_get_wim_info(wim, &info);
3641
3642         if (argc >= 3) {
3643                 /* Image explicitly specified.  */
3644                 image = wimlib_resolve_image(wim, argv[1]);
3645                 dir = argv[2];
3646                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3647                 if (ret)
3648                         goto out_free_wim;
3649         } else {
3650                 /* No image specified; default to image 1, but only if the WIM
3651                  * contains exactly one image.  */
3652
3653                 if (info.image_count != 1) {
3654                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3655                                        "select one."), wimfile, info.image_count);
3656                         wimlib_free(wim);
3657                         goto out_usage;
3658                 }
3659                 image = 1;
3660                 dir = argv[1];
3661         }
3662
3663         if (refglobs.num_strings) {
3664                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3665                 if (ret)
3666                         goto out_free_wim;
3667         }
3668
3669         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3670         if (ret) {
3671                 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3672                         do_metadata_not_found_warning(wimfile, &info);
3673                 } else {
3674                         imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3675                                        "on \"%"TS"\""),
3676                                      image, wimfile, dir);
3677                 }
3678         }
3679 out_free_wim:
3680         wimlib_free(wim);
3681 out_free_refglobs:
3682         string_set_destroy(&refglobs);
3683         return ret;
3684
3685 out_usage:
3686         usage(cmd, stderr);
3687         ret = -1;
3688         goto out_free_refglobs;
3689 }
3690 #endif /* WIM_MOUNTING_SUPPORTED */
3691
3692 /* Rebuild a WIM file */
3693 static int
3694 imagex_optimize(int argc, tchar **argv, int cmd)
3695 {
3696         int c;
3697         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3698         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3699         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3700         uint32_t chunk_size = UINT32_MAX;
3701         uint32_t solid_chunk_size = UINT32_MAX;
3702         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3703         int ret;
3704         WIMStruct *wim;
3705         const tchar *wimfile;
3706         off_t old_size;
3707         off_t new_size;
3708         unsigned num_threads = 0;
3709
3710         for_opt(c, optimize_options) {
3711                 switch (c) {
3712                 case IMAGEX_CHECK_OPTION:
3713                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3714                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3715                         break;
3716                 case IMAGEX_NOCHECK_OPTION:
3717                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3718                         break;
3719                 case IMAGEX_COMPRESS_OPTION:
3720                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3721                         compression_type = get_compression_type(optarg);
3722                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3723                                 goto out_err;
3724                         break;
3725                 case IMAGEX_COMPRESS_SLOW_OPTION:
3726                         set_compress_slow();
3727                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3728                         break;
3729                 case IMAGEX_RECOMPRESS_OPTION:
3730                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3731                         break;
3732                 case IMAGEX_CHUNK_SIZE_OPTION:
3733                         chunk_size = parse_chunk_size(optarg);
3734                         if (chunk_size == UINT32_MAX)
3735                                 goto out_err;
3736                         break;
3737                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3738                         solid_chunk_size = parse_chunk_size(optarg);
3739                         if (solid_chunk_size == UINT32_MAX)
3740                                 goto out_err;
3741                         break;
3742                 case IMAGEX_SOLID_COMPRESS_OPTION:
3743                         solid_ctype = get_compression_type(optarg);
3744                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3745                                 goto out_err;
3746                         break;
3747                 case IMAGEX_SOLID_OPTION:
3748                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3749                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3750                         break;
3751                 case IMAGEX_NO_SOLID_SORT_OPTION:
3752                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3753                         break;
3754                 case IMAGEX_THREADS_OPTION:
3755                         num_threads = parse_num_threads(optarg);
3756                         if (num_threads == UINT_MAX)
3757                                 goto out_err;
3758                         break;
3759                 case IMAGEX_PIPABLE_OPTION:
3760                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3761                         break;
3762                 case IMAGEX_NOT_PIPABLE_OPTION:
3763                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3764                         break;
3765                 case IMAGEX_UNSAFE_COMPACT_OPTION:
3766                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3767                         break;
3768                 default:
3769                         goto out_usage;
3770                 }
3771         }
3772         argc -= optind;
3773         argv += optind;
3774
3775         if (argc != 1)
3776                 goto out_usage;
3777
3778         wimfile = argv[0];
3779
3780         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3781                                             imagex_progress_func, NULL);
3782         if (ret)
3783                 goto out;
3784
3785         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3786                 /* Change compression type.  */
3787                 ret = wimlib_set_output_compression_type(wim, compression_type);
3788                 if (ret)
3789                         goto out_wimlib_free;
3790         }
3791
3792         if (chunk_size != UINT32_MAX) {
3793                 /* Change chunk size.  */
3794                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3795                 if (ret)
3796                         goto out_wimlib_free;
3797         }
3798         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3799                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3800                 if (ret)
3801                         goto out_wimlib_free;
3802         }
3803         if (solid_chunk_size != UINT32_MAX) {
3804                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3805                 if (ret)
3806                         goto out_wimlib_free;
3807         }
3808
3809         old_size = file_get_size(wimfile);
3810         tprintf(T("\"%"TS"\" original size: "), wimfile);
3811         if (old_size == -1)
3812                 tputs(T("Unknown"));
3813         else
3814                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3815
3816         ret = wimlib_overwrite(wim, write_flags, num_threads);
3817         if (ret) {
3818                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3819                 goto out_wimlib_free;
3820         }
3821
3822         new_size = file_get_size(wimfile);
3823         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3824         if (new_size == -1)
3825                 tputs(T("Unknown"));
3826         else
3827                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3828
3829         tfputs(T("Space saved: "), stdout);
3830         if (new_size != -1 && old_size != -1) {
3831                 tprintf(T("%lld KiB\n"),
3832                        ((long long)old_size - (long long)new_size) >> 10);
3833         } else {
3834                 tputs(T("Unknown"));
3835         }
3836         ret = 0;
3837 out_wimlib_free:
3838         wimlib_free(wim);
3839 out:
3840         return ret;
3841
3842 out_usage:
3843         usage(CMD_OPTIMIZE, stderr);
3844 out_err:
3845         ret = -1;
3846         goto out;
3847 }
3848
3849 /* Split a WIM into a spanned set */
3850 static int
3851 imagex_split(int argc, tchar **argv, int cmd)
3852 {
3853         int c;
3854         int open_flags = 0;
3855         int write_flags = 0;
3856         unsigned long part_size;
3857         tchar *tmp;
3858         int ret;
3859         WIMStruct *wim;
3860
3861         for_opt(c, split_options) {
3862                 switch (c) {
3863                 case IMAGEX_CHECK_OPTION:
3864                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3865                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3866                         break;
3867                 default:
3868                         goto out_usage;
3869                 }
3870         }
3871         argc -= optind;
3872         argv += optind;
3873
3874         if (argc != 3)
3875                 goto out_usage;
3876
3877         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3878         if (tmp == argv[2] || *tmp) {
3879                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3880                 imagex_error(T("The part size must be an integer or "
3881                                "floating-point number of megabytes."));
3882                 goto out_err;
3883         }
3884         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3885                                             imagex_progress_func, NULL);
3886         if (ret)
3887                 goto out;
3888
3889         ret = wimlib_split(wim, argv[1], part_size, write_flags);
3890         wimlib_free(wim);
3891 out:
3892         return ret;
3893
3894 out_usage:
3895         usage(CMD_SPLIT, stderr);
3896 out_err:
3897         ret = -1;
3898         goto out;
3899 }
3900
3901 #if WIM_MOUNTING_SUPPORTED
3902 /* Unmounts a mounted WIM image. */
3903 static int
3904 imagex_unmount(int argc, tchar **argv, int cmd)
3905 {
3906         int c;
3907         int unmount_flags = 0;
3908         int ret;
3909
3910         for_opt(c, unmount_options) {
3911                 switch (c) {
3912                 case IMAGEX_COMMIT_OPTION:
3913                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3914                         break;
3915                 case IMAGEX_CHECK_OPTION:
3916                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3917                         break;
3918                 case IMAGEX_REBUILD_OPTION:
3919                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3920                         break;
3921                 case IMAGEX_LAZY_OPTION:
3922                 case IMAGEX_FORCE_OPTION:
3923                         /* Now, unmount is lazy by default.  However, committing
3924                          * the image will fail with
3925                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3926                          * file descriptors on the WIM image.  The
3927                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3928                          * descriptors to be closed.  */
3929                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3930                         break;
3931                 case IMAGEX_NEW_IMAGE_OPTION:
3932                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3933                         break;
3934                 default:
3935                         goto out_usage;
3936                 }
3937         }
3938         argc -= optind;
3939         argv += optind;
3940         if (argc != 1)
3941                 goto out_usage;
3942
3943         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3944                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3945                         imagex_error(T("--new-image is meaningless "
3946                                        "without --commit also specified!"));
3947                         goto out_err;
3948                 }
3949         }
3950
3951         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3952                                                  imagex_progress_func, NULL);
3953         if (ret) {
3954                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3955                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3956                         imagex_printf(T(
3957                                 "\tNote: Use --commit --force to force changes "
3958                                         "to be committed, regardless\n"
3959                                 "\t      of open files.\n"));
3960                 }
3961         }
3962 out:
3963         return ret;
3964
3965 out_usage:
3966         usage(CMD_UNMOUNT, stderr);
3967 out_err:
3968         ret = -1;
3969         goto out;
3970 }
3971 #endif /* WIM_MOUNTING_SUPPORTED */
3972
3973 /*
3974  * Add, delete, or rename files in a WIM image.
3975  */
3976 static int
3977 imagex_update(int argc, tchar **argv, int cmd)
3978 {
3979         const tchar *wimfile;
3980         int image;
3981         WIMStruct *wim;
3982         int ret;
3983         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3984         int write_flags = 0;
3985         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3986         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3987                                 WIMLIB_ADD_FLAG_VERBOSE |
3988                                 WIMLIB_ADD_FLAG_WINCONFIG;
3989         int default_delete_flags = 0;
3990         unsigned num_threads = 0;
3991         int c;
3992         tchar *cmd_file_contents;
3993         size_t cmd_file_nchars;
3994         struct wimlib_update_command *cmds;
3995         size_t num_cmds;
3996         tchar *command_str = NULL;
3997         tchar *config_file = NULL;
3998         tchar *wimboot_config = NULL;
3999
4000         for_opt(c, update_options) {
4001                 switch (c) {
4002                 /* Generic or write options */
4003                 case IMAGEX_THREADS_OPTION:
4004                         num_threads = parse_num_threads(optarg);
4005                         if (num_threads == UINT_MAX)
4006                                 goto out_err;
4007                         break;
4008                 case IMAGEX_CHECK_OPTION:
4009                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4010                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4011                         break;
4012                 case IMAGEX_REBUILD_OPTION:
4013                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4014                         break;
4015                 case IMAGEX_COMMAND_OPTION:
4016                         if (command_str) {
4017                                 imagex_error(T("--command may only be specified "
4018                                                "one time.  Please provide\n"
4019                                                "       the update commands "
4020                                                "on standard input instead."));
4021                                 goto out_err;
4022                         }
4023                         command_str = tstrdup(optarg);
4024                         if (!command_str) {
4025                                 imagex_error(T("Out of memory!"));
4026                                 goto out_err;
4027                         }
4028                         break;
4029                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4030                         wimboot_config = optarg;
4031                         break;
4032                 /* Default delete options */
4033                 case IMAGEX_FORCE_OPTION:
4034                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4035                         break;
4036                 case IMAGEX_RECURSIVE_OPTION:
4037                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4038                         break;
4039
4040                 /* Global add option */
4041                 case IMAGEX_CONFIG_OPTION:
4042                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4043                         config_file = optarg;
4044                         break;
4045
4046                 /* Default add options */
4047                 case IMAGEX_VERBOSE_OPTION:
4048                         /* No longer does anything.  */
4049                         break;
4050                 case IMAGEX_DEREFERENCE_OPTION:
4051                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4052                         break;
4053                 case IMAGEX_UNIX_DATA_OPTION:
4054                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4055                         break;
4056                 case IMAGEX_NO_ACLS_OPTION:
4057                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4058                         break;
4059                 case IMAGEX_STRICT_ACLS_OPTION:
4060                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4061                         break;
4062                 case IMAGEX_NO_REPLACE_OPTION:
4063                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4064                         break;
4065                 case IMAGEX_UNSAFE_COMPACT_OPTION:
4066                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4067                         break;
4068                 default:
4069                         goto out_usage;
4070                 }
4071         }
4072         argv += optind;
4073         argc -= optind;
4074
4075         if (argc != 1 && argc != 2)
4076                 goto out_usage;
4077         wimfile = argv[0];
4078
4079         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4080                                             imagex_progress_func, NULL);
4081         if (ret)
4082                 goto out_free_command_str;
4083
4084         if (argc >= 2) {
4085                 /* Image explicitly specified.  */
4086                 image = wimlib_resolve_image(wim, argv[1]);
4087                 ret = verify_image_exists_and_is_single(image, argv[1],
4088                                                         wimfile);
4089                 if (ret)
4090                         goto out_wimlib_free;
4091         } else {
4092                 /* No image specified; default to image 1, but only if the WIM
4093                  * contains exactly one image.  */
4094                 struct wimlib_wim_info info;
4095
4096                 wimlib_get_wim_info(wim, &info);
4097                 if (info.image_count != 1) {
4098                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4099                                      wimfile, info.image_count);
4100                         wimlib_free(wim);
4101                         goto out_usage;
4102                 }
4103                 image = 1;
4104         }
4105
4106         /* Read update commands from standard input, or the command string if
4107          * specified.  */
4108         if (command_str) {
4109                 cmd_file_contents = NULL;
4110                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4111                                                  &num_cmds);
4112                 if (!cmds) {
4113                         ret = -1;
4114                         goto out_free_cmd_file_contents;
4115                 }
4116         } else if (!wimboot_config) {
4117                 if (isatty(STDIN_FILENO)) {
4118                         tputs(T("Reading update commands from standard input..."));
4119                         recommend_man_page(CMD_UPDATE, stdout);
4120                 }
4121                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4122                 if (!cmd_file_contents) {
4123                         ret = -1;
4124                         goto out_wimlib_free;
4125                 }
4126
4127                 /* Parse the update commands */
4128                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4129                                                  &num_cmds);
4130                 if (!cmds) {
4131                         ret = -1;
4132                         goto out_free_cmd_file_contents;
4133                 }
4134         } else {
4135                 cmd_file_contents = NULL;
4136                 cmds = NULL;
4137                 num_cmds = 0;
4138         }
4139
4140         /* Set default flags and capture config on the update commands */
4141         for (size_t i = 0; i < num_cmds; i++) {
4142                 switch (cmds[i].op) {
4143                 case WIMLIB_UPDATE_OP_ADD:
4144                         cmds[i].add.add_flags |= default_add_flags;
4145                         cmds[i].add.config_file = config_file;
4146                         break;
4147                 case WIMLIB_UPDATE_OP_DELETE:
4148                         cmds[i].delete_.delete_flags |= default_delete_flags;
4149                         break;
4150                 default:
4151                         break;
4152                 }
4153         }
4154
4155         /* Execute the update commands */
4156         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4157         if (ret)
4158                 goto out_free_cmds;
4159
4160         if (wimboot_config) {
4161                 /* --wimboot-config=FILE is short for an
4162                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
4163                  */
4164                 struct wimlib_update_command cmd;
4165
4166                 cmd.op = WIMLIB_UPDATE_OP_ADD;
4167                 cmd.add.fs_source_path = wimboot_config;
4168                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4169                 cmd.add.config_file = NULL;
4170                 cmd.add.add_flags = 0;
4171
4172                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4173                 if (ret)
4174                         goto out_free_cmds;
4175         }
4176
4177         /* Overwrite the updated WIM */
4178         ret = wimlib_overwrite(wim, write_flags, num_threads);
4179 out_free_cmds:
4180         free(cmds);
4181 out_free_cmd_file_contents:
4182         free(cmd_file_contents);
4183 out_wimlib_free:
4184         wimlib_free(wim);
4185 out_free_command_str:
4186         free(command_str);
4187         return ret;
4188
4189 out_usage:
4190         usage(CMD_UPDATE, stderr);
4191 out_err:
4192         ret = -1;
4193         goto out_free_command_str;
4194 }
4195
4196 /* Verify a WIM file.  */
4197 static int
4198 imagex_verify(int argc, tchar **argv, int cmd)
4199 {
4200         int ret;
4201         const tchar *wimfile;
4202         WIMStruct *wim;
4203         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4204         int verify_flags = 0;
4205         STRING_SET(refglobs);
4206         int c;
4207
4208         for_opt(c, verify_options) {
4209                 switch (c) {
4210                 case IMAGEX_REF_OPTION:
4211                         ret = string_set_append(&refglobs, optarg);
4212                         if (ret)
4213                                 goto out_free_refglobs;
4214                         break;
4215                 case IMAGEX_NOCHECK_OPTION:
4216                         open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4217                         break;
4218                 default:
4219                         goto out_usage;
4220                 }
4221         }
4222
4223         argv += optind;
4224         argc -= optind;
4225
4226         if (argc != 1) {
4227                 if (argc == 0)
4228                         imagex_error(T("Must specify a WIM file!"));
4229                 else
4230                         imagex_error(T("At most one WIM file can be specified!"));
4231                 goto out_usage;
4232         }
4233
4234         wimfile = argv[0];
4235
4236         ret = wimlib_open_wim_with_progress(wimfile,
4237                                             open_flags,
4238                                             &wim,
4239                                             imagex_progress_func,
4240                                             NULL);
4241         if (ret)
4242                 goto out_free_refglobs;
4243
4244         ret = wim_reference_globs(wim, &refglobs, open_flags);
4245         if (ret)
4246                 goto out_wimlib_free;
4247
4248         ret = wimlib_verify_wim(wim, verify_flags);
4249         if (ret) {
4250                 tputc(T('\n'), stderr);
4251                 imagex_error(T("\"%"TS"\" failed verification!"),
4252                              wimfile);
4253                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4254                     refglobs.num_strings == 0)
4255                 {
4256                         imagex_printf(T("Note: if this WIM file is not standalone, "
4257                                         "use the --ref option to specify the other parts.\n"));
4258                 }
4259         } else {
4260                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4261                               wimfile);
4262         }
4263
4264 out_wimlib_free:
4265         wimlib_free(wim);
4266 out_free_refglobs:
4267         string_set_destroy(&refglobs);
4268         return ret;
4269
4270 out_usage:
4271         usage(CMD_VERIFY, stderr);
4272         ret = -1;
4273         goto out_free_refglobs;
4274 }
4275
4276 struct imagex_command {
4277         const tchar *name;
4278         int (*func)(int argc, tchar **argv, int cmd);
4279 };
4280
4281 static const struct imagex_command imagex_commands[] = {
4282         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4283         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4284         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4285         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4286         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4287         [CMD_EXPORT]   = {T("export"),   imagex_export},
4288         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4289         [CMD_INFO]     = {T("info"),     imagex_info},
4290         [CMD_JOIN]     = {T("join"),     imagex_join},
4291 #if WIM_MOUNTING_SUPPORTED
4292         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4293         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4294 #endif
4295         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4296         [CMD_SPLIT]    = {T("split"),    imagex_split},
4297 #if WIM_MOUNTING_SUPPORTED
4298         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4299 #endif
4300         [CMD_UPDATE]   = {T("update"),   imagex_update},
4301         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4302 };
4303
4304 #ifdef __WIN32__
4305
4306    /* Can be a directory or source list file.  But source list file is probably
4307     * a rare use case, so just say directory.  */
4308 #  define SOURCE_STR T("DIRECTORY")
4309
4310    /* Can only be a directory  */
4311 #  define TARGET_STR T("DIRECTORY")
4312
4313 #else
4314    /* Can be a directory, NTFS volume, or source list file. */
4315 #  define SOURCE_STR T("SOURCE")
4316
4317    /* Can be a directory or NTFS volume.  */
4318 #  define TARGET_STR T("TARGET")
4319
4320 #endif
4321
4322 static const tchar *usage_strings[] = {
4323 [CMD_APPEND] =
4324 T(
4325 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4326 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4327 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4328 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4329 "                    [--wimboot] [--unix-data] [--dereference]\n"
4330 ),
4331 [CMD_APPLY] =
4332 T(
4333 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4334 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4335 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4336 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4337 "                    [--compact=FORMAT]\n"
4338 ),
4339 [CMD_CAPTURE] =
4340 T(
4341 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4342 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4343 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4344 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4345 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4346 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4347 ),
4348 [CMD_DELETE] =
4349 T(
4350 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4351 ),
4352 [CMD_DIR] =
4353 T(
4354 "    %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4355 ),
4356 [CMD_EXPORT] =
4357 T(
4358 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4359 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4360 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4361 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4362 "                    [--wimboot] [--solid]\n"
4363 ),
4364 [CMD_EXTRACT] =
4365 T(
4366 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4367 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4368 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4369 "                    [--no-attributes] [--include-invalid-names]\n"
4370 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4371 ),
4372 [CMD_INFO] =
4373 T(
4374 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4375 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4376 "                    [--extract-xml FILE] [--header] [--blobs]\n"
4377 ),
4378 [CMD_JOIN] =
4379 T(
4380 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4381 ),
4382 #if WIM_MOUNTING_SUPPORTED
4383 [CMD_MOUNT] =
4384 T(
4385 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4386 "                    [--check] [--streams-interface=INTERFACE]\n"
4387 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4388 ),
4389 [CMD_MOUNTRW] =
4390 T(
4391 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4392 "                    [--check] [--streams-interface=INTERFACE]\n"
4393 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4394 ),
4395 #endif
4396 [CMD_OPTIMIZE] =
4397 T(
4398 "    %"TS" WIMFILE\n"
4399 "                    [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4400 "                    [--check] [--nocheck] [--solid]\n"
4401 "\n"
4402 ),
4403 [CMD_SPLIT] =
4404 T(
4405 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4406 ),
4407 #if WIM_MOUNTING_SUPPORTED
4408 [CMD_UNMOUNT] =
4409 T(
4410 "    %"TS" DIRECTORY\n"
4411 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4412 ),
4413 #endif
4414 [CMD_UPDATE] =
4415 T(
4416 "    %"TS" WIMFILE [IMAGE]\n"
4417 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4418 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4419 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4420 "                    [< CMDFILE]\n"
4421 ),
4422 [CMD_VERIFY] =
4423 T(
4424 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4425 ),
4426 };
4427
4428 static const tchar *invocation_name;
4429 static int invocation_cmd = CMD_NONE;
4430
4431 static const tchar *get_cmd_string(int cmd, bool nospace)
4432 {
4433         static tchar buf[50];
4434         if (cmd == CMD_NONE) {
4435                 return T("wimlib-imagex");
4436         } else if (invocation_cmd != CMD_NONE) {
4437                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4438         } else {
4439                 const tchar *format;
4440
4441                 if (nospace)
4442                         format = T("%"TS"-%"TS"");
4443                 else
4444                         format = T("%"TS" %"TS"");
4445                 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4446         }
4447         return buf;
4448 }
4449
4450 static void
4451 version(void)
4452 {
4453         static const tchar *s =
4454         T(
4455 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4456 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4457 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4458 "This is free software: you are free to change and redistribute it.\n"
4459 "There is NO WARRANTY, to the extent permitted by law.\n"
4460 "\n"
4461 "Report bugs to "PACKAGE_BUGREPORT".\n"
4462         );
4463         tfputs(s, stdout);
4464 }
4465
4466
4467 static void
4468 help_or_version(int argc, tchar **argv, int cmd)
4469 {
4470         int i;
4471         const tchar *p;
4472
4473         for (i = 1; i < argc; i++) {
4474                 p = argv[i];
4475                 if (p[0] == T('-') && p[1] == T('-')) {
4476                         p += 2;
4477                         if (!tstrcmp(p, T("help"))) {
4478                                 if (cmd == CMD_NONE)
4479                                         usage_all(stdout);
4480                                 else
4481                                         usage(cmd, stdout);
4482                                 exit(0);
4483                         } else if (!tstrcmp(p, T("version"))) {
4484                                 version();
4485                                 exit(0);
4486                         }
4487                 }
4488         }
4489 }
4490
4491 static void
4492 print_usage_string(int cmd, FILE *fp)
4493 {
4494         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4495 }
4496
4497 static void
4498 recommend_man_page(int cmd, FILE *fp)
4499 {
4500         const tchar *format_str;
4501 #ifdef __WIN32__
4502         format_str = T("Some uncommon options are not listed;\n"
4503                        "See %"TS".pdf in the doc directory for more details.\n");
4504 #else
4505         format_str = T("Some uncommon options are not listed;\n"
4506                        "Try `man %"TS"' for more details.\n");
4507 #endif
4508         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4509 }
4510
4511 static void
4512 usage(int cmd, FILE *fp)
4513 {
4514         tfprintf(fp, T("Usage:\n"));
4515         print_usage_string(cmd, fp);
4516         tfprintf(fp, T("\n"));
4517         recommend_man_page(cmd, fp);
4518 }
4519
4520 static void
4521 usage_all(FILE *fp)
4522 {
4523         tfprintf(fp, T("Usage:\n"));
4524         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4525                 print_usage_string(cmd, fp);
4526                 tfprintf(fp, T("\n"));
4527         }
4528         static const tchar *extra =
4529         T(
4530 "    %"TS" --help\n"
4531 "    %"TS" --version\n"
4532 "\n"
4533         );
4534         tfprintf(fp, extra, invocation_name, invocation_name);
4535         tfprintf(fp,
4536                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4537                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4538                    "For some commands IMAGE may be \"all\".\n"
4539                    "\n"));
4540         recommend_man_page(CMD_NONE, fp);
4541 }
4542
4543 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4544  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4545  * something else), while on Windows the command arguments will be UTF-16LE
4546  * encoded 'wchar_t' strings. */
4547 int
4548 #ifdef __WIN32__
4549 wmain(int argc, wchar_t **argv, wchar_t **envp)
4550 #else
4551 main(int argc, char **argv)
4552 #endif
4553 {
4554         int ret;
4555         int init_flags = 0;
4556         int cmd;
4557
4558         imagex_info_file = stdout;
4559         invocation_name = tbasename(argv[0]);
4560
4561 #ifndef __WIN32__
4562         if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4563                 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4564         } else {
4565                 char *codeset;
4566
4567                 setlocale(LC_ALL, "");
4568                 codeset = nl_langinfo(CODESET);
4569                 if (!strstr(codeset, "UTF-8") &&
4570                     !strstr(codeset, "UTF8") &&
4571                     !strstr(codeset, "utf-8") &&
4572                     !strstr(codeset, "utf8"))
4573                 {
4574                         fprintf(stderr,
4575 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4576 "         Maybe try: `export LANG=en_US.UTF-8'?\n"
4577 "         Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4578 "         to any value to force wimlib to use UTF-8.\n",
4579                         invocation_name);
4580
4581                 }
4582         }
4583
4584 #endif /* !__WIN32__ */
4585
4586         {
4587                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4588                 if (igcase != NULL) {
4589                         if (!tstrcmp(igcase, T("no")) ||
4590                             !tstrcmp(igcase, T("0")))
4591                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4592                         else if (!tstrcmp(igcase, T("yes")) ||
4593                                  !tstrcmp(igcase, T("1")))
4594                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4595                         else {
4596                                 fprintf(stderr,
4597                                         "WARNING: Ignoring unknown setting of "
4598                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4599                         }
4600                 }
4601         }
4602
4603         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4604         cmd = CMD_NONE;
4605         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4606             tstrcmp(invocation_name, T("wimlib-imagex"))) {
4607                 for (int i = 0; i < CMD_MAX; i++) {
4608                         if (!tstrcmp(invocation_name + 3,
4609                                      imagex_commands[i].name))
4610                         {
4611                                 invocation_cmd = i;
4612                                 cmd = i;
4613                                 break;
4614                         }
4615                 }
4616         }
4617
4618         /* Unless already known from the invocation name, determine which
4619          * command was specified.  */
4620         if (cmd == CMD_NONE) {
4621                 if (argc < 2) {
4622                         imagex_error(T("No command specified!\n"));
4623                         usage_all(stderr);
4624                         exit(2);
4625                 }
4626                 for (int i = 0; i < CMD_MAX; i++) {
4627                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4628                                 cmd = i;
4629                                 break;
4630                         }
4631                 }
4632                 if (cmd != CMD_NONE) {
4633                         argc--;
4634                         argv++;
4635                 }
4636         }
4637
4638         /* Handle --help and --version.  --help can be either for the program as
4639          * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4640          * CMD_NONE).  Note: help_or_version() will not return if a --help or
4641          * --version argument was found.  */
4642         help_or_version(argc, argv, cmd);
4643
4644         /* Bail if a valid command was not specified.  */
4645         if (cmd == CMD_NONE) {
4646                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4647                 usage_all(stderr);
4648                 exit(2);
4649         }
4650
4651         /* Enable warning and error messages in wimlib to be more user-friendly.
4652          * */
4653         wimlib_set_print_errors(true);
4654
4655         /* Initialize wimlib.  */
4656         ret = wimlib_global_init(init_flags);
4657         if (ret)
4658                 goto out_check_status;
4659
4660         /* Call the command handler function.  */
4661         ret = imagex_commands[cmd].func(argc, argv, cmd);
4662
4663         /* Check for error writing to standard output, especially since for some
4664          * commands, writing to standard output is part of the program's actual
4665          * behavior and not just for informational purposes.  */
4666         if (ferror(stdout) || fclose(stdout)) {
4667                 imagex_error_with_errno(T("error writing to standard output"));
4668                 if (ret == 0)
4669                         ret = -1;
4670         }
4671 out_check_status:
4672         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4673          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4674          * error code from which an error message can be printed.  */
4675         if (ret > 0) {
4676                 imagex_error(T("Exiting with error code %d:\n"
4677                                "       %"TS"."), ret,
4678                              wimlib_get_error_string(ret));
4679                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4680                         imagex_error_with_errno(T("errno"));
4681         }
4682         /* Make wimlib free any resources it's holding (although this is not
4683          * strictly necessary because the process is ending anyway).  */
4684         wimlib_global_cleanup();
4685         return ret;
4686 }