]> wimlib.net Git - wimlib/blob - programs/imagex.c
82354feb7d47f3c24bb86e1a991dcb74eeed1c55
[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_FILE_STRUCTURE:
1164                 if (info->extract.end_file_count >= 2000) {
1165                         percent_done = TO_PERCENT(info->extract.current_file_count,
1166                                                   info->extract.end_file_count);
1167                         imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1168                                       info->extract.current_file_count,
1169                                       info->extract.end_file_count, percent_done);
1170                         if (info->extract.current_file_count == info->extract.end_file_count)
1171                                 imagex_printf(T("\n"));
1172                 }
1173                 break;
1174         case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1175                 percent_done = TO_PERCENT(info->extract.completed_bytes,
1176                                           info->extract.total_bytes);
1177                 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1178                 imagex_printf(T("\rExtracting files: "
1179                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1180                         info->extract.completed_bytes >> unit_shift,
1181                         unit_name,
1182                         info->extract.total_bytes >> unit_shift,
1183                         unit_name,
1184                         percent_done);
1185                 if (info->extract.completed_bytes >= info->extract.total_bytes)
1186                         imagex_printf(T("\n"));
1187                 break;
1188         case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1189                 if (info->extract.end_file_count >= 2000) {
1190                         percent_done = TO_PERCENT(info->extract.current_file_count,
1191                                                   info->extract.end_file_count);
1192                         imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1193                                       info->extract.current_file_count,
1194                                       info->extract.end_file_count, percent_done);
1195                         if (info->extract.current_file_count == info->extract.end_file_count)
1196                                 imagex_printf(T("\n"));
1197                 }
1198                 break;
1199         case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1200                 if (info->extract.total_parts != 1) {
1201                         imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1202                                       info->extract.part_number,
1203                                       info->extract.total_parts);
1204                 }
1205                 break;
1206         case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1207                 percent_done = TO_PERCENT(info->split.completed_bytes,
1208                                           info->split.total_bytes);
1209                 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1210                 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1211                           "%"PRIu64" %"TS" (%u%%) written\n"),
1212                         info->split.part_name,
1213                         info->split.cur_part_number,
1214                         info->split.total_parts,
1215                         info->split.completed_bytes >> unit_shift,
1216                         unit_name,
1217                         info->split.total_bytes >> unit_shift,
1218                         unit_name,
1219                         percent_done);
1220                 break;
1221         case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1222                 if (info->split.completed_bytes == info->split.total_bytes) {
1223                         imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1224                                 info->split.cur_part_number,
1225                                 info->split.total_parts);
1226                 }
1227                 break;
1228         case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1229                 switch (info->update.command->op) {
1230                 case WIMLIB_UPDATE_OP_DELETE:
1231                         imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1232                                 info->update.command->delete_.wim_path);
1233                         break;
1234                 case WIMLIB_UPDATE_OP_RENAME:
1235                         imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1236                                 info->update.command->rename.wim_source_path,
1237                                 info->update.command->rename.wim_target_path);
1238                         break;
1239                 case WIMLIB_UPDATE_OP_ADD:
1240                 default:
1241                         break;
1242                 }
1243                 break;
1244         case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1245                 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1246                               info->replace.path_in_wim);
1247                 break;
1248         case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1249                 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1250                               info->wimboot_exclude.path_in_wim);
1251                 break;
1252         case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1253                 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1254                         if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1255                                 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1256                                               info->unmount.mounted_wim,
1257                                               info->unmount.mounted_image);
1258                         } else {
1259                                 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1260                                               info->unmount.mounted_wim,
1261                                               info->unmount.mounted_image);
1262                                 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1263                         }
1264                 }
1265                 break;
1266         case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1267                 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1268                               info->verify_image.current_image,
1269                               info->verify_image.total_images);
1270                 break;
1271         case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1272                 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1273                                           info->verify_streams.total_bytes);
1274                 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1275                 imagex_printf(T("\rVerifying streams: "
1276                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1277                         info->verify_streams.completed_bytes >> unit_shift,
1278                         unit_name,
1279                         info->verify_streams.total_bytes >> unit_shift,
1280                         unit_name,
1281                         percent_done);
1282                 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1283                         imagex_printf(T("\n"));
1284                 break;
1285         default:
1286                 break;
1287         }
1288         fflush(imagex_info_file);
1289         return WIMLIB_PROGRESS_STATUS_CONTINUE;
1290 }
1291
1292 static unsigned
1293 parse_num_threads(const tchar *optarg)
1294 {
1295         tchar *tmp;
1296         unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1297         if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1298                 imagex_error(T("Number of threads must be a non-negative integer!"));
1299                 return UINT_MAX;
1300         } else {
1301                 return ul_nthreads;
1302         }
1303 }
1304
1305 static uint32_t parse_chunk_size(const tchar *optarg)
1306 {
1307        tchar *tmp;
1308        unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
1309        if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
1310                imagex_error(T("Chunk size must be a non-negative integer!"));
1311                return UINT32_MAX;
1312        } else {
1313                return chunk_size;
1314        }
1315 }
1316
1317
1318 /*
1319  * Parse an option passed to an update command.
1320  *
1321  * @op:         One of WIMLIB_UPDATE_OP_* that indicates the command being
1322  *              parsed.
1323  *
1324  * @option:     Text string for the option (beginning with --)
1325  *
1326  * @cmd:        `struct wimlib_update_command' that is being constructed for
1327  *              this command.
1328  *
1329  * Returns true if the option was recognized; false if not.
1330  */
1331 static bool
1332 update_command_add_option(int op, const tchar *option,
1333                           struct wimlib_update_command *cmd)
1334 {
1335         bool recognized = true;
1336         switch (op) {
1337         case WIMLIB_UPDATE_OP_ADD:
1338                 if (!tstrcmp(option, T("--verbose")))
1339                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1340                 else if (!tstrcmp(option, T("--unix-data")))
1341                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1342                 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1343                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1344                 else if (!tstrcmp(option, T("--strict-acls")))
1345                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1346                 else if (!tstrcmp(option, T("--dereference")))
1347                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1348                 else if (!tstrcmp(option, T("--no-replace")))
1349                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1350                 else
1351                         recognized = false;
1352                 break;
1353         case WIMLIB_UPDATE_OP_DELETE:
1354                 if (!tstrcmp(option, T("--force")))
1355                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1356                 else if (!tstrcmp(option, T("--recursive")))
1357                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1358                 else
1359                         recognized = false;
1360                 break;
1361         default:
1362                 recognized = false;
1363                 break;
1364         }
1365         return recognized;
1366 }
1367
1368 /* How many nonoption arguments each `imagex update' command expects */
1369 static const unsigned update_command_num_nonoptions[] = {
1370         [WIMLIB_UPDATE_OP_ADD] = 2,
1371         [WIMLIB_UPDATE_OP_DELETE] = 1,
1372         [WIMLIB_UPDATE_OP_RENAME] = 2,
1373 };
1374
1375 static void
1376 update_command_add_nonoption(int op, const tchar *nonoption,
1377                              struct wimlib_update_command *cmd,
1378                              unsigned num_nonoptions)
1379 {
1380         switch (op) {
1381         case WIMLIB_UPDATE_OP_ADD:
1382                 if (num_nonoptions == 0)
1383                         cmd->add.fs_source_path = (tchar*)nonoption;
1384                 else
1385                         cmd->add.wim_target_path = (tchar*)nonoption;
1386                 break;
1387         case WIMLIB_UPDATE_OP_DELETE:
1388                 cmd->delete_.wim_path = (tchar*)nonoption;
1389                 break;
1390         case WIMLIB_UPDATE_OP_RENAME:
1391                 if (num_nonoptions == 0)
1392                         cmd->rename.wim_source_path = (tchar*)nonoption;
1393                 else
1394                         cmd->rename.wim_target_path = (tchar*)nonoption;
1395                 break;
1396         }
1397 }
1398
1399 /*
1400  * Parse a command passed on stdin to `imagex update'.
1401  *
1402  * @line:       Text of the command.
1403  * @len:        Length of the line, including a null terminator
1404  *              at line[len - 1].
1405  *
1406  * @command:    A `struct wimlib_update_command' to fill in from the parsed
1407  *              line.
1408  *
1409  * @line_number: Line number of the command, for diagnostics.
1410  *
1411  * Returns true on success; returns false on parse error.
1412  */
1413 static bool
1414 parse_update_command(tchar *line, size_t len,
1415                      struct wimlib_update_command *command,
1416                      size_t line_number)
1417 {
1418         int ret;
1419         tchar *command_name;
1420         int op;
1421         size_t num_nonoptions;
1422
1423         /* Get the command name ("add", "delete", "rename") */
1424         ret = parse_string(&line, &len, &command_name);
1425         if (ret != PARSE_STRING_SUCCESS)
1426                 return false;
1427
1428         if (!tstrcasecmp(command_name, T("add"))) {
1429                 op = WIMLIB_UPDATE_OP_ADD;
1430         } else if (!tstrcasecmp(command_name, T("delete"))) {
1431                 op = WIMLIB_UPDATE_OP_DELETE;
1432         } else if (!tstrcasecmp(command_name, T("rename"))) {
1433                 op = WIMLIB_UPDATE_OP_RENAME;
1434         } else {
1435                 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1436                              command_name, line_number);
1437                 return false;
1438         }
1439         command->op = op;
1440
1441         /* Parse additional options and non-options as needed */
1442         num_nonoptions = 0;
1443         for (;;) {
1444                 tchar *next_string;
1445
1446                 ret = parse_string(&line, &len, &next_string);
1447                 if (ret == PARSE_STRING_NONE) /* End of line */
1448                         break;
1449                 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1450                         return false;
1451                 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1452                         /* Option */
1453                         if (!update_command_add_option(op, next_string, command))
1454                         {
1455                                 imagex_error(T("Unrecognized option \"%"TS"\" to "
1456                                                "update command \"%"TS"\" on line %zu"),
1457                                              next_string, command_name, line_number);
1458
1459                                 return false;
1460                         }
1461                 } else {
1462                         /* Nonoption */
1463                         if (num_nonoptions == update_command_num_nonoptions[op])
1464                         {
1465                                 imagex_error(T("Unexpected argument \"%"TS"\" in "
1466                                                "update command on line %zu\n"
1467                                                "       (The \"%"TS"\" command only "
1468                                                "takes %zu nonoption arguments!)\n"),
1469                                              next_string, line_number,
1470                                              command_name, num_nonoptions);
1471                                 return false;
1472                         }
1473                         update_command_add_nonoption(op, next_string,
1474                                                      command, num_nonoptions);
1475                         num_nonoptions++;
1476                 }
1477         }
1478
1479         if (num_nonoptions != update_command_num_nonoptions[op]) {
1480                 imagex_error(T("Not enough arguments to update command "
1481                                "\"%"TS"\" on line %zu"), command_name, line_number);
1482                 return false;
1483         }
1484         return true;
1485 }
1486
1487 static struct wimlib_update_command *
1488 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1489                           size_t *num_cmds_ret)
1490 {
1491         ssize_t nlines;
1492         tchar *p;
1493         struct wimlib_update_command *cmds;
1494         size_t i, j;
1495
1496         nlines = text_file_count_lines(cmd_file_contents_p,
1497                                        &cmd_file_nchars);
1498         if (nlines < 0)
1499                 return NULL;
1500
1501         /* Always allocate at least 1 slot, just in case the implementation of
1502          * calloc() returns NULL if 0 bytes are requested. */
1503         cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1504         if (!cmds) {
1505                 imagex_error(T("out of memory"));
1506                 return NULL;
1507         }
1508         p = *cmd_file_contents_p;
1509         j = 0;
1510         for (i = 0; i < nlines; i++) {
1511                 /* XXX: Could use rawmemchr() here instead, but it may not be
1512                  * available on all platforms. */
1513                 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1514                 size_t len = endp - p + 1;
1515                 *endp = T('\0');
1516                 if (!is_comment_line(p, len)) {
1517                         if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1518                                 free(cmds);
1519                                 return NULL;
1520                         }
1521                 }
1522                 p = endp + 1;
1523         }
1524         *num_cmds_ret = j;
1525         return cmds;
1526 }
1527
1528 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1529  * one image from a WIM file to an NTFS volume.  */
1530 static int
1531 imagex_apply(int argc, tchar **argv, int cmd)
1532 {
1533         int c;
1534         int open_flags = 0;
1535         int image = WIMLIB_NO_IMAGE;
1536         WIMStruct *wim;
1537         struct wimlib_wim_info info;
1538         int ret;
1539         const tchar *wimfile;
1540         const tchar *target;
1541         const tchar *image_num_or_name = NULL;
1542         int extract_flags = 0;
1543
1544         STRING_SET(refglobs);
1545
1546         for_opt(c, apply_options) {
1547                 switch (c) {
1548                 case IMAGEX_CHECK_OPTION:
1549                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1550                         break;
1551                 case IMAGEX_VERBOSE_OPTION:
1552                         /* No longer does anything.  */
1553                         break;
1554                 case IMAGEX_REF_OPTION:
1555                         ret = string_set_append(&refglobs, optarg);
1556                         if (ret)
1557                                 goto out_free_refglobs;
1558                         break;
1559                 case IMAGEX_UNIX_DATA_OPTION:
1560                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1561                         break;
1562                 case IMAGEX_NO_ACLS_OPTION:
1563                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1564                         break;
1565                 case IMAGEX_STRICT_ACLS_OPTION:
1566                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1567                         break;
1568                 case IMAGEX_NO_ATTRIBUTES_OPTION:
1569                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1570                         break;
1571                 case IMAGEX_NORPFIX_OPTION:
1572                         extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1573                         break;
1574                 case IMAGEX_RPFIX_OPTION:
1575                         extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1576                         break;
1577                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1578                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1579                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1580                         break;
1581                 case IMAGEX_RESUME_OPTION:
1582                         extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1583                         break;
1584                 case IMAGEX_WIMBOOT_OPTION:
1585                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1586                         break;
1587                 default:
1588                         goto out_usage;
1589                 }
1590         }
1591         argc -= optind;
1592         argv += optind;
1593         if (argc != 2 && argc != 3)
1594                 goto out_usage;
1595
1596         wimfile = argv[0];
1597
1598         if (!tstrcmp(wimfile, T("-"))) {
1599                 /* Attempt to apply pipable WIM from standard input.  */
1600                 if (argc == 2) {
1601                         image_num_or_name = NULL;
1602                         target = argv[1];
1603                 } else {
1604                         image_num_or_name = argv[1];
1605                         target = argv[2];
1606                 }
1607                 wim = NULL;
1608         } else {
1609                 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1610                                                     imagex_progress_func, NULL);
1611                 if (ret)
1612                         goto out_free_refglobs;
1613
1614                 wimlib_get_wim_info(wim, &info);
1615
1616                 if (argc >= 3) {
1617                         /* Image explicitly specified.  */
1618                         image_num_or_name = argv[1];
1619                         image = wimlib_resolve_image(wim, image_num_or_name);
1620                         ret = verify_image_exists(image, image_num_or_name, wimfile);
1621                         if (ret)
1622                                 goto out_wimlib_free;
1623                         target = argv[2];
1624                 } else {
1625                         /* No image specified; default to image 1, but only if the WIM
1626                          * contains exactly one image.  */
1627
1628                         if (info.image_count != 1) {
1629                                 imagex_error(T("\"%"TS"\" contains %d images; "
1630                                                "Please select one (or all)."),
1631                                              wimfile, info.image_count);
1632                                 wimlib_free(wim);
1633                                 goto out_usage;
1634                         }
1635                         image = 1;
1636                         target = argv[1];
1637                 }
1638         }
1639
1640         if (refglobs.num_strings) {
1641                 if (wim == NULL) {
1642                         imagex_error(T("Can't specify --ref when applying from stdin!"));
1643                         ret = -1;
1644                         goto out_wimlib_free;
1645                 }
1646                 ret = wim_reference_globs(wim, &refglobs, open_flags);
1647                 if (ret)
1648                         goto out_wimlib_free;
1649         }
1650
1651 #ifndef __WIN32__
1652         {
1653                 /* Interpret a regular file or block device target as an NTFS
1654                  * volume.  */
1655                 struct stat stbuf;
1656
1657                 if (tstat(target, &stbuf)) {
1658                         if (errno != ENOENT) {
1659                                 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1660                                                         target);
1661                                 ret = -1;
1662                                 goto out_wimlib_free;
1663                         }
1664                 } else {
1665                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1666                                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1667                 }
1668         }
1669 #endif
1670
1671         if (wim) {
1672                 ret = wimlib_extract_image(wim, image, target, extract_flags);
1673         } else {
1674                 set_fd_to_binary_mode(STDIN_FILENO);
1675                 ret = wimlib_extract_image_from_pipe_with_progress(
1676                                            STDIN_FILENO,
1677                                            image_num_or_name,
1678                                            target,
1679                                            extract_flags,
1680                                            imagex_progress_func,
1681                                            NULL);
1682         }
1683         if (ret == 0) {
1684                 imagex_printf(T("Done applying WIM image.\n"));
1685         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1686                 if (wim) {
1687                         do_resource_not_found_warning(wimfile, &info, &refglobs);
1688                 } else {
1689                         imagex_error(T(        "If you are applying an image "
1690                                                "from a split pipable WIM,\n"
1691                                        "       make sure you have "
1692                                        "concatenated together all parts."));
1693                 }
1694         }
1695 out_wimlib_free:
1696         wimlib_free(wim);
1697 out_free_refglobs:
1698         string_set_destroy(&refglobs);
1699         return ret;
1700
1701 out_usage:
1702         usage(CMD_APPLY, stderr);
1703         ret = -1;
1704         goto out_free_refglobs;
1705 }
1706
1707 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1708  * directory trees.  'wimlib-imagex capture': create a new WIM file containing
1709  * the desired image.  'wimlib-imagex append': add a new image to an existing
1710  * WIM file. */
1711 static int
1712 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1713 {
1714         int c;
1715         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
1716         int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE |
1717                               WIMLIB_ADD_IMAGE_FLAG_WINCONFIG |
1718                               WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1719         int write_flags = 0;
1720         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1721         uint32_t chunk_size = UINT32_MAX;
1722         uint32_t solid_chunk_size = UINT32_MAX;
1723         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1724         const tchar *wimfile;
1725         int wim_fd;
1726         const tchar *name;
1727         const tchar *desc;
1728         const tchar *flags_element = NULL;
1729
1730         WIMStruct *wim;
1731         STRING_SET(base_wimfiles);
1732         WIMStruct **base_wims;
1733
1734         WIMStruct *template_wim;
1735         const tchar *template_wimfile = NULL;
1736         const tchar *template_image_name_or_num = NULL;
1737         int template_image = WIMLIB_NO_IMAGE;
1738
1739         int ret;
1740         unsigned num_threads = 0;
1741
1742         tchar *source;
1743         tchar *source_copy;
1744
1745         tchar *config_file = NULL;
1746
1747         bool source_list = false;
1748         size_t source_list_nchars = 0;
1749         tchar *source_list_contents;
1750         bool capture_sources_malloced;
1751         struct wimlib_capture_source *capture_sources;
1752         size_t num_sources;
1753         bool name_defaulted;
1754
1755         for_opt(c, capture_or_append_options) {
1756                 switch (c) {
1757                 case IMAGEX_BOOT_OPTION:
1758                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
1759                         break;
1760                 case IMAGEX_CHECK_OPTION:
1761                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1762                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1763                         break;
1764                 case IMAGEX_NOCHECK_OPTION:
1765                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1766                         break;
1767                 case IMAGEX_CONFIG_OPTION:
1768                         config_file = optarg;
1769                         add_image_flags &= ~WIMLIB_ADD_IMAGE_FLAG_WINCONFIG;
1770                         break;
1771                 case IMAGEX_COMPRESS_OPTION:
1772                         compression_type = get_compression_type(optarg);
1773                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1774                                 goto out_err;
1775                         break;
1776                 case IMAGEX_COMPRESS_SLOW_OPTION:
1777                         set_compress_slow();
1778                         break;
1779                 case IMAGEX_CHUNK_SIZE_OPTION:
1780                         chunk_size = parse_chunk_size(optarg);
1781                         if (chunk_size == UINT32_MAX)
1782                                 goto out_err;
1783                         break;
1784                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1785                         solid_chunk_size = parse_chunk_size(optarg);
1786                         if (solid_chunk_size == UINT32_MAX)
1787                                 goto out_err;
1788                         break;
1789                 case IMAGEX_SOLID_COMPRESS_OPTION:
1790                         solid_ctype = get_compression_type(optarg);
1791                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1792                                 goto out_err;
1793                         break;
1794                 case IMAGEX_SOLID_OPTION:
1795                         write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
1796                         break;
1797                 case IMAGEX_FLAGS_OPTION:
1798                         flags_element = optarg;
1799                         break;
1800                 case IMAGEX_DEREFERENCE_OPTION:
1801                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1802                         break;
1803                 case IMAGEX_VERBOSE_OPTION:
1804                         /* No longer does anything.  */
1805                         break;
1806                 case IMAGEX_THREADS_OPTION:
1807                         num_threads = parse_num_threads(optarg);
1808                         if (num_threads == UINT_MAX)
1809                                 goto out_err;
1810                         break;
1811                 case IMAGEX_REBUILD_OPTION:
1812                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1813                         break;
1814                 case IMAGEX_UNIX_DATA_OPTION:
1815                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1816                         break;
1817                 case IMAGEX_SOURCE_LIST_OPTION:
1818                         source_list = true;
1819                         break;
1820                 case IMAGEX_NO_ACLS_OPTION:
1821                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1822                         break;
1823                 case IMAGEX_STRICT_ACLS_OPTION:
1824                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1825                         break;
1826                 case IMAGEX_RPFIX_OPTION:
1827                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_RPFIX;
1828                         break;
1829                 case IMAGEX_NORPFIX_OPTION:
1830                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NORPFIX;
1831                         break;
1832                 case IMAGEX_PIPABLE_OPTION:
1833                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1834                         break;
1835                 case IMAGEX_NOT_PIPABLE_OPTION:
1836                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1837                         break;
1838                 case IMAGEX_UPDATE_OF_OPTION:
1839                         if (template_image_name_or_num) {
1840                                 imagex_error(T("'--update-of' can only be "
1841                                                "specified one time!"));
1842                                 goto out_err;
1843                         } else {
1844                                 tchar *colon;
1845                                 colon = tstrrchr(optarg, T(':'));
1846
1847                                 if (colon) {
1848                                         template_wimfile = optarg;
1849                                         *colon = T('\0');
1850                                         template_image_name_or_num = colon + 1;
1851                                 } else {
1852                                         template_wimfile = NULL;
1853                                         template_image_name_or_num = optarg;
1854                                 }
1855                         }
1856                         break;
1857                 case IMAGEX_DELTA_FROM_OPTION:
1858                         if (cmd != CMD_CAPTURE) {
1859                                 imagex_error(T("'--delta-from' is only "
1860                                                "valid for capture!"));
1861                                 goto out_usage;
1862                         }
1863                         ret = string_set_append(&base_wimfiles, optarg);
1864                         if (ret)
1865                                 goto out_free_base_wimfiles;
1866                         write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1867                         break;
1868                 case IMAGEX_WIMBOOT_OPTION:
1869                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_WIMBOOT;
1870                         break;
1871                 default:
1872                         goto out_usage;
1873                 }
1874         }
1875         argc -= optind;
1876         argv += optind;
1877
1878         if (argc < 2 || argc > 4)
1879                 goto out_usage;
1880
1881         source = argv[0];
1882         wimfile = argv[1];
1883
1884         /* Set default compression type and parameters.  */
1885
1886
1887         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1888                 /* No compression type specified.  Use the default.  */
1889
1890                 if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) {
1891                         /* With --wimboot, default to XPRESS compression.  */
1892                         compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1893                 } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
1894                         /* With --solid, default to LZMS compression.  (However,
1895                          * this will not affect solid blocks!)  */
1896                         compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1897                 } else {
1898                         /* Otherwise, default to LZX compression.  */
1899                         compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1900                 }
1901         }
1902
1903         if (!tstrcmp(wimfile, T("-"))) {
1904                 /* Writing captured WIM to standard output.  */
1905         #if 0
1906                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1907                         imagex_error("Can't write a non-pipable WIM to "
1908                                      "standard output!  Specify --pipable\n"
1909                                      "       if you want to create a pipable WIM "
1910                                      "(but read the docs first).");
1911                         goto out_err;
1912                 }
1913         #else
1914                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1915         #endif
1916                 if (cmd == CMD_APPEND) {
1917                         imagex_error(T("Using standard output for append does "
1918                                        "not make sense."));
1919                         goto out_err;
1920                 }
1921                 wim_fd = STDOUT_FILENO;
1922                 wimfile = NULL;
1923                 imagex_info_file = stderr;
1924                 set_fd_to_binary_mode(wim_fd);
1925         }
1926
1927         /* If template image was specified using --update-of=IMAGE rather
1928          * than --update-of=WIMFILE:IMAGE, set the default WIMFILE.  */
1929         if (template_image_name_or_num && !template_wimfile) {
1930                 if (base_wimfiles.num_strings == 1) {
1931                         /* Capturing delta WIM based on single WIM:  default to
1932                          * base WIM.  */
1933                         template_wimfile = base_wimfiles.strings[0];
1934                 } else if (cmd == CMD_APPEND) {
1935                         /* Appending to WIM:  default to WIM being appended to.
1936                          */
1937                         template_wimfile = wimfile;
1938                 } else {
1939                         /* Capturing a normal (non-delta) WIM, so the WIM file
1940                          * *must* be explicitly specified.  */
1941                         if (base_wimfiles.num_strings > 1) {
1942                                 imagex_error(T("For capture of delta WIM "
1943                                                "based on multiple existing "
1944                                                "WIMs,\n"
1945                                                "      '--update-of' must "
1946                                                "specify WIMFILE:IMAGE!"));
1947                         } else {
1948                                 imagex_error(T("For capture of non-delta WIM, "
1949                                                "'--update-of' must specify "
1950                                                "WIMFILE:IMAGE!"));
1951                         }
1952                         goto out_usage;
1953                 }
1954         }
1955
1956         if (argc >= 3) {
1957                 name = argv[2];
1958                 name_defaulted = false;
1959         } else {
1960                 /* Set default name to SOURCE argument, omitting any directory
1961                  * prefixes and trailing slashes.  This requires making a copy
1962                  * of @source.  Leave some free characters at the end in case we
1963                  * append a number to keep the name unique. */
1964                 size_t source_name_len;
1965
1966                 source_name_len = tstrlen(source);
1967                 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1968                 name = tbasename(tstrcpy(source_copy, source));
1969                 name_defaulted = true;
1970         }
1971         /* Image description defaults to NULL if not given. */
1972         if (argc >= 4)
1973                 desc = argv[3];
1974         else
1975                 desc = NULL;
1976
1977         if (source_list) {
1978                 /* Set up capture sources in source list mode */
1979                 if (source[0] == T('-') && source[1] == T('\0')) {
1980                         source_list_contents = stdin_get_text_contents(&source_list_nchars);
1981                 } else {
1982                         source_list_contents = file_get_text_contents(source,
1983                                                                       &source_list_nchars);
1984                 }
1985                 if (!source_list_contents)
1986                         goto out_err;
1987
1988                 capture_sources = parse_source_list(&source_list_contents,
1989                                                     source_list_nchars,
1990                                                     &num_sources);
1991                 if (!capture_sources) {
1992                         ret = -1;
1993                         goto out_free_source_list_contents;
1994                 }
1995                 capture_sources_malloced = true;
1996         } else {
1997                 /* Set up capture source in non-source-list mode.  */
1998                 capture_sources = alloca(sizeof(struct wimlib_capture_source));
1999                 capture_sources[0].fs_source_path = source;
2000                 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2001                 capture_sources[0].reserved = 0;
2002                 num_sources = 1;
2003                 capture_sources_malloced = false;
2004                 source_list_contents = NULL;
2005         }
2006
2007         /* Open the existing WIM, or create a new one.  */
2008         if (cmd == CMD_APPEND) {
2009                 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2010                                                     imagex_progress_func, NULL);
2011                 if (ret)
2012                         goto out_free_capture_sources;
2013         } else {
2014                 ret = wimlib_create_new_wim(compression_type, &wim);
2015                 if (ret)
2016                         goto out_free_capture_sources;
2017                 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2018         }
2019
2020         /* Set chunk size if non-default.  */
2021         if (chunk_size != UINT32_MAX) {
2022                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2023                 if (ret)
2024                         goto out_free_wim;
2025         } else if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) &&
2026                    compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2027                 ret = wimlib_set_output_chunk_size(wim, 4096);
2028                 if (ret)
2029                         goto out_free_wim;
2030         }
2031         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2032                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2033                 if (ret)
2034                         goto out_free_wim;
2035         }
2036         if (solid_chunk_size != UINT32_MAX) {
2037                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2038                 if (ret)
2039                         goto out_free_wim;
2040         }
2041
2042 #ifndef __WIN32__
2043         /* Detect if source is regular file or block device and set NTFS volume
2044          * capture mode.  */
2045         if (!source_list) {
2046                 struct stat stbuf;
2047
2048                 if (tstat(source, &stbuf) == 0) {
2049                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2050                                 imagex_printf(T("Capturing WIM image from NTFS "
2051                                           "filesystem on \"%"TS"\"\n"), source);
2052                                 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
2053                         }
2054                 } else {
2055                         if (errno != ENOENT) {
2056                                 imagex_error_with_errno(T("Failed to stat "
2057                                                           "\"%"TS"\""), source);
2058                                 ret = -1;
2059                                 goto out_free_wim;
2060                         }
2061                 }
2062         }
2063 #endif
2064
2065         /* If the user did not specify an image name, and the basename of the
2066          * source already exists as an image name in the WIM file, append a
2067          * suffix to make it unique. */
2068         if (cmd == CMD_APPEND && name_defaulted) {
2069                 unsigned long conflict_idx;
2070                 tchar *name_end = tstrchr(name, T('\0'));
2071                 for (conflict_idx = 1;
2072                      wimlib_image_name_in_use(wim, name);
2073                      conflict_idx++)
2074                 {
2075                         tsprintf(name_end, T(" (%lu)"), conflict_idx);
2076                 }
2077         }
2078
2079         /* If capturing a delta WIM, reference resources from the base WIMs
2080          * before adding the new image.  */
2081         if (base_wimfiles.num_strings) {
2082                 base_wims = calloc(base_wimfiles.num_strings,
2083                                    sizeof(base_wims[0]));
2084                 if (base_wims == NULL) {
2085                         imagex_error(T("Out of memory!"));
2086                         ret = -1;
2087                         goto out_free_wim;
2088                 }
2089
2090                 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2091                         ret = wimlib_open_wim_with_progress(
2092                                     base_wimfiles.strings[i],
2093                                     open_flags, &base_wims[i],
2094                                     imagex_progress_func, NULL);
2095                         if (ret)
2096                                 goto out_free_base_wims;
2097
2098                 }
2099
2100                 ret = wimlib_reference_resources(wim, base_wims,
2101                                                  base_wimfiles.num_strings, 0);
2102                 if (ret)
2103                         goto out_free_base_wims;
2104
2105                 if (base_wimfiles.num_strings == 1) {
2106                         imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2107                                       base_wimfiles.strings[0]);
2108                 } else {
2109                         imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2110                                       base_wimfiles.num_strings);
2111                 }
2112
2113         } else {
2114                 base_wims = NULL;
2115         }
2116
2117         /* If capturing or appending as an update of an existing (template) image,
2118          * open the WIM if needed and parse the image index.  */
2119         if (template_image_name_or_num) {
2120
2121
2122                 if (base_wimfiles.num_strings == 1 &&
2123                     template_wimfile == base_wimfiles.strings[0]) {
2124                         template_wim = base_wims[0];
2125                 } else if (template_wimfile == wimfile) {
2126                         template_wim = wim;
2127                 } else {
2128                         ret = wimlib_open_wim_with_progress(template_wimfile,
2129                                                             open_flags,
2130                                                             &template_wim,
2131                                                             imagex_progress_func,
2132                                                             NULL);
2133                         if (ret)
2134                                 goto out_free_base_wims;
2135                 }
2136
2137                 template_image = wimlib_resolve_image(template_wim,
2138                                                       template_image_name_or_num);
2139
2140                 if (template_image_name_or_num[0] == T('-')) {
2141                         tchar *tmp;
2142                         unsigned long n;
2143                         struct wimlib_wim_info info;
2144
2145                         wimlib_get_wim_info(template_wim, &info);
2146                         n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2147                         if (n >= 1 && n <= info.image_count &&
2148                             *tmp == T('\0') &&
2149                             tmp != template_image_name_or_num + 1)
2150                         {
2151                                 template_image = info.image_count - (n - 1);
2152                         }
2153                 }
2154                 ret = verify_image_exists_and_is_single(template_image,
2155                                                         template_image_name_or_num,
2156                                                         template_wimfile);
2157                 if (ret)
2158                         goto out_free_template_wim;
2159         } else {
2160                 template_wim = NULL;
2161         }
2162
2163         ret = wimlib_add_image_multisource(wim,
2164                                            capture_sources,
2165                                            num_sources,
2166                                            name,
2167                                            config_file,
2168                                            add_image_flags);
2169         if (ret)
2170                 goto out_free_template_wim;
2171
2172         if (desc || flags_element || template_image_name_or_num) {
2173                 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2174                  * on which the added one is to be based has been specified with
2175                  * --update-of.  Get the index of the image we just
2176                  *  added, then use it to call the appropriate functions.  */
2177                 struct wimlib_wim_info info;
2178
2179                 wimlib_get_wim_info(wim, &info);
2180
2181                 if (desc) {
2182                         ret = wimlib_set_image_descripton(wim,
2183                                                           info.image_count,
2184                                                           desc);
2185                         if (ret)
2186                                 goto out_free_template_wim;
2187                 }
2188
2189                 if (flags_element) {
2190                         ret = wimlib_set_image_flags(wim, info.image_count,
2191                                                      flags_element);
2192                         if (ret)
2193                                 goto out_free_template_wim;
2194                 }
2195
2196                 /* Reference template image if the user provided one.  */
2197                 if (template_image_name_or_num) {
2198                         imagex_printf(T("Using image %d "
2199                                         "from \"%"TS"\" as template\n"),
2200                                         template_image, template_wimfile);
2201                         ret = wimlib_reference_template_image(wim,
2202                                                               info.image_count,
2203                                                               template_wim,
2204                                                               template_image,
2205                                                               0);
2206                         if (ret)
2207                                 goto out_free_template_wim;
2208                 }
2209         }
2210
2211         /* Write the new WIM or overwrite the existing WIM with the new image
2212          * appended.  */
2213         if (cmd == CMD_APPEND) {
2214                 ret = wimlib_overwrite(wim, write_flags, num_threads);
2215         } else if (wimfile) {
2216                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2217                                    write_flags, num_threads);
2218         } else {
2219                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2220                                          write_flags, num_threads);
2221         }
2222 out_free_template_wim:
2223         /* template_wim may alias base_wims[0] or wim.  */
2224         if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2225             template_wim != wim)
2226                 wimlib_free(template_wim);
2227 out_free_base_wims:
2228         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2229                 wimlib_free(base_wims[i]);
2230         free(base_wims);
2231 out_free_wim:
2232         wimlib_free(wim);
2233 out_free_capture_sources:
2234         if (capture_sources_malloced)
2235                 free(capture_sources);
2236 out_free_source_list_contents:
2237         free(source_list_contents);
2238 out_free_base_wimfiles:
2239         string_set_destroy(&base_wimfiles);
2240         return ret;
2241
2242 out_usage:
2243         usage(cmd, stderr);
2244 out_err:
2245         ret = -1;
2246         goto out_free_base_wimfiles;
2247 }
2248
2249 /* Remove image(s) from a WIM. */
2250 static int
2251 imagex_delete(int argc, tchar **argv, int cmd)
2252 {
2253         int c;
2254         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2255         int write_flags = 0;
2256         const tchar *wimfile;
2257         const tchar *image_num_or_name;
2258         WIMStruct *wim;
2259         int image;
2260         int ret;
2261
2262         for_opt(c, delete_options) {
2263                 switch (c) {
2264                 case IMAGEX_CHECK_OPTION:
2265                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2266                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2267                         break;
2268                 case IMAGEX_SOFT_OPTION:
2269                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2270                         break;
2271                 default:
2272                         goto out_usage;
2273                 }
2274         }
2275         argc -= optind;
2276         argv += optind;
2277
2278         if (argc != 2) {
2279                 if (argc < 1)
2280                         imagex_error(T("Must specify a WIM file"));
2281                 if (argc < 2)
2282                         imagex_error(T("Must specify an image"));
2283                 goto out_usage;
2284         }
2285         wimfile = argv[0];
2286         image_num_or_name = argv[1];
2287
2288         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2289                                             imagex_progress_func, NULL);
2290         if (ret)
2291                 goto out;
2292
2293         image = wimlib_resolve_image(wim, image_num_or_name);
2294
2295         ret = verify_image_exists(image, image_num_or_name, wimfile);
2296         if (ret)
2297                 goto out_wimlib_free;
2298
2299         ret = wimlib_delete_image(wim, image);
2300         if (ret) {
2301                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2302                              wimfile);
2303                 goto out_wimlib_free;
2304         }
2305
2306         ret = wimlib_overwrite(wim, write_flags, 0);
2307         if (ret) {
2308                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2309                                "deleted"), wimfile);
2310         }
2311 out_wimlib_free:
2312         wimlib_free(wim);
2313 out:
2314         return ret;
2315
2316 out_usage:
2317         usage(CMD_DELETE, stderr);
2318         ret = -1;
2319         goto out;
2320 }
2321
2322 struct print_dentry_options {
2323         bool detailed;
2324 };
2325
2326 static void
2327 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2328 {
2329         tprintf(T("%"TS"\n"), dentry->full_path);
2330 }
2331
2332 static const struct {
2333         uint32_t flag;
2334         const tchar *name;
2335 } file_attr_flags[] = {
2336         {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
2337         {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
2338         {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
2339         {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
2340         {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
2341         {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
2342         {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
2343         {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
2344         {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
2345         {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
2346         {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
2347         {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
2348         {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2349         {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
2350         {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
2351 };
2352
2353 #define TIMESTR_MAX 100
2354
2355 static void
2356 timespec_to_string(const struct timespec *spec, tchar *buf)
2357 {
2358         time_t t = spec->tv_sec;
2359         struct tm tm;
2360         gmtime_r(&t, &tm);
2361         tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2362         buf[TIMESTR_MAX - 1] = '\0';
2363 }
2364
2365 static void
2366 print_time(const tchar *type, const struct timespec *spec)
2367 {
2368         tchar timestr[TIMESTR_MAX];
2369
2370         timespec_to_string(spec, timestr);
2371
2372         tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2373 }
2374
2375 static void print_byte_field(const uint8_t field[], size_t len)
2376 {
2377         while (len--)
2378                 tprintf(T("%02hhx"), *field++);
2379 }
2380
2381 static void
2382 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2383 {
2384         tputs(T("WIM Information:"));
2385         tputs(T("----------------"));
2386         tprintf(T("Path:           %"TS"\n"), wimfile);
2387         tprintf(T("GUID:           0x"));
2388         print_byte_field(info->guid, sizeof(info->guid));
2389         tputchar(T('\n'));
2390         tprintf(T("Version:        %u\n"), info->wim_version);
2391         tprintf(T("Image Count:    %d\n"), info->image_count);
2392         tprintf(T("Compression:    %"TS"\n"),
2393                 wimlib_get_compression_type_string(info->compression_type));
2394         tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
2395                 info->chunk_size);
2396         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2397         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2398         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2399         tprintf(T("Integrity Info: %"TS"\n"),
2400                 info->has_integrity_table ? T("yes") : T("no"));
2401         tprintf(T("Relative path junction: %"TS"\n"),
2402                 info->has_rpfix ? T("yes") : T("no"));
2403         tprintf(T("Pipable:        %"TS"\n"),
2404                 info->pipable ? T("yes") : T("no"));
2405         tputchar(T('\n'));
2406 }
2407
2408 static int
2409 print_resource(const struct wimlib_resource_entry *resource,
2410                void *_ignore)
2411 {
2412         tprintf(T("Hash                = 0x"));
2413         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2414         tputchar(T('\n'));
2415
2416         if (!resource->is_missing) {
2417                 tprintf(T("Uncompressed size   = %"PRIu64" bytes\n"),
2418                         resource->uncompressed_size);
2419                 if (resource->packed) {
2420                         tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2421                                 resource->raw_resource_compressed_size);
2422
2423                         tprintf(T("Raw offset in WIM   = %"PRIu64" bytes\n"),
2424                                 resource->raw_resource_offset_in_wim);
2425
2426                         tprintf(T("Offset in raw       = %"PRIu64" bytes\n"),
2427                                 resource->offset);
2428                 } else {
2429                         tprintf(T("Compressed size     = %"PRIu64" bytes\n"),
2430                                 resource->compressed_size);
2431
2432                         tprintf(T("Offset in WIM       = %"PRIu64" bytes\n"),
2433                                 resource->offset);
2434                 }
2435
2436                 tprintf(T("Part Number         = %u\n"), resource->part_number);
2437                 tprintf(T("Reference Count     = %u\n"), resource->reference_count);
2438
2439                 tprintf(T("Flags               = "));
2440                 if (resource->is_compressed)
2441                         tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2442                 if (resource->is_metadata)
2443                         tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2444                 if (resource->is_free)
2445                         tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2446                 if (resource->is_spanned)
2447                         tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2448                 if (resource->packed)
2449                         tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS  "));
2450                 tputchar(T('\n'));
2451         }
2452         tputchar(T('\n'));
2453         return 0;
2454 }
2455
2456 static void
2457 print_lookup_table(WIMStruct *wim)
2458 {
2459         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2460 }
2461
2462 static void
2463 default_print_security_descriptor(const uint8_t *sd, size_t size)
2464 {
2465         tprintf(T("Security Descriptor = "));
2466         print_byte_field(sd, size);
2467         tputchar(T('\n'));
2468 }
2469
2470 static void
2471 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2472 {
2473
2474         tprintf(T(
2475 "----------------------------------------------------------------------------\n"));
2476         tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
2477         if (dentry->dos_name)
2478                 tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
2479         tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
2480         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2481                 if (file_attr_flags[i].flag & dentry->attributes)
2482                         tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
2483                                 file_attr_flags[i].name);
2484
2485         if (dentry->security_descriptor) {
2486                 print_security_descriptor(dentry->security_descriptor,
2487                                           dentry->security_descriptor_size);
2488         }
2489
2490         print_time(T("Creation Time"), &dentry->creation_time);
2491         print_time(T("Last Write Time"), &dentry->last_write_time);
2492         print_time(T("Last Access Time"), &dentry->last_access_time);
2493
2494
2495         if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2496                 tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
2497
2498         tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2499         tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
2500
2501         if (dentry->unix_mode != 0) {
2502                 tprintf(T("UNIX Data           = uid:%"PRIu32" gid:%"PRIu32" "
2503                           "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2504                         dentry->unix_uid, dentry->unix_gid,
2505                         dentry->unix_mode, dentry->unix_rdev);
2506         }
2507
2508         for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2509                 if (dentry->streams[i].stream_name) {
2510                         tprintf(T("\tData stream \"%"TS"\":\n"),
2511                                 dentry->streams[i].stream_name);
2512                 } else {
2513                         tprintf(T("\tUnnamed data stream:\n"));
2514                 }
2515                 print_resource(&dentry->streams[i].resource, NULL);
2516         }
2517 }
2518
2519 static int
2520 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2521 {
2522         const struct print_dentry_options *options = _options;
2523         if (!options->detailed)
2524                 print_dentry_full_path(dentry);
2525         else
2526                 print_dentry_detailed(dentry);
2527         return 0;
2528 }
2529
2530 /* Print the files contained in an image(s) in a WIM file. */
2531 static int
2532 imagex_dir(int argc, tchar **argv, int cmd)
2533 {
2534         const tchar *wimfile;
2535         WIMStruct *wim = NULL;
2536         int image;
2537         int ret;
2538         const tchar *path = WIMLIB_WIM_ROOT_PATH;
2539         int c;
2540         struct print_dentry_options options = {
2541                 .detailed = false,
2542         };
2543         int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2544
2545         for_opt(c, dir_options) {
2546                 switch (c) {
2547                 case IMAGEX_PATH_OPTION:
2548                         path = optarg;
2549                         break;
2550                 case IMAGEX_DETAILED_OPTION:
2551                         options.detailed = true;
2552                         break;
2553                 case IMAGEX_ONE_FILE_ONLY_OPTION:
2554                         iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2555                         break;
2556                 default:
2557                         goto out_usage;
2558                 }
2559         }
2560         argc -= optind;
2561         argv += optind;
2562
2563         if (argc < 1) {
2564                 imagex_error(T("Must specify a WIM file"));
2565                 goto out_usage;
2566         }
2567         if (argc > 2) {
2568                 imagex_error(T("Too many arguments"));
2569                 goto out_usage;
2570         }
2571
2572         wimfile = argv[0];
2573         ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2574                                             imagex_progress_func, NULL);
2575         if (ret)
2576                 goto out;
2577
2578         if (argc >= 2) {
2579                 image = wimlib_resolve_image(wim, argv[1]);
2580                 ret = verify_image_exists(image, argv[1], wimfile);
2581                 if (ret)
2582                         goto out_wimlib_free;
2583         } else {
2584                 /* No image specified; default to image 1, but only if the WIM
2585                  * contains exactly one image.  */
2586
2587                 struct wimlib_wim_info info;
2588
2589                 wimlib_get_wim_info(wim, &info);
2590                 if (info.image_count != 1) {
2591                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2592                                        "select one (or all)."),
2593                                      wimfile, info.image_count);
2594                         wimlib_free(wim);
2595                         goto out_usage;
2596                 }
2597                 image = 1;
2598         }
2599
2600         ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2601                                       print_dentry, &options);
2602 out_wimlib_free:
2603         wimlib_free(wim);
2604 out:
2605         return ret;
2606
2607 out_usage:
2608         usage(CMD_DIR, stderr);
2609         ret = -1;
2610         goto out;
2611 }
2612
2613 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2614  * WIM file. */
2615 static int
2616 imagex_export(int argc, tchar **argv, int cmd)
2617 {
2618         int c;
2619         int open_flags = 0;
2620         int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2621         int write_flags = 0;
2622         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2623         const tchar *src_wimfile;
2624         const tchar *src_image_num_or_name;
2625         const tchar *dest_wimfile;
2626         int dest_wim_fd;
2627         const tchar *dest_name;
2628         const tchar *dest_desc;
2629         WIMStruct *src_wim;
2630         struct wimlib_wim_info src_info;
2631         WIMStruct *dest_wim;
2632         int ret;
2633         int image;
2634         struct stat stbuf;
2635         bool wim_is_new;
2636         STRING_SET(refglobs);
2637         unsigned num_threads = 0;
2638         uint32_t chunk_size = UINT32_MAX;
2639         uint32_t solid_chunk_size = UINT32_MAX;
2640         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2641
2642         for_opt(c, export_options) {
2643                 switch (c) {
2644                 case IMAGEX_BOOT_OPTION:
2645                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2646                         break;
2647                 case IMAGEX_CHECK_OPTION:
2648                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2649                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2650                         break;
2651                 case IMAGEX_NOCHECK_OPTION:
2652                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2653                         break;
2654                 case IMAGEX_COMPRESS_OPTION:
2655                         compression_type = get_compression_type(optarg);
2656                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2657                                 goto out_err;
2658                         break;
2659                 case IMAGEX_COMPRESS_SLOW_OPTION:
2660                         set_compress_slow();
2661                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2662                         break;
2663                 case IMAGEX_RECOMPRESS_OPTION:
2664                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2665                         break;
2666                 case IMAGEX_SOLID_OPTION:
2667                         write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
2668                         break;
2669                 case IMAGEX_CHUNK_SIZE_OPTION:
2670                         chunk_size = parse_chunk_size(optarg);
2671                         if (chunk_size == UINT32_MAX)
2672                                 goto out_err;
2673                         break;
2674                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2675                         solid_chunk_size = parse_chunk_size(optarg);
2676                         if (solid_chunk_size == UINT32_MAX)
2677                                 goto out_err;
2678                         break;
2679                 case IMAGEX_SOLID_COMPRESS_OPTION:
2680                         solid_ctype = get_compression_type(optarg);
2681                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2682                                 goto out_err;
2683                         break;
2684                 case IMAGEX_REF_OPTION:
2685                         ret = string_set_append(&refglobs, optarg);
2686                         if (ret)
2687                                 goto out_free_refglobs;
2688                         break;
2689                 case IMAGEX_THREADS_OPTION:
2690                         num_threads = parse_num_threads(optarg);
2691                         if (num_threads == UINT_MAX)
2692                                 goto out_err;
2693                         break;
2694                 case IMAGEX_REBUILD_OPTION:
2695                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2696                         break;
2697                 case IMAGEX_PIPABLE_OPTION:
2698                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2699                         break;
2700                 case IMAGEX_NOT_PIPABLE_OPTION:
2701                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2702                         break;
2703                 case IMAGEX_WIMBOOT_OPTION:
2704                         export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2705                         break;
2706                 default:
2707                         goto out_usage;
2708                 }
2709         }
2710         argc -= optind;
2711         argv += optind;
2712         if (argc < 3 || argc > 5)
2713                 goto out_usage;
2714         src_wimfile           = argv[0];
2715         src_image_num_or_name = argv[1];
2716         dest_wimfile          = argv[2];
2717         dest_name             = (argc >= 4) ? argv[3] : NULL;
2718         dest_desc             = (argc >= 5) ? argv[4] : NULL;
2719         ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2720                                             imagex_progress_func, NULL);
2721         if (ret)
2722                 goto out_free_refglobs;
2723
2724         wimlib_get_wim_info(src_wim, &src_info);
2725
2726         /* Determine if the destination is an existing file or not.  If so, we
2727          * try to append the exported image(s) to it; otherwise, we create a new
2728          * WIM containing the exported image(s).  Furthermore, determine if we
2729          * need to write a pipable WIM directly to standard output.  */
2730
2731         if (tstrcmp(dest_wimfile, T("-")) == 0) {
2732         #if 0
2733                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2734                         imagex_error("Can't write a non-pipable WIM to "
2735                                      "standard output!  Specify --pipable\n"
2736                                      "       if you want to create a pipable WIM "
2737                                      "(but read the docs first).");
2738                         ret = -1;
2739                         goto out_free_src_wim;
2740                 }
2741         #else
2742                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2743         #endif
2744                 dest_wimfile = NULL;
2745                 dest_wim_fd = STDOUT_FILENO;
2746                 imagex_info_file = stderr;
2747                 set_fd_to_binary_mode(dest_wim_fd);
2748         }
2749         errno = ENOENT;
2750         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2751                 wim_is_new = false;
2752                 /* Destination file exists. */
2753
2754                 if (!S_ISREG(stbuf.st_mode)) {
2755                         imagex_error(T("\"%"TS"\" is not a regular file"),
2756                                      dest_wimfile);
2757                         ret = -1;
2758                         goto out_free_src_wim;
2759                 }
2760                 ret = wimlib_open_wim_with_progress(dest_wimfile,
2761                                                     open_flags |
2762                                                         WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2763                                                     &dest_wim,
2764                                                     imagex_progress_func,
2765                                                     NULL);
2766                 if (ret)
2767                         goto out_free_src_wim;
2768
2769                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2770                         /* The user specified a compression type, but we're
2771                          * exporting to an existing WIM.  Make sure the
2772                          * specified compression type is the same as the
2773                          * compression type of the existing destination WIM. */
2774                         struct wimlib_wim_info dest_info;
2775
2776                         wimlib_get_wim_info(dest_wim, &dest_info);
2777                         if (compression_type != dest_info.compression_type) {
2778                                 imagex_error(T("Cannot specify a compression type that is "
2779                                                "not the same as that used in the "
2780                                                "destination WIM"));
2781                                 ret = -1;
2782                                 goto out_free_dest_wim;
2783                         }
2784                 }
2785         } else {
2786                 wim_is_new = true;
2787
2788                 if (errno != ENOENT) {
2789                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2790                                                 dest_wimfile);
2791                         ret = -1;
2792                         goto out_free_src_wim;
2793                 }
2794
2795                 /* dest_wimfile is not an existing file, so create a new WIM. */
2796
2797                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2798                         /* The user did not specify a compression type; default
2799                          * to that of the source WIM, unless --solid or
2800                          * --wimboot was specified.   */
2801
2802                         if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
2803                                 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2804                         else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2805                                 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2806                         else
2807                                 compression_type = src_info.compression_type;
2808                 }
2809                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2810                 if (ret)
2811                         goto out_free_src_wim;
2812
2813                 wimlib_register_progress_function(dest_wim,
2814                                                   imagex_progress_func, NULL);
2815
2816                 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2817                     && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2818                 {
2819                         /* For --wimboot export, use small XPRESS chunks.  */
2820                         wimlib_set_output_chunk_size(dest_wim, 4096);
2821                 } else if (compression_type == src_info.compression_type &&
2822                            chunk_size == UINT32_MAX)
2823                 {
2824                         /* Use same chunk size if compression type is the same.  */
2825                         wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2826                 }
2827         }
2828
2829         if (chunk_size != UINT32_MAX) {
2830                 /* Set destination chunk size.  */
2831                 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2832                 if (ret)
2833                         goto out_free_dest_wim;
2834         }
2835         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2836                 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2837                 if (ret)
2838                         goto out_free_dest_wim;
2839         }
2840         if (solid_chunk_size != UINT32_MAX) {
2841                 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2842                 if (ret)
2843                         goto out_free_dest_wim;
2844         }
2845
2846         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2847         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2848         if (ret)
2849                 goto out_free_dest_wim;
2850
2851         if (refglobs.num_strings) {
2852                 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2853                 if (ret)
2854                         goto out_free_dest_wim;
2855         }
2856
2857         if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2858             image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2859         {
2860                 imagex_error(T("--boot specified for all-images export, but source WIM "
2861                                "has no bootable image."));
2862                 ret = -1;
2863                 goto out_free_dest_wim;
2864         }
2865
2866         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2867                                   dest_desc, export_flags);
2868         if (ret) {
2869                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2870                         do_resource_not_found_warning(src_wimfile,
2871                                                       &src_info, &refglobs);
2872                 }
2873                 goto out_free_dest_wim;
2874         }
2875
2876         if (!wim_is_new)
2877                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2878         else if (dest_wimfile)
2879                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2880                                    write_flags, num_threads);
2881         else
2882                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2883                                          WIMLIB_ALL_IMAGES, write_flags,
2884                                          num_threads);
2885 out_free_dest_wim:
2886         wimlib_free(dest_wim);
2887 out_free_src_wim:
2888         wimlib_free(src_wim);
2889 out_free_refglobs:
2890         string_set_destroy(&refglobs);
2891         return ret;
2892
2893 out_usage:
2894         usage(CMD_EXPORT, stderr);
2895 out_err:
2896         ret = -1;
2897         goto out_free_refglobs;
2898 }
2899
2900 /* Extract files or directories from a WIM image */
2901 static int
2902 imagex_extract(int argc, tchar **argv, int cmd)
2903 {
2904         int c;
2905         int open_flags = 0;
2906         int image;
2907         WIMStruct *wim;
2908         int ret;
2909         const tchar *wimfile;
2910         const tchar *image_num_or_name;
2911         tchar *dest_dir = T(".");
2912         int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2913                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2914                             WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2915         int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2916
2917         STRING_SET(refglobs);
2918
2919         tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2920
2921         for_opt(c, extract_options) {
2922                 switch (c) {
2923                 case IMAGEX_CHECK_OPTION:
2924                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2925                         break;
2926                 case IMAGEX_VERBOSE_OPTION:
2927                         /* No longer does anything.  */
2928                         break;
2929                 case IMAGEX_REF_OPTION:
2930                         ret = string_set_append(&refglobs, optarg);
2931                         if (ret)
2932                                 goto out_free_refglobs;
2933                         break;
2934                 case IMAGEX_UNIX_DATA_OPTION:
2935                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2936                         break;
2937                 case IMAGEX_NO_ACLS_OPTION:
2938                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2939                         break;
2940                 case IMAGEX_STRICT_ACLS_OPTION:
2941                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2942                         break;
2943                 case IMAGEX_NO_ATTRIBUTES_OPTION:
2944                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2945                         break;
2946                 case IMAGEX_DEST_DIR_OPTION:
2947                         dest_dir = optarg;
2948                         break;
2949                 case IMAGEX_TO_STDOUT_OPTION:
2950                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2951                         imagex_info_file = stderr;
2952                         imagex_be_quiet = true;
2953                         set_fd_to_binary_mode(STDOUT_FILENO);
2954                         break;
2955                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2956                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2957                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2958                         break;
2959                 case IMAGEX_NO_GLOBS_OPTION:
2960                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2961                         break;
2962                 case IMAGEX_NULLGLOB_OPTION:
2963                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2964                         break;
2965                 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2966                         notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2967                         break;
2968                 case IMAGEX_WIMBOOT_OPTION:
2969                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
2970                         break;
2971                 default:
2972                         goto out_usage;
2973                 }
2974         }
2975         argc -= optind;
2976         argv += optind;
2977
2978         if (argc < 2)
2979                 goto out_usage;
2980
2981         if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2982                                WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
2983         {
2984                 imagex_error(T("Can't combine --no-globs and --nullglob!"));
2985                 goto out_err;
2986         }
2987
2988         wimfile = argv[0];
2989         image_num_or_name = argv[1];
2990
2991         argc -= 2;
2992         argv += 2;
2993
2994         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2995                                             imagex_progress_func, NULL);
2996         if (ret)
2997                 goto out_free_refglobs;
2998
2999         image = wimlib_resolve_image(wim, image_num_or_name);
3000         ret = verify_image_exists_and_is_single(image,
3001                                                 image_num_or_name,
3002                                                 wimfile);
3003         if (ret)
3004                 goto out_wimlib_free;
3005
3006         if (refglobs.num_strings) {
3007                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3008                 if (ret)
3009                         goto out_wimlib_free;
3010         }
3011
3012         if (argc == 0) {
3013                 argv = &root_path;
3014                 argc = 1;
3015                 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3016         }
3017
3018         while (argc != 0 && ret == 0) {
3019                 int num_paths;
3020
3021                 for (num_paths = 0;
3022                      num_paths < argc && argv[num_paths][0] != T('@');
3023                      num_paths++)
3024                         ;
3025
3026                 if (num_paths) {
3027                         ret = wimlib_extract_paths(wim, image, dest_dir,
3028                                                    (const tchar **)argv,
3029                                                    num_paths,
3030                                                    extract_flags | notlist_extract_flags);
3031                         argc -= num_paths;
3032                         argv += num_paths;
3033                 } else {
3034                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3035                                                       argv[0] + 1,
3036                                                       extract_flags);
3037                         argc--;
3038                         argv++;
3039                 }
3040         }
3041
3042         if (ret == 0) {
3043                 if (!imagex_be_quiet)
3044                         imagex_printf(T("Done extracting files.\n"));
3045         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3046                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3047                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3048                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3049                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3050                 {
3051                         tfprintf(stderr,
3052                                  T("Note: You can use the '--nullglob' "
3053                                    "option to ignore missing files.\n"));
3054                 }
3055                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3056                                    "files and directories\n"
3057                                    "      are in the WIM image.\n"),
3058                                 get_cmd_string(CMD_DIR, false));
3059         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3060                 struct wimlib_wim_info info;
3061
3062                 wimlib_get_wim_info(wim, &info);
3063                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3064         }
3065 out_wimlib_free:
3066         wimlib_free(wim);
3067 out_free_refglobs:
3068         string_set_destroy(&refglobs);
3069         return ret;
3070
3071 out_usage:
3072         usage(CMD_EXTRACT, stderr);
3073 out_err:
3074         ret = -1;
3075         goto out_free_refglobs;
3076 }
3077
3078 /* Prints information about a WIM file; also can mark an image as bootable,
3079  * change the name of an image, or change the description of an image. */
3080 static int
3081 imagex_info(int argc, tchar **argv, int cmd)
3082 {
3083         int c;
3084         bool boot         = false;
3085         bool check        = false;
3086         bool nocheck      = false;
3087         bool header       = false;
3088         bool lookup_table = false;
3089         bool xml          = false;
3090         bool short_header = true;
3091         const tchar *xml_out_file = NULL;
3092         const tchar *wimfile;
3093         const tchar *image_num_or_name;
3094         const tchar *new_name;
3095         const tchar *new_desc;
3096         WIMStruct *wim;
3097         int image;
3098         int ret;
3099         int open_flags = 0;
3100         struct wimlib_wim_info info;
3101
3102         for_opt(c, info_options) {
3103                 switch (c) {
3104                 case IMAGEX_BOOT_OPTION:
3105                         boot = true;
3106                         break;
3107                 case IMAGEX_CHECK_OPTION:
3108                         check = true;
3109                         break;
3110                 case IMAGEX_NOCHECK_OPTION:
3111                         nocheck = true;
3112                         break;
3113                 case IMAGEX_HEADER_OPTION:
3114                         header = true;
3115                         short_header = false;
3116                         break;
3117                 case IMAGEX_LOOKUP_TABLE_OPTION:
3118                         lookup_table = true;
3119                         short_header = false;
3120                         break;
3121                 case IMAGEX_XML_OPTION:
3122                         xml = true;
3123                         short_header = false;
3124                         break;
3125                 case IMAGEX_EXTRACT_XML_OPTION:
3126                         xml_out_file = optarg;
3127                         short_header = false;
3128                         break;
3129                 case IMAGEX_METADATA_OPTION:
3130                         imagex_error(T("The --metadata option has been removed. "
3131                                        "Use 'wimdir --detail' instead."));
3132                         goto out_err;
3133                 default:
3134                         goto out_usage;
3135                 }
3136         }
3137
3138         argc -= optind;
3139         argv += optind;
3140         if (argc < 1 || argc > 4)
3141                 goto out_usage;
3142
3143         wimfile           = argv[0];
3144         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3145         new_name          = (argc >= 3) ? argv[2] : NULL;
3146         new_desc          = (argc >= 4) ? argv[3] : NULL;
3147
3148         if (check && nocheck) {
3149                 imagex_error(T("Can't specify both --check and --nocheck"));
3150                 goto out_err;
3151         }
3152
3153         if (check)
3154                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3155
3156         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3157                                             imagex_progress_func, NULL);
3158         if (ret)
3159                 goto out;
3160
3161         wimlib_get_wim_info(wim, &info);
3162
3163         image = wimlib_resolve_image(wim, image_num_or_name);
3164         ret = WIMLIB_ERR_INVALID_IMAGE;
3165         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3166                 verify_image_exists(image, image_num_or_name, wimfile);
3167                 if (boot) {
3168                         imagex_error(T("If you would like to set the boot "
3169                                        "index to 0, specify image \"0\" with "
3170                                        "the --boot flag."));
3171                 }
3172                 goto out_wimlib_free;
3173         }
3174
3175         if (boot && info.image_count == 0) {
3176                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3177                 goto out_wimlib_free;
3178         }
3179
3180         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3181                 if (boot) {
3182                         imagex_error(T("Cannot specify the --boot flag "
3183                                        "without specifying a specific "
3184                                        "image in a multi-image WIM"));
3185                         goto out_wimlib_free;
3186                 }
3187                 if (new_name) {
3188                         imagex_error(T("Cannot specify the NEW_NAME "
3189                                        "without specifying a specific "
3190                                        "image in a multi-image WIM"));
3191                         goto out_wimlib_free;
3192                 }
3193         }
3194
3195         /* Operations that print information are separated from operations that
3196          * recreate the WIM file. */
3197         if (!new_name && !boot) {
3198
3199                 /* Read-only operations */
3200
3201                 if (image == WIMLIB_NO_IMAGE) {
3202                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3203                                      image_num_or_name, wimfile);
3204                         goto out_wimlib_free;
3205                 }
3206
3207                 if (image == WIMLIB_ALL_IMAGES && short_header)
3208                         print_wim_information(wimfile, &info);
3209
3210                 if (header)
3211                         wimlib_print_header(wim);
3212
3213                 if (lookup_table) {
3214                         if (info.total_parts != 1) {
3215                                 tfprintf(stderr, T("Warning: Only showing the lookup table "
3216                                                    "for part %d of a %d-part WIM.\n"),
3217                                          info.part_number, info.total_parts);
3218                         }
3219                         print_lookup_table(wim);
3220                 }
3221
3222                 if (xml) {
3223                         ret = wimlib_extract_xml_data(wim, stdout);
3224                         if (ret)
3225                                 goto out_wimlib_free;
3226                 }
3227
3228                 if (xml_out_file) {
3229                         FILE *fp;
3230
3231                         fp = tfopen(xml_out_file, T("wb"));
3232                         if (!fp) {
3233                                 imagex_error_with_errno(T("Failed to open the "
3234                                                           "file \"%"TS"\" for "
3235                                                           "writing"),
3236                                                         xml_out_file);
3237                                 ret = -1;
3238                                 goto out_wimlib_free;
3239                         }
3240                         ret = wimlib_extract_xml_data(wim, fp);
3241                         if (fclose(fp)) {
3242                                 imagex_error(T("Failed to close the file "
3243                                                "\"%"TS"\""),
3244                                              xml_out_file);
3245                                 ret = -1;
3246                         }
3247                         if (ret)
3248                                 goto out_wimlib_free;
3249                 }
3250
3251                 if (short_header)
3252                         wimlib_print_available_images(wim, image);
3253
3254                 ret = 0;
3255         } else {
3256
3257                 /* Modification operations */
3258
3259                 if (image == WIMLIB_ALL_IMAGES)
3260                         image = 1;
3261
3262                 if (image == WIMLIB_NO_IMAGE && new_name) {
3263                         imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3264                                        "when using image 0"), new_name);
3265                         ret = -1;
3266                         goto out_wimlib_free;
3267                 }
3268
3269                 if (boot) {
3270                         if (image == info.boot_index) {
3271                                 imagex_printf(T("Image %d is already marked as "
3272                                           "bootable.\n"), image);
3273                                 boot = false;
3274                         } else {
3275                                 imagex_printf(T("Marking image %d as bootable.\n"),
3276                                         image);
3277                                 info.boot_index = image;
3278                                 ret = wimlib_set_wim_info(wim, &info,
3279                                                           WIMLIB_CHANGE_BOOT_INDEX);
3280                                 if (ret)
3281                                         goto out_wimlib_free;
3282                         }
3283                 }
3284                 if (new_name) {
3285                         if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3286                         {
3287                                 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3288                                         image, new_name);
3289                                 new_name = NULL;
3290                         } else {
3291                                 imagex_printf(T("Changing the name of image %d to "
3292                                           "\"%"TS"\".\n"), image, new_name);
3293                                 ret = wimlib_set_image_name(wim, image, new_name);
3294                                 if (ret)
3295                                         goto out_wimlib_free;
3296                         }
3297                 }
3298                 if (new_desc) {
3299                         const tchar *old_desc;
3300                         old_desc = wimlib_get_image_description(wim, image);
3301                         if (old_desc && !tstrcmp(old_desc, new_desc)) {
3302                                 imagex_printf(T("The description of image %d is already "
3303                                           "\"%"TS"\".\n"), image, new_desc);
3304                                 new_desc = NULL;
3305                         } else {
3306                                 imagex_printf(T("Changing the description of image %d "
3307                                           "to \"%"TS"\".\n"), image, new_desc);
3308                                 ret = wimlib_set_image_descripton(wim, image,
3309                                                                   new_desc);
3310                                 if (ret)
3311                                         goto out_wimlib_free;
3312                         }
3313                 }
3314
3315                 /* Only call wimlib_overwrite() if something actually needs to
3316                  * be changed.  */
3317                 if (boot || new_name || new_desc ||
3318                     (check && !info.has_integrity_table) ||
3319                     (nocheck && info.has_integrity_table))
3320                 {
3321                         int write_flags = 0;
3322
3323                         if (check)
3324                                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3325                         if (nocheck)
3326                                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3327                         ret = wimlib_overwrite(wim, write_flags, 1);
3328                 } else {
3329                         imagex_printf(T("The file \"%"TS"\" was not modified "
3330                                         "because nothing needed to be done.\n"),
3331                                       wimfile);
3332                         ret = 0;
3333                 }
3334         }
3335 out_wimlib_free:
3336         wimlib_free(wim);
3337 out:
3338         return ret;
3339
3340 out_usage:
3341         usage(CMD_INFO, stderr);
3342 out_err:
3343         ret = -1;
3344         goto out;
3345 }
3346
3347 /* Join split WIMs into one part WIM */
3348 static int
3349 imagex_join(int argc, tchar **argv, int cmd)
3350 {
3351         int c;
3352         int swm_open_flags = 0;
3353         int wim_write_flags = 0;
3354         const tchar *output_path;
3355         int ret;
3356
3357         for_opt(c, join_options) {
3358                 switch (c) {
3359                 case IMAGEX_CHECK_OPTION:
3360                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3361                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3362                         break;
3363                 default:
3364                         goto out_usage;
3365                 }
3366         }
3367         argc -= optind;
3368         argv += optind;
3369
3370         if (argc < 2) {
3371                 imagex_error(T("Must specify one or more split WIM (.swm) "
3372                                "parts to join"));
3373                 goto out_usage;
3374         }
3375         output_path = argv[0];
3376         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3377                                         --argc,
3378                                         output_path,
3379                                         swm_open_flags,
3380                                         wim_write_flags,
3381                                         imagex_progress_func,
3382                                         NULL);
3383 out:
3384         return ret;
3385
3386 out_usage:
3387         usage(CMD_JOIN, stderr);
3388         ret = -1;
3389         goto out;
3390 }
3391
3392 #if WIM_MOUNTING_SUPPORTED
3393
3394 /* Mounts a WIM image.  */
3395 static int
3396 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3397 {
3398         int c;
3399         int mount_flags = 0;
3400         int open_flags = 0;
3401         const tchar *staging_dir = NULL;
3402         const tchar *wimfile;
3403         const tchar *dir;
3404         WIMStruct *wim;
3405         struct wimlib_wim_info info;
3406         int image;
3407         int ret;
3408
3409         STRING_SET(refglobs);
3410
3411         if (cmd == CMD_MOUNTRW) {
3412                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3413                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3414         }
3415
3416         for_opt(c, mount_options) {
3417                 switch (c) {
3418                 case IMAGEX_ALLOW_OTHER_OPTION:
3419                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3420                         break;
3421                 case IMAGEX_CHECK_OPTION:
3422                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3423                         break;
3424                 case IMAGEX_DEBUG_OPTION:
3425                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3426                         break;
3427                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3428                         if (!tstrcasecmp(optarg, T("none")))
3429                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3430                         else if (!tstrcasecmp(optarg, T("xattr")))
3431                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3432                         else if (!tstrcasecmp(optarg, T("windows")))
3433                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3434                         else {
3435                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3436                                              optarg);
3437                                 goto out_usage;
3438                         }
3439                         break;
3440                 case IMAGEX_REF_OPTION:
3441                         ret = string_set_append(&refglobs, optarg);
3442                         if (ret)
3443                                 goto out_free_refglobs;
3444                         break;
3445                 case IMAGEX_STAGING_DIR_OPTION:
3446                         staging_dir = optarg;
3447                         break;
3448                 case IMAGEX_UNIX_DATA_OPTION:
3449                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3450                         break;
3451                 default:
3452                         goto out_usage;
3453                 }
3454         }
3455         argc -= optind;
3456         argv += optind;
3457         if (argc != 2 && argc != 3)
3458                 goto out_usage;
3459
3460         wimfile = argv[0];
3461
3462         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3463                                             imagex_progress_func, NULL);
3464         if (ret)
3465                 goto out_free_refglobs;
3466
3467         wimlib_get_wim_info(wim, &info);
3468
3469         if (argc >= 3) {
3470                 /* Image explicitly specified.  */
3471                 image = wimlib_resolve_image(wim, argv[1]);
3472                 dir = argv[2];
3473                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3474                 if (ret)
3475                         goto out_free_wim;
3476         } else {
3477                 /* No image specified; default to image 1, but only if the WIM
3478                  * contains exactly one image.  */
3479
3480                 if (info.image_count != 1) {
3481                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3482                                        "select one."), wimfile, info.image_count);
3483                         wimlib_free(wim);
3484                         goto out_usage;
3485                 }
3486                 image = 1;
3487                 dir = argv[1];
3488         }
3489
3490         if (refglobs.num_strings) {
3491                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3492                 if (ret)
3493                         goto out_free_wim;
3494         }
3495
3496         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3497         if (ret) {
3498                 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3499                                "on \"%"TS"\""),
3500                              image, wimfile, dir);
3501         }
3502 out_free_wim:
3503         wimlib_free(wim);
3504 out_free_refglobs:
3505         string_set_destroy(&refglobs);
3506         return ret;
3507
3508 out_usage:
3509         usage(cmd, stderr);
3510         ret = -1;
3511         goto out_free_refglobs;
3512 }
3513 #endif /* WIM_MOUNTING_SUPPORTED */
3514
3515 /* Rebuild a WIM file */
3516 static int
3517 imagex_optimize(int argc, tchar **argv, int cmd)
3518 {
3519         int c;
3520         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3521         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3522         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3523         uint32_t chunk_size = UINT32_MAX;
3524         uint32_t solid_chunk_size = UINT32_MAX;
3525         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3526         int ret;
3527         WIMStruct *wim;
3528         const tchar *wimfile;
3529         off_t old_size;
3530         off_t new_size;
3531         unsigned num_threads = 0;
3532
3533         for_opt(c, optimize_options) {
3534                 switch (c) {
3535                 case IMAGEX_CHECK_OPTION:
3536                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3537                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3538                         break;
3539                 case IMAGEX_NOCHECK_OPTION:
3540                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3541                         break;
3542                 case IMAGEX_COMPRESS_OPTION:
3543                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3544                         compression_type = get_compression_type(optarg);
3545                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3546                                 goto out_err;
3547                         break;
3548                 case IMAGEX_COMPRESS_SLOW_OPTION:
3549                         set_compress_slow();
3550                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3551                         break;
3552                 case IMAGEX_RECOMPRESS_OPTION:
3553                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3554                         break;
3555                 case IMAGEX_CHUNK_SIZE_OPTION:
3556                         chunk_size = parse_chunk_size(optarg);
3557                         if (chunk_size == UINT32_MAX)
3558                                 goto out_err;
3559                         break;
3560                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3561                         solid_chunk_size = parse_chunk_size(optarg);
3562                         if (solid_chunk_size == UINT32_MAX)
3563                                 goto out_err;
3564                         break;
3565                 case IMAGEX_SOLID_COMPRESS_OPTION:
3566                         solid_ctype = get_compression_type(optarg);
3567                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3568                                 goto out_err;
3569                         break;
3570                 case IMAGEX_SOLID_OPTION:
3571                         write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
3572                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3573                         break;
3574                 case IMAGEX_THREADS_OPTION:
3575                         num_threads = parse_num_threads(optarg);
3576                         if (num_threads == UINT_MAX)
3577                                 goto out_err;
3578                         break;
3579                 case IMAGEX_PIPABLE_OPTION:
3580                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3581                         break;
3582                 case IMAGEX_NOT_PIPABLE_OPTION:
3583                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3584                         break;
3585                 default:
3586                         goto out_usage;
3587                 }
3588         }
3589         argc -= optind;
3590         argv += optind;
3591
3592         if (argc != 1)
3593                 goto out_usage;
3594
3595         wimfile = argv[0];
3596
3597         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3598                                             imagex_progress_func, NULL);
3599         if (ret)
3600                 goto out;
3601
3602         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3603                 /* Change compression type.  */
3604                 ret = wimlib_set_output_compression_type(wim, compression_type);
3605                 if (ret)
3606                         goto out_wimlib_free;
3607         }
3608
3609         if (chunk_size != UINT32_MAX) {
3610                 /* Change chunk size.  */
3611                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3612                 if (ret)
3613                         goto out_wimlib_free;
3614         }
3615         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3616                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3617                 if (ret)
3618                         goto out_wimlib_free;
3619         }
3620         if (solid_chunk_size != UINT32_MAX) {
3621                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3622                 if (ret)
3623                         goto out_wimlib_free;
3624         }
3625
3626         old_size = file_get_size(wimfile);
3627         tprintf(T("\"%"TS"\" original size: "), wimfile);
3628         if (old_size == -1)
3629                 tputs(T("Unknown"));
3630         else
3631                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3632
3633         ret = wimlib_overwrite(wim, write_flags, num_threads);
3634         if (ret) {
3635                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3636                 goto out_wimlib_free;
3637         }
3638
3639         new_size = file_get_size(wimfile);
3640         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3641         if (new_size == -1)
3642                 tputs(T("Unknown"));
3643         else
3644                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3645
3646         tfputs(T("Space saved: "), stdout);
3647         if (new_size != -1 && old_size != -1) {
3648                 tprintf(T("%lld KiB\n"),
3649                        ((long long)old_size - (long long)new_size) >> 10);
3650         } else {
3651                 tputs(T("Unknown"));
3652         }
3653         ret = 0;
3654 out_wimlib_free:
3655         wimlib_free(wim);
3656 out:
3657         return ret;
3658
3659 out_usage:
3660         usage(CMD_OPTIMIZE, stderr);
3661 out_err:
3662         ret = -1;
3663         goto out;
3664 }
3665
3666 /* Split a WIM into a spanned set */
3667 static int
3668 imagex_split(int argc, tchar **argv, int cmd)
3669 {
3670         int c;
3671         int open_flags = 0;
3672         int write_flags = 0;
3673         unsigned long part_size;
3674         tchar *tmp;
3675         int ret;
3676         WIMStruct *wim;
3677
3678         for_opt(c, split_options) {
3679                 switch (c) {
3680                 case IMAGEX_CHECK_OPTION:
3681                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3682                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3683                         break;
3684                 default:
3685                         goto out_usage;
3686                 }
3687         }
3688         argc -= optind;
3689         argv += optind;
3690
3691         if (argc != 3)
3692                 goto out_usage;
3693
3694         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3695         if (tmp == argv[2] || *tmp) {
3696                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3697                 imagex_error(T("The part size must be an integer or "
3698                                "floating-point number of megabytes."));
3699                 goto out_err;
3700         }
3701         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3702                                             imagex_progress_func, NULL);
3703         if (ret)
3704                 goto out;
3705
3706         ret = wimlib_split(wim, argv[1], part_size, write_flags);
3707         wimlib_free(wim);
3708 out:
3709         return ret;
3710
3711 out_usage:
3712         usage(CMD_SPLIT, stderr);
3713 out_err:
3714         ret = -1;
3715         goto out;
3716 }
3717
3718 #if WIM_MOUNTING_SUPPORTED
3719 /* Unmounts a mounted WIM image. */
3720 static int
3721 imagex_unmount(int argc, tchar **argv, int cmd)
3722 {
3723         int c;
3724         int unmount_flags = 0;
3725         int ret;
3726
3727         for_opt(c, unmount_options) {
3728                 switch (c) {
3729                 case IMAGEX_COMMIT_OPTION:
3730                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3731                         break;
3732                 case IMAGEX_CHECK_OPTION:
3733                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3734                         break;
3735                 case IMAGEX_REBUILD_OPTION:
3736                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3737                         break;
3738                 case IMAGEX_LAZY_OPTION:
3739                 case IMAGEX_FORCE_OPTION:
3740                         /* Now, unmount is lazy by default.  However, committing
3741                          * the image will fail with
3742                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3743                          * file descriptors on the WIM image.  The
3744                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3745                          * descriptors to be closed.  */
3746                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3747                         break;
3748                 case IMAGEX_NEW_IMAGE_OPTION:
3749                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3750                         break;
3751                 default:
3752                         goto out_usage;
3753                 }
3754         }
3755         argc -= optind;
3756         argv += optind;
3757         if (argc != 1)
3758                 goto out_usage;
3759
3760         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3761                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3762                         imagex_error(T("--new-image is meaningless "
3763                                        "without --commit also specified!"));
3764                         goto out_err;
3765                 }
3766         }
3767
3768         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3769                                                  imagex_progress_func, NULL);
3770         if (ret) {
3771                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3772                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3773                         imagex_printf(T(
3774                                 "\tNote: Use --commit --force to force changes "
3775                                         "to be committed, regardless\n"
3776                                 "\t      of open files.\n"));
3777                 }
3778         }
3779 out:
3780         return ret;
3781
3782 out_usage:
3783         usage(CMD_UNMOUNT, stderr);
3784 out_err:
3785         ret = -1;
3786         goto out;
3787 }
3788 #endif /* WIM_MOUNTING_SUPPORTED */
3789
3790 /*
3791  * Add, delete, or rename files in a WIM image.
3792  */
3793 static int
3794 imagex_update(int argc, tchar **argv, int cmd)
3795 {
3796         const tchar *wimfile;
3797         int image;
3798         WIMStruct *wim;
3799         int ret;
3800         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3801         int write_flags = 0;
3802         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3803         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3804                                 WIMLIB_ADD_FLAG_VERBOSE |
3805                                 WIMLIB_ADD_FLAG_WINCONFIG;
3806         int default_delete_flags = 0;
3807         unsigned num_threads = 0;
3808         int c;
3809         tchar *cmd_file_contents;
3810         size_t cmd_file_nchars;
3811         struct wimlib_update_command *cmds;
3812         size_t num_cmds;
3813         tchar *command_str = NULL;
3814         tchar *config_file = NULL;
3815         tchar *wimboot_config = NULL;
3816
3817         for_opt(c, update_options) {
3818                 switch (c) {
3819                 /* Generic or write options */
3820                 case IMAGEX_THREADS_OPTION:
3821                         num_threads = parse_num_threads(optarg);
3822                         if (num_threads == UINT_MAX)
3823                                 goto out_err;
3824                         break;
3825                 case IMAGEX_CHECK_OPTION:
3826                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3827                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3828                         break;
3829                 case IMAGEX_REBUILD_OPTION:
3830                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3831                         break;
3832                 case IMAGEX_COMMAND_OPTION:
3833                         if (command_str) {
3834                                 imagex_error(T("--command may only be specified "
3835                                                "one time.  Please provide\n"
3836                                                "       the update commands "
3837                                                "on standard input instead."));
3838                                 goto out_err;
3839                         }
3840                         command_str = tstrdup(optarg);
3841                         if (!command_str) {
3842                                 imagex_error(T("Out of memory!"));
3843                                 goto out_err;
3844                         }
3845                         break;
3846                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3847                         wimboot_config = optarg;
3848                         break;
3849                 /* Default delete options */
3850                 case IMAGEX_FORCE_OPTION:
3851                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3852                         break;
3853                 case IMAGEX_RECURSIVE_OPTION:
3854                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3855                         break;
3856
3857                 /* Global add option */
3858                 case IMAGEX_CONFIG_OPTION:
3859                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3860                         config_file = optarg;
3861                         break;
3862
3863                 /* Default add options */
3864                 case IMAGEX_VERBOSE_OPTION:
3865                         /* No longer does anything.  */
3866                         break;
3867                 case IMAGEX_DEREFERENCE_OPTION:
3868                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3869                         break;
3870                 case IMAGEX_UNIX_DATA_OPTION:
3871                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3872                         break;
3873                 case IMAGEX_NO_ACLS_OPTION:
3874                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3875                         break;
3876                 case IMAGEX_STRICT_ACLS_OPTION:
3877                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3878                         break;
3879                 case IMAGEX_NO_REPLACE_OPTION:
3880                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3881                         break;
3882                 default:
3883                         goto out_usage;
3884                 }
3885         }
3886         argv += optind;
3887         argc -= optind;
3888
3889         if (argc != 1 && argc != 2)
3890                 goto out_usage;
3891         wimfile = argv[0];
3892
3893         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3894                                             imagex_progress_func, NULL);
3895         if (ret)
3896                 goto out_free_command_str;
3897
3898         if (argc >= 2) {
3899                 /* Image explicitly specified.  */
3900                 image = wimlib_resolve_image(wim, argv[1]);
3901                 ret = verify_image_exists_and_is_single(image, argv[1],
3902                                                         wimfile);
3903                 if (ret)
3904                         goto out_wimlib_free;
3905         } else {
3906                 /* No image specified; default to image 1, but only if the WIM
3907                  * contains exactly one image.  */
3908                 struct wimlib_wim_info info;
3909
3910                 wimlib_get_wim_info(wim, &info);
3911                 if (info.image_count != 1) {
3912                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3913                                      wimfile, info.image_count);
3914                         wimlib_free(wim);
3915                         goto out_usage;
3916                 }
3917                 image = 1;
3918         }
3919
3920         /* Read update commands from standard input, or the command string if
3921          * specified.  */
3922         if (command_str) {
3923                 cmd_file_contents = NULL;
3924                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3925                                                  &num_cmds);
3926                 if (!cmds) {
3927                         ret = -1;
3928                         goto out_free_cmd_file_contents;
3929                 }
3930         } else if (!wimboot_config) {
3931                 if (isatty(STDIN_FILENO)) {
3932                         tputs(T("Reading update commands from standard input..."));
3933                         recommend_man_page(CMD_UPDATE, stdout);
3934                 }
3935                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3936                 if (!cmd_file_contents) {
3937                         ret = -1;
3938                         goto out_wimlib_free;
3939                 }
3940
3941                 /* Parse the update commands */
3942                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3943                                                  &num_cmds);
3944                 if (!cmds) {
3945                         ret = -1;
3946                         goto out_free_cmd_file_contents;
3947                 }
3948         } else {
3949                 cmd_file_contents = NULL;
3950                 cmds = NULL;
3951                 num_cmds = 0;
3952         }
3953
3954         /* Set default flags and capture config on the update commands */
3955         for (size_t i = 0; i < num_cmds; i++) {
3956                 switch (cmds[i].op) {
3957                 case WIMLIB_UPDATE_OP_ADD:
3958                         cmds[i].add.add_flags |= default_add_flags;
3959                         cmds[i].add.config_file = config_file;
3960                         break;
3961                 case WIMLIB_UPDATE_OP_DELETE:
3962                         cmds[i].delete_.delete_flags |= default_delete_flags;
3963                         break;
3964                 default:
3965                         break;
3966                 }
3967         }
3968
3969         /* Execute the update commands */
3970         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3971         if (ret)
3972                 goto out_free_cmds;
3973
3974         if (wimboot_config) {
3975                 /* --wimboot-config=FILE is short for an
3976                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
3977                  */
3978                 struct wimlib_update_command cmd;
3979
3980                 cmd.op = WIMLIB_UPDATE_OP_ADD;
3981                 cmd.add.fs_source_path = wimboot_config;
3982                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
3983                 cmd.add.config_file = NULL;
3984                 cmd.add.add_flags = 0;
3985
3986                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
3987                 if (ret)
3988                         goto out_free_cmds;
3989         }
3990
3991         /* Overwrite the updated WIM */
3992         ret = wimlib_overwrite(wim, write_flags, num_threads);
3993 out_free_cmds:
3994         free(cmds);
3995 out_free_cmd_file_contents:
3996         free(cmd_file_contents);
3997 out_wimlib_free:
3998         wimlib_free(wim);
3999 out_free_command_str:
4000         free(command_str);
4001         return ret;
4002
4003 out_usage:
4004         usage(CMD_UPDATE, stderr);
4005 out_err:
4006         ret = -1;
4007         goto out_free_command_str;
4008 }
4009
4010 /* Verify a WIM file.  */
4011 static int
4012 imagex_verify(int argc, tchar **argv, int cmd)
4013 {
4014         int ret;
4015         const tchar *wimfile;
4016         WIMStruct *wim;
4017         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4018         int verify_flags = 0;
4019         STRING_SET(refglobs);
4020         int c;
4021
4022         for_opt(c, verify_options) {
4023                 switch (c) {
4024                 case IMAGEX_REF_OPTION:
4025                         ret = string_set_append(&refglobs, optarg);
4026                         if (ret)
4027                                 goto out_free_refglobs;
4028                         break;
4029                 default:
4030                         goto out_usage;
4031                 }
4032         }
4033
4034         argv += optind;
4035         argc -= optind;
4036
4037         if (argc != 1) {
4038                 if (argc == 0)
4039                         imagex_error(T("Must specify a WIM file!"));
4040                 else
4041                         imagex_error(T("At most one WIM file can be specified!"));
4042                 goto out_usage;
4043         }
4044
4045         wimfile = argv[0];
4046
4047         ret = wimlib_open_wim_with_progress(wimfile,
4048                                             open_flags,
4049                                             &wim,
4050                                             imagex_progress_func,
4051                                             NULL);
4052         if (ret)
4053                 goto out_free_refglobs;
4054
4055         ret = wim_reference_globs(wim, &refglobs, open_flags);
4056         if (ret)
4057                 goto out_wimlib_free;
4058
4059         ret = wimlib_verify_wim(wim, verify_flags);
4060         if (ret) {
4061                 tputc(T('\n'), stderr);
4062                 imagex_error(T("\"%"TS"\" failed verification!"),
4063                              wimfile);
4064                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4065                     refglobs.num_strings == 0)
4066                 {
4067                         imagex_printf(T("Note: if this WIM file is not standalone, "
4068                                         "use the --ref option to specify the other parts.\n"));
4069                 }
4070         } else {
4071                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4072                               wimfile);
4073         }
4074
4075 out_wimlib_free:
4076         wimlib_free(wim);
4077 out_free_refglobs:
4078         string_set_destroy(&refglobs);
4079         return ret;
4080
4081 out_usage:
4082         usage(CMD_VERIFY, stderr);
4083         ret = -1;
4084         goto out_free_refglobs;
4085 }
4086
4087 struct imagex_command {
4088         const tchar *name;
4089         int (*func)(int argc, tchar **argv, int cmd);
4090 };
4091
4092 static const struct imagex_command imagex_commands[] = {
4093         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4094         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4095         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4096         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4097         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4098         [CMD_EXPORT]   = {T("export"),   imagex_export},
4099         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4100         [CMD_INFO]     = {T("info"),     imagex_info},
4101         [CMD_JOIN]     = {T("join"),     imagex_join},
4102 #if WIM_MOUNTING_SUPPORTED
4103         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4104         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4105 #endif
4106         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4107         [CMD_SPLIT]    = {T("split"),    imagex_split},
4108 #if WIM_MOUNTING_SUPPORTED
4109         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4110 #endif
4111         [CMD_UPDATE]   = {T("update"),   imagex_update},
4112         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4113 };
4114
4115 #ifdef __WIN32__
4116
4117    /* Can be a directory or source list file.  But source list file is probably
4118     * a rare use case, so just say directory.  */
4119 #  define SOURCE_STR T("DIRECTORY")
4120
4121    /* Can only be a directory  */
4122 #  define TARGET_STR T("DIRECTORY")
4123
4124 #else
4125    /* Can be a directory, NTFS volume, or source list file. */
4126 #  define SOURCE_STR T("SOURCE")
4127
4128    /* Can be a directory or NTFS volume.  */
4129 #  define TARGET_STR T("TARGET")
4130
4131 #endif
4132
4133 static const tchar *usage_strings[] = {
4134 [CMD_APPEND] =
4135 T(
4136 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4137 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4138 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4139 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4140 "                    [--wimboot] [--unix-data] [--dereference]\n"
4141 ),
4142 [CMD_APPLY] =
4143 T(
4144 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4145 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4146 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4147 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4148 ),
4149 [CMD_CAPTURE] =
4150 T(
4151 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4152 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4153 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4154 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4155 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4156 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4157 ),
4158 [CMD_DELETE] =
4159 T(
4160 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4161 ),
4162 [CMD_DIR] =
4163 T(
4164 "    %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4165 ),
4166 [CMD_EXPORT] =
4167 T(
4168 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4169 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4170 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4171 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4172 "                    [--wimboot]\n"
4173 ),
4174 [CMD_EXTRACT] =
4175 T(
4176 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4177 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4178 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4179 "                    [--no-attributes] [--include-invalid-names]\n"
4180 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4181 ),
4182 [CMD_INFO] =
4183 T(
4184 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4185 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4186 "                    [--extract-xml FILE] [--header] [--lookup-table]\n"
4187 ),
4188 [CMD_JOIN] =
4189 T(
4190 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4191 ),
4192 #if WIM_MOUNTING_SUPPORTED
4193 [CMD_MOUNT] =
4194 T(
4195 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4196 "                    [--check] [--streams-interface=INTERFACE]\n"
4197 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4198 ),
4199 [CMD_MOUNTRW] =
4200 T(
4201 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4202 "                    [--check] [--streams-interface=INTERFACE]\n"
4203 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4204 ),
4205 #endif
4206 [CMD_OPTIMIZE] =
4207 T(
4208 "    %"TS" WIMFILE\n"
4209 "                    [--recompress] [--compress=TYPE]\n"
4210 "                    [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4211 "\n"
4212 ),
4213 [CMD_SPLIT] =
4214 T(
4215 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4216 ),
4217 #if WIM_MOUNTING_SUPPORTED
4218 [CMD_UNMOUNT] =
4219 T(
4220 "    %"TS" DIRECTORY\n"
4221 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4222 ),
4223 #endif
4224 [CMD_UPDATE] =
4225 T(
4226 "    %"TS" WIMFILE [IMAGE]\n"
4227 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4228 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4229 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4230 "                    [< CMDFILE]\n"
4231 ),
4232 [CMD_VERIFY] =
4233 T(
4234 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4235 ),
4236 };
4237
4238 static const tchar *invocation_name;
4239 static int invocation_cmd = CMD_NONE;
4240
4241 static const tchar *get_cmd_string(int cmd, bool nospace)
4242 {
4243         static tchar buf[50];
4244         if (cmd == CMD_NONE) {
4245                 tsprintf(buf, T("%"TS), T(IMAGEX_PROGNAME));
4246         } else if (invocation_cmd != CMD_NONE) {
4247                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4248         } else {
4249                 const tchar *format;
4250
4251                 if (nospace)
4252                         format = T("%"TS"-%"TS"");
4253                 else
4254                         format = T("%"TS" %"TS"");
4255                 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4256         }
4257         return buf;
4258 }
4259
4260 static void
4261 version(void)
4262 {
4263         static const tchar *s =
4264         T(
4265 IMAGEX_PROGNAME " (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4266 "Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
4267 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4268 "This is free software: you are free to change and redistribute it.\n"
4269 "There is NO WARRANTY, to the extent permitted by law.\n"
4270 "\n"
4271 "Report bugs to "PACKAGE_BUGREPORT".\n"
4272         );
4273         tfputs(s, stdout);
4274 }
4275
4276
4277 static void
4278 help_or_version(int argc, tchar **argv, int cmd)
4279 {
4280         int i;
4281         const tchar *p;
4282
4283         for (i = 1; i < argc; i++) {
4284                 p = argv[i];
4285                 if (p[0] == T('-') && p[1] == T('-')) {
4286                         p += 2;
4287                         if (!tstrcmp(p, T("help"))) {
4288                                 if (cmd == CMD_NONE)
4289                                         usage_all(stdout);
4290                                 else
4291                                         usage(cmd, stdout);
4292                                 exit(0);
4293                         } else if (!tstrcmp(p, T("version"))) {
4294                                 version();
4295                                 exit(0);
4296                         }
4297                 }
4298         }
4299 }
4300
4301 static void
4302 print_usage_string(int cmd, FILE *fp)
4303 {
4304         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4305 }
4306
4307 static void
4308 recommend_man_page(int cmd, FILE *fp)
4309 {
4310         const tchar *format_str;
4311 #ifdef __WIN32__
4312         format_str = T("Some uncommon options are not listed;\n"
4313                        "See %"TS".pdf in the doc directory for more details.\n");
4314 #else
4315         format_str = T("Some uncommon options are not listed;\n"
4316                        "Try `man %"TS"' for more details.\n");
4317 #endif
4318         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4319 }
4320
4321 static void
4322 usage(int cmd, FILE *fp)
4323 {
4324         tfprintf(fp, T("Usage:\n"));
4325         print_usage_string(cmd, fp);
4326         tfprintf(fp, T("\n"));
4327         recommend_man_page(cmd, fp);
4328 }
4329
4330 static void
4331 usage_all(FILE *fp)
4332 {
4333         tfprintf(fp, T("Usage:\n"));
4334         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4335                 print_usage_string(cmd, fp);
4336                 tfprintf(fp, T("\n"));
4337         }
4338         static const tchar *extra =
4339         T(
4340 "    %"TS" --help\n"
4341 "    %"TS" --version\n"
4342 "\n"
4343         );
4344         tfprintf(fp, extra, invocation_name, invocation_name);
4345         tfprintf(fp,
4346                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4347                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4348                    "For some commands IMAGE may be \"all\".\n"
4349                    "\n"));
4350         recommend_man_page(CMD_NONE, fp);
4351 }
4352
4353 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4354  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4355  * something else), while on Windows the command arguments will be UTF-16LE
4356  * encoded 'wchar_t' strings. */
4357 int
4358 #ifdef __WIN32__
4359 wmain(int argc, wchar_t **argv, wchar_t **envp)
4360 #else
4361 main(int argc, char **argv)
4362 #endif
4363 {
4364         int ret;
4365         int init_flags = 0;
4366         int cmd;
4367
4368         imagex_info_file = stdout;
4369         invocation_name = tbasename(argv[0]);
4370
4371 #ifndef __WIN32__
4372         if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4373                 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4374         } else {
4375                 char *codeset;
4376
4377                 setlocale(LC_ALL, "");
4378                 codeset = nl_langinfo(CODESET);
4379                 if (!strstr(codeset, "UTF-8") &&
4380                     !strstr(codeset, "UTF8") &&
4381                     !strstr(codeset, "utf-8") &&
4382                     !strstr(codeset, "utf8"))
4383                 {
4384                         fprintf(stderr,
4385 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4386 "         Maybe try: `export LANG=en_US.UTF-8'?\n"
4387 "         Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4388 "         to any value to force wimlib to use UTF-8.\n",
4389                         invocation_name);
4390
4391                 }
4392         }
4393
4394 #endif /* !__WIN32__ */
4395
4396         {
4397                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4398                 if (igcase != NULL) {
4399                         if (!tstrcmp(igcase, T("no")) ||
4400                             !tstrcmp(igcase, T("0")))
4401                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4402                         else if (!tstrcmp(igcase, T("yes")) ||
4403                                  !tstrcmp(igcase, T("1")))
4404                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4405                         else {
4406                                 fprintf(stderr,
4407                                         "WARNING: Ignoring unknown setting of "
4408                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4409                         }
4410                 }
4411         }
4412
4413         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4414         cmd = CMD_NONE;
4415         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4416             tstrcmp(invocation_name, T(IMAGEX_PROGNAME))) {
4417                 for (int i = 0; i < CMD_MAX; i++) {
4418                         if (!tstrcmp(invocation_name + 3,
4419                                      imagex_commands[i].name))
4420                         {
4421                                 invocation_cmd = i;
4422                                 cmd = i;
4423                                 break;
4424                         }
4425                 }
4426         }
4427
4428         /* Unless already known from the invocation name, determine which
4429          * command was specified.  */
4430         if (cmd == CMD_NONE) {
4431                 if (argc < 2) {
4432                         imagex_error(T("No command specified!\n"));
4433                         usage_all(stderr);
4434                         exit(2);
4435                 }
4436                 for (int i = 0; i < CMD_MAX; i++) {
4437                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4438                                 cmd = i;
4439                                 break;
4440                         }
4441                 }
4442                 if (cmd != CMD_NONE) {
4443                         argc--;
4444                         argv++;
4445                 }
4446         }
4447
4448         /* Handle --help and --version.  --help can be either for the program as
4449          * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4450          * CMD_NONE).  Note: help_or_version() will not return if a --help or
4451          * --version argument was found.  */
4452         help_or_version(argc, argv, cmd);
4453
4454         /* Bail if a valid command was not specified.  */
4455         if (cmd == CMD_NONE) {
4456                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4457                 usage_all(stderr);
4458                 exit(2);
4459         }
4460
4461         /* Enable warning and error messages in wimlib to be more user-friendly.
4462          * */
4463         wimlib_set_print_errors(true);
4464
4465         /* Initialize wimlib.  */
4466         ret = wimlib_global_init(init_flags);
4467         if (ret)
4468                 goto out_check_status;
4469
4470         /* Call the command handler function.  */
4471         ret = imagex_commands[cmd].func(argc, argv, cmd);
4472
4473         /* Check for error writing to standard output, especially since for some
4474          * commands, writing to standard output is part of the program's actual
4475          * behavior and not just for informational purposes.  */
4476         if (ferror(stdout) || fclose(stdout)) {
4477                 imagex_error_with_errno(T("error writing to standard output"));
4478                 if (ret == 0)
4479                         ret = -1;
4480         }
4481 out_check_status:
4482         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4483          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4484          * error code from which an error message can be printed.  */
4485         if (ret > 0) {
4486                 imagex_error(T("Exiting with error code %d:\n"
4487                                "       %"TS"."), ret,
4488                              wimlib_get_error_string(ret));
4489                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4490                         imagex_error_with_errno(T("errno"));
4491         }
4492         /* Make wimlib free any resources it's holding (although this is not
4493          * strictly necessary because the process is ending anyway).  */
4494         wimlib_global_cleanup();
4495         return ret;
4496 }