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