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