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