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