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