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