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