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