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