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