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