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