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