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