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