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