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