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