]> wimlib.net Git - wimlib/blob - programs/imagex.c
wimlib-imagex: allow specifying suffix in chunk size options
[wimlib] / programs / imagex.c
1 /*
2  * imagex.c
3  *
4  * Use wimlib to create, modify, extract, mount, unmount, or display information
5  * about a WIM file
6  */
7
8 /*
9  * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h" /* Need for PACKAGE_VERSION, etc. */
27 #endif
28
29 #include "wimlib.h"
30 #include "wimlib_tchar.h"
31
32 #include <ctype.h>
33 #include <errno.h>
34
35 #include <inttypes.h>
36 #include <libgen.h>
37 #include <limits.h>
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <locale.h>
44
45 #ifdef HAVE_ALLOCA_H
46 #  include <alloca.h>
47 #endif
48
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
50
51 #ifdef __WIN32__
52 #  include "imagex-win32.h"
53 #  define print_security_descriptor     win32_print_security_descriptor
54 #else /* __WIN32__ */
55 #  include <getopt.h>
56 #  include <langinfo.h>
57 #  define print_security_descriptor     default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
59 {
60 }
61 #endif /* !__WIN32 */
62
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64  * they will never work.  However on UNIX-like systems we always present them,
65  * even if WITH_FUSE is not defined at this point, as to not tie the build of
66  * wimlib-imagex to a specific build of wimlib.  */
67 #ifdef __WIN32__
68 #  define WIM_MOUNTING_SUPPORTED 0
69 #else
70 #  define WIM_MOUNTING_SUPPORTED 1
71 #endif
72
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
74
75 static inline bool
76 is_any_path_separator(tchar c)
77 {
78         return c == T('/') || c == T('\\');
79 }
80
81 /* Like basename(), but handles both forward and backwards slashes.  */
82 static tchar *
83 tbasename(tchar *path)
84 {
85         tchar *p = tstrchr(path, T('\0'));
86
87         for (;;) {
88                 if (p == path)
89                         return path;
90                 if (!is_any_path_separator(*--p))
91                         break;
92                 *p = T('\0');
93         }
94
95         for (;;) {
96                 if (p == path)
97                         return path;
98                 if (is_any_path_separator(*--p))
99                         return ++p;
100         }
101 }
102
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
104                                 opts, NULL)) != -1)
105
106 enum {
107         CMD_NONE = -1,
108         CMD_APPEND = 0,
109         CMD_APPLY,
110         CMD_CAPTURE,
111         CMD_DELETE,
112         CMD_DIR,
113         CMD_EXPORT,
114         CMD_EXTRACT,
115         CMD_INFO,
116         CMD_JOIN,
117 #if WIM_MOUNTING_SUPPORTED
118         CMD_MOUNT,
119         CMD_MOUNTRW,
120 #endif
121         CMD_OPTIMIZE,
122         CMD_SPLIT,
123 #if WIM_MOUNTING_SUPPORTED
124         CMD_UNMOUNT,
125 #endif
126         CMD_UPDATE,
127         CMD_VERIFY,
128         CMD_MAX,
129 };
130
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
135
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
138
139 #define imagex_printf(format, ...) \
140                 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
141
142 enum {
143         IMAGEX_ALLOW_OTHER_OPTION,
144         IMAGEX_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
1307 parse_chunk_size(const tchar *optarg)
1308 {
1309         tchar *tmp;
1310         uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1311         if (chunk_size == 0) {
1312                 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1313                                "       with optional K, M, or G suffix"));
1314                 return UINT32_MAX;
1315         }
1316         if (*tmp) {
1317                 if (*tmp == T('k') || *tmp == T('K')) {
1318                         chunk_size <<= 10;
1319                         tmp++;
1320                 } else if (*tmp == T('m') || *tmp == T('M')) {
1321                         chunk_size <<= 20;
1322                         tmp++;
1323                 } else if (*tmp == T('g') || *tmp == T('G')) {
1324                         chunk_size <<= 30;
1325                         tmp++;
1326                 }
1327                 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1328                         imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1329                         return UINT32_MAX;
1330                 }
1331         }
1332         if (chunk_size >= UINT32_MAX) {
1333                 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1334                 return UINT32_MAX;
1335         }
1336         return chunk_size;
1337 }
1338
1339
1340 /*
1341  * Parse an option passed to an update command.
1342  *
1343  * @op:         One of WIMLIB_UPDATE_OP_* that indicates the command being
1344  *              parsed.
1345  *
1346  * @option:     Text string for the option (beginning with --)
1347  *
1348  * @cmd:        `struct wimlib_update_command' that is being constructed for
1349  *              this command.
1350  *
1351  * Returns true if the option was recognized; false if not.
1352  */
1353 static bool
1354 update_command_add_option(int op, const tchar *option,
1355                           struct wimlib_update_command *cmd)
1356 {
1357         bool recognized = true;
1358         switch (op) {
1359         case WIMLIB_UPDATE_OP_ADD:
1360                 if (!tstrcmp(option, T("--verbose")))
1361                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1362                 else if (!tstrcmp(option, T("--unix-data")))
1363                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1364                 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1365                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1366                 else if (!tstrcmp(option, T("--strict-acls")))
1367                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1368                 else if (!tstrcmp(option, T("--dereference")))
1369                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1370                 else if (!tstrcmp(option, T("--no-replace")))
1371                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1372                 else
1373                         recognized = false;
1374                 break;
1375         case WIMLIB_UPDATE_OP_DELETE:
1376                 if (!tstrcmp(option, T("--force")))
1377                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1378                 else if (!tstrcmp(option, T("--recursive")))
1379                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1380                 else
1381                         recognized = false;
1382                 break;
1383         default:
1384                 recognized = false;
1385                 break;
1386         }
1387         return recognized;
1388 }
1389
1390 /* How many nonoption arguments each `imagex update' command expects */
1391 static const unsigned update_command_num_nonoptions[] = {
1392         [WIMLIB_UPDATE_OP_ADD] = 2,
1393         [WIMLIB_UPDATE_OP_DELETE] = 1,
1394         [WIMLIB_UPDATE_OP_RENAME] = 2,
1395 };
1396
1397 static void
1398 update_command_add_nonoption(int op, const tchar *nonoption,
1399                              struct wimlib_update_command *cmd,
1400                              unsigned num_nonoptions)
1401 {
1402         switch (op) {
1403         case WIMLIB_UPDATE_OP_ADD:
1404                 if (num_nonoptions == 0)
1405                         cmd->add.fs_source_path = (tchar*)nonoption;
1406                 else
1407                         cmd->add.wim_target_path = (tchar*)nonoption;
1408                 break;
1409         case WIMLIB_UPDATE_OP_DELETE:
1410                 cmd->delete_.wim_path = (tchar*)nonoption;
1411                 break;
1412         case WIMLIB_UPDATE_OP_RENAME:
1413                 if (num_nonoptions == 0)
1414                         cmd->rename.wim_source_path = (tchar*)nonoption;
1415                 else
1416                         cmd->rename.wim_target_path = (tchar*)nonoption;
1417                 break;
1418         }
1419 }
1420
1421 /*
1422  * Parse a command passed on stdin to `imagex update'.
1423  *
1424  * @line:       Text of the command.
1425  * @len:        Length of the line, including a null terminator
1426  *              at line[len - 1].
1427  *
1428  * @command:    A `struct wimlib_update_command' to fill in from the parsed
1429  *              line.
1430  *
1431  * @line_number: Line number of the command, for diagnostics.
1432  *
1433  * Returns true on success; returns false on parse error.
1434  */
1435 static bool
1436 parse_update_command(tchar *line, size_t len,
1437                      struct wimlib_update_command *command,
1438                      size_t line_number)
1439 {
1440         int ret;
1441         tchar *command_name;
1442         int op;
1443         size_t num_nonoptions;
1444
1445         /* Get the command name ("add", "delete", "rename") */
1446         ret = parse_string(&line, &len, &command_name);
1447         if (ret != PARSE_STRING_SUCCESS)
1448                 return false;
1449
1450         if (!tstrcasecmp(command_name, T("add"))) {
1451                 op = WIMLIB_UPDATE_OP_ADD;
1452         } else if (!tstrcasecmp(command_name, T("delete"))) {
1453                 op = WIMLIB_UPDATE_OP_DELETE;
1454         } else if (!tstrcasecmp(command_name, T("rename"))) {
1455                 op = WIMLIB_UPDATE_OP_RENAME;
1456         } else {
1457                 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1458                              command_name, line_number);
1459                 return false;
1460         }
1461         command->op = op;
1462
1463         /* Parse additional options and non-options as needed */
1464         num_nonoptions = 0;
1465         for (;;) {
1466                 tchar *next_string;
1467
1468                 ret = parse_string(&line, &len, &next_string);
1469                 if (ret == PARSE_STRING_NONE) /* End of line */
1470                         break;
1471                 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1472                         return false;
1473                 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1474                         /* Option */
1475                         if (!update_command_add_option(op, next_string, command))
1476                         {
1477                                 imagex_error(T("Unrecognized option \"%"TS"\" to "
1478                                                "update command \"%"TS"\" on line %zu"),
1479                                              next_string, command_name, line_number);
1480
1481                                 return false;
1482                         }
1483                 } else {
1484                         /* Nonoption */
1485                         if (num_nonoptions == update_command_num_nonoptions[op])
1486                         {
1487                                 imagex_error(T("Unexpected argument \"%"TS"\" in "
1488                                                "update command on line %zu\n"
1489                                                "       (The \"%"TS"\" command only "
1490                                                "takes %zu nonoption arguments!)\n"),
1491                                              next_string, line_number,
1492                                              command_name, num_nonoptions);
1493                                 return false;
1494                         }
1495                         update_command_add_nonoption(op, next_string,
1496                                                      command, num_nonoptions);
1497                         num_nonoptions++;
1498                 }
1499         }
1500
1501         if (num_nonoptions != update_command_num_nonoptions[op]) {
1502                 imagex_error(T("Not enough arguments to update command "
1503                                "\"%"TS"\" on line %zu"), command_name, line_number);
1504                 return false;
1505         }
1506         return true;
1507 }
1508
1509 static struct wimlib_update_command *
1510 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1511                           size_t *num_cmds_ret)
1512 {
1513         ssize_t nlines;
1514         tchar *p;
1515         struct wimlib_update_command *cmds;
1516         size_t i, j;
1517
1518         nlines = text_file_count_lines(cmd_file_contents_p,
1519                                        &cmd_file_nchars);
1520         if (nlines < 0)
1521                 return NULL;
1522
1523         /* Always allocate at least 1 slot, just in case the implementation of
1524          * calloc() returns NULL if 0 bytes are requested. */
1525         cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1526         if (!cmds) {
1527                 imagex_error(T("out of memory"));
1528                 return NULL;
1529         }
1530         p = *cmd_file_contents_p;
1531         j = 0;
1532         for (i = 0; i < nlines; i++) {
1533                 /* XXX: Could use rawmemchr() here instead, but it may not be
1534                  * available on all platforms. */
1535                 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1536                 size_t len = endp - p + 1;
1537                 *endp = T('\0');
1538                 if (!is_comment_line(p, len)) {
1539                         if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1540                                 free(cmds);
1541                                 return NULL;
1542                         }
1543                 }
1544                 p = endp + 1;
1545         }
1546         *num_cmds_ret = j;
1547         return cmds;
1548 }
1549
1550 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1551  * one image from a WIM file to an NTFS volume.  */
1552 static int
1553 imagex_apply(int argc, tchar **argv, int cmd)
1554 {
1555         int c;
1556         int open_flags = 0;
1557         int image = WIMLIB_NO_IMAGE;
1558         WIMStruct *wim;
1559         struct wimlib_wim_info info;
1560         int ret;
1561         const tchar *wimfile;
1562         const tchar *target;
1563         const tchar *image_num_or_name = NULL;
1564         int extract_flags = 0;
1565
1566         STRING_SET(refglobs);
1567
1568         for_opt(c, apply_options) {
1569                 switch (c) {
1570                 case IMAGEX_CHECK_OPTION:
1571                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1572                         break;
1573                 case IMAGEX_VERBOSE_OPTION:
1574                         /* No longer does anything.  */
1575                         break;
1576                 case IMAGEX_REF_OPTION:
1577                         ret = string_set_append(&refglobs, optarg);
1578                         if (ret)
1579                                 goto out_free_refglobs;
1580                         break;
1581                 case IMAGEX_UNIX_DATA_OPTION:
1582                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1583                         break;
1584                 case IMAGEX_NO_ACLS_OPTION:
1585                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1586                         break;
1587                 case IMAGEX_STRICT_ACLS_OPTION:
1588                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1589                         break;
1590                 case IMAGEX_NO_ATTRIBUTES_OPTION:
1591                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1592                         break;
1593                 case IMAGEX_NORPFIX_OPTION:
1594                         extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1595                         break;
1596                 case IMAGEX_RPFIX_OPTION:
1597                         extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1598                         break;
1599                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1600                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1601                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1602                         break;
1603                 case IMAGEX_RESUME_OPTION:
1604                         extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1605                         break;
1606                 case IMAGEX_WIMBOOT_OPTION:
1607                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1608                         break;
1609                 default:
1610                         goto out_usage;
1611                 }
1612         }
1613         argc -= optind;
1614         argv += optind;
1615         if (argc != 2 && argc != 3)
1616                 goto out_usage;
1617
1618         wimfile = argv[0];
1619
1620         if (!tstrcmp(wimfile, T("-"))) {
1621                 /* Attempt to apply pipable WIM from standard input.  */
1622                 if (argc == 2) {
1623                         image_num_or_name = NULL;
1624                         target = argv[1];
1625                 } else {
1626                         image_num_or_name = argv[1];
1627                         target = argv[2];
1628                 }
1629                 wim = NULL;
1630         } else {
1631                 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1632                                                     imagex_progress_func, NULL);
1633                 if (ret)
1634                         goto out_free_refglobs;
1635
1636                 wimlib_get_wim_info(wim, &info);
1637
1638                 if (argc >= 3) {
1639                         /* Image explicitly specified.  */
1640                         image_num_or_name = argv[1];
1641                         image = wimlib_resolve_image(wim, image_num_or_name);
1642                         ret = verify_image_exists(image, image_num_or_name, wimfile);
1643                         if (ret)
1644                                 goto out_wimlib_free;
1645                         target = argv[2];
1646                 } else {
1647                         /* No image specified; default to image 1, but only if the WIM
1648                          * contains exactly one image.  */
1649
1650                         if (info.image_count != 1) {
1651                                 imagex_error(T("\"%"TS"\" contains %d images; "
1652                                                "Please select one (or all)."),
1653                                              wimfile, info.image_count);
1654                                 wimlib_free(wim);
1655                                 goto out_usage;
1656                         }
1657                         image = 1;
1658                         target = argv[1];
1659                 }
1660         }
1661
1662         if (refglobs.num_strings) {
1663                 if (wim == NULL) {
1664                         imagex_error(T("Can't specify --ref when applying from stdin!"));
1665                         ret = -1;
1666                         goto out_wimlib_free;
1667                 }
1668                 ret = wim_reference_globs(wim, &refglobs, open_flags);
1669                 if (ret)
1670                         goto out_wimlib_free;
1671         }
1672
1673 #ifndef __WIN32__
1674         {
1675                 /* Interpret a regular file or block device target as an NTFS
1676                  * volume.  */
1677                 struct stat stbuf;
1678
1679                 if (tstat(target, &stbuf)) {
1680                         if (errno != ENOENT) {
1681                                 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1682                                                         target);
1683                                 ret = -1;
1684                                 goto out_wimlib_free;
1685                         }
1686                 } else {
1687                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1688                                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1689                 }
1690         }
1691 #endif
1692
1693         if (wim) {
1694                 ret = wimlib_extract_image(wim, image, target, extract_flags);
1695         } else {
1696                 set_fd_to_binary_mode(STDIN_FILENO);
1697                 ret = wimlib_extract_image_from_pipe_with_progress(
1698                                            STDIN_FILENO,
1699                                            image_num_or_name,
1700                                            target,
1701                                            extract_flags,
1702                                            imagex_progress_func,
1703                                            NULL);
1704         }
1705         if (ret == 0) {
1706                 imagex_printf(T("Done applying WIM image.\n"));
1707         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1708                 if (wim) {
1709                         do_resource_not_found_warning(wimfile, &info, &refglobs);
1710                 } else {
1711                         imagex_error(T(        "If you are applying an image "
1712                                                "from a split pipable WIM,\n"
1713                                        "       make sure you have "
1714                                        "concatenated together all parts."));
1715                 }
1716         }
1717 out_wimlib_free:
1718         wimlib_free(wim);
1719 out_free_refglobs:
1720         string_set_destroy(&refglobs);
1721         return ret;
1722
1723 out_usage:
1724         usage(CMD_APPLY, stderr);
1725         ret = -1;
1726         goto out_free_refglobs;
1727 }
1728
1729 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1730  * directory trees.  'wimlib-imagex capture': create a new WIM file containing
1731  * the desired image.  'wimlib-imagex append': add a new image to an existing
1732  * WIM file. */
1733 static int
1734 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1735 {
1736         int c;
1737         int open_flags = 0;
1738         int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1739                         WIMLIB_ADD_FLAG_WINCONFIG |
1740                         WIMLIB_ADD_FLAG_VERBOSE;
1741         int write_flags = 0;
1742         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1743         uint32_t chunk_size = UINT32_MAX;
1744         uint32_t solid_chunk_size = UINT32_MAX;
1745         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1746         const tchar *wimfile;
1747         int wim_fd;
1748         const tchar *name;
1749         const tchar *desc;
1750         const tchar *flags_element = NULL;
1751
1752         WIMStruct *wim;
1753         STRING_SET(base_wimfiles);
1754         WIMStruct **base_wims;
1755
1756         WIMStruct *template_wim;
1757         const tchar *template_wimfile = NULL;
1758         const tchar *template_image_name_or_num = NULL;
1759         int template_image = WIMLIB_NO_IMAGE;
1760
1761         int ret;
1762         unsigned num_threads = 0;
1763
1764         tchar *source;
1765         tchar *source_copy;
1766
1767         tchar *config_file = NULL;
1768
1769         bool source_list = false;
1770         size_t source_list_nchars = 0;
1771         tchar *source_list_contents;
1772         bool capture_sources_malloced;
1773         struct wimlib_capture_source *capture_sources;
1774         size_t num_sources;
1775         bool name_defaulted;
1776
1777         for_opt(c, capture_or_append_options) {
1778                 switch (c) {
1779                 case IMAGEX_BOOT_OPTION:
1780                         add_flags |= WIMLIB_ADD_FLAG_BOOT;
1781                         break;
1782                 case IMAGEX_CHECK_OPTION:
1783                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1784                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1785                         break;
1786                 case IMAGEX_NOCHECK_OPTION:
1787                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1788                         break;
1789                 case IMAGEX_CONFIG_OPTION:
1790                         config_file = optarg;
1791                         add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1792                         break;
1793                 case IMAGEX_COMPRESS_OPTION:
1794                         compression_type = get_compression_type(optarg);
1795                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1796                                 goto out_err;
1797                         break;
1798                 case IMAGEX_COMPRESS_SLOW_OPTION:
1799                         set_compress_slow();
1800                         break;
1801                 case IMAGEX_CHUNK_SIZE_OPTION:
1802                         chunk_size = parse_chunk_size(optarg);
1803                         if (chunk_size == UINT32_MAX)
1804                                 goto out_err;
1805                         break;
1806                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1807                         solid_chunk_size = parse_chunk_size(optarg);
1808                         if (solid_chunk_size == UINT32_MAX)
1809                                 goto out_err;
1810                         break;
1811                 case IMAGEX_SOLID_COMPRESS_OPTION:
1812                         solid_ctype = get_compression_type(optarg);
1813                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1814                                 goto out_err;
1815                         break;
1816                 case IMAGEX_SOLID_OPTION:
1817                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1818                         break;
1819                 case IMAGEX_FLAGS_OPTION:
1820                         flags_element = optarg;
1821                         break;
1822                 case IMAGEX_DEREFERENCE_OPTION:
1823                         add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1824                         break;
1825                 case IMAGEX_VERBOSE_OPTION:
1826                         /* No longer does anything.  */
1827                         break;
1828                 case IMAGEX_THREADS_OPTION:
1829                         num_threads = parse_num_threads(optarg);
1830                         if (num_threads == UINT_MAX)
1831                                 goto out_err;
1832                         break;
1833                 case IMAGEX_REBUILD_OPTION:
1834                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1835                         break;
1836                 case IMAGEX_UNIX_DATA_OPTION:
1837                         add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1838                         break;
1839                 case IMAGEX_SOURCE_LIST_OPTION:
1840                         source_list = true;
1841                         break;
1842                 case IMAGEX_NO_ACLS_OPTION:
1843                         add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1844                         break;
1845                 case IMAGEX_STRICT_ACLS_OPTION:
1846                         add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1847                         break;
1848                 case IMAGEX_RPFIX_OPTION:
1849                         add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1850                         break;
1851                 case IMAGEX_NORPFIX_OPTION:
1852                         add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1853                         break;
1854                 case IMAGEX_PIPABLE_OPTION:
1855                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1856                         break;
1857                 case IMAGEX_NOT_PIPABLE_OPTION:
1858                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1859                         break;
1860                 case IMAGEX_UPDATE_OF_OPTION:
1861                         if (template_image_name_or_num) {
1862                                 imagex_error(T("'--update-of' can only be "
1863                                                "specified one time!"));
1864                                 goto out_err;
1865                         } else {
1866                                 tchar *colon;
1867                                 colon = tstrrchr(optarg, T(':'));
1868
1869                                 if (colon) {
1870                                         template_wimfile = optarg;
1871                                         *colon = T('\0');
1872                                         template_image_name_or_num = colon + 1;
1873                                 } else {
1874                                         template_wimfile = NULL;
1875                                         template_image_name_or_num = optarg;
1876                                 }
1877                         }
1878                         break;
1879                 case IMAGEX_DELTA_FROM_OPTION:
1880                         if (cmd != CMD_CAPTURE) {
1881                                 imagex_error(T("'--delta-from' is only "
1882                                                "valid for capture!"));
1883                                 goto out_usage;
1884                         }
1885                         ret = string_set_append(&base_wimfiles, optarg);
1886                         if (ret)
1887                                 goto out_free_base_wimfiles;
1888                         write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1889                         break;
1890                 case IMAGEX_WIMBOOT_OPTION:
1891                         add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1892                         break;
1893                 default:
1894                         goto out_usage;
1895                 }
1896         }
1897         argc -= optind;
1898         argv += optind;
1899
1900         if (argc < 2 || argc > 4)
1901                 goto out_usage;
1902
1903         source = argv[0];
1904         wimfile = argv[1];
1905
1906         /* Set default compression type and parameters.  */
1907
1908
1909         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1910                 /* No compression type specified.  Use the default.  */
1911
1912                 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1913                         /* With --wimboot, default to XPRESS compression.  */
1914                         compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1915                 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1916                         /* With --solid, default to LZMS compression.  (However,
1917                          * this will not affect solid resources!)  */
1918                         compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1919                 } else {
1920                         /* Otherwise, default to LZX compression.  */
1921                         compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1922                 }
1923         }
1924
1925         if (!tstrcmp(wimfile, T("-"))) {
1926                 /* Writing captured WIM to standard output.  */
1927         #if 0
1928                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1929                         imagex_error("Can't write a non-pipable WIM to "
1930                                      "standard output!  Specify --pipable\n"
1931                                      "       if you want to create a pipable WIM "
1932                                      "(but read the docs first).");
1933                         goto out_err;
1934                 }
1935         #else
1936                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1937         #endif
1938                 if (cmd == CMD_APPEND) {
1939                         imagex_error(T("Using standard output for append does "
1940                                        "not make sense."));
1941                         goto out_err;
1942                 }
1943                 wim_fd = STDOUT_FILENO;
1944                 wimfile = NULL;
1945                 imagex_info_file = stderr;
1946                 set_fd_to_binary_mode(wim_fd);
1947         }
1948
1949         /* If template image was specified using --update-of=IMAGE rather
1950          * than --update-of=WIMFILE:IMAGE, set the default WIMFILE.  */
1951         if (template_image_name_or_num && !template_wimfile) {
1952                 if (base_wimfiles.num_strings == 1) {
1953                         /* Capturing delta WIM based on single WIM:  default to
1954                          * base WIM.  */
1955                         template_wimfile = base_wimfiles.strings[0];
1956                 } else if (cmd == CMD_APPEND) {
1957                         /* Appending to WIM:  default to WIM being appended to.
1958                          */
1959                         template_wimfile = wimfile;
1960                 } else {
1961                         /* Capturing a normal (non-delta) WIM, so the WIM file
1962                          * *must* be explicitly specified.  */
1963                         if (base_wimfiles.num_strings > 1) {
1964                                 imagex_error(T("For capture of delta WIM "
1965                                                "based on multiple existing "
1966                                                "WIMs,\n"
1967                                                "      '--update-of' must "
1968                                                "specify WIMFILE:IMAGE!"));
1969                         } else {
1970                                 imagex_error(T("For capture of non-delta WIM, "
1971                                                "'--update-of' must specify "
1972                                                "WIMFILE:IMAGE!"));
1973                         }
1974                         goto out_usage;
1975                 }
1976         }
1977
1978         if (argc >= 3) {
1979                 name = argv[2];
1980                 name_defaulted = false;
1981         } else {
1982                 /* Set default name to SOURCE argument, omitting any directory
1983                  * prefixes and trailing slashes.  This requires making a copy
1984                  * of @source.  Leave some free characters at the end in case we
1985                  * append a number to keep the name unique. */
1986                 size_t source_name_len;
1987
1988                 source_name_len = tstrlen(source);
1989                 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1990                 name = tbasename(tstrcpy(source_copy, source));
1991                 name_defaulted = true;
1992         }
1993         /* Image description defaults to NULL if not given. */
1994         if (argc >= 4)
1995                 desc = argv[3];
1996         else
1997                 desc = NULL;
1998
1999         if (source_list) {
2000                 /* Set up capture sources in source list mode */
2001                 if (source[0] == T('-') && source[1] == T('\0')) {
2002                         source_list_contents = stdin_get_text_contents(&source_list_nchars);
2003                 } else {
2004                         source_list_contents = file_get_text_contents(source,
2005                                                                       &source_list_nchars);
2006                 }
2007                 if (!source_list_contents)
2008                         goto out_err;
2009
2010                 capture_sources = parse_source_list(&source_list_contents,
2011                                                     source_list_nchars,
2012                                                     &num_sources);
2013                 if (!capture_sources) {
2014                         ret = -1;
2015                         goto out_free_source_list_contents;
2016                 }
2017                 capture_sources_malloced = true;
2018         } else {
2019                 /* Set up capture source in non-source-list mode.  */
2020                 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2021                 capture_sources[0].fs_source_path = source;
2022                 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2023                 capture_sources[0].reserved = 0;
2024                 num_sources = 1;
2025                 capture_sources_malloced = false;
2026                 source_list_contents = NULL;
2027         }
2028
2029         /* Open the existing WIM, or create a new one.  */
2030         if (cmd == CMD_APPEND) {
2031                 ret = wimlib_open_wim_with_progress(wimfile,
2032                                                     open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2033                                                     &wim,
2034                                                     imagex_progress_func,
2035                                                     NULL);
2036                 if (ret)
2037                         goto out_free_capture_sources;
2038         } else {
2039                 ret = wimlib_create_new_wim(compression_type, &wim);
2040                 if (ret)
2041                         goto out_free_capture_sources;
2042                 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2043         }
2044
2045         /* Set chunk size if non-default.  */
2046         if (chunk_size != UINT32_MAX) {
2047                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2048                 if (ret)
2049                         goto out_free_wim;
2050         } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2051                    compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2052                 ret = wimlib_set_output_chunk_size(wim, 4096);
2053                 if (ret)
2054                         goto out_free_wim;
2055         }
2056         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2057                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2058                 if (ret)
2059                         goto out_free_wim;
2060         }
2061         if (solid_chunk_size != UINT32_MAX) {
2062                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2063                 if (ret)
2064                         goto out_free_wim;
2065         }
2066
2067 #ifndef __WIN32__
2068         /* Detect if source is regular file or block device and set NTFS volume
2069          * capture mode.  */
2070         if (!source_list) {
2071                 struct stat stbuf;
2072
2073                 if (tstat(source, &stbuf) == 0) {
2074                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2075                                 imagex_printf(T("Capturing WIM image from NTFS "
2076                                           "filesystem on \"%"TS"\"\n"), source);
2077                                 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2078                         }
2079                 } else {
2080                         if (errno != ENOENT) {
2081                                 imagex_error_with_errno(T("Failed to stat "
2082                                                           "\"%"TS"\""), source);
2083                                 ret = -1;
2084                                 goto out_free_wim;
2085                         }
2086                 }
2087         }
2088 #endif
2089
2090         /* If the user did not specify an image name, and the basename of the
2091          * source already exists as an image name in the WIM file, append a
2092          * suffix to make it unique. */
2093         if (cmd == CMD_APPEND && name_defaulted) {
2094                 unsigned long conflict_idx;
2095                 tchar *name_end = tstrchr(name, T('\0'));
2096                 for (conflict_idx = 1;
2097                      wimlib_image_name_in_use(wim, name);
2098                      conflict_idx++)
2099                 {
2100                         tsprintf(name_end, T(" (%lu)"), conflict_idx);
2101                 }
2102         }
2103
2104         /* If capturing a delta WIM, reference resources from the base WIMs
2105          * before adding the new image.  */
2106         if (base_wimfiles.num_strings) {
2107                 base_wims = calloc(base_wimfiles.num_strings,
2108                                    sizeof(base_wims[0]));
2109                 if (base_wims == NULL) {
2110                         imagex_error(T("Out of memory!"));
2111                         ret = -1;
2112                         goto out_free_wim;
2113                 }
2114
2115                 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2116                         ret = wimlib_open_wim_with_progress(
2117                                     base_wimfiles.strings[i], open_flags,
2118                                     &base_wims[i], imagex_progress_func, NULL);
2119                         if (ret)
2120                                 goto out_free_base_wims;
2121
2122                 }
2123
2124                 ret = wimlib_reference_resources(wim, base_wims,
2125                                                  base_wimfiles.num_strings, 0);
2126                 if (ret)
2127                         goto out_free_base_wims;
2128
2129                 if (base_wimfiles.num_strings == 1) {
2130                         imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2131                                       base_wimfiles.strings[0]);
2132                 } else {
2133                         imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2134                                       base_wimfiles.num_strings);
2135                 }
2136
2137         } else {
2138                 base_wims = NULL;
2139         }
2140
2141         /* If capturing or appending as an update of an existing (template) image,
2142          * open the WIM if needed and parse the image index.  */
2143         if (template_image_name_or_num) {
2144
2145
2146                 if (base_wimfiles.num_strings == 1 &&
2147                     template_wimfile == base_wimfiles.strings[0]) {
2148                         template_wim = base_wims[0];
2149                 } else if (template_wimfile == wimfile) {
2150                         template_wim = wim;
2151                 } else {
2152                         ret = wimlib_open_wim_with_progress(template_wimfile,
2153                                                             open_flags,
2154                                                             &template_wim,
2155                                                             imagex_progress_func,
2156                                                             NULL);
2157                         if (ret)
2158                                 goto out_free_base_wims;
2159                 }
2160
2161                 template_image = wimlib_resolve_image(template_wim,
2162                                                       template_image_name_or_num);
2163
2164                 if (template_image_name_or_num[0] == T('-')) {
2165                         tchar *tmp;
2166                         unsigned long n;
2167                         struct wimlib_wim_info info;
2168
2169                         wimlib_get_wim_info(template_wim, &info);
2170                         n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2171                         if (n >= 1 && n <= info.image_count &&
2172                             *tmp == T('\0') &&
2173                             tmp != template_image_name_or_num + 1)
2174                         {
2175                                 template_image = info.image_count - (n - 1);
2176                         }
2177                 }
2178                 ret = verify_image_exists_and_is_single(template_image,
2179                                                         template_image_name_or_num,
2180                                                         template_wimfile);
2181                 if (ret)
2182                         goto out_free_template_wim;
2183         } else {
2184                 template_wim = NULL;
2185         }
2186
2187         ret = wimlib_add_image_multisource(wim,
2188                                            capture_sources,
2189                                            num_sources,
2190                                            name,
2191                                            config_file,
2192                                            add_flags);
2193         if (ret)
2194                 goto out_free_template_wim;
2195
2196         if (desc || flags_element || template_image_name_or_num) {
2197                 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2198                  * on which the added one is to be based has been specified with
2199                  * --update-of.  Get the index of the image we just
2200                  *  added, then use it to call the appropriate functions.  */
2201                 struct wimlib_wim_info info;
2202
2203                 wimlib_get_wim_info(wim, &info);
2204
2205                 if (desc) {
2206                         ret = wimlib_set_image_descripton(wim,
2207                                                           info.image_count,
2208                                                           desc);
2209                         if (ret)
2210                                 goto out_free_template_wim;
2211                 }
2212
2213                 if (flags_element) {
2214                         ret = wimlib_set_image_flags(wim, info.image_count,
2215                                                      flags_element);
2216                         if (ret)
2217                                 goto out_free_template_wim;
2218                 }
2219
2220                 /* Reference template image if the user provided one.  */
2221                 if (template_image_name_or_num) {
2222                         imagex_printf(T("Using image %d "
2223                                         "from \"%"TS"\" as template\n"),
2224                                         template_image, template_wimfile);
2225                         ret = wimlib_reference_template_image(wim,
2226                                                               info.image_count,
2227                                                               template_wim,
2228                                                               template_image,
2229                                                               0);
2230                         if (ret)
2231                                 goto out_free_template_wim;
2232                 }
2233         }
2234
2235         /* Write the new WIM or overwrite the existing WIM with the new image
2236          * appended.  */
2237         if (cmd == CMD_APPEND) {
2238                 ret = wimlib_overwrite(wim, write_flags, num_threads);
2239         } else if (wimfile) {
2240                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2241                                    write_flags, num_threads);
2242         } else {
2243                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2244                                          write_flags, num_threads);
2245         }
2246 out_free_template_wim:
2247         /* template_wim may alias base_wims[0] or wim.  */
2248         if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2249             template_wim != wim)
2250                 wimlib_free(template_wim);
2251 out_free_base_wims:
2252         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2253                 wimlib_free(base_wims[i]);
2254         free(base_wims);
2255 out_free_wim:
2256         wimlib_free(wim);
2257 out_free_capture_sources:
2258         if (capture_sources_malloced)
2259                 free(capture_sources);
2260 out_free_source_list_contents:
2261         free(source_list_contents);
2262 out_free_base_wimfiles:
2263         string_set_destroy(&base_wimfiles);
2264         return ret;
2265
2266 out_usage:
2267         usage(cmd, stderr);
2268 out_err:
2269         ret = -1;
2270         goto out_free_base_wimfiles;
2271 }
2272
2273 /* Remove image(s) from a WIM. */
2274 static int
2275 imagex_delete(int argc, tchar **argv, int cmd)
2276 {
2277         int c;
2278         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2279         int write_flags = 0;
2280         const tchar *wimfile;
2281         const tchar *image_num_or_name;
2282         WIMStruct *wim;
2283         int image;
2284         int ret;
2285
2286         for_opt(c, delete_options) {
2287                 switch (c) {
2288                 case IMAGEX_CHECK_OPTION:
2289                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2290                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2291                         break;
2292                 case IMAGEX_SOFT_OPTION:
2293                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2294                         break;
2295                 default:
2296                         goto out_usage;
2297                 }
2298         }
2299         argc -= optind;
2300         argv += optind;
2301
2302         if (argc != 2) {
2303                 if (argc < 1)
2304                         imagex_error(T("Must specify a WIM file"));
2305                 if (argc < 2)
2306                         imagex_error(T("Must specify an image"));
2307                 goto out_usage;
2308         }
2309         wimfile = argv[0];
2310         image_num_or_name = argv[1];
2311
2312         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2313                                             imagex_progress_func, NULL);
2314         if (ret)
2315                 goto out;
2316
2317         image = wimlib_resolve_image(wim, image_num_or_name);
2318
2319         ret = verify_image_exists(image, image_num_or_name, wimfile);
2320         if (ret)
2321                 goto out_wimlib_free;
2322
2323         ret = wimlib_delete_image(wim, image);
2324         if (ret) {
2325                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2326                              wimfile);
2327                 goto out_wimlib_free;
2328         }
2329
2330         ret = wimlib_overwrite(wim, write_flags, 0);
2331         if (ret) {
2332                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2333                                "deleted"), wimfile);
2334         }
2335 out_wimlib_free:
2336         wimlib_free(wim);
2337 out:
2338         return ret;
2339
2340 out_usage:
2341         usage(CMD_DELETE, stderr);
2342         ret = -1;
2343         goto out;
2344 }
2345
2346 struct print_dentry_options {
2347         bool detailed;
2348 };
2349
2350 static void
2351 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2352 {
2353         tprintf(T("%"TS"\n"), dentry->full_path);
2354 }
2355
2356 static const struct {
2357         uint32_t flag;
2358         const tchar *name;
2359 } file_attr_flags[] = {
2360         {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
2361         {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
2362         {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
2363         {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
2364         {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
2365         {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
2366         {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
2367         {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
2368         {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
2369         {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
2370         {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
2371         {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
2372         {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2373         {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
2374         {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
2375 };
2376
2377 #define TIMESTR_MAX 100
2378
2379 static void
2380 timespec_to_string(const struct timespec *spec, tchar *buf)
2381 {
2382         time_t t = spec->tv_sec;
2383         struct tm tm;
2384         gmtime_r(&t, &tm);
2385         tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2386         buf[TIMESTR_MAX - 1] = '\0';
2387 }
2388
2389 static void
2390 print_time(const tchar *type, const struct timespec *spec)
2391 {
2392         tchar timestr[TIMESTR_MAX];
2393
2394         timespec_to_string(spec, timestr);
2395
2396         tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2397 }
2398
2399 static void print_byte_field(const uint8_t field[], size_t len)
2400 {
2401         while (len--)
2402                 tprintf(T("%02hhx"), *field++);
2403 }
2404
2405 static void
2406 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2407 {
2408         tputs(T("WIM Information:"));
2409         tputs(T("----------------"));
2410         tprintf(T("Path:           %"TS"\n"), wimfile);
2411         tprintf(T("GUID:           0x"));
2412         print_byte_field(info->guid, sizeof(info->guid));
2413         tputchar(T('\n'));
2414         tprintf(T("Version:        %u\n"), info->wim_version);
2415         tprintf(T("Image Count:    %d\n"), info->image_count);
2416         tprintf(T("Compression:    %"TS"\n"),
2417                 wimlib_get_compression_type_string(info->compression_type));
2418         tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
2419                 info->chunk_size);
2420         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2421         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2422         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2423         tprintf(T("Integrity Info: %"TS"\n"),
2424                 info->has_integrity_table ? T("yes") : T("no"));
2425         tprintf(T("Relative path junction: %"TS"\n"),
2426                 info->has_rpfix ? T("yes") : T("no"));
2427         tprintf(T("Pipable:        %"TS"\n"),
2428                 info->pipable ? T("yes") : T("no"));
2429         tputchar(T('\n'));
2430 }
2431
2432 static int
2433 print_resource(const struct wimlib_resource_entry *resource,
2434                void *_ignore)
2435 {
2436         tprintf(T("Hash                = 0x"));
2437         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2438         tputchar(T('\n'));
2439
2440         if (!resource->is_missing) {
2441                 tprintf(T("Uncompressed size   = %"PRIu64" bytes\n"),
2442                         resource->uncompressed_size);
2443                 if (resource->packed) {
2444                         tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2445                                 resource->raw_resource_compressed_size);
2446
2447                         tprintf(T("Raw offset in WIM   = %"PRIu64" bytes\n"),
2448                                 resource->raw_resource_offset_in_wim);
2449
2450                         tprintf(T("Offset in raw       = %"PRIu64" bytes\n"),
2451                                 resource->offset);
2452                 } else {
2453                         tprintf(T("Compressed size     = %"PRIu64" bytes\n"),
2454                                 resource->compressed_size);
2455
2456                         tprintf(T("Offset in WIM       = %"PRIu64" bytes\n"),
2457                                 resource->offset);
2458                 }
2459
2460                 tprintf(T("Part Number         = %u\n"), resource->part_number);
2461                 tprintf(T("Reference Count     = %u\n"), resource->reference_count);
2462
2463                 tprintf(T("Flags               = "));
2464                 if (resource->is_compressed)
2465                         tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2466                 if (resource->is_metadata)
2467                         tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2468                 if (resource->is_free)
2469                         tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2470                 if (resource->is_spanned)
2471                         tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2472                 if (resource->packed)
2473                         tprintf(T("WIM_RESHDR_FLAG_SOLID  "));
2474                 tputchar(T('\n'));
2475         }
2476         tputchar(T('\n'));
2477         return 0;
2478 }
2479
2480 static void
2481 print_lookup_table(WIMStruct *wim)
2482 {
2483         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2484 }
2485
2486 static void
2487 default_print_security_descriptor(const uint8_t *sd, size_t size)
2488 {
2489         tprintf(T("Security Descriptor = "));
2490         print_byte_field(sd, size);
2491         tputchar(T('\n'));
2492 }
2493
2494 static void
2495 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2496 {
2497
2498         tprintf(T(
2499 "----------------------------------------------------------------------------\n"));
2500         tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
2501         if (dentry->dos_name)
2502                 tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
2503         tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
2504         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2505                 if (file_attr_flags[i].flag & dentry->attributes)
2506                         tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
2507                                 file_attr_flags[i].name);
2508
2509         if (dentry->security_descriptor) {
2510                 print_security_descriptor(dentry->security_descriptor,
2511                                           dentry->security_descriptor_size);
2512         }
2513
2514         print_time(T("Creation Time"), &dentry->creation_time);
2515         print_time(T("Last Write Time"), &dentry->last_write_time);
2516         print_time(T("Last Access Time"), &dentry->last_access_time);
2517
2518
2519         if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2520                 tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
2521
2522         tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2523         tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
2524
2525         if (dentry->unix_mode != 0) {
2526                 tprintf(T("UNIX Data           = uid:%"PRIu32" gid:%"PRIu32" "
2527                           "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2528                         dentry->unix_uid, dentry->unix_gid,
2529                         dentry->unix_mode, dentry->unix_rdev);
2530         }
2531
2532         for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2533                 if (dentry->streams[i].stream_name) {
2534                         tprintf(T("\tData stream \"%"TS"\":\n"),
2535                                 dentry->streams[i].stream_name);
2536                 } else {
2537                         tprintf(T("\tUnnamed data stream:\n"));
2538                 }
2539                 print_resource(&dentry->streams[i].resource, NULL);
2540         }
2541 }
2542
2543 static int
2544 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2545 {
2546         const struct print_dentry_options *options = _options;
2547         if (!options->detailed)
2548                 print_dentry_full_path(dentry);
2549         else
2550                 print_dentry_detailed(dentry);
2551         return 0;
2552 }
2553
2554 /* Print the files contained in an image(s) in a WIM file. */
2555 static int
2556 imagex_dir(int argc, tchar **argv, int cmd)
2557 {
2558         const tchar *wimfile;
2559         WIMStruct *wim = NULL;
2560         int image;
2561         int ret;
2562         const tchar *path = WIMLIB_WIM_ROOT_PATH;
2563         int c;
2564         struct print_dentry_options options = {
2565                 .detailed = false,
2566         };
2567         int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2568
2569         for_opt(c, dir_options) {
2570                 switch (c) {
2571                 case IMAGEX_PATH_OPTION:
2572                         path = optarg;
2573                         break;
2574                 case IMAGEX_DETAILED_OPTION:
2575                         options.detailed = true;
2576                         break;
2577                 case IMAGEX_ONE_FILE_ONLY_OPTION:
2578                         iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2579                         break;
2580                 default:
2581                         goto out_usage;
2582                 }
2583         }
2584         argc -= optind;
2585         argv += optind;
2586
2587         if (argc < 1) {
2588                 imagex_error(T("Must specify a WIM file"));
2589                 goto out_usage;
2590         }
2591         if (argc > 2) {
2592                 imagex_error(T("Too many arguments"));
2593                 goto out_usage;
2594         }
2595
2596         wimfile = argv[0];
2597         ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2598                                             imagex_progress_func, NULL);
2599         if (ret)
2600                 goto out;
2601
2602         if (argc >= 2) {
2603                 image = wimlib_resolve_image(wim, argv[1]);
2604                 ret = verify_image_exists(image, argv[1], wimfile);
2605                 if (ret)
2606                         goto out_wimlib_free;
2607         } else {
2608                 /* No image specified; default to image 1, but only if the WIM
2609                  * contains exactly one image.  */
2610
2611                 struct wimlib_wim_info info;
2612
2613                 wimlib_get_wim_info(wim, &info);
2614                 if (info.image_count != 1) {
2615                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2616                                        "select one (or all)."),
2617                                      wimfile, info.image_count);
2618                         wimlib_free(wim);
2619                         goto out_usage;
2620                 }
2621                 image = 1;
2622         }
2623
2624         ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2625                                       print_dentry, &options);
2626 out_wimlib_free:
2627         wimlib_free(wim);
2628 out:
2629         return ret;
2630
2631 out_usage:
2632         usage(CMD_DIR, stderr);
2633         ret = -1;
2634         goto out;
2635 }
2636
2637 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2638  * WIM file. */
2639 static int
2640 imagex_export(int argc, tchar **argv, int cmd)
2641 {
2642         int c;
2643         int open_flags = 0;
2644         int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2645         int write_flags = 0;
2646         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2647         const tchar *src_wimfile;
2648         const tchar *src_image_num_or_name;
2649         const tchar *dest_wimfile;
2650         int dest_wim_fd;
2651         const tchar *dest_name;
2652         const tchar *dest_desc;
2653         WIMStruct *src_wim;
2654         struct wimlib_wim_info src_info;
2655         WIMStruct *dest_wim;
2656         int ret;
2657         int image;
2658         struct stat stbuf;
2659         bool wim_is_new;
2660         STRING_SET(refglobs);
2661         unsigned num_threads = 0;
2662         uint32_t chunk_size = UINT32_MAX;
2663         uint32_t solid_chunk_size = UINT32_MAX;
2664         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2665
2666         for_opt(c, export_options) {
2667                 switch (c) {
2668                 case IMAGEX_BOOT_OPTION:
2669                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2670                         break;
2671                 case IMAGEX_CHECK_OPTION:
2672                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2673                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2674                         break;
2675                 case IMAGEX_NOCHECK_OPTION:
2676                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2677                         break;
2678                 case IMAGEX_COMPRESS_OPTION:
2679                         compression_type = get_compression_type(optarg);
2680                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2681                                 goto out_err;
2682                         break;
2683                 case IMAGEX_COMPRESS_SLOW_OPTION:
2684                         set_compress_slow();
2685                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2686                         break;
2687                 case IMAGEX_RECOMPRESS_OPTION:
2688                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2689                         break;
2690                 case IMAGEX_SOLID_OPTION:
2691                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2692                         break;
2693                 case IMAGEX_CHUNK_SIZE_OPTION:
2694                         chunk_size = parse_chunk_size(optarg);
2695                         if (chunk_size == UINT32_MAX)
2696                                 goto out_err;
2697                         break;
2698                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2699                         solid_chunk_size = parse_chunk_size(optarg);
2700                         if (solid_chunk_size == UINT32_MAX)
2701                                 goto out_err;
2702                         break;
2703                 case IMAGEX_SOLID_COMPRESS_OPTION:
2704                         solid_ctype = get_compression_type(optarg);
2705                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2706                                 goto out_err;
2707                         break;
2708                 case IMAGEX_REF_OPTION:
2709                         ret = string_set_append(&refglobs, optarg);
2710                         if (ret)
2711                                 goto out_free_refglobs;
2712                         break;
2713                 case IMAGEX_THREADS_OPTION:
2714                         num_threads = parse_num_threads(optarg);
2715                         if (num_threads == UINT_MAX)
2716                                 goto out_err;
2717                         break;
2718                 case IMAGEX_REBUILD_OPTION:
2719                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2720                         break;
2721                 case IMAGEX_PIPABLE_OPTION:
2722                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2723                         break;
2724                 case IMAGEX_NOT_PIPABLE_OPTION:
2725                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2726                         break;
2727                 case IMAGEX_WIMBOOT_OPTION:
2728                         export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2729                         break;
2730                 default:
2731                         goto out_usage;
2732                 }
2733         }
2734         argc -= optind;
2735         argv += optind;
2736         if (argc < 3 || argc > 5)
2737                 goto out_usage;
2738         src_wimfile           = argv[0];
2739         src_image_num_or_name = argv[1];
2740         dest_wimfile          = argv[2];
2741         dest_name             = (argc >= 4) ? argv[3] : NULL;
2742         dest_desc             = (argc >= 5) ? argv[4] : NULL;
2743         ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2744                                             imagex_progress_func, NULL);
2745         if (ret)
2746                 goto out_free_refglobs;
2747
2748         wimlib_get_wim_info(src_wim, &src_info);
2749
2750         /* Determine if the destination is an existing file or not.  If so, we
2751          * try to append the exported image(s) to it; otherwise, we create a new
2752          * WIM containing the exported image(s).  Furthermore, determine if we
2753          * need to write a pipable WIM directly to standard output.  */
2754
2755         if (tstrcmp(dest_wimfile, T("-")) == 0) {
2756         #if 0
2757                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2758                         imagex_error("Can't write a non-pipable WIM to "
2759                                      "standard output!  Specify --pipable\n"
2760                                      "       if you want to create a pipable WIM "
2761                                      "(but read the docs first).");
2762                         ret = -1;
2763                         goto out_free_src_wim;
2764                 }
2765         #else
2766                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2767         #endif
2768                 dest_wimfile = NULL;
2769                 dest_wim_fd = STDOUT_FILENO;
2770                 imagex_info_file = stderr;
2771                 set_fd_to_binary_mode(dest_wim_fd);
2772         }
2773         errno = ENOENT;
2774         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2775                 wim_is_new = false;
2776                 /* Destination file exists. */
2777
2778                 if (!S_ISREG(stbuf.st_mode)) {
2779                         imagex_error(T("\"%"TS"\" is not a regular file"),
2780                                      dest_wimfile);
2781                         ret = -1;
2782                         goto out_free_src_wim;
2783                 }
2784                 ret = wimlib_open_wim_with_progress(dest_wimfile,
2785                                                     open_flags |
2786                                                         WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2787                                                     &dest_wim,
2788                                                     imagex_progress_func,
2789                                                     NULL);
2790                 if (ret)
2791                         goto out_free_src_wim;
2792
2793                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2794                         /* The user specified a compression type, but we're
2795                          * exporting to an existing WIM.  Make sure the
2796                          * specified compression type is the same as the
2797                          * compression type of the existing destination WIM. */
2798                         struct wimlib_wim_info dest_info;
2799
2800                         wimlib_get_wim_info(dest_wim, &dest_info);
2801                         if (compression_type != dest_info.compression_type) {
2802                                 imagex_error(T("Cannot specify a compression type that is "
2803                                                "not the same as that used in the "
2804                                                "destination WIM"));
2805                                 ret = -1;
2806                                 goto out_free_dest_wim;
2807                         }
2808                 }
2809         } else {
2810                 wim_is_new = true;
2811
2812                 if (errno != ENOENT) {
2813                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2814                                                 dest_wimfile);
2815                         ret = -1;
2816                         goto out_free_src_wim;
2817                 }
2818
2819                 /* dest_wimfile is not an existing file, so create a new WIM. */
2820
2821                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2822                         /* The user did not specify a compression type; default
2823                          * to that of the source WIM, unless --solid or
2824                          * --wimboot was specified.   */
2825
2826                         if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2827                                 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2828                         else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2829                                 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2830                         else
2831                                 compression_type = src_info.compression_type;
2832                 }
2833                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2834                 if (ret)
2835                         goto out_free_src_wim;
2836
2837                 wimlib_register_progress_function(dest_wim,
2838                                                   imagex_progress_func, NULL);
2839
2840                 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2841                     && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2842                 {
2843                         /* For --wimboot export, use small XPRESS chunks.  */
2844                         wimlib_set_output_chunk_size(dest_wim, 4096);
2845                 } else if (compression_type == src_info.compression_type &&
2846                            chunk_size == UINT32_MAX)
2847                 {
2848                         /* Use same chunk size if compression type is the same.  */
2849                         wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2850                 }
2851         }
2852
2853         if (chunk_size != UINT32_MAX) {
2854                 /* Set destination chunk size.  */
2855                 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2856                 if (ret)
2857                         goto out_free_dest_wim;
2858         }
2859         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2860                 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2861                 if (ret)
2862                         goto out_free_dest_wim;
2863         }
2864         if (solid_chunk_size != UINT32_MAX) {
2865                 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2866                 if (ret)
2867                         goto out_free_dest_wim;
2868         }
2869
2870         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2871         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2872         if (ret)
2873                 goto out_free_dest_wim;
2874
2875         if (refglobs.num_strings) {
2876                 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2877                 if (ret)
2878                         goto out_free_dest_wim;
2879         }
2880
2881         if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2882             image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2883         {
2884                 imagex_error(T("--boot specified for all-images export, but source WIM "
2885                                "has no bootable image."));
2886                 ret = -1;
2887                 goto out_free_dest_wim;
2888         }
2889
2890         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2891                                   dest_desc, export_flags);
2892         if (ret) {
2893                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2894                         do_resource_not_found_warning(src_wimfile,
2895                                                       &src_info, &refglobs);
2896                 }
2897                 goto out_free_dest_wim;
2898         }
2899
2900         if (!wim_is_new)
2901                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2902         else if (dest_wimfile)
2903                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2904                                    write_flags, num_threads);
2905         else
2906                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2907                                          WIMLIB_ALL_IMAGES, write_flags,
2908                                          num_threads);
2909 out_free_dest_wim:
2910         wimlib_free(dest_wim);
2911 out_free_src_wim:
2912         wimlib_free(src_wim);
2913 out_free_refglobs:
2914         string_set_destroy(&refglobs);
2915         return ret;
2916
2917 out_usage:
2918         usage(CMD_EXPORT, stderr);
2919 out_err:
2920         ret = -1;
2921         goto out_free_refglobs;
2922 }
2923
2924 /* Extract files or directories from a WIM image */
2925 static int
2926 imagex_extract(int argc, tchar **argv, int cmd)
2927 {
2928         int c;
2929         int open_flags = 0;
2930         int image;
2931         WIMStruct *wim;
2932         int ret;
2933         const tchar *wimfile;
2934         const tchar *image_num_or_name;
2935         tchar *dest_dir = T(".");
2936         int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2937                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2938                             WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2939         int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2940
2941         STRING_SET(refglobs);
2942
2943         tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2944
2945         for_opt(c, extract_options) {
2946                 switch (c) {
2947                 case IMAGEX_CHECK_OPTION:
2948                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2949                         break;
2950                 case IMAGEX_VERBOSE_OPTION:
2951                         /* No longer does anything.  */
2952                         break;
2953                 case IMAGEX_REF_OPTION:
2954                         ret = string_set_append(&refglobs, optarg);
2955                         if (ret)
2956                                 goto out_free_refglobs;
2957                         break;
2958                 case IMAGEX_UNIX_DATA_OPTION:
2959                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2960                         break;
2961                 case IMAGEX_NO_ACLS_OPTION:
2962                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2963                         break;
2964                 case IMAGEX_STRICT_ACLS_OPTION:
2965                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2966                         break;
2967                 case IMAGEX_NO_ATTRIBUTES_OPTION:
2968                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2969                         break;
2970                 case IMAGEX_DEST_DIR_OPTION:
2971                         dest_dir = optarg;
2972                         break;
2973                 case IMAGEX_TO_STDOUT_OPTION:
2974                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2975                         imagex_info_file = stderr;
2976                         imagex_be_quiet = true;
2977                         set_fd_to_binary_mode(STDOUT_FILENO);
2978                         break;
2979                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2980                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2981                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2982                         break;
2983                 case IMAGEX_NO_GLOBS_OPTION:
2984                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2985                         break;
2986                 case IMAGEX_NULLGLOB_OPTION:
2987                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2988                         break;
2989                 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2990                         notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2991                         break;
2992                 case IMAGEX_WIMBOOT_OPTION:
2993                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
2994                         break;
2995                 default:
2996                         goto out_usage;
2997                 }
2998         }
2999         argc -= optind;
3000         argv += optind;
3001
3002         if (argc < 2)
3003                 goto out_usage;
3004
3005         if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3006                                WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3007         {
3008                 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3009                 goto out_err;
3010         }
3011
3012         wimfile = argv[0];
3013         image_num_or_name = argv[1];
3014
3015         argc -= 2;
3016         argv += 2;
3017
3018         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3019                                             imagex_progress_func, NULL);
3020         if (ret)
3021                 goto out_free_refglobs;
3022
3023         image = wimlib_resolve_image(wim, image_num_or_name);
3024         ret = verify_image_exists_and_is_single(image,
3025                                                 image_num_or_name,
3026                                                 wimfile);
3027         if (ret)
3028                 goto out_wimlib_free;
3029
3030         if (refglobs.num_strings) {
3031                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3032                 if (ret)
3033                         goto out_wimlib_free;
3034         }
3035
3036         if (argc == 0) {
3037                 argv = &root_path;
3038                 argc = 1;
3039                 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3040         }
3041
3042         while (argc != 0 && ret == 0) {
3043                 int num_paths;
3044
3045                 for (num_paths = 0;
3046                      num_paths < argc && argv[num_paths][0] != T('@');
3047                      num_paths++)
3048                         ;
3049
3050                 if (num_paths) {
3051                         ret = wimlib_extract_paths(wim, image, dest_dir,
3052                                                    (const tchar **)argv,
3053                                                    num_paths,
3054                                                    extract_flags | notlist_extract_flags);
3055                         argc -= num_paths;
3056                         argv += num_paths;
3057                 } else {
3058                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3059                                                       argv[0] + 1,
3060                                                       extract_flags);
3061                         argc--;
3062                         argv++;
3063                 }
3064         }
3065
3066         if (ret == 0) {
3067                 if (!imagex_be_quiet)
3068                         imagex_printf(T("Done extracting files.\n"));
3069         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3070                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3071                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3072                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3073                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3074                 {
3075                         tfprintf(stderr,
3076                                  T("Note: You can use the '--nullglob' "
3077                                    "option to ignore missing files.\n"));
3078                 }
3079                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3080                                    "files and directories\n"
3081                                    "      are in the WIM image.\n"),
3082                                 get_cmd_string(CMD_DIR, false));
3083         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3084                 struct wimlib_wim_info info;
3085
3086                 wimlib_get_wim_info(wim, &info);
3087                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3088         }
3089 out_wimlib_free:
3090         wimlib_free(wim);
3091 out_free_refglobs:
3092         string_set_destroy(&refglobs);
3093         return ret;
3094
3095 out_usage:
3096         usage(CMD_EXTRACT, stderr);
3097 out_err:
3098         ret = -1;
3099         goto out_free_refglobs;
3100 }
3101
3102 /* Prints information about a WIM file; also can mark an image as bootable,
3103  * change the name of an image, or change the description of an image. */
3104 static int
3105 imagex_info(int argc, tchar **argv, int cmd)
3106 {
3107         int c;
3108         bool boot         = false;
3109         bool check        = false;
3110         bool nocheck      = false;
3111         bool header       = false;
3112         bool lookup_table = false;
3113         bool xml          = false;
3114         bool short_header = true;
3115         const tchar *xml_out_file = NULL;
3116         const tchar *wimfile;
3117         const tchar *image_num_or_name;
3118         const tchar *new_name;
3119         const tchar *new_desc;
3120         WIMStruct *wim;
3121         int image;
3122         int ret;
3123         int open_flags = 0;
3124         struct wimlib_wim_info info;
3125
3126         for_opt(c, info_options) {
3127                 switch (c) {
3128                 case IMAGEX_BOOT_OPTION:
3129                         boot = true;
3130                         break;
3131                 case IMAGEX_CHECK_OPTION:
3132                         check = true;
3133                         break;
3134                 case IMAGEX_NOCHECK_OPTION:
3135                         nocheck = true;
3136                         break;
3137                 case IMAGEX_HEADER_OPTION:
3138                         header = true;
3139                         short_header = false;
3140                         break;
3141                 case IMAGEX_LOOKUP_TABLE_OPTION:
3142                         lookup_table = true;
3143                         short_header = false;
3144                         break;
3145                 case IMAGEX_XML_OPTION:
3146                         xml = true;
3147                         short_header = false;
3148                         break;
3149                 case IMAGEX_EXTRACT_XML_OPTION:
3150                         xml_out_file = optarg;
3151                         short_header = false;
3152                         break;
3153                 case IMAGEX_METADATA_OPTION:
3154                         imagex_error(T("The --metadata option has been removed. "
3155                                        "Use 'wimdir --detail' instead."));
3156                         goto out_err;
3157                 default:
3158                         goto out_usage;
3159                 }
3160         }
3161
3162         argc -= optind;
3163         argv += optind;
3164         if (argc < 1 || argc > 4)
3165                 goto out_usage;
3166
3167         wimfile           = argv[0];
3168         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3169         new_name          = (argc >= 3) ? argv[2] : NULL;
3170         new_desc          = (argc >= 4) ? argv[3] : NULL;
3171
3172         if (check && nocheck) {
3173                 imagex_error(T("Can't specify both --check and --nocheck"));
3174                 goto out_err;
3175         }
3176
3177         if (check)
3178                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3179
3180         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3181                                             imagex_progress_func, NULL);
3182         if (ret)
3183                 goto out;
3184
3185         wimlib_get_wim_info(wim, &info);
3186
3187         image = wimlib_resolve_image(wim, image_num_or_name);
3188         ret = WIMLIB_ERR_INVALID_IMAGE;
3189         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3190                 verify_image_exists(image, image_num_or_name, wimfile);
3191                 if (boot) {
3192                         imagex_error(T("If you would like to set the boot "
3193                                        "index to 0, specify image \"0\" with "
3194                                        "the --boot flag."));
3195                 }
3196                 goto out_wimlib_free;
3197         }
3198
3199         if (boot && info.image_count == 0) {
3200                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3201                 goto out_wimlib_free;
3202         }
3203
3204         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3205                 if (boot) {
3206                         imagex_error(T("Cannot specify the --boot flag "
3207                                        "without specifying a specific "
3208                                        "image in a multi-image WIM"));
3209                         goto out_wimlib_free;
3210                 }
3211                 if (new_name) {
3212                         imagex_error(T("Cannot specify the NEW_NAME "
3213                                        "without specifying a specific "
3214                                        "image in a multi-image WIM"));
3215                         goto out_wimlib_free;
3216                 }
3217         }
3218
3219         /* Operations that print information are separated from operations that
3220          * recreate the WIM file. */
3221         if (!new_name && !boot) {
3222
3223                 /* Read-only operations */
3224
3225                 if (image == WIMLIB_NO_IMAGE) {
3226                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3227                                      image_num_or_name, wimfile);
3228                         goto out_wimlib_free;
3229                 }
3230
3231                 if (image == WIMLIB_ALL_IMAGES && short_header)
3232                         print_wim_information(wimfile, &info);
3233
3234                 if (header)
3235                         wimlib_print_header(wim);
3236
3237                 if (lookup_table) {
3238                         if (info.total_parts != 1) {
3239                                 tfprintf(stderr, T("Warning: Only showing the lookup table "
3240                                                    "for part %d of a %d-part WIM.\n"),
3241                                          info.part_number, info.total_parts);
3242                         }
3243                         print_lookup_table(wim);
3244                 }
3245
3246                 if (xml) {
3247                         ret = wimlib_extract_xml_data(wim, stdout);
3248                         if (ret)
3249                                 goto out_wimlib_free;
3250                 }
3251
3252                 if (xml_out_file) {
3253                         FILE *fp;
3254
3255                         fp = tfopen(xml_out_file, T("wb"));
3256                         if (!fp) {
3257                                 imagex_error_with_errno(T("Failed to open the "
3258                                                           "file \"%"TS"\" for "
3259                                                           "writing"),
3260                                                         xml_out_file);
3261                                 ret = -1;
3262                                 goto out_wimlib_free;
3263                         }
3264                         ret = wimlib_extract_xml_data(wim, fp);
3265                         if (fclose(fp)) {
3266                                 imagex_error(T("Failed to close the file "
3267                                                "\"%"TS"\""),
3268                                              xml_out_file);
3269                                 ret = -1;
3270                         }
3271                         if (ret)
3272                                 goto out_wimlib_free;
3273                 }
3274
3275                 if (short_header)
3276                         wimlib_print_available_images(wim, image);
3277
3278                 ret = 0;
3279         } else {
3280
3281                 /* Modification operations */
3282
3283                 if (image == WIMLIB_ALL_IMAGES)
3284                         image = 1;
3285
3286                 if (image == WIMLIB_NO_IMAGE && new_name) {
3287                         imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3288                                        "when using image 0"), new_name);
3289                         ret = -1;
3290                         goto out_wimlib_free;
3291                 }
3292
3293                 if (boot) {
3294                         if (image == info.boot_index) {
3295                                 imagex_printf(T("Image %d is already marked as "
3296                                           "bootable.\n"), image);
3297                                 boot = false;
3298                         } else {
3299                                 imagex_printf(T("Marking image %d as bootable.\n"),
3300                                         image);
3301                                 info.boot_index = image;
3302                                 ret = wimlib_set_wim_info(wim, &info,
3303                                                           WIMLIB_CHANGE_BOOT_INDEX);
3304                                 if (ret)
3305                                         goto out_wimlib_free;
3306                         }
3307                 }
3308                 if (new_name) {
3309                         if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3310                         {
3311                                 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3312                                         image, new_name);
3313                                 new_name = NULL;
3314                         } else {
3315                                 imagex_printf(T("Changing the name of image %d to "
3316                                           "\"%"TS"\".\n"), image, new_name);
3317                                 ret = wimlib_set_image_name(wim, image, new_name);
3318                                 if (ret)
3319                                         goto out_wimlib_free;
3320                         }
3321                 }
3322                 if (new_desc) {
3323                         const tchar *old_desc;
3324                         old_desc = wimlib_get_image_description(wim, image);
3325                         if (old_desc && !tstrcmp(old_desc, new_desc)) {
3326                                 imagex_printf(T("The description of image %d is already "
3327                                           "\"%"TS"\".\n"), image, new_desc);
3328                                 new_desc = NULL;
3329                         } else {
3330                                 imagex_printf(T("Changing the description of image %d "
3331                                           "to \"%"TS"\".\n"), image, new_desc);
3332                                 ret = wimlib_set_image_descripton(wim, image,
3333                                                                   new_desc);
3334                                 if (ret)
3335                                         goto out_wimlib_free;
3336                         }
3337                 }
3338
3339                 /* Only call wimlib_overwrite() if something actually needs to
3340                  * be changed.  */
3341                 if (boot || new_name || new_desc ||
3342                     (check && !info.has_integrity_table) ||
3343                     (nocheck && info.has_integrity_table))
3344                 {
3345                         int write_flags = 0;
3346
3347                         if (check)
3348                                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3349                         if (nocheck)
3350                                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3351                         ret = wimlib_overwrite(wim, write_flags, 1);
3352                 } else {
3353                         imagex_printf(T("The file \"%"TS"\" was not modified "
3354                                         "because nothing needed to be done.\n"),
3355                                       wimfile);
3356                         ret = 0;
3357                 }
3358         }
3359 out_wimlib_free:
3360         wimlib_free(wim);
3361 out:
3362         return ret;
3363
3364 out_usage:
3365         usage(CMD_INFO, stderr);
3366 out_err:
3367         ret = -1;
3368         goto out;
3369 }
3370
3371 /* Join split WIMs into one part WIM */
3372 static int
3373 imagex_join(int argc, tchar **argv, int cmd)
3374 {
3375         int c;
3376         int swm_open_flags = 0;
3377         int wim_write_flags = 0;
3378         const tchar *output_path;
3379         int ret;
3380
3381         for_opt(c, join_options) {
3382                 switch (c) {
3383                 case IMAGEX_CHECK_OPTION:
3384                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3385                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3386                         break;
3387                 default:
3388                         goto out_usage;
3389                 }
3390         }
3391         argc -= optind;
3392         argv += optind;
3393
3394         if (argc < 2) {
3395                 imagex_error(T("Must specify one or more split WIM (.swm) "
3396                                "parts to join"));
3397                 goto out_usage;
3398         }
3399         output_path = argv[0];
3400         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3401                                         --argc,
3402                                         output_path,
3403                                         swm_open_flags,
3404                                         wim_write_flags,
3405                                         imagex_progress_func,
3406                                         NULL);
3407 out:
3408         return ret;
3409
3410 out_usage:
3411         usage(CMD_JOIN, stderr);
3412         ret = -1;
3413         goto out;
3414 }
3415
3416 #if WIM_MOUNTING_SUPPORTED
3417
3418 /* Mounts a WIM image.  */
3419 static int
3420 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3421 {
3422         int c;
3423         int mount_flags = 0;
3424         int open_flags = 0;
3425         const tchar *staging_dir = NULL;
3426         const tchar *wimfile;
3427         const tchar *dir;
3428         WIMStruct *wim;
3429         struct wimlib_wim_info info;
3430         int image;
3431         int ret;
3432
3433         STRING_SET(refglobs);
3434
3435         if (cmd == CMD_MOUNTRW) {
3436                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3437                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3438         }
3439
3440         for_opt(c, mount_options) {
3441                 switch (c) {
3442                 case IMAGEX_ALLOW_OTHER_OPTION:
3443                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3444                         break;
3445                 case IMAGEX_CHECK_OPTION:
3446                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3447                         break;
3448                 case IMAGEX_DEBUG_OPTION:
3449                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3450                         break;
3451                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3452                         if (!tstrcasecmp(optarg, T("none")))
3453                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3454                         else if (!tstrcasecmp(optarg, T("xattr")))
3455                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3456                         else if (!tstrcasecmp(optarg, T("windows")))
3457                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3458                         else {
3459                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3460                                              optarg);
3461                                 goto out_usage;
3462                         }
3463                         break;
3464                 case IMAGEX_REF_OPTION:
3465                         ret = string_set_append(&refglobs, optarg);
3466                         if (ret)
3467                                 goto out_free_refglobs;
3468                         break;
3469                 case IMAGEX_STAGING_DIR_OPTION:
3470                         staging_dir = optarg;
3471                         break;
3472                 case IMAGEX_UNIX_DATA_OPTION:
3473                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3474                         break;
3475                 default:
3476                         goto out_usage;
3477                 }
3478         }
3479         argc -= optind;
3480         argv += optind;
3481         if (argc != 2 && argc != 3)
3482                 goto out_usage;
3483
3484         wimfile = argv[0];
3485
3486         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3487                                             imagex_progress_func, NULL);
3488         if (ret)
3489                 goto out_free_refglobs;
3490
3491         wimlib_get_wim_info(wim, &info);
3492
3493         if (argc >= 3) {
3494                 /* Image explicitly specified.  */
3495                 image = wimlib_resolve_image(wim, argv[1]);
3496                 dir = argv[2];
3497                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3498                 if (ret)
3499                         goto out_free_wim;
3500         } else {
3501                 /* No image specified; default to image 1, but only if the WIM
3502                  * contains exactly one image.  */
3503
3504                 if (info.image_count != 1) {
3505                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3506                                        "select one."), wimfile, info.image_count);
3507                         wimlib_free(wim);
3508                         goto out_usage;
3509                 }
3510                 image = 1;
3511                 dir = argv[1];
3512         }
3513
3514         if (refglobs.num_strings) {
3515                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3516                 if (ret)
3517                         goto out_free_wim;
3518         }
3519
3520         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3521         if (ret) {
3522                 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3523                                "on \"%"TS"\""),
3524                              image, wimfile, dir);
3525         }
3526 out_free_wim:
3527         wimlib_free(wim);
3528 out_free_refglobs:
3529         string_set_destroy(&refglobs);
3530         return ret;
3531
3532 out_usage:
3533         usage(cmd, stderr);
3534         ret = -1;
3535         goto out_free_refglobs;
3536 }
3537 #endif /* WIM_MOUNTING_SUPPORTED */
3538
3539 /* Rebuild a WIM file */
3540 static int
3541 imagex_optimize(int argc, tchar **argv, int cmd)
3542 {
3543         int c;
3544         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3545         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3546         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3547         uint32_t chunk_size = UINT32_MAX;
3548         uint32_t solid_chunk_size = UINT32_MAX;
3549         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3550         int ret;
3551         WIMStruct *wim;
3552         const tchar *wimfile;
3553         off_t old_size;
3554         off_t new_size;
3555         unsigned num_threads = 0;
3556
3557         for_opt(c, optimize_options) {
3558                 switch (c) {
3559                 case IMAGEX_CHECK_OPTION:
3560                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3561                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3562                         break;
3563                 case IMAGEX_NOCHECK_OPTION:
3564                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3565                         break;
3566                 case IMAGEX_COMPRESS_OPTION:
3567                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3568                         compression_type = get_compression_type(optarg);
3569                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3570                                 goto out_err;
3571                         break;
3572                 case IMAGEX_COMPRESS_SLOW_OPTION:
3573                         set_compress_slow();
3574                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3575                         break;
3576                 case IMAGEX_RECOMPRESS_OPTION:
3577                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3578                         break;
3579                 case IMAGEX_CHUNK_SIZE_OPTION:
3580                         chunk_size = parse_chunk_size(optarg);
3581                         if (chunk_size == UINT32_MAX)
3582                                 goto out_err;
3583                         break;
3584                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3585                         solid_chunk_size = parse_chunk_size(optarg);
3586                         if (solid_chunk_size == UINT32_MAX)
3587                                 goto out_err;
3588                         break;
3589                 case IMAGEX_SOLID_COMPRESS_OPTION:
3590                         solid_ctype = get_compression_type(optarg);
3591                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3592                                 goto out_err;
3593                         break;
3594                 case IMAGEX_SOLID_OPTION:
3595                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3596                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3597                         break;
3598                 case IMAGEX_THREADS_OPTION:
3599                         num_threads = parse_num_threads(optarg);
3600                         if (num_threads == UINT_MAX)
3601                                 goto out_err;
3602                         break;
3603                 case IMAGEX_PIPABLE_OPTION:
3604                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3605                         break;
3606                 case IMAGEX_NOT_PIPABLE_OPTION:
3607                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3608                         break;
3609                 default:
3610                         goto out_usage;
3611                 }
3612         }
3613         argc -= optind;
3614         argv += optind;
3615
3616         if (argc != 1)
3617                 goto out_usage;
3618
3619         wimfile = argv[0];
3620
3621         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3622                                             imagex_progress_func, NULL);
3623         if (ret)
3624                 goto out;
3625
3626         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3627                 /* Change compression type.  */
3628                 ret = wimlib_set_output_compression_type(wim, compression_type);
3629                 if (ret)
3630                         goto out_wimlib_free;
3631         }
3632
3633         if (chunk_size != UINT32_MAX) {
3634                 /* Change chunk size.  */
3635                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3636                 if (ret)
3637                         goto out_wimlib_free;
3638         }
3639         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3640                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3641                 if (ret)
3642                         goto out_wimlib_free;
3643         }
3644         if (solid_chunk_size != UINT32_MAX) {
3645                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3646                 if (ret)
3647                         goto out_wimlib_free;
3648         }
3649
3650         old_size = file_get_size(wimfile);
3651         tprintf(T("\"%"TS"\" original size: "), wimfile);
3652         if (old_size == -1)
3653                 tputs(T("Unknown"));
3654         else
3655                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3656
3657         ret = wimlib_overwrite(wim, write_flags, num_threads);
3658         if (ret) {
3659                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3660                 goto out_wimlib_free;
3661         }
3662
3663         new_size = file_get_size(wimfile);
3664         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3665         if (new_size == -1)
3666                 tputs(T("Unknown"));
3667         else
3668                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3669
3670         tfputs(T("Space saved: "), stdout);
3671         if (new_size != -1 && old_size != -1) {
3672                 tprintf(T("%lld KiB\n"),
3673                        ((long long)old_size - (long long)new_size) >> 10);
3674         } else {
3675                 tputs(T("Unknown"));
3676         }
3677         ret = 0;
3678 out_wimlib_free:
3679         wimlib_free(wim);
3680 out:
3681         return ret;
3682
3683 out_usage:
3684         usage(CMD_OPTIMIZE, stderr);
3685 out_err:
3686         ret = -1;
3687         goto out;
3688 }
3689
3690 /* Split a WIM into a spanned set */
3691 static int
3692 imagex_split(int argc, tchar **argv, int cmd)
3693 {
3694         int c;
3695         int open_flags = 0;
3696         int write_flags = 0;
3697         unsigned long part_size;
3698         tchar *tmp;
3699         int ret;
3700         WIMStruct *wim;
3701
3702         for_opt(c, split_options) {
3703                 switch (c) {
3704                 case IMAGEX_CHECK_OPTION:
3705                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3706                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3707                         break;
3708                 default:
3709                         goto out_usage;
3710                 }
3711         }
3712         argc -= optind;
3713         argv += optind;
3714
3715         if (argc != 3)
3716                 goto out_usage;
3717
3718         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3719         if (tmp == argv[2] || *tmp) {
3720                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3721                 imagex_error(T("The part size must be an integer or "
3722                                "floating-point number of megabytes."));
3723                 goto out_err;
3724         }
3725         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3726                                             imagex_progress_func, NULL);
3727         if (ret)
3728                 goto out;
3729
3730         ret = wimlib_split(wim, argv[1], part_size, write_flags);
3731         wimlib_free(wim);
3732 out:
3733         return ret;
3734
3735 out_usage:
3736         usage(CMD_SPLIT, stderr);
3737 out_err:
3738         ret = -1;
3739         goto out;
3740 }
3741
3742 #if WIM_MOUNTING_SUPPORTED
3743 /* Unmounts a mounted WIM image. */
3744 static int
3745 imagex_unmount(int argc, tchar **argv, int cmd)
3746 {
3747         int c;
3748         int unmount_flags = 0;
3749         int ret;
3750
3751         for_opt(c, unmount_options) {
3752                 switch (c) {
3753                 case IMAGEX_COMMIT_OPTION:
3754                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3755                         break;
3756                 case IMAGEX_CHECK_OPTION:
3757                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3758                         break;
3759                 case IMAGEX_REBUILD_OPTION:
3760                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3761                         break;
3762                 case IMAGEX_LAZY_OPTION:
3763                 case IMAGEX_FORCE_OPTION:
3764                         /* Now, unmount is lazy by default.  However, committing
3765                          * the image will fail with
3766                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3767                          * file descriptors on the WIM image.  The
3768                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3769                          * descriptors to be closed.  */
3770                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3771                         break;
3772                 case IMAGEX_NEW_IMAGE_OPTION:
3773                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3774                         break;
3775                 default:
3776                         goto out_usage;
3777                 }
3778         }
3779         argc -= optind;
3780         argv += optind;
3781         if (argc != 1)
3782                 goto out_usage;
3783
3784         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3785                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3786                         imagex_error(T("--new-image is meaningless "
3787                                        "without --commit also specified!"));
3788                         goto out_err;
3789                 }
3790         }
3791
3792         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3793                                                  imagex_progress_func, NULL);
3794         if (ret) {
3795                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3796                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3797                         imagex_printf(T(
3798                                 "\tNote: Use --commit --force to force changes "
3799                                         "to be committed, regardless\n"
3800                                 "\t      of open files.\n"));
3801                 }
3802         }
3803 out:
3804         return ret;
3805
3806 out_usage:
3807         usage(CMD_UNMOUNT, stderr);
3808 out_err:
3809         ret = -1;
3810         goto out;
3811 }
3812 #endif /* WIM_MOUNTING_SUPPORTED */
3813
3814 /*
3815  * Add, delete, or rename files in a WIM image.
3816  */
3817 static int
3818 imagex_update(int argc, tchar **argv, int cmd)
3819 {
3820         const tchar *wimfile;
3821         int image;
3822         WIMStruct *wim;
3823         int ret;
3824         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3825         int write_flags = 0;
3826         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3827         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3828                                 WIMLIB_ADD_FLAG_VERBOSE |
3829                                 WIMLIB_ADD_FLAG_WINCONFIG;
3830         int default_delete_flags = 0;
3831         unsigned num_threads = 0;
3832         int c;
3833         tchar *cmd_file_contents;
3834         size_t cmd_file_nchars;
3835         struct wimlib_update_command *cmds;
3836         size_t num_cmds;
3837         tchar *command_str = NULL;
3838         tchar *config_file = NULL;
3839         tchar *wimboot_config = NULL;
3840
3841         for_opt(c, update_options) {
3842                 switch (c) {
3843                 /* Generic or write options */
3844                 case IMAGEX_THREADS_OPTION:
3845                         num_threads = parse_num_threads(optarg);
3846                         if (num_threads == UINT_MAX)
3847                                 goto out_err;
3848                         break;
3849                 case IMAGEX_CHECK_OPTION:
3850                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3851                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3852                         break;
3853                 case IMAGEX_REBUILD_OPTION:
3854                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3855                         break;
3856                 case IMAGEX_COMMAND_OPTION:
3857                         if (command_str) {
3858                                 imagex_error(T("--command may only be specified "
3859                                                "one time.  Please provide\n"
3860                                                "       the update commands "
3861                                                "on standard input instead."));
3862                                 goto out_err;
3863                         }
3864                         command_str = tstrdup(optarg);
3865                         if (!command_str) {
3866                                 imagex_error(T("Out of memory!"));
3867                                 goto out_err;
3868                         }
3869                         break;
3870                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3871                         wimboot_config = optarg;
3872                         break;
3873                 /* Default delete options */
3874                 case IMAGEX_FORCE_OPTION:
3875                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3876                         break;
3877                 case IMAGEX_RECURSIVE_OPTION:
3878                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3879                         break;
3880
3881                 /* Global add option */
3882                 case IMAGEX_CONFIG_OPTION:
3883                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3884                         config_file = optarg;
3885                         break;
3886
3887                 /* Default add options */
3888                 case IMAGEX_VERBOSE_OPTION:
3889                         /* No longer does anything.  */
3890                         break;
3891                 case IMAGEX_DEREFERENCE_OPTION:
3892                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3893                         break;
3894                 case IMAGEX_UNIX_DATA_OPTION:
3895                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3896                         break;
3897                 case IMAGEX_NO_ACLS_OPTION:
3898                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3899                         break;
3900                 case IMAGEX_STRICT_ACLS_OPTION:
3901                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3902                         break;
3903                 case IMAGEX_NO_REPLACE_OPTION:
3904                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3905                         break;
3906                 default:
3907                         goto out_usage;
3908                 }
3909         }
3910         argv += optind;
3911         argc -= optind;
3912
3913         if (argc != 1 && argc != 2)
3914                 goto out_usage;
3915         wimfile = argv[0];
3916
3917         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3918                                             imagex_progress_func, NULL);
3919         if (ret)
3920                 goto out_free_command_str;
3921
3922         if (argc >= 2) {
3923                 /* Image explicitly specified.  */
3924                 image = wimlib_resolve_image(wim, argv[1]);
3925                 ret = verify_image_exists_and_is_single(image, argv[1],
3926                                                         wimfile);
3927                 if (ret)
3928                         goto out_wimlib_free;
3929         } else {
3930                 /* No image specified; default to image 1, but only if the WIM
3931                  * contains exactly one image.  */
3932                 struct wimlib_wim_info info;
3933
3934                 wimlib_get_wim_info(wim, &info);
3935                 if (info.image_count != 1) {
3936                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3937                                      wimfile, info.image_count);
3938                         wimlib_free(wim);
3939                         goto out_usage;
3940                 }
3941                 image = 1;
3942         }
3943
3944         /* Read update commands from standard input, or the command string if
3945          * specified.  */
3946         if (command_str) {
3947                 cmd_file_contents = NULL;
3948                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3949                                                  &num_cmds);
3950                 if (!cmds) {
3951                         ret = -1;
3952                         goto out_free_cmd_file_contents;
3953                 }
3954         } else if (!wimboot_config) {
3955                 if (isatty(STDIN_FILENO)) {
3956                         tputs(T("Reading update commands from standard input..."));
3957                         recommend_man_page(CMD_UPDATE, stdout);
3958                 }
3959                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3960                 if (!cmd_file_contents) {
3961                         ret = -1;
3962                         goto out_wimlib_free;
3963                 }
3964
3965                 /* Parse the update commands */
3966                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3967                                                  &num_cmds);
3968                 if (!cmds) {
3969                         ret = -1;
3970                         goto out_free_cmd_file_contents;
3971                 }
3972         } else {
3973                 cmd_file_contents = NULL;
3974                 cmds = NULL;
3975                 num_cmds = 0;
3976         }
3977
3978         /* Set default flags and capture config on the update commands */
3979         for (size_t i = 0; i < num_cmds; i++) {
3980                 switch (cmds[i].op) {
3981                 case WIMLIB_UPDATE_OP_ADD:
3982                         cmds[i].add.add_flags |= default_add_flags;
3983                         cmds[i].add.config_file = config_file;
3984                         break;
3985                 case WIMLIB_UPDATE_OP_DELETE:
3986                         cmds[i].delete_.delete_flags |= default_delete_flags;
3987                         break;
3988                 default:
3989                         break;
3990                 }
3991         }
3992
3993         /* Execute the update commands */
3994         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3995         if (ret)
3996                 goto out_free_cmds;
3997
3998         if (wimboot_config) {
3999                 /* --wimboot-config=FILE is short for an
4000                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
4001                  */
4002                 struct wimlib_update_command cmd;
4003
4004                 cmd.op = WIMLIB_UPDATE_OP_ADD;
4005                 cmd.add.fs_source_path = wimboot_config;
4006                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4007                 cmd.add.config_file = NULL;
4008                 cmd.add.add_flags = 0;
4009
4010                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4011                 if (ret)
4012                         goto out_free_cmds;
4013         }
4014
4015         /* Overwrite the updated WIM */
4016         ret = wimlib_overwrite(wim, write_flags, num_threads);
4017 out_free_cmds:
4018         free(cmds);
4019 out_free_cmd_file_contents:
4020         free(cmd_file_contents);
4021 out_wimlib_free:
4022         wimlib_free(wim);
4023 out_free_command_str:
4024         free(command_str);
4025         return ret;
4026
4027 out_usage:
4028         usage(CMD_UPDATE, stderr);
4029 out_err:
4030         ret = -1;
4031         goto out_free_command_str;
4032 }
4033
4034 /* Verify a WIM file.  */
4035 static int
4036 imagex_verify(int argc, tchar **argv, int cmd)
4037 {
4038         int ret;
4039         const tchar *wimfile;
4040         WIMStruct *wim;
4041         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4042         int verify_flags = 0;
4043         STRING_SET(refglobs);
4044         int c;
4045
4046         for_opt(c, verify_options) {
4047                 switch (c) {
4048                 case IMAGEX_REF_OPTION:
4049                         ret = string_set_append(&refglobs, optarg);
4050                         if (ret)
4051                                 goto out_free_refglobs;
4052                         break;
4053                 case IMAGEX_NOCHECK_OPTION:
4054                         open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4055                         break;
4056                 default:
4057                         goto out_usage;
4058                 }
4059         }
4060
4061         argv += optind;
4062         argc -= optind;
4063
4064         if (argc != 1) {
4065                 if (argc == 0)
4066                         imagex_error(T("Must specify a WIM file!"));
4067                 else
4068                         imagex_error(T("At most one WIM file can be specified!"));
4069                 goto out_usage;
4070         }
4071
4072         wimfile = argv[0];
4073
4074         ret = wimlib_open_wim_with_progress(wimfile,
4075                                             open_flags,
4076                                             &wim,
4077                                             imagex_progress_func,
4078                                             NULL);
4079         if (ret)
4080                 goto out_free_refglobs;
4081
4082         ret = wim_reference_globs(wim, &refglobs, open_flags);
4083         if (ret)
4084                 goto out_wimlib_free;
4085
4086         ret = wimlib_verify_wim(wim, verify_flags);
4087         if (ret) {
4088                 tputc(T('\n'), stderr);
4089                 imagex_error(T("\"%"TS"\" failed verification!"),
4090                              wimfile);
4091                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4092                     refglobs.num_strings == 0)
4093                 {
4094                         imagex_printf(T("Note: if this WIM file is not standalone, "
4095                                         "use the --ref option to specify the other parts.\n"));
4096                 }
4097         } else {
4098                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4099                               wimfile);
4100         }
4101
4102 out_wimlib_free:
4103         wimlib_free(wim);
4104 out_free_refglobs:
4105         string_set_destroy(&refglobs);
4106         return ret;
4107
4108 out_usage:
4109         usage(CMD_VERIFY, stderr);
4110         ret = -1;
4111         goto out_free_refglobs;
4112 }
4113
4114 struct imagex_command {
4115         const tchar *name;
4116         int (*func)(int argc, tchar **argv, int cmd);
4117 };
4118
4119 static const struct imagex_command imagex_commands[] = {
4120         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4121         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4122         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4123         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4124         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4125         [CMD_EXPORT]   = {T("export"),   imagex_export},
4126         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4127         [CMD_INFO]     = {T("info"),     imagex_info},
4128         [CMD_JOIN]     = {T("join"),     imagex_join},
4129 #if WIM_MOUNTING_SUPPORTED
4130         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4131         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4132 #endif
4133         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4134         [CMD_SPLIT]    = {T("split"),    imagex_split},
4135 #if WIM_MOUNTING_SUPPORTED
4136         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4137 #endif
4138         [CMD_UPDATE]   = {T("update"),   imagex_update},
4139         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4140 };
4141
4142 #ifdef __WIN32__
4143
4144    /* Can be a directory or source list file.  But source list file is probably
4145     * a rare use case, so just say directory.  */
4146 #  define SOURCE_STR T("DIRECTORY")
4147
4148    /* Can only be a directory  */
4149 #  define TARGET_STR T("DIRECTORY")
4150
4151 #else
4152    /* Can be a directory, NTFS volume, or source list file. */
4153 #  define SOURCE_STR T("SOURCE")
4154
4155    /* Can be a directory or NTFS volume.  */
4156 #  define TARGET_STR T("TARGET")
4157
4158 #endif
4159
4160 static const tchar *usage_strings[] = {
4161 [CMD_APPEND] =
4162 T(
4163 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4164 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4165 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4166 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4167 "                    [--wimboot] [--unix-data] [--dereference]\n"
4168 ),
4169 [CMD_APPLY] =
4170 T(
4171 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4172 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4173 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4174 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4175 ),
4176 [CMD_CAPTURE] =
4177 T(
4178 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4179 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4180 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4181 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4182 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4183 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4184 ),
4185 [CMD_DELETE] =
4186 T(
4187 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4188 ),
4189 [CMD_DIR] =
4190 T(
4191 "    %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4192 ),
4193 [CMD_EXPORT] =
4194 T(
4195 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4196 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4197 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4198 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4199 "                    [--wimboot]\n"
4200 ),
4201 [CMD_EXTRACT] =
4202 T(
4203 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4204 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4205 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4206 "                    [--no-attributes] [--include-invalid-names]\n"
4207 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4208 ),
4209 [CMD_INFO] =
4210 T(
4211 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4212 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4213 "                    [--extract-xml FILE] [--header] [--lookup-table]\n"
4214 ),
4215 [CMD_JOIN] =
4216 T(
4217 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4218 ),
4219 #if WIM_MOUNTING_SUPPORTED
4220 [CMD_MOUNT] =
4221 T(
4222 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4223 "                    [--check] [--streams-interface=INTERFACE]\n"
4224 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4225 ),
4226 [CMD_MOUNTRW] =
4227 T(
4228 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4229 "                    [--check] [--streams-interface=INTERFACE]\n"
4230 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4231 ),
4232 #endif
4233 [CMD_OPTIMIZE] =
4234 T(
4235 "    %"TS" WIMFILE\n"
4236 "                    [--recompress] [--compress=TYPE]\n"
4237 "                    [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4238 "\n"
4239 ),
4240 [CMD_SPLIT] =
4241 T(
4242 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4243 ),
4244 #if WIM_MOUNTING_SUPPORTED
4245 [CMD_UNMOUNT] =
4246 T(
4247 "    %"TS" DIRECTORY\n"
4248 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4249 ),
4250 #endif
4251 [CMD_UPDATE] =
4252 T(
4253 "    %"TS" WIMFILE [IMAGE]\n"
4254 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4255 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4256 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4257 "                    [< CMDFILE]\n"
4258 ),
4259 [CMD_VERIFY] =
4260 T(
4261 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4262 ),
4263 };
4264
4265 static const tchar *invocation_name;
4266 static int invocation_cmd = CMD_NONE;
4267
4268 static const tchar *get_cmd_string(int cmd, bool nospace)
4269 {
4270         static tchar buf[50];
4271         if (cmd == CMD_NONE) {
4272                 return T("wimlib-imagex");
4273         } else if (invocation_cmd != CMD_NONE) {
4274                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4275         } else {
4276                 const tchar *format;
4277
4278                 if (nospace)
4279                         format = T("%"TS"-%"TS"");
4280                 else
4281                         format = T("%"TS" %"TS"");
4282                 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4283         }
4284         return buf;
4285 }
4286
4287 static void
4288 version(void)
4289 {
4290         static const tchar *s =
4291         T(
4292 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4293 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4294 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4295 "This is free software: you are free to change and redistribute it.\n"
4296 "There is NO WARRANTY, to the extent permitted by law.\n"
4297 "\n"
4298 "Report bugs to "PACKAGE_BUGREPORT".\n"
4299         );
4300         tfputs(s, stdout);
4301 }
4302
4303
4304 static void
4305 help_or_version(int argc, tchar **argv, int cmd)
4306 {
4307         int i;
4308         const tchar *p;
4309
4310         for (i = 1; i < argc; i++) {
4311                 p = argv[i];
4312                 if (p[0] == T('-') && p[1] == T('-')) {
4313                         p += 2;
4314                         if (!tstrcmp(p, T("help"))) {
4315                                 if (cmd == CMD_NONE)
4316                                         usage_all(stdout);
4317                                 else
4318                                         usage(cmd, stdout);
4319                                 exit(0);
4320                         } else if (!tstrcmp(p, T("version"))) {
4321                                 version();
4322                                 exit(0);
4323                         }
4324                 }
4325         }
4326 }
4327
4328 static void
4329 print_usage_string(int cmd, FILE *fp)
4330 {
4331         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4332 }
4333
4334 static void
4335 recommend_man_page(int cmd, FILE *fp)
4336 {
4337         const tchar *format_str;
4338 #ifdef __WIN32__
4339         format_str = T("Some uncommon options are not listed;\n"
4340                        "See %"TS".pdf in the doc directory for more details.\n");
4341 #else
4342         format_str = T("Some uncommon options are not listed;\n"
4343                        "Try `man %"TS"' for more details.\n");
4344 #endif
4345         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4346 }
4347
4348 static void
4349 usage(int cmd, FILE *fp)
4350 {
4351         tfprintf(fp, T("Usage:\n"));
4352         print_usage_string(cmd, fp);
4353         tfprintf(fp, T("\n"));
4354         recommend_man_page(cmd, fp);
4355 }
4356
4357 static void
4358 usage_all(FILE *fp)
4359 {
4360         tfprintf(fp, T("Usage:\n"));
4361         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4362                 print_usage_string(cmd, fp);
4363                 tfprintf(fp, T("\n"));
4364         }
4365         static const tchar *extra =
4366         T(
4367 "    %"TS" --help\n"
4368 "    %"TS" --version\n"
4369 "\n"
4370         );
4371         tfprintf(fp, extra, invocation_name, invocation_name);
4372         tfprintf(fp,
4373                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4374                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4375                    "For some commands IMAGE may be \"all\".\n"
4376                    "\n"));
4377         recommend_man_page(CMD_NONE, fp);
4378 }
4379
4380 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4381  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4382  * something else), while on Windows the command arguments will be UTF-16LE
4383  * encoded 'wchar_t' strings. */
4384 int
4385 #ifdef __WIN32__
4386 wmain(int argc, wchar_t **argv, wchar_t **envp)
4387 #else
4388 main(int argc, char **argv)
4389 #endif
4390 {
4391         int ret;
4392         int init_flags = 0;
4393         int cmd;
4394
4395         imagex_info_file = stdout;
4396         invocation_name = tbasename(argv[0]);
4397
4398 #ifndef __WIN32__
4399         if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4400                 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4401         } else {
4402                 char *codeset;
4403
4404                 setlocale(LC_ALL, "");
4405                 codeset = nl_langinfo(CODESET);
4406                 if (!strstr(codeset, "UTF-8") &&
4407                     !strstr(codeset, "UTF8") &&
4408                     !strstr(codeset, "utf-8") &&
4409                     !strstr(codeset, "utf8"))
4410                 {
4411                         fprintf(stderr,
4412 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4413 "         Maybe try: `export LANG=en_US.UTF-8'?\n"
4414 "         Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4415 "         to any value to force wimlib to use UTF-8.\n",
4416                         invocation_name);
4417
4418                 }
4419         }
4420
4421 #endif /* !__WIN32__ */
4422
4423         {
4424                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4425                 if (igcase != NULL) {
4426                         if (!tstrcmp(igcase, T("no")) ||
4427                             !tstrcmp(igcase, T("0")))
4428                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4429                         else if (!tstrcmp(igcase, T("yes")) ||
4430                                  !tstrcmp(igcase, T("1")))
4431                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4432                         else {
4433                                 fprintf(stderr,
4434                                         "WARNING: Ignoring unknown setting of "
4435                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4436                         }
4437                 }
4438         }
4439
4440         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4441         cmd = CMD_NONE;
4442         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4443             tstrcmp(invocation_name, T("wimlib-imagex"))) {
4444                 for (int i = 0; i < CMD_MAX; i++) {
4445                         if (!tstrcmp(invocation_name + 3,
4446                                      imagex_commands[i].name))
4447                         {
4448                                 invocation_cmd = i;
4449                                 cmd = i;
4450                                 break;
4451                         }
4452                 }
4453         }
4454
4455         /* Unless already known from the invocation name, determine which
4456          * command was specified.  */
4457         if (cmd == CMD_NONE) {
4458                 if (argc < 2) {
4459                         imagex_error(T("No command specified!\n"));
4460                         usage_all(stderr);
4461                         exit(2);
4462                 }
4463                 for (int i = 0; i < CMD_MAX; i++) {
4464                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4465                                 cmd = i;
4466                                 break;
4467                         }
4468                 }
4469                 if (cmd != CMD_NONE) {
4470                         argc--;
4471                         argv++;
4472                 }
4473         }
4474
4475         /* Handle --help and --version.  --help can be either for the program as
4476          * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4477          * CMD_NONE).  Note: help_or_version() will not return if a --help or
4478          * --version argument was found.  */
4479         help_or_version(argc, argv, cmd);
4480
4481         /* Bail if a valid command was not specified.  */
4482         if (cmd == CMD_NONE) {
4483                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4484                 usage_all(stderr);
4485                 exit(2);
4486         }
4487
4488         /* Enable warning and error messages in wimlib to be more user-friendly.
4489          * */
4490         wimlib_set_print_errors(true);
4491
4492         /* Initialize wimlib.  */
4493         ret = wimlib_global_init(init_flags);
4494         if (ret)
4495                 goto out_check_status;
4496
4497         /* Call the command handler function.  */
4498         ret = imagex_commands[cmd].func(argc, argv, cmd);
4499
4500         /* Check for error writing to standard output, especially since for some
4501          * commands, writing to standard output is part of the program's actual
4502          * behavior and not just for informational purposes.  */
4503         if (ferror(stdout) || fclose(stdout)) {
4504                 imagex_error_with_errno(T("error writing to standard output"));
4505                 if (ret == 0)
4506                         ret = -1;
4507         }
4508 out_check_status:
4509         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4510          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4511          * error code from which an error message can be printed.  */
4512         if (ret > 0) {
4513                 imagex_error(T("Exiting with error code %d:\n"
4514                                "       %"TS"."), ret,
4515                              wimlib_get_error_string(ret));
4516                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4517                         imagex_error_with_errno(T("errno"));
4518         }
4519         /* Make wimlib free any resources it's holding (although this is not
4520          * strictly necessary because the process is ending anyway).  */
4521         wimlib_global_cleanup();
4522         return ret;
4523 }