]> wimlib.net Git - wimlib/blob - programs/imagex.c
e69d74fd91a6ab8879825cc19d3f08c752c1abd6
[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                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3312                                                       argv[0] + 1,
3313                                                       extract_flags);
3314                         argc--;
3315                         argv++;
3316                 }
3317         }
3318
3319         if (ret == 0) {
3320                 imagex_printf(T("Done extracting files.\n"));
3321         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3322                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3323                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3324                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3325                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3326                 {
3327                         tfprintf(stderr,
3328                                  T("Note: You can use the '--nullglob' "
3329                                    "option to ignore missing files.\n"));
3330                 }
3331                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3332                                    "files and directories\n"
3333                                    "      are in the WIM image.\n"),
3334                                 get_cmd_string(CMD_DIR, false));
3335         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3336                 struct wimlib_wim_info info;
3337
3338                 wimlib_get_wim_info(wim, &info);
3339                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3340         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3341                 struct wimlib_wim_info info;
3342
3343                 wimlib_get_wim_info(wim, &info);
3344                 do_metadata_not_found_warning(wimfile, &info);
3345         }
3346 out_wimlib_free:
3347         wimlib_free(wim);
3348 out_free_refglobs:
3349         string_list_destroy(&refglobs);
3350         return ret;
3351
3352 out_usage:
3353         usage(CMD_EXTRACT, stderr);
3354 out_err:
3355         ret = -1;
3356         goto out_free_refglobs;
3357 }
3358
3359 /* Prints information about a WIM file; also can mark an image as bootable,
3360  * change the name of an image, or change the description of an image. */
3361 static int
3362 imagex_info(int argc, tchar **argv, int cmd)
3363 {
3364         int c;
3365         bool boot         = false;
3366         bool header       = false;
3367         bool blobs        = false;
3368         bool xml          = false;
3369         bool short_header = true;
3370         const tchar *xml_out_file = NULL;
3371         const tchar *wimfile;
3372         const tchar *image_num_or_name;
3373         STRING_LIST(image_properties);
3374         WIMStruct *wim;
3375         int image;
3376         int ret;
3377         int open_flags = 0;
3378         int write_flags = 0;
3379         struct wimlib_wim_info info;
3380
3381         for_opt(c, info_options) {
3382                 switch (c) {
3383                 case IMAGEX_BOOT_OPTION:
3384                         boot = true;
3385                         break;
3386                 case IMAGEX_CHECK_OPTION:
3387                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3388                         /* fall-through */
3389                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3390                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3391                         break;
3392                 case IMAGEX_NOCHECK_OPTION:
3393                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3394                         break;
3395                 case IMAGEX_HEADER_OPTION:
3396                         header = true;
3397                         short_header = false;
3398                         break;
3399                 case IMAGEX_BLOBS_OPTION:
3400                         blobs = true;
3401                         short_header = false;
3402                         break;
3403                 case IMAGEX_XML_OPTION:
3404                         xml = true;
3405                         short_header = false;
3406                         break;
3407                 case IMAGEX_EXTRACT_XML_OPTION:
3408                         xml_out_file = optarg;
3409                         short_header = false;
3410                         break;
3411                 case IMAGEX_IMAGE_PROPERTY_OPTION:
3412                         ret = append_image_property_argument(&image_properties);
3413                         if (ret)
3414                                 goto out;
3415                         break;
3416                 default:
3417                         goto out_usage;
3418                 }
3419         }
3420
3421         argc -= optind;
3422         argv += optind;
3423         if (argc < 1 || argc > 4)
3424                 goto out_usage;
3425
3426         wimfile           = argv[0];
3427         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3428
3429         if (argc >= 3) {
3430                 /* NEW_NAME */
3431                 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3432                 tsprintf(p, T("NAME=%"TS), argv[2]);
3433                 ret = string_list_append(&image_properties, p);
3434                 if (ret)
3435                         goto out;
3436         }
3437
3438         if (argc >= 4) {
3439                 /* NEW_DESC */
3440                 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3441                 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3442                 ret = string_list_append(&image_properties, p);
3443                 if (ret)
3444                         goto out;
3445         }
3446
3447         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3448                                             imagex_progress_func, NULL);
3449         if (ret)
3450                 goto out;
3451
3452         wimlib_get_wim_info(wim, &info);
3453
3454         image = wimlib_resolve_image(wim, image_num_or_name);
3455         ret = WIMLIB_ERR_INVALID_IMAGE;
3456         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3457                 verify_image_exists(image, image_num_or_name, wimfile);
3458                 if (boot) {
3459                         imagex_error(T("If you would like to set the boot "
3460                                        "index to 0, specify image \"0\" with "
3461                                        "the --boot flag."));
3462                 }
3463                 goto out_wimlib_free;
3464         }
3465
3466         if (boot && info.image_count == 0) {
3467                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3468                 goto out_wimlib_free;
3469         }
3470
3471         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3472                 if (boot) {
3473                         imagex_error(T("Cannot specify the --boot flag "
3474                                        "without specifying a specific "
3475                                        "image in a multi-image WIM"));
3476                         goto out_wimlib_free;
3477                 }
3478                 if (image_properties.num_strings) {
3479                         imagex_error(T("Can't change image properties without "
3480                                        "specifying a specific image in a "
3481                                        "multi-image WIM"));
3482                         goto out_wimlib_free;
3483                 }
3484         }
3485
3486         /* Operations that print information are separated from operations that
3487          * recreate the WIM file. */
3488         if (!image_properties.num_strings && !boot) {
3489
3490                 /* Read-only operations */
3491
3492                 if (image == WIMLIB_NO_IMAGE) {
3493                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3494                                      image_num_or_name, wimfile);
3495                         goto out_wimlib_free;
3496                 }
3497
3498                 if (image == WIMLIB_ALL_IMAGES && short_header)
3499                         print_wim_information(wimfile, &info);
3500
3501                 if (header)
3502                         wimlib_print_header(wim);
3503
3504                 if (blobs) {
3505                         if (info.total_parts != 1) {
3506                                 tfprintf(stderr, T("Warning: Only showing the blobs "
3507                                                    "for part %d of a %d-part WIM.\n"),
3508                                          info.part_number, info.total_parts);
3509                         }
3510                         print_blobs(wim);
3511                 }
3512
3513                 if (xml) {
3514                         ret = wimlib_extract_xml_data(wim, stdout);
3515                         if (ret)
3516                                 goto out_wimlib_free;
3517                 }
3518
3519                 if (xml_out_file) {
3520                         FILE *fp;
3521
3522                         fp = tfopen(xml_out_file, T("wb"));
3523                         if (!fp) {
3524                                 imagex_error_with_errno(T("Failed to open the "
3525                                                           "file \"%"TS"\" for "
3526                                                           "writing"),
3527                                                         xml_out_file);
3528                                 ret = -1;
3529                                 goto out_wimlib_free;
3530                         }
3531                         ret = wimlib_extract_xml_data(wim, fp);
3532                         if (fclose(fp)) {
3533                                 imagex_error(T("Failed to close the file "
3534                                                "\"%"TS"\""),
3535                                              xml_out_file);
3536                                 ret = -1;
3537                         }
3538                         if (ret)
3539                                 goto out_wimlib_free;
3540                 }
3541
3542                 if (short_header)
3543                         wimlib_print_available_images(wim, image);
3544
3545                 ret = 0;
3546         } else {
3547                 /* Modification operations */
3548                 bool any_property_changes;
3549
3550                 if (image == WIMLIB_ALL_IMAGES)
3551                         image = 1;
3552
3553                 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3554                         imagex_error(T("Cannot change image properties "
3555                                        "when using image 0"));
3556                         ret = -1;
3557                         goto out_wimlib_free;
3558                 }
3559
3560                 if (boot) {
3561                         if (image == info.boot_index) {
3562                                 imagex_printf(T("Image %d is already marked as "
3563                                           "bootable.\n"), image);
3564                                 boot = false;
3565                         } else {
3566                                 imagex_printf(T("Marking image %d as bootable.\n"),
3567                                         image);
3568                                 info.boot_index = image;
3569                                 ret = wimlib_set_wim_info(wim, &info,
3570                                                           WIMLIB_CHANGE_BOOT_INDEX);
3571                                 if (ret)
3572                                         goto out_wimlib_free;
3573                         }
3574                 }
3575
3576                 ret = apply_image_properties(&image_properties, wim, image,
3577                                              &any_property_changes);
3578                 if (ret)
3579                         goto out_wimlib_free;
3580
3581                 /* Only call wimlib_overwrite() if something actually needs to
3582                  * be changed.  */
3583                 if (boot || any_property_changes ||
3584                     ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
3585                      !info.has_integrity_table) ||
3586                     ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
3587                      info.has_integrity_table))
3588                 {
3589                         ret = wimlib_overwrite(wim, write_flags, 1);
3590                 } else {
3591                         imagex_printf(T("The file \"%"TS"\" was not modified "
3592                                         "because nothing needed to be done.\n"),
3593                                       wimfile);
3594                         ret = 0;
3595                 }
3596         }
3597 out_wimlib_free:
3598         wimlib_free(wim);
3599 out:
3600         string_list_destroy(&image_properties);
3601         return ret;
3602
3603 out_usage:
3604         usage(CMD_INFO, stderr);
3605         ret = -1;
3606         goto out;
3607 }
3608
3609 /* Join split WIMs into one part WIM */
3610 static int
3611 imagex_join(int argc, tchar **argv, int cmd)
3612 {
3613         int c;
3614         int swm_open_flags = 0;
3615         int wim_write_flags = 0;
3616         const tchar *output_path;
3617         int ret;
3618
3619         for_opt(c, join_options) {
3620                 switch (c) {
3621                 case IMAGEX_CHECK_OPTION:
3622                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3623                         /* fall-through */
3624                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3625                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3626                         break;
3627                 default:
3628                         goto out_usage;
3629                 }
3630         }
3631         argc -= optind;
3632         argv += optind;
3633
3634         if (argc < 2) {
3635                 imagex_error(T("Must specify one or more split WIM (.swm) "
3636                                "parts to join"));
3637                 goto out_usage;
3638         }
3639         output_path = argv[0];
3640         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3641                                         --argc,
3642                                         output_path,
3643                                         swm_open_flags,
3644                                         wim_write_flags,
3645                                         imagex_progress_func,
3646                                         NULL);
3647 out:
3648         return ret;
3649
3650 out_usage:
3651         usage(CMD_JOIN, stderr);
3652         ret = -1;
3653         goto out;
3654 }
3655
3656 #if WIM_MOUNTING_SUPPORTED
3657
3658 /* Mounts a WIM image.  */
3659 static int
3660 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3661 {
3662         int c;
3663         int mount_flags = 0;
3664         int open_flags = 0;
3665         const tchar *staging_dir = NULL;
3666         const tchar *wimfile;
3667         const tchar *dir;
3668         WIMStruct *wim;
3669         struct wimlib_wim_info info;
3670         int image;
3671         int ret;
3672
3673         STRING_LIST(refglobs);
3674
3675         if (cmd == CMD_MOUNTRW) {
3676                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3677                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3678         }
3679
3680         for_opt(c, mount_options) {
3681                 switch (c) {
3682                 case IMAGEX_ALLOW_OTHER_OPTION:
3683                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3684                         break;
3685                 case IMAGEX_CHECK_OPTION:
3686                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3687                         break;
3688                 case IMAGEX_DEBUG_OPTION:
3689                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3690                         break;
3691                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3692                         if (!tstrcasecmp(optarg, T("none")))
3693                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3694                         else if (!tstrcasecmp(optarg, T("xattr")))
3695                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3696                         else if (!tstrcasecmp(optarg, T("windows")))
3697                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3698                         else {
3699                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3700                                              optarg);
3701                                 goto out_usage;
3702                         }
3703                         break;
3704                 case IMAGEX_REF_OPTION:
3705                         ret = string_list_append(&refglobs, optarg);
3706                         if (ret)
3707                                 goto out_free_refglobs;
3708                         break;
3709                 case IMAGEX_STAGING_DIR_OPTION:
3710                         staging_dir = optarg;
3711                         break;
3712                 case IMAGEX_UNIX_DATA_OPTION:
3713                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3714                         break;
3715                 default:
3716                         goto out_usage;
3717                 }
3718         }
3719         argc -= optind;
3720         argv += optind;
3721         if (argc != 2 && argc != 3)
3722                 goto out_usage;
3723
3724         wimfile = argv[0];
3725
3726         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3727                                             imagex_progress_func, NULL);
3728         if (ret)
3729                 goto out_free_refglobs;
3730
3731         wimlib_get_wim_info(wim, &info);
3732
3733         if (argc >= 3) {
3734                 /* Image explicitly specified.  */
3735                 image = wimlib_resolve_image(wim, argv[1]);
3736                 dir = argv[2];
3737                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3738                 if (ret)
3739                         goto out_free_wim;
3740         } else {
3741                 /* No image specified; default to image 1, but only if the WIM
3742                  * contains exactly one image.  */
3743
3744                 if (info.image_count != 1) {
3745                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3746                                        "select one."), wimfile, info.image_count);
3747                         wimlib_free(wim);
3748                         goto out_usage;
3749                 }
3750                 image = 1;
3751                 dir = argv[1];
3752         }
3753
3754         if (refglobs.num_strings) {
3755                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3756                 if (ret)
3757                         goto out_free_wim;
3758         }
3759
3760         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3761         if (ret) {
3762                 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3763                         do_metadata_not_found_warning(wimfile, &info);
3764                 } else {
3765                         imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3766                                        "on \"%"TS"\""),
3767                                      image, wimfile, dir);
3768                 }
3769         }
3770 out_free_wim:
3771         wimlib_free(wim);
3772 out_free_refglobs:
3773         string_list_destroy(&refglobs);
3774         return ret;
3775
3776 out_usage:
3777         usage(cmd, stderr);
3778         ret = -1;
3779         goto out_free_refglobs;
3780 }
3781 #endif /* WIM_MOUNTING_SUPPORTED */
3782
3783 /* Rebuild a WIM file */
3784 static int
3785 imagex_optimize(int argc, tchar **argv, int cmd)
3786 {
3787         int c;
3788         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3789         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3790         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3791         uint32_t chunk_size = UINT32_MAX;
3792         uint32_t solid_chunk_size = UINT32_MAX;
3793         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3794         int ret;
3795         WIMStruct *wim;
3796         const tchar *wimfile;
3797         off_t old_size;
3798         off_t new_size;
3799         unsigned num_threads = 0;
3800
3801         for_opt(c, optimize_options) {
3802                 switch (c) {
3803                 case IMAGEX_CHECK_OPTION:
3804                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3805                         /* fall-through */
3806                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3807                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3808                         break;
3809                 case IMAGEX_NOCHECK_OPTION:
3810                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3811                         break;
3812                 case IMAGEX_COMPRESS_OPTION:
3813                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3814                         compression_type = get_compression_type(optarg, false);
3815                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3816                                 goto out_err;
3817                         break;
3818                 case IMAGEX_RECOMPRESS_OPTION:
3819                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3820                         break;
3821                 case IMAGEX_CHUNK_SIZE_OPTION:
3822                         chunk_size = parse_chunk_size(optarg);
3823                         if (chunk_size == UINT32_MAX)
3824                                 goto out_err;
3825                         break;
3826                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3827                         solid_chunk_size = parse_chunk_size(optarg);
3828                         if (solid_chunk_size == UINT32_MAX)
3829                                 goto out_err;
3830                         break;
3831                 case IMAGEX_SOLID_COMPRESS_OPTION:
3832                         solid_ctype = get_compression_type(optarg, true);
3833                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3834                                 goto out_err;
3835                         break;
3836                 case IMAGEX_SOLID_OPTION:
3837                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3838                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3839                         break;
3840                 case IMAGEX_NO_SOLID_SORT_OPTION:
3841                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3842                         break;
3843                 case IMAGEX_THREADS_OPTION:
3844                         num_threads = parse_num_threads(optarg);
3845                         if (num_threads == UINT_MAX)
3846                                 goto out_err;
3847                         break;
3848                 case IMAGEX_PIPABLE_OPTION:
3849                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3850                         break;
3851                 case IMAGEX_NOT_PIPABLE_OPTION:
3852                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3853                         break;
3854                 case IMAGEX_UNSAFE_COMPACT_OPTION:
3855                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3856                         break;
3857                 default:
3858                         goto out_usage;
3859                 }
3860         }
3861         argc -= optind;
3862         argv += optind;
3863
3864         if (argc != 1)
3865                 goto out_usage;
3866
3867         wimfile = argv[0];
3868
3869         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3870                                             imagex_progress_func, NULL);
3871         if (ret)
3872                 goto out;
3873
3874         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3875                 /* Change compression type.  */
3876                 ret = wimlib_set_output_compression_type(wim, compression_type);
3877                 if (ret)
3878                         goto out_wimlib_free;
3879         }
3880
3881         if (chunk_size != UINT32_MAX) {
3882                 /* Change chunk size.  */
3883                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3884                 if (ret)
3885                         goto out_wimlib_free;
3886         }
3887         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3888                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3889                 if (ret)
3890                         goto out_wimlib_free;
3891         }
3892         if (solid_chunk_size != UINT32_MAX) {
3893                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3894                 if (ret)
3895                         goto out_wimlib_free;
3896         }
3897
3898         old_size = file_get_size(wimfile);
3899         tprintf(T("\"%"TS"\" original size: "), wimfile);
3900         if (old_size == -1)
3901                 tputs(T("Unknown"));
3902         else
3903                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3904
3905         ret = wimlib_overwrite(wim, write_flags, num_threads);
3906         if (ret) {
3907                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3908                 goto out_wimlib_free;
3909         }
3910
3911         new_size = file_get_size(wimfile);
3912         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3913         if (new_size == -1)
3914                 tputs(T("Unknown"));
3915         else
3916                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3917
3918         tfputs(T("Space saved: "), stdout);
3919         if (new_size != -1 && old_size != -1) {
3920                 tprintf(T("%lld KiB\n"),
3921                        ((long long)old_size - (long long)new_size) >> 10);
3922         } else {
3923                 tputs(T("Unknown"));
3924         }
3925         ret = 0;
3926 out_wimlib_free:
3927         wimlib_free(wim);
3928 out:
3929         return ret;
3930
3931 out_usage:
3932         usage(CMD_OPTIMIZE, stderr);
3933 out_err:
3934         ret = -1;
3935         goto out;
3936 }
3937
3938 /* Split a WIM into a spanned set */
3939 static int
3940 imagex_split(int argc, tchar **argv, int cmd)
3941 {
3942         int c;
3943         int open_flags = 0;
3944         int write_flags = 0;
3945         unsigned long part_size;
3946         tchar *tmp;
3947         int ret;
3948         WIMStruct *wim;
3949
3950         for_opt(c, split_options) {
3951                 switch (c) {
3952                 case IMAGEX_CHECK_OPTION:
3953                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3954                         /* fall-through */
3955                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3956                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3957                         break;
3958                 default:
3959                         goto out_usage;
3960                 }
3961         }
3962         argc -= optind;
3963         argv += optind;
3964
3965         if (argc != 3)
3966                 goto out_usage;
3967
3968         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3969         if (tmp == argv[2] || *tmp) {
3970                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3971                 imagex_error(T("The part size must be an integer or "
3972                                "floating-point number of megabytes."));
3973                 goto out_err;
3974         }
3975         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3976                                             imagex_progress_func, NULL);
3977         if (ret)
3978                 goto out;
3979
3980         ret = wimlib_split(wim, argv[1], part_size, write_flags);
3981         wimlib_free(wim);
3982 out:
3983         return ret;
3984
3985 out_usage:
3986         usage(CMD_SPLIT, stderr);
3987 out_err:
3988         ret = -1;
3989         goto out;
3990 }
3991
3992 #if WIM_MOUNTING_SUPPORTED
3993 /* Unmounts a mounted WIM image. */
3994 static int
3995 imagex_unmount(int argc, tchar **argv, int cmd)
3996 {
3997         int c;
3998         int unmount_flags = 0;
3999         int ret;
4000
4001         for_opt(c, unmount_options) {
4002                 switch (c) {
4003                 case IMAGEX_COMMIT_OPTION:
4004                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
4005                         break;
4006                 case IMAGEX_CHECK_OPTION:
4007                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
4008                         break;
4009                 case IMAGEX_REBUILD_OPTION:
4010                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
4011                         break;
4012                 case IMAGEX_LAZY_OPTION:
4013                 case IMAGEX_FORCE_OPTION:
4014                         /* Now, unmount is lazy by default.  However, committing
4015                          * the image will fail with
4016                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
4017                          * file descriptors on the WIM image.  The
4018                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
4019                          * descriptors to be closed.  */
4020                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4021                         break;
4022                 case IMAGEX_NEW_IMAGE_OPTION:
4023                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4024                         break;
4025                 default:
4026                         goto out_usage;
4027                 }
4028         }
4029         argc -= optind;
4030         argv += optind;
4031         if (argc != 1)
4032                 goto out_usage;
4033
4034         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4035                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4036                         imagex_error(T("--new-image is meaningless "
4037                                        "without --commit also specified!"));
4038                         goto out_err;
4039                 }
4040         }
4041
4042         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4043                                                  imagex_progress_func, NULL);
4044         if (ret) {
4045                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4046                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4047                         imagex_printf(T(
4048                                 "\tNote: Use --commit --force to force changes "
4049                                         "to be committed, regardless\n"
4050                                 "\t      of open files.\n"));
4051                 }
4052         }
4053 out:
4054         return ret;
4055
4056 out_usage:
4057         usage(CMD_UNMOUNT, stderr);
4058 out_err:
4059         ret = -1;
4060         goto out;
4061 }
4062 #endif /* WIM_MOUNTING_SUPPORTED */
4063
4064 /*
4065  * Add, delete, or rename files in a WIM image.
4066  */
4067 static int
4068 imagex_update(int argc, tchar **argv, int cmd)
4069 {
4070         const tchar *wimfile;
4071         int image;
4072         WIMStruct *wim;
4073         int ret;
4074         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4075         int write_flags = 0;
4076         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4077         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4078                                 WIMLIB_ADD_FLAG_VERBOSE |
4079                                 WIMLIB_ADD_FLAG_WINCONFIG;
4080         int default_delete_flags = 0;
4081         unsigned num_threads = 0;
4082         int c;
4083         tchar *cmd_file_contents;
4084         size_t cmd_file_nchars;
4085         struct wimlib_update_command *cmds;
4086         size_t num_cmds;
4087         tchar *command_str = NULL;
4088         tchar *config_file = NULL;
4089         tchar *wimboot_config = NULL;
4090
4091         for_opt(c, update_options) {
4092                 switch (c) {
4093                 /* Generic or write options */
4094                 case IMAGEX_THREADS_OPTION:
4095                         num_threads = parse_num_threads(optarg);
4096                         if (num_threads == UINT_MAX)
4097                                 goto out_err;
4098                         break;
4099                 case IMAGEX_CHECK_OPTION:
4100                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4101                         /* fall-through */
4102                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4103                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4104                         break;
4105                 case IMAGEX_REBUILD_OPTION:
4106                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4107                         break;
4108                 case IMAGEX_COMMAND_OPTION:
4109                         if (command_str) {
4110                                 imagex_error(T("--command may only be specified "
4111                                                "one time.  Please provide\n"
4112                                                "       the update commands "
4113                                                "on standard input instead."));
4114                                 goto out_err;
4115                         }
4116                         command_str = tstrdup(optarg);
4117                         if (!command_str) {
4118                                 imagex_error(T("Out of memory!"));
4119                                 goto out_err;
4120                         }
4121                         break;
4122                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4123                         wimboot_config = optarg;
4124                         break;
4125                 /* Default delete options */
4126                 case IMAGEX_FORCE_OPTION:
4127                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4128                         break;
4129                 case IMAGEX_RECURSIVE_OPTION:
4130                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4131                         break;
4132
4133                 /* Global add option */
4134                 case IMAGEX_CONFIG_OPTION:
4135                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4136                         config_file = optarg;
4137                         break;
4138
4139                 /* Default add options */
4140                 case IMAGEX_VERBOSE_OPTION:
4141                         /* No longer does anything.  */
4142                         break;
4143                 case IMAGEX_DEREFERENCE_OPTION:
4144                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4145                         break;
4146                 case IMAGEX_UNIX_DATA_OPTION:
4147                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4148                         break;
4149                 case IMAGEX_NO_ACLS_OPTION:
4150                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4151                         break;
4152                 case IMAGEX_STRICT_ACLS_OPTION:
4153                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4154                         break;
4155                 case IMAGEX_NO_REPLACE_OPTION:
4156                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4157                         break;
4158                 case IMAGEX_UNSAFE_COMPACT_OPTION:
4159                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4160                         break;
4161                 default:
4162                         goto out_usage;
4163                 }
4164         }
4165         argv += optind;
4166         argc -= optind;
4167
4168         if (argc != 1 && argc != 2)
4169                 goto out_usage;
4170         wimfile = argv[0];
4171
4172         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4173                                             imagex_progress_func, NULL);
4174         if (ret)
4175                 goto out_free_command_str;
4176
4177         if (argc >= 2) {
4178                 /* Image explicitly specified.  */
4179                 image = wimlib_resolve_image(wim, argv[1]);
4180                 ret = verify_image_exists_and_is_single(image, argv[1],
4181                                                         wimfile);
4182                 if (ret)
4183                         goto out_wimlib_free;
4184         } else {
4185                 /* No image specified; default to image 1, but only if the WIM
4186                  * contains exactly one image.  */
4187                 struct wimlib_wim_info info;
4188
4189                 wimlib_get_wim_info(wim, &info);
4190                 if (info.image_count != 1) {
4191                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4192                                      wimfile, info.image_count);
4193                         wimlib_free(wim);
4194                         goto out_usage;
4195                 }
4196                 image = 1;
4197         }
4198
4199         /* Read update commands from standard input, or the command string if
4200          * specified.  */
4201         if (command_str) {
4202                 cmd_file_contents = NULL;
4203                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4204                                                  &num_cmds);
4205                 if (!cmds) {
4206                         ret = -1;
4207                         goto out_free_cmd_file_contents;
4208                 }
4209         } else if (!wimboot_config) {
4210                 if (isatty(STDIN_FILENO)) {
4211                         tputs(T("Reading update commands from standard input..."));
4212                         recommend_man_page(CMD_UPDATE, stdout);
4213                 }
4214                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4215                 if (!cmd_file_contents) {
4216                         ret = -1;
4217                         goto out_wimlib_free;
4218                 }
4219
4220                 /* Parse the update commands */
4221                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4222                                                  &num_cmds);
4223                 if (!cmds) {
4224                         ret = -1;
4225                         goto out_free_cmd_file_contents;
4226                 }
4227         } else {
4228                 cmd_file_contents = NULL;
4229                 cmds = NULL;
4230                 num_cmds = 0;
4231         }
4232
4233         /* Set default flags and capture config on the update commands */
4234         for (size_t i = 0; i < num_cmds; i++) {
4235                 switch (cmds[i].op) {
4236                 case WIMLIB_UPDATE_OP_ADD:
4237                         cmds[i].add.add_flags |= default_add_flags;
4238                         cmds[i].add.config_file = config_file;
4239                         break;
4240                 case WIMLIB_UPDATE_OP_DELETE:
4241                         cmds[i].delete_.delete_flags |= default_delete_flags;
4242                         break;
4243                 default:
4244                         break;
4245                 }
4246         }
4247
4248         /* Execute the update commands */
4249         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4250         if (ret)
4251                 goto out_free_cmds;
4252
4253         if (wimboot_config) {
4254                 /* --wimboot-config=FILE is short for an
4255                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
4256                  */
4257                 struct wimlib_update_command cmd;
4258
4259                 cmd.op = WIMLIB_UPDATE_OP_ADD;
4260                 cmd.add.fs_source_path = wimboot_config;
4261                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4262                 cmd.add.config_file = NULL;
4263                 cmd.add.add_flags = 0;
4264
4265                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4266                 if (ret)
4267                         goto out_free_cmds;
4268         }
4269
4270         /* Overwrite the updated WIM */
4271         ret = wimlib_overwrite(wim, write_flags, num_threads);
4272 out_free_cmds:
4273         free(cmds);
4274 out_free_cmd_file_contents:
4275         free(cmd_file_contents);
4276 out_wimlib_free:
4277         wimlib_free(wim);
4278 out_free_command_str:
4279         free(command_str);
4280         return ret;
4281
4282 out_usage:
4283         usage(CMD_UPDATE, stderr);
4284 out_err:
4285         ret = -1;
4286         goto out_free_command_str;
4287 }
4288
4289 /* Verify a WIM file.  */
4290 static int
4291 imagex_verify(int argc, tchar **argv, int cmd)
4292 {
4293         int ret;
4294         const tchar *wimfile;
4295         WIMStruct *wim;
4296         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4297         int verify_flags = 0;
4298         STRING_LIST(refglobs);
4299         int c;
4300
4301         for_opt(c, verify_options) {
4302                 switch (c) {
4303                 case IMAGEX_REF_OPTION:
4304                         ret = string_list_append(&refglobs, optarg);
4305                         if (ret)
4306                                 goto out_free_refglobs;
4307                         break;
4308                 case IMAGEX_NOCHECK_OPTION:
4309                         open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4310                         break;
4311                 default:
4312                         goto out_usage;
4313                 }
4314         }
4315
4316         argv += optind;
4317         argc -= optind;
4318
4319         if (argc != 1) {
4320                 if (argc == 0)
4321                         imagex_error(T("Must specify a WIM file!"));
4322                 else
4323                         imagex_error(T("At most one WIM file can be specified!"));
4324                 goto out_usage;
4325         }
4326
4327         wimfile = argv[0];
4328
4329         ret = wimlib_open_wim_with_progress(wimfile,
4330                                             open_flags,
4331                                             &wim,
4332                                             imagex_progress_func,
4333                                             NULL);
4334         if (ret)
4335                 goto out_free_refglobs;
4336
4337         ret = wim_reference_globs(wim, &refglobs, open_flags);
4338         if (ret)
4339                 goto out_wimlib_free;
4340
4341         ret = wimlib_verify_wim(wim, verify_flags);
4342         if (ret) {
4343                 tputc(T('\n'), stderr);
4344                 imagex_error(T("\"%"TS"\" failed verification!"),
4345                              wimfile);
4346                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4347                     refglobs.num_strings == 0)
4348                 {
4349                         imagex_printf(T("Note: if this WIM file is not standalone, "
4350                                         "use the --ref option to specify the other parts.\n"));
4351                 }
4352         } else {
4353                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4354                               wimfile);
4355         }
4356
4357 out_wimlib_free:
4358         wimlib_free(wim);
4359 out_free_refglobs:
4360         string_list_destroy(&refglobs);
4361         return ret;
4362
4363 out_usage:
4364         usage(CMD_VERIFY, stderr);
4365         ret = -1;
4366         goto out_free_refglobs;
4367 }
4368
4369 struct imagex_command {
4370         const tchar *name;
4371         int (*func)(int argc, tchar **argv, int cmd);
4372 };
4373
4374 static const struct imagex_command imagex_commands[] = {
4375         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4376         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4377         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4378         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4379         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4380         [CMD_EXPORT]   = {T("export"),   imagex_export},
4381         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4382         [CMD_INFO]     = {T("info"),     imagex_info},
4383         [CMD_JOIN]     = {T("join"),     imagex_join},
4384 #if WIM_MOUNTING_SUPPORTED
4385         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4386         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4387 #endif
4388         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4389         [CMD_SPLIT]    = {T("split"),    imagex_split},
4390 #if WIM_MOUNTING_SUPPORTED
4391         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4392 #endif
4393         [CMD_UPDATE]   = {T("update"),   imagex_update},
4394         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4395 };
4396
4397 #ifdef __WIN32__
4398
4399    /* Can be a directory or source list file.  But source list file is probably
4400     * a rare use case, so just say directory.  */
4401 #  define SOURCE_STR T("DIRECTORY")
4402
4403    /* Can only be a directory  */
4404 #  define TARGET_STR T("DIRECTORY")
4405
4406 #else
4407    /* Can be a directory, NTFS volume, or source list file. */
4408 #  define SOURCE_STR T("SOURCE")
4409
4410    /* Can be a directory or NTFS volume.  */
4411 #  define TARGET_STR T("TARGET")
4412
4413 #endif
4414
4415 static const tchar * const usage_strings[] = {
4416 [CMD_APPEND] =
4417 T(
4418 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4419 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4420 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4421 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4422 "                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4423 "                    [--dereference] [--snapshot]\n"
4424 ),
4425 [CMD_APPLY] =
4426 T(
4427 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4428 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4429 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4430 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4431 "                    [--compact=FORMAT]\n"
4432 ),
4433 [CMD_CAPTURE] =
4434 T(
4435 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4436 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4437 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4438 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4439 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4440 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4441 "                    [--snapshot]\n"
4442 ),
4443 [CMD_DELETE] =
4444 T(
4445 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4446 ),
4447 [CMD_DIR] =
4448 T(
4449 "    %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4450 ),
4451 [CMD_EXPORT] =
4452 T(
4453 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4454 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4455 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4456 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4457 "                    [--wimboot] [--solid]\n"
4458 ),
4459 [CMD_EXTRACT] =
4460 T(
4461 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4462 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4463 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4464 "                    [--no-attributes] [--include-invalid-names]\n"
4465 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4466 ),
4467 [CMD_INFO] =
4468 T(
4469 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4470 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4471 "                    [--extract-xml FILE] [--header] [--blobs]\n"
4472 "                    [--image-property NAME=VALUE]\n"
4473 ),
4474 [CMD_JOIN] =
4475 T(
4476 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4477 ),
4478 #if WIM_MOUNTING_SUPPORTED
4479 [CMD_MOUNT] =
4480 T(
4481 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4482 "                    [--check] [--streams-interface=INTERFACE]\n"
4483 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4484 ),
4485 [CMD_MOUNTRW] =
4486 T(
4487 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4488 "                    [--check] [--streams-interface=INTERFACE]\n"
4489 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4490 ),
4491 #endif
4492 [CMD_OPTIMIZE] =
4493 T(
4494 "    %"TS" WIMFILE\n"
4495 "                    [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4496 "                    [--check] [--nocheck] [--solid]\n"
4497 "\n"
4498 ),
4499 [CMD_SPLIT] =
4500 T(
4501 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4502 ),
4503 #if WIM_MOUNTING_SUPPORTED
4504 [CMD_UNMOUNT] =
4505 T(
4506 "    %"TS" DIRECTORY\n"
4507 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4508 ),
4509 #endif
4510 [CMD_UPDATE] =
4511 T(
4512 "    %"TS" WIMFILE [IMAGE]\n"
4513 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4514 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4515 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4516 "                    [< CMDFILE]\n"
4517 ),
4518 [CMD_VERIFY] =
4519 T(
4520 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4521 ),
4522 };
4523
4524 static const tchar *invocation_name;
4525 static int invocation_cmd = CMD_NONE;
4526
4527 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4528 {
4529         static tchar buf[50];
4530
4531         if (cmd == CMD_NONE)
4532                 return T("wimlib-imagex");
4533
4534         if (only_short_form || invocation_cmd != CMD_NONE) {
4535                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4536         } else {
4537                 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4538                          imagex_commands[cmd].name);
4539         }
4540         return buf;
4541 }
4542
4543 static void
4544 version(void)
4545 {
4546         static const tchar * const fmt =
4547         T(
4548 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4549 "Copyright (C) 2012-2018 Eric Biggers\n"
4550 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4551 "This is free software: you are free to change and redistribute it.\n"
4552 "There is NO WARRANTY, to the extent permitted by law.\n"
4553 "\n"
4554 "Report bugs to "PACKAGE_BUGREPORT".\n"
4555         );
4556         tfprintf(stdout, fmt, wimlib_get_version_string());
4557 }
4558
4559 static void
4560 do_common_options(int *argc_p, tchar **argv, int cmd)
4561 {
4562         int argc = *argc_p;
4563         int i;
4564         const tchar *p;
4565
4566         for (i = 1; i < argc; i++) {
4567                 p = argv[i];
4568                 if (p[0] == T('-') && p[1] == T('-')) {
4569                         p += 2;
4570                         if (!tstrcmp(p, T("help"))) {
4571                                 if (cmd == CMD_NONE)
4572                                         usage_all(stdout);
4573                                 else
4574                                         usage(cmd, stdout);
4575                                 exit(0);
4576                         } else if (!tstrcmp(p, T("version"))) {
4577                                 version();
4578                                 exit(0);
4579                         } else if (!tstrcmp(p, T("quiet"))) {
4580                                 imagex_suppress_output();
4581                                 memmove(&argv[i], &argv[i + 1],
4582                                         (argc - i) * sizeof(argv[i]));
4583                                 argc--;
4584                                 i--;
4585                         } else if (!*p) /* reached "--", no more options */
4586                                 break;
4587                 }
4588         }
4589
4590         *argc_p = argc;
4591 }
4592
4593 static void
4594 print_usage_string(int cmd, FILE *fp)
4595 {
4596         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4597 }
4598
4599 static void
4600 recommend_man_page(int cmd, FILE *fp)
4601 {
4602         const tchar *format_str;
4603 #ifdef __WIN32__
4604         format_str = T("Some uncommon options are not listed;\n"
4605                        "See %"TS".pdf in the doc directory for more details.\n");
4606 #else
4607         format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4608 #endif
4609         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4610 }
4611
4612 static void
4613 usage(int cmd, FILE *fp)
4614 {
4615         tfprintf(fp, T("Usage:\n"));
4616         print_usage_string(cmd, fp);
4617         tfprintf(fp, T("\n"));
4618         recommend_man_page(cmd, fp);
4619 }
4620
4621 static void
4622 usage_all(FILE *fp)
4623 {
4624         tfprintf(fp, T("Usage:\n"));
4625         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4626                 print_usage_string(cmd, fp);
4627                 tfprintf(fp, T("\n"));
4628         }
4629         static const tchar * const extra =
4630         T(
4631 "    %"TS" --help\n"
4632 "    %"TS" --version\n"
4633 "\n"
4634         );
4635         tfprintf(fp, extra, invocation_name, invocation_name);
4636         tfprintf(fp,
4637                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4638                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4639                    "For some commands IMAGE may be \"all\".\n"
4640                    "\n"));
4641         recommend_man_page(CMD_NONE, fp);
4642 }
4643
4644 #ifdef __WIN32__
4645 extern int wmain(int argc, wchar_t **argv);
4646 #define main wmain
4647 #endif
4648
4649 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4650  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4651  * something else), while on Windows the command arguments will be UTF-16LE
4652  * encoded 'wchar_t' strings. */
4653 int
4654 main(int argc, tchar **argv)
4655 {
4656         int ret;
4657         int init_flags = 0;
4658         int cmd;
4659
4660         imagex_info_file = stdout;
4661         invocation_name = tbasename(argv[0]);
4662
4663         {
4664                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4665                 if (igcase != NULL) {
4666                         if (!tstrcmp(igcase, T("no")) ||
4667                             !tstrcmp(igcase, T("0")))
4668                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4669                         else if (!tstrcmp(igcase, T("yes")) ||
4670                                  !tstrcmp(igcase, T("1")))
4671                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4672                         else {
4673                                 fprintf(stderr,
4674                                         "WARNING: Ignoring unknown setting of "
4675                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4676                         }
4677                 }
4678         }
4679
4680         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4681         cmd = CMD_NONE;
4682         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4683             tstrcmp(invocation_name, T("wimlib-imagex"))) {
4684                 for (int i = 0; i < CMD_MAX; i++) {
4685                         if (!tstrcmp(invocation_name + 3,
4686                                      imagex_commands[i].name))
4687                         {
4688                                 invocation_cmd = i;
4689                                 cmd = i;
4690                                 break;
4691                         }
4692                 }
4693         }
4694
4695         /* Unless already known from the invocation name, determine which
4696          * command was specified.  */
4697         if (cmd == CMD_NONE) {
4698                 if (argc < 2) {
4699                         imagex_error(T("No command specified!\n"));
4700                         usage_all(stderr);
4701                         exit(2);
4702                 }
4703                 for (int i = 0; i < CMD_MAX; i++) {
4704                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4705                                 cmd = i;
4706                                 break;
4707                         }
4708                 }
4709                 if (cmd != CMD_NONE) {
4710                         argc--;
4711                         argv++;
4712                 }
4713         }
4714
4715         /* Handle common options.  May exit early (for --help or --version).  */
4716         do_common_options(&argc, argv, cmd);
4717
4718         /* Bail if a valid command was not specified.  */
4719         if (cmd == CMD_NONE) {
4720                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4721                 usage_all(stderr);
4722                 exit(2);
4723         }
4724
4725         /* Enable warning and error messages in wimlib to be more user-friendly.
4726          * */
4727         wimlib_set_print_errors(true);
4728
4729         /* Initialize wimlib.  */
4730         ret = wimlib_global_init(init_flags);
4731         if (ret)
4732                 goto out_check_status;
4733
4734         /* Call the command handler function.  */
4735         ret = imagex_commands[cmd].func(argc, argv, cmd);
4736
4737         /* Check for error writing to standard output, especially since for some
4738          * commands, writing to standard output is part of the program's actual
4739          * behavior and not just for informational purposes.  */
4740         if (ferror(stdout) || fclose(stdout)) {
4741                 imagex_error_with_errno(T("error writing to standard output"));
4742                 if (ret == 0)
4743                         ret = -1;
4744         }
4745 out_check_status:
4746         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4747          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4748          * error code from which an error message can be printed.  */
4749         if (ret > 0) {
4750                 imagex_error(T("Exiting with error code %d:\n"
4751                                "       %"TS"."), ret,
4752                              wimlib_get_error_string(ret));
4753                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4754                         imagex_error_with_errno(T("errno"));
4755         }
4756         /* Make wimlib free any resources it's holding (although this is not
4757          * strictly necessary because the process is ending anyway).  */
4758         wimlib_global_cleanup();
4759         return ret;
4760 }