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