]> wimlib.net Git - wimlib/blob - programs/imagex.c
dd43d75c059e494d79f95c5d15371c4f4dd19414
[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
1167 static struct wimlib_progress_info_split last_split_progress;
1168
1169 static void
1170 report_split_progress(uint64_t bytes_completed_in_part)
1171 {
1172         uint64_t completed_bytes = last_split_progress.completed_bytes +
1173                                    bytes_completed_in_part;
1174         unsigned percent_done = TO_PERCENT(completed_bytes,
1175                                            last_split_progress.total_bytes);
1176         unsigned unit_shift;
1177         const tchar *unit_name;
1178
1179         unit_shift = get_unit(last_split_progress.total_bytes, &unit_name);
1180         imagex_printf(T("\rSplitting WIM: %"PRIu64" %"TS" of "
1181                         "%"PRIu64" %"TS" (%u%%) written, part %u of %u"),
1182                       completed_bytes >> unit_shift,
1183                       unit_name,
1184                       last_split_progress.total_bytes >> unit_shift,
1185                       unit_name,
1186                       percent_done,
1187                       last_split_progress.cur_part_number,
1188                       last_split_progress.total_parts);
1189 }
1190
1191 /* Progress callback function passed to various wimlib functions. */
1192 static enum wimlib_progress_status
1193 imagex_progress_func(enum wimlib_progress_msg msg,
1194                      union wimlib_progress_info *info,
1195                      void *_ignored_context)
1196 {
1197         unsigned percent_done;
1198         unsigned unit_shift;
1199         const tchar *unit_name;
1200
1201         switch (msg) {
1202         case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1203                 if (last_split_progress.total_bytes != 0) {
1204                         /* wimlib_split() in progress; use the split-specific
1205                          * progress message.  */
1206                         report_split_progress(info->write_streams.completed_compressed_bytes);
1207                         break;
1208                 }
1209                 {
1210                         static bool started;
1211                         if (!started) {
1212                                 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1213                                         imagex_printf(T("Using %"TS" compression "
1214                                                         "with %u thread%"TS"\n"),
1215                                                       wimlib_get_compression_type_string(
1216                                                                 info->write_streams.compression_type),
1217                                                 info->write_streams.num_threads,
1218                                                 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1219                                 }
1220                                 started = true;
1221                         }
1222                 }
1223                 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1224                 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1225                                           info->write_streams.total_bytes);
1226
1227                 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1228                         info->write_streams.completed_bytes >> unit_shift,
1229                         unit_name,
1230                         info->write_streams.total_bytes >> unit_shift,
1231                         unit_name,
1232                         percent_done);
1233                 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1234                         imagex_printf(T("\n"));
1235                 break;
1236         case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1237                 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1238                 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1239                         imagex_printf(T("\n"));
1240                 } else {
1241                         imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1242                                       info->scan.wim_target_path);
1243                 }
1244                 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1245                 break;
1246         case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1247                 switch (info->scan.status) {
1248                 case WIMLIB_SCAN_DENTRY_OK:
1249                         report_scan_progress(&info->scan, false);
1250                         break;
1251                 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1252                         imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1253                         break;
1254                 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1255                         imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1256                                         "         \"%"TS"\" from capture\n"), info->scan.cur_path);
1257                         break;
1258                 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1259                         /* Symlink fixups are enabled by default.  This is
1260                          * mainly intended for Windows, which for some reason
1261                          * uses absolute junctions (with drive letters!) in the
1262                          * default installation.  On UNIX-like systems, warn the
1263                          * user when fixing the target of an absolute symbolic
1264                          * link, so they know to disable this if they want.  */
1265                 #ifndef __WIN32__
1266                         imagex_printf(T("\nWARNING: Adjusted target of "
1267                                         "absolute symbolic link \"%"TS"\"\n"
1268                                         "           (Use --norpfix to capture "
1269                                         "absolute symbolic links as-is)\n"),
1270                                         info->scan.cur_path);
1271                 #endif
1272                         break;
1273                 default:
1274                         break;
1275                 }
1276                 break;
1277         case WIMLIB_PROGRESS_MSG_SCAN_END:
1278                 report_scan_progress(&info->scan, true);
1279                 imagex_printf(T("\n"));
1280                 break;
1281         case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1282                 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1283                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1284                                           info->integrity.total_bytes);
1285                 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1286                         "of %"PRIu64" %"TS" (%u%%) done"),
1287                         info->integrity.filename,
1288                         info->integrity.completed_bytes >> unit_shift,
1289                         unit_name,
1290                         info->integrity.total_bytes >> unit_shift,
1291                         unit_name,
1292                         percent_done);
1293                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1294                         imagex_printf(T("\n"));
1295                 break;
1296         case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1297                 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1298                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1299                                           info->integrity.total_bytes);
1300                 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1301                           "of %"PRIu64" %"TS" (%u%%) done"),
1302                         info->integrity.completed_bytes >> unit_shift,
1303                         unit_name,
1304                         info->integrity.total_bytes >> unit_shift,
1305                         unit_name,
1306                         percent_done);
1307                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1308                         imagex_printf(T("\n"));
1309                 break;
1310         case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1311                 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1312                           "to %"TS" \"%"TS"\"\n"),
1313                         info->extract.image,
1314                         info->extract.image_name,
1315                         info->extract.wimfile_name,
1316                         ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1317                          T("NTFS volume") : T("directory")),
1318                         info->extract.target);
1319                 break;
1320         case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1321                 if (info->extract.end_file_count >= 2000) {
1322                         percent_done = TO_PERCENT(info->extract.current_file_count,
1323                                                   info->extract.end_file_count);
1324                         imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1325                                       info->extract.current_file_count,
1326                                       info->extract.end_file_count, percent_done);
1327                         if (info->extract.current_file_count == info->extract.end_file_count)
1328                                 imagex_printf(T("\n"));
1329                 }
1330                 break;
1331         case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1332                 percent_done = TO_PERCENT(info->extract.completed_bytes,
1333                                           info->extract.total_bytes);
1334                 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1335                 imagex_printf(T("\rExtracting file data: "
1336                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1337                         info->extract.completed_bytes >> unit_shift,
1338                         unit_name,
1339                         info->extract.total_bytes >> unit_shift,
1340                         unit_name,
1341                         percent_done);
1342                 if (info->extract.completed_bytes >= info->extract.total_bytes)
1343                         imagex_printf(T("\n"));
1344                 break;
1345         case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1346                 if (info->extract.end_file_count >= 2000) {
1347                         percent_done = TO_PERCENT(info->extract.current_file_count,
1348                                                   info->extract.end_file_count);
1349                         imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1350                                       info->extract.current_file_count,
1351                                       info->extract.end_file_count, percent_done);
1352                         if (info->extract.current_file_count == info->extract.end_file_count)
1353                                 imagex_printf(T("\n"));
1354                 }
1355                 break;
1356         case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1357                 if (info->extract.total_parts != 1) {
1358                         imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1359                                       info->extract.part_number,
1360                                       info->extract.total_parts);
1361                 }
1362                 break;
1363         case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1364         case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1365                 last_split_progress = info->split;
1366                 report_split_progress(0);
1367                 break;
1368         case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1369                 switch (info->update.command->op) {
1370                 case WIMLIB_UPDATE_OP_DELETE:
1371                         imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1372                                 info->update.command->delete_.wim_path);
1373                         break;
1374                 case WIMLIB_UPDATE_OP_RENAME:
1375                         imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1376                                 info->update.command->rename.wim_source_path,
1377                                 info->update.command->rename.wim_target_path);
1378                         break;
1379                 case WIMLIB_UPDATE_OP_ADD:
1380                 default:
1381                         break;
1382                 }
1383                 break;
1384         case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1385                 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1386                               info->replace.path_in_wim);
1387                 break;
1388         case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1389                 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1390                               info->wimboot_exclude.path_in_wim);
1391                 break;
1392         case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1393                 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1394                         if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1395                                 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1396                                               info->unmount.mounted_wim,
1397                                               info->unmount.mounted_image);
1398                         } else {
1399                                 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1400                                               info->unmount.mounted_wim,
1401                                               info->unmount.mounted_image);
1402                                 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1403                         }
1404                 }
1405                 break;
1406         case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1407                 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1408                               info->verify_image.current_image,
1409                               info->verify_image.total_images);
1410                 break;
1411         case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1412                 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1413                                           info->verify_streams.total_bytes);
1414                 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1415                 imagex_printf(T("\rVerifying file data: "
1416                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1417                         info->verify_streams.completed_bytes >> unit_shift,
1418                         unit_name,
1419                         info->verify_streams.total_bytes >> unit_shift,
1420                         unit_name,
1421                         percent_done);
1422                 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1423                         imagex_printf(T("\n"));
1424                 break;
1425         default:
1426                 break;
1427         }
1428         imagex_flush_output();
1429         return WIMLIB_PROGRESS_STATUS_CONTINUE;
1430 }
1431
1432 static unsigned
1433 parse_num_threads(const tchar *optarg)
1434 {
1435         tchar *tmp;
1436         unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1437         if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1438                 imagex_error(T("Number of threads must be a non-negative integer!"));
1439                 return UINT_MAX;
1440         } else {
1441                 return ul_nthreads;
1442         }
1443 }
1444
1445 static uint32_t
1446 parse_chunk_size(const tchar *optarg)
1447 {
1448         tchar *tmp;
1449         uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1450         if (chunk_size == 0) {
1451                 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1452                                "       with optional K, M, or G suffix"));
1453                 return UINT32_MAX;
1454         }
1455         if (*tmp) {
1456                 if (*tmp == T('k') || *tmp == T('K')) {
1457                         chunk_size <<= 10;
1458                         tmp++;
1459                 } else if (*tmp == T('m') || *tmp == T('M')) {
1460                         chunk_size <<= 20;
1461                         tmp++;
1462                 } else if (*tmp == T('g') || *tmp == T('G')) {
1463                         chunk_size <<= 30;
1464                         tmp++;
1465                 }
1466                 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1467                         imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1468                         return UINT32_MAX;
1469                 }
1470         }
1471         if (chunk_size >= UINT32_MAX) {
1472                 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1473                 return UINT32_MAX;
1474         }
1475         return chunk_size;
1476 }
1477
1478
1479 /*
1480  * Parse an option passed to an update command.
1481  *
1482  * @op:         One of WIMLIB_UPDATE_OP_* that indicates the command being
1483  *              parsed.
1484  *
1485  * @option:     Text string for the option (beginning with --)
1486  *
1487  * @cmd:        `struct wimlib_update_command' that is being constructed for
1488  *              this command.
1489  *
1490  * Returns true if the option was recognized; false if not.
1491  */
1492 static bool
1493 update_command_add_option(int op, const tchar *option,
1494                           struct wimlib_update_command *cmd)
1495 {
1496         bool recognized = true;
1497         switch (op) {
1498         case WIMLIB_UPDATE_OP_ADD:
1499                 if (!tstrcmp(option, T("--verbose")))
1500                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1501                 else if (!tstrcmp(option, T("--unix-data")))
1502                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1503                 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1504                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1505                 else if (!tstrcmp(option, T("--strict-acls")))
1506                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1507                 else if (!tstrcmp(option, T("--dereference")))
1508                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1509                 else if (!tstrcmp(option, T("--no-replace")))
1510                         cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1511                 else
1512                         recognized = false;
1513                 break;
1514         case WIMLIB_UPDATE_OP_DELETE:
1515                 if (!tstrcmp(option, T("--force")))
1516                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1517                 else if (!tstrcmp(option, T("--recursive")))
1518                         cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1519                 else
1520                         recognized = false;
1521                 break;
1522         default:
1523                 recognized = false;
1524                 break;
1525         }
1526         return recognized;
1527 }
1528
1529 /* How many nonoption arguments each `imagex update' command expects */
1530 static const unsigned update_command_num_nonoptions[] = {
1531         [WIMLIB_UPDATE_OP_ADD] = 2,
1532         [WIMLIB_UPDATE_OP_DELETE] = 1,
1533         [WIMLIB_UPDATE_OP_RENAME] = 2,
1534 };
1535
1536 static void
1537 update_command_add_nonoption(int op, const tchar *nonoption,
1538                              struct wimlib_update_command *cmd,
1539                              unsigned num_nonoptions)
1540 {
1541         switch (op) {
1542         case WIMLIB_UPDATE_OP_ADD:
1543                 if (num_nonoptions == 0)
1544                         cmd->add.fs_source_path = (tchar*)nonoption;
1545                 else
1546                         cmd->add.wim_target_path = (tchar*)nonoption;
1547                 break;
1548         case WIMLIB_UPDATE_OP_DELETE:
1549                 cmd->delete_.wim_path = (tchar*)nonoption;
1550                 break;
1551         case WIMLIB_UPDATE_OP_RENAME:
1552                 if (num_nonoptions == 0)
1553                         cmd->rename.wim_source_path = (tchar*)nonoption;
1554                 else
1555                         cmd->rename.wim_target_path = (tchar*)nonoption;
1556                 break;
1557         }
1558 }
1559
1560 /*
1561  * Parse a command passed on stdin to `imagex update'.
1562  *
1563  * @line:       Text of the command.
1564  * @len:        Length of the line, including a null terminator
1565  *              at line[len - 1].
1566  *
1567  * @command:    A `struct wimlib_update_command' to fill in from the parsed
1568  *              line.
1569  *
1570  * @line_number: Line number of the command, for diagnostics.
1571  *
1572  * Returns true on success; returns false on parse error.
1573  */
1574 static bool
1575 parse_update_command(tchar *line, size_t len,
1576                      struct wimlib_update_command *command,
1577                      size_t line_number)
1578 {
1579         int ret;
1580         tchar *command_name;
1581         int op;
1582         size_t num_nonoptions;
1583
1584         /* Get the command name ("add", "delete", "rename") */
1585         ret = parse_string(&line, &len, &command_name);
1586         if (ret != PARSE_STRING_SUCCESS)
1587                 return false;
1588
1589         if (!tstrcasecmp(command_name, T("add"))) {
1590                 op = WIMLIB_UPDATE_OP_ADD;
1591         } else if (!tstrcasecmp(command_name, T("delete"))) {
1592                 op = WIMLIB_UPDATE_OP_DELETE;
1593         } else if (!tstrcasecmp(command_name, T("rename"))) {
1594                 op = WIMLIB_UPDATE_OP_RENAME;
1595         } else {
1596                 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1597                              command_name, line_number);
1598                 return false;
1599         }
1600         command->op = op;
1601
1602         /* Parse additional options and non-options as needed */
1603         num_nonoptions = 0;
1604         for (;;) {
1605                 tchar *next_string;
1606
1607                 ret = parse_string(&line, &len, &next_string);
1608                 if (ret == PARSE_STRING_NONE) /* End of line */
1609                         break;
1610                 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1611                         return false;
1612                 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1613                         /* Option */
1614                         if (!update_command_add_option(op, next_string, command))
1615                         {
1616                                 imagex_error(T("Unrecognized option \"%"TS"\" to "
1617                                                "update command \"%"TS"\" on line %zu"),
1618                                              next_string, command_name, line_number);
1619
1620                                 return false;
1621                         }
1622                 } else {
1623                         /* Nonoption */
1624                         if (num_nonoptions == update_command_num_nonoptions[op])
1625                         {
1626                                 imagex_error(T("Unexpected argument \"%"TS"\" in "
1627                                                "update command on line %zu\n"
1628                                                "       (The \"%"TS"\" command only "
1629                                                "takes %zu nonoption arguments!)\n"),
1630                                              next_string, line_number,
1631                                              command_name, num_nonoptions);
1632                                 return false;
1633                         }
1634                         update_command_add_nonoption(op, next_string,
1635                                                      command, num_nonoptions);
1636                         num_nonoptions++;
1637                 }
1638         }
1639
1640         if (num_nonoptions != update_command_num_nonoptions[op]) {
1641                 imagex_error(T("Not enough arguments to update command "
1642                                "\"%"TS"\" on line %zu"), command_name, line_number);
1643                 return false;
1644         }
1645         return true;
1646 }
1647
1648 static struct wimlib_update_command *
1649 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1650                           size_t *num_cmds_ret)
1651 {
1652         ssize_t nlines;
1653         tchar *p;
1654         struct wimlib_update_command *cmds;
1655         size_t i, j;
1656
1657         nlines = text_file_count_lines(cmd_file_contents_p,
1658                                        &cmd_file_nchars);
1659         if (nlines < 0)
1660                 return NULL;
1661
1662         /* Always allocate at least 1 slot, just in case the implementation of
1663          * calloc() returns NULL if 0 bytes are requested. */
1664         cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1665         if (!cmds) {
1666                 imagex_error(T("out of memory"));
1667                 return NULL;
1668         }
1669         p = *cmd_file_contents_p;
1670         j = 0;
1671         for (i = 0; i < nlines; i++) {
1672                 /* XXX: Could use rawmemchr() here instead, but it may not be
1673                  * available on all platforms. */
1674                 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1675                 size_t len = endp - p + 1;
1676                 *endp = T('\0');
1677                 if (!is_comment_line(p, len)) {
1678                         if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1679                                 free(cmds);
1680                                 return NULL;
1681                         }
1682                 }
1683                 p = endp + 1;
1684         }
1685         *num_cmds_ret = j;
1686         return cmds;
1687 }
1688
1689 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1690  * one image from a WIM file to an NTFS volume.  */
1691 static int
1692 imagex_apply(int argc, tchar **argv, int cmd)
1693 {
1694         int c;
1695         int open_flags = 0;
1696         int image = WIMLIB_NO_IMAGE;
1697         WIMStruct *wim;
1698         struct wimlib_wim_info info;
1699         int ret;
1700         const tchar *wimfile;
1701         const tchar *target;
1702         const tchar *image_num_or_name = NULL;
1703         int extract_flags = 0;
1704
1705         STRING_LIST(refglobs);
1706
1707         for_opt(c, apply_options) {
1708                 switch (c) {
1709                 case IMAGEX_CHECK_OPTION:
1710                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1711                         break;
1712                 case IMAGEX_VERBOSE_OPTION:
1713                         /* No longer does anything.  */
1714                         break;
1715                 case IMAGEX_REF_OPTION:
1716                         ret = string_list_append(&refglobs, optarg);
1717                         if (ret)
1718                                 goto out_free_refglobs;
1719                         break;
1720                 case IMAGEX_UNIX_DATA_OPTION:
1721                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1722                         break;
1723                 case IMAGEX_NO_ACLS_OPTION:
1724                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1725                         break;
1726                 case IMAGEX_STRICT_ACLS_OPTION:
1727                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1728                         break;
1729                 case IMAGEX_NO_ATTRIBUTES_OPTION:
1730                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1731                         break;
1732                 case IMAGEX_NORPFIX_OPTION:
1733                         extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1734                         break;
1735                 case IMAGEX_RPFIX_OPTION:
1736                         extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1737                         break;
1738                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1739                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1740                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1741                         break;
1742                 case IMAGEX_WIMBOOT_OPTION:
1743                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1744                         break;
1745                 case IMAGEX_COMPACT_OPTION:
1746                         ret = set_compact_mode(optarg, &extract_flags);
1747                         if (ret)
1748                                 goto out_free_refglobs;
1749                         break;
1750                 default:
1751                         goto out_usage;
1752                 }
1753         }
1754         argc -= optind;
1755         argv += optind;
1756         if (argc != 2 && argc != 3)
1757                 goto out_usage;
1758
1759         wimfile = argv[0];
1760
1761         if (!tstrcmp(wimfile, T("-"))) {
1762                 /* Attempt to apply pipable WIM from standard input.  */
1763                 if (argc == 2) {
1764                         image_num_or_name = NULL;
1765                         target = argv[1];
1766                 } else {
1767                         image_num_or_name = argv[1];
1768                         target = argv[2];
1769                 }
1770                 wim = NULL;
1771         } else {
1772                 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1773                                                     imagex_progress_func, NULL);
1774                 if (ret)
1775                         goto out_free_refglobs;
1776
1777                 wimlib_get_wim_info(wim, &info);
1778
1779                 if (argc >= 3) {
1780                         /* Image explicitly specified.  */
1781                         image_num_or_name = argv[1];
1782                         image = wimlib_resolve_image(wim, image_num_or_name);
1783                         ret = verify_image_exists(image, image_num_or_name, wimfile);
1784                         if (ret)
1785                                 goto out_wimlib_free;
1786                         target = argv[2];
1787                 } else {
1788                         /* No image specified; default to image 1, but only if the WIM
1789                          * contains exactly one image.  */
1790
1791                         if (info.image_count != 1) {
1792                                 imagex_error(T("\"%"TS"\" contains %d images; "
1793                                                "Please select one (or all)."),
1794                                              wimfile, info.image_count);
1795                                 wimlib_free(wim);
1796                                 goto out_usage;
1797                         }
1798                         image = 1;
1799                         target = argv[1];
1800                 }
1801         }
1802
1803         if (refglobs.num_strings) {
1804                 if (wim == NULL) {
1805                         imagex_error(T("Can't specify --ref when applying from stdin!"));
1806                         ret = -1;
1807                         goto out_wimlib_free;
1808                 }
1809                 ret = wim_reference_globs(wim, &refglobs, open_flags);
1810                 if (ret)
1811                         goto out_wimlib_free;
1812         }
1813
1814 #ifndef __WIN32__
1815         {
1816                 /* Interpret a regular file or block device target as an NTFS
1817                  * volume.  */
1818                 struct stat stbuf;
1819
1820                 if (tstat(target, &stbuf)) {
1821                         if (errno != ENOENT) {
1822                                 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1823                                                         target);
1824                                 ret = -1;
1825                                 goto out_wimlib_free;
1826                         }
1827                 } else {
1828                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1829                                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1830                 }
1831         }
1832 #endif
1833
1834         if (wim) {
1835                 ret = wimlib_extract_image(wim, image, target, extract_flags);
1836         } else {
1837                 set_fd_to_binary_mode(STDIN_FILENO);
1838                 ret = wimlib_extract_image_from_pipe_with_progress(
1839                                            STDIN_FILENO,
1840                                            image_num_or_name,
1841                                            target,
1842                                            extract_flags,
1843                                            imagex_progress_func,
1844                                            NULL);
1845         }
1846         if (ret == 0) {
1847                 imagex_printf(T("Done applying WIM image.\n"));
1848         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1849                 if (wim) {
1850                         do_resource_not_found_warning(wimfile, &info, &refglobs);
1851                 } else {
1852                         imagex_error(T(        "If you are applying an image "
1853                                                "from a split pipable WIM,\n"
1854                                        "       make sure you have "
1855                                        "concatenated together all parts."));
1856                 }
1857         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1858                 do_metadata_not_found_warning(wimfile, &info);
1859         }
1860 out_wimlib_free:
1861         wimlib_free(wim);
1862 out_free_refglobs:
1863         string_list_destroy(&refglobs);
1864         return ret;
1865
1866 out_usage:
1867         usage(CMD_APPLY, stderr);
1868         ret = -1;
1869         goto out_free_refglobs;
1870 }
1871
1872 /*
1873  * Create a WIM image from a directory tree, NTFS volume, or multiple files or
1874  * directory trees.  'wimcapture': create a new WIM file containing the desired
1875  * image.  'wimappend': add a new image to an existing WIM file; or, with
1876  * '--create' behave like 'wimcapture' if the WIM file doesn't exist.
1877  */
1878 static int
1879 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1880 {
1881         int c;
1882         bool create = false;
1883         bool appending = (cmd == CMD_APPEND);
1884         int open_flags = 0;
1885         int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1886                         WIMLIB_ADD_FLAG_WINCONFIG |
1887                         WIMLIB_ADD_FLAG_VERBOSE |
1888                         WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1889         int write_flags = 0;
1890         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1891         uint32_t chunk_size = UINT32_MAX;
1892         uint32_t solid_chunk_size = UINT32_MAX;
1893         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1894         const tchar *wimfile;
1895         int wim_fd;
1896         const tchar *name;
1897         STRING_LIST(image_properties);
1898
1899         WIMStruct *wim;
1900         STRING_LIST(base_wimfiles);
1901         WIMStruct **base_wims;
1902
1903         WIMStruct *template_wim = NULL;
1904         const tchar *template_wimfile = NULL;
1905         const tchar *template_image_name_or_num = NULL;
1906         int template_image = WIMLIB_NO_IMAGE;
1907
1908         int ret;
1909         unsigned num_threads = 0;
1910
1911         tchar *source;
1912         tchar *source_copy;
1913
1914         tchar *config_file = NULL;
1915
1916         bool source_list = false;
1917         size_t source_list_nchars = 0;
1918         tchar *source_list_contents;
1919         bool capture_sources_malloced;
1920         struct wimlib_capture_source *capture_sources;
1921         size_t num_sources;
1922         bool name_defaulted;
1923
1924         for_opt(c, capture_or_append_options) {
1925                 switch (c) {
1926                 case IMAGEX_BOOT_OPTION:
1927                         add_flags |= WIMLIB_ADD_FLAG_BOOT;
1928                         break;
1929                 case IMAGEX_CHECK_OPTION:
1930                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1931                         /* fall-through */
1932                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
1933                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1934                         break;
1935                 case IMAGEX_NOCHECK_OPTION:
1936                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1937                         break;
1938                 case IMAGEX_CONFIG_OPTION:
1939                         config_file = optarg;
1940                         add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1941                         break;
1942                 case IMAGEX_COMPRESS_OPTION:
1943                         compression_type = get_compression_type(optarg, false);
1944                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1945                                 goto out_err;
1946                         break;
1947                 case IMAGEX_CHUNK_SIZE_OPTION:
1948                         chunk_size = parse_chunk_size(optarg);
1949                         if (chunk_size == UINT32_MAX)
1950                                 goto out_err;
1951                         break;
1952                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1953                         solid_chunk_size = parse_chunk_size(optarg);
1954                         if (solid_chunk_size == UINT32_MAX)
1955                                 goto out_err;
1956                         break;
1957                 case IMAGEX_SOLID_COMPRESS_OPTION:
1958                         solid_ctype = get_compression_type(optarg, true);
1959                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1960                                 goto out_err;
1961                         break;
1962                 case IMAGEX_SOLID_OPTION:
1963                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1964                         break;
1965                 case IMAGEX_NO_SOLID_SORT_OPTION:
1966                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1967                         break;
1968                 case IMAGEX_FLAGS_OPTION: {
1969                         tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1970                         tsprintf(p, T("FLAGS=%"TS), optarg);
1971                         ret = string_list_append(&image_properties, p);
1972                         if (ret)
1973                                 goto out;
1974                         break;
1975                 }
1976                 case IMAGEX_IMAGE_PROPERTY_OPTION:
1977                         ret = append_image_property_argument(&image_properties);
1978                         if (ret)
1979                                 goto out;
1980                         break;
1981                 case IMAGEX_DEREFERENCE_OPTION:
1982                         add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1983                         break;
1984                 case IMAGEX_VERBOSE_OPTION:
1985                         /* No longer does anything.  */
1986                         break;
1987                 case IMAGEX_THREADS_OPTION:
1988                         num_threads = parse_num_threads(optarg);
1989                         if (num_threads == UINT_MAX)
1990                                 goto out_err;
1991                         break;
1992                 case IMAGEX_REBUILD_OPTION:
1993                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1994                         break;
1995                 case IMAGEX_UNIX_DATA_OPTION:
1996                         add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1997                         break;
1998                 case IMAGEX_SOURCE_LIST_OPTION:
1999                         source_list = true;
2000                         break;
2001                 case IMAGEX_NO_ACLS_OPTION:
2002                         add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
2003                         break;
2004                 case IMAGEX_STRICT_ACLS_OPTION:
2005                         add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
2006                         break;
2007                 case IMAGEX_RPFIX_OPTION:
2008                         add_flags |= WIMLIB_ADD_FLAG_RPFIX;
2009                         break;
2010                 case IMAGEX_NORPFIX_OPTION:
2011                         add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
2012                         break;
2013                 case IMAGEX_PIPABLE_OPTION:
2014                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2015                         break;
2016                 case IMAGEX_NOT_PIPABLE_OPTION:
2017                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2018                         break;
2019                 case IMAGEX_UPDATE_OF_OPTION:
2020                         if (template_image_name_or_num) {
2021                                 imagex_error(T("'--update-of' can only be "
2022                                                "specified one time!"));
2023                                 goto out_err;
2024                         } else {
2025                                 tchar *colon;
2026                                 colon = tstrrchr(optarg, T(':'));
2027
2028                                 if (colon) {
2029                                         template_wimfile = optarg;
2030                                         *colon = T('\0');
2031                                         template_image_name_or_num = colon + 1;
2032                                 } else {
2033                                         template_wimfile = NULL;
2034                                         template_image_name_or_num = optarg;
2035                                 }
2036                         }
2037                 #ifdef __WIN32__
2038                         imagex_printf(T("[WARNING] '--update-of' is unreliable on Windows!\n"));
2039                 #endif
2040                         break;
2041                 case IMAGEX_DELTA_FROM_OPTION:
2042                         ret = string_list_append(&base_wimfiles, optarg);
2043                         if (ret)
2044                                 goto out;
2045                         write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2046                         break;
2047                 case IMAGEX_WIMBOOT_OPTION:
2048                         add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2049                         break;
2050                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2051                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2052                         break;
2053                 case IMAGEX_SNAPSHOT_OPTION:
2054                         add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2055                         break;
2056                 case IMAGEX_CREATE_OPTION:
2057                         if (cmd == CMD_CAPTURE) {
2058                                 imagex_error(T("'--create' is only valid for 'wimappend', not 'wimcapture'"));
2059                                 goto out_err;
2060                         }
2061                         create = true;
2062                         break;
2063                 default:
2064                         goto out_usage;
2065                 }
2066         }
2067         argc -= optind;
2068         argv += optind;
2069
2070         if (argc < 2 || argc > 4)
2071                 goto out_usage;
2072
2073         source = argv[0];
2074         wimfile = argv[1];
2075
2076         /* Set default compression type and parameters.  */
2077
2078
2079         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2080                 /* No compression type specified.  Use the default.  */
2081
2082                 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2083                         /* With --wimboot, default to XPRESS compression.  */
2084                         compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2085                 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2086                         /* With --solid, default to LZMS compression.  (However,
2087                          * this will not affect solid resources!)  */
2088                         compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2089                 } else {
2090                         /* Otherwise, default to LZX compression.  */
2091                         compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2092                 }
2093         }
2094
2095         if (!tstrcmp(wimfile, T("-"))) {
2096                 /* Writing captured WIM to standard output.  */
2097                 if (create)
2098                         appending = false;
2099         #if 0
2100                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2101                         imagex_error("Can't write a non-pipable WIM to "
2102                                      "standard output!  Specify --pipable\n"
2103                                      "       if you want to create a pipable WIM "
2104                                      "(but read the docs first).");
2105                         goto out_err;
2106                 }
2107         #else
2108                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2109         #endif
2110                 if (appending) {
2111                         imagex_error(T("Using standard output for append does "
2112                                        "not make sense."));
2113                         goto out_err;
2114                 }
2115                 wim_fd = STDOUT_FILENO;
2116                 wimfile = NULL;
2117                 imagex_output_to_stderr();
2118                 set_fd_to_binary_mode(wim_fd);
2119         } else {
2120                 struct stat stbuf;
2121
2122                 /* Check for 'wimappend --create' acting as wimcapture */
2123                 if (create && tstat(wimfile, &stbuf) != 0 && errno == ENOENT) {
2124
2125                         appending = false;
2126
2127                         /* Ignore '--update-of' for the target WIMFILE */
2128                         if (template_image_name_or_num &&
2129                             (!template_wimfile ||
2130                              !tstrcmp(template_wimfile, wimfile)))
2131                         {
2132                                 template_image_name_or_num = NULL;
2133                                 template_wimfile = NULL;
2134                         }
2135                 }
2136         }
2137
2138         if ((write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) && !appending) {
2139                 imagex_error(T("'--unsafe-compact' is only valid for append!"));
2140                 goto out_err;
2141         }
2142
2143         /* If template image was specified using --update-of=IMAGE rather
2144          * than --update-of=WIMFILE:IMAGE, set the default WIMFILE.  */
2145         if (template_image_name_or_num && !template_wimfile) {
2146                 if (base_wimfiles.num_strings == 1) {
2147                         /* Capturing delta WIM based on single WIM:  default to
2148                          * base WIM.  */
2149                         template_wimfile = base_wimfiles.strings[0];
2150                 } else if (appending) {
2151                         /* Appending to WIM:  default to WIM being appended to.
2152                          */
2153                         template_wimfile = wimfile;
2154                 } else {
2155                         /* Capturing a normal (non-delta) WIM, so the WIM file
2156                          * *must* be explicitly specified.  */
2157                         if (base_wimfiles.num_strings > 1) {
2158                                 imagex_error(T("For capture of delta WIM "
2159                                                "based on multiple existing "
2160                                                "WIMs,\n"
2161                                                "      '--update-of' must "
2162                                                "specify WIMFILE:IMAGE!"));
2163                         } else {
2164                                 imagex_error(T("For capture of non-delta WIM, "
2165                                                "'--update-of' must specify "
2166                                                "WIMFILE:IMAGE!"));
2167                         }
2168                         goto out_usage;
2169                 }
2170         }
2171
2172         if (argc >= 3) {
2173                 name = argv[2];
2174                 name_defaulted = false;
2175         } else {
2176                 /* Set default name to SOURCE argument, omitting any directory
2177                  * prefixes and trailing slashes.  This requires making a copy
2178                  * of @source.  Leave some free characters at the end in case we
2179                  * append a number to keep the name unique. */
2180                 size_t source_name_len;
2181
2182                 source_name_len = tstrlen(source);
2183                 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2184                 name = tbasename(tstrcpy(source_copy, source));
2185                 name_defaulted = true;
2186         }
2187
2188         /* Image description (if given). */
2189         if (argc >= 4) {
2190                 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2191                 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2192                 ret = string_list_append(&image_properties, p);
2193                 if (ret)
2194                         goto out;
2195         }
2196
2197         if (source_list) {
2198                 /* Set up capture sources in source list mode */
2199                 if (source[0] == T('-') && source[1] == T('\0')) {
2200                         source_list_contents = stdin_get_text_contents(&source_list_nchars);
2201                 } else {
2202                         source_list_contents = file_get_text_contents(source,
2203                                                                       &source_list_nchars);
2204                 }
2205                 if (!source_list_contents)
2206                         goto out_err;
2207
2208                 capture_sources = parse_source_list(&source_list_contents,
2209                                                     source_list_nchars,
2210                                                     &num_sources);
2211                 if (!capture_sources) {
2212                         ret = -1;
2213                         goto out_free_source_list_contents;
2214                 }
2215                 capture_sources_malloced = true;
2216         } else {
2217                 /* Set up capture source in non-source-list mode.  */
2218                 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2219                 capture_sources[0].fs_source_path = source;
2220                 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2221                 capture_sources[0].reserved = 0;
2222                 num_sources = 1;
2223                 capture_sources_malloced = false;
2224                 source_list_contents = NULL;
2225         }
2226
2227         /* Open the existing WIM, or create a new one.  */
2228         if (appending) {
2229                 ret = wimlib_open_wim_with_progress(wimfile,
2230                                                     open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2231                                                     &wim,
2232                                                     imagex_progress_func,
2233                                                     NULL);
2234                 if (ret)
2235                         goto out_free_capture_sources;
2236         } else {
2237                 ret = wimlib_create_new_wim(compression_type, &wim);
2238                 if (ret)
2239                         goto out_free_capture_sources;
2240                 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2241         }
2242
2243         /* Set chunk size if non-default.  */
2244         if (chunk_size != UINT32_MAX) {
2245                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2246                 if (ret)
2247                         goto out_free_wim;
2248         } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2249
2250                 int ctype = compression_type;
2251
2252                 if (appending) {
2253                         struct wimlib_wim_info info;
2254                         wimlib_get_wim_info(wim, &info);
2255                         ctype = info.compression_type;
2256                 }
2257
2258                 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2259                         ret = wimlib_set_output_chunk_size(wim, 4096);
2260                         if (ret)
2261                                 goto out_free_wim;
2262                 }
2263         }
2264         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2265                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2266                 if (ret)
2267                         goto out_free_wim;
2268         }
2269         if (solid_chunk_size != UINT32_MAX) {
2270                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2271                 if (ret)
2272                         goto out_free_wim;
2273         }
2274
2275 #ifndef __WIN32__
2276         /* Detect if source is regular file or block device and set NTFS volume
2277          * capture mode.  */
2278         if (!source_list) {
2279                 struct stat stbuf;
2280
2281                 if (tstat(source, &stbuf) == 0) {
2282                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2283                                 imagex_printf(T("Capturing WIM image from NTFS "
2284                                           "filesystem on \"%"TS"\"\n"), source);
2285                                 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2286                         }
2287                 } else {
2288                         if (errno != ENOENT) {
2289                                 imagex_error_with_errno(T("Failed to stat "
2290                                                           "\"%"TS"\""), source);
2291                                 ret = -1;
2292                                 goto out_free_wim;
2293                         }
2294                 }
2295         }
2296 #endif
2297
2298         /* If the user did not specify an image name, and the basename of the
2299          * source already exists as an image name in the WIM file, append a
2300          * suffix to make it unique. */
2301         if (appending && name_defaulted) {
2302                 unsigned long conflict_idx;
2303                 tchar *name_end = tstrchr(name, T('\0'));
2304                 for (conflict_idx = 1;
2305                      wimlib_image_name_in_use(wim, name);
2306                      conflict_idx++)
2307                 {
2308                         tsprintf(name_end, T(" (%lu)"), conflict_idx);
2309                 }
2310         }
2311
2312         /* If capturing a delta WIM, reference resources from the base WIMs
2313          * before adding the new image.  */
2314         if (base_wimfiles.num_strings) {
2315                 base_wims = calloc(base_wimfiles.num_strings,
2316                                    sizeof(base_wims[0]));
2317                 if (base_wims == NULL) {
2318                         imagex_error(T("Out of memory!"));
2319                         ret = -1;
2320                         goto out_free_wim;
2321                 }
2322
2323                 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2324                         ret = wimlib_open_wim_with_progress(
2325                                     base_wimfiles.strings[i], open_flags,
2326                                     &base_wims[i], imagex_progress_func, NULL);
2327                         if (ret)
2328                                 goto out_free_base_wims;
2329
2330                 }
2331
2332                 ret = wimlib_reference_resources(wim, base_wims,
2333                                                  base_wimfiles.num_strings, 0);
2334                 if (ret)
2335                         goto out_free_base_wims;
2336
2337                 if (base_wimfiles.num_strings == 1) {
2338                         imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2339                                       base_wimfiles.strings[0]);
2340                 } else {
2341                         imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2342                                       base_wimfiles.num_strings);
2343                 }
2344
2345         } else {
2346                 base_wims = NULL;
2347         }
2348
2349         /* If capturing or appending as an update of an existing (template) image,
2350          * open the WIM if needed and parse the image index.  */
2351         if (template_image_name_or_num) {
2352
2353                 if (appending && !tstrcmp(template_wimfile, wimfile)) {
2354                         template_wim = wim;
2355                 } else {
2356                         for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2357                                 if (!tstrcmp(template_wimfile,
2358                                              base_wimfiles.strings[i])) {
2359                                         template_wim = base_wims[i];
2360                                         break;
2361                                 }
2362                         }
2363                 }
2364
2365                 if (!template_wim) {
2366                         ret = wimlib_open_wim_with_progress(template_wimfile,
2367                                                             open_flags,
2368                                                             &template_wim,
2369                                                             imagex_progress_func,
2370                                                             NULL);
2371                         if (ret)
2372                                 goto out_free_base_wims;
2373                 }
2374
2375                 template_image = wimlib_resolve_image(template_wim,
2376                                                       template_image_name_or_num);
2377
2378                 if (template_image_name_or_num[0] == T('-')) {
2379                         tchar *tmp;
2380                         unsigned long n;
2381                         struct wimlib_wim_info info;
2382
2383                         wimlib_get_wim_info(template_wim, &info);
2384                         n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2385                         if (n >= 1 && n <= info.image_count &&
2386                             *tmp == T('\0') &&
2387                             tmp != template_image_name_or_num + 1)
2388                         {
2389                                 template_image = info.image_count - (n - 1);
2390                         }
2391                 }
2392                 ret = verify_image_exists_and_is_single(template_image,
2393                                                         template_image_name_or_num,
2394                                                         template_wimfile);
2395                 if (ret)
2396                         goto out_free_template_wim;
2397         }
2398
2399         ret = wimlib_add_image_multisource(wim,
2400                                            capture_sources,
2401                                            num_sources,
2402                                            name,
2403                                            config_file,
2404                                            add_flags);
2405         if (ret)
2406                 goto out_free_template_wim;
2407
2408         if (image_properties.num_strings || template_image_name_or_num) {
2409                 /* User asked to set additional image properties, or an image on
2410                  * which the added one is to be based has been specified with
2411                  * --update-of.  */
2412                 struct wimlib_wim_info info;
2413
2414                 wimlib_get_wim_info(wim, &info);
2415
2416                 ret = apply_image_properties(&image_properties, wim,
2417                                              info.image_count, NULL);
2418                 if (ret)
2419                         goto out_free_template_wim;
2420
2421                 /* Reference template image if the user provided one.  */
2422                 if (template_image_name_or_num) {
2423                         imagex_printf(T("Using image %d "
2424                                         "from \"%"TS"\" as template\n"),
2425                                         template_image, template_wimfile);
2426                         ret = wimlib_reference_template_image(wim,
2427                                                               info.image_count,
2428                                                               template_wim,
2429                                                               template_image,
2430                                                               0);
2431                         if (ret)
2432                                 goto out_free_template_wim;
2433                 }
2434         }
2435
2436         /* Write the new WIM or overwrite the existing WIM with the new image
2437          * appended.  */
2438         if (appending) {
2439                 ret = wimlib_overwrite(wim, write_flags, num_threads);
2440         } else if (wimfile) {
2441                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2442                                    write_flags, num_threads);
2443         } else {
2444                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2445                                          write_flags, num_threads);
2446         }
2447 out_free_template_wim:
2448         /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
2449         if (template_wim == wim)
2450                 goto out_free_base_wims;
2451         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2452                 if (template_wim == base_wims[i])
2453                         goto out_free_base_wims;
2454         wimlib_free(template_wim);
2455 out_free_base_wims:
2456         for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2457                 wimlib_free(base_wims[i]);
2458         free(base_wims);
2459 out_free_wim:
2460         wimlib_free(wim);
2461 out_free_capture_sources:
2462         if (capture_sources_malloced)
2463                 free(capture_sources);
2464 out_free_source_list_contents:
2465         free(source_list_contents);
2466 out:
2467         string_list_destroy(&image_properties);
2468         string_list_destroy(&base_wimfiles);
2469         return ret;
2470
2471 out_usage:
2472         usage(cmd, stderr);
2473 out_err:
2474         ret = -1;
2475         goto out;
2476 }
2477
2478 /* Remove image(s) from a WIM. */
2479 static int
2480 imagex_delete(int argc, tchar **argv, int cmd)
2481 {
2482         int c;
2483         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2484         int write_flags = 0;
2485         const tchar *wimfile;
2486         const tchar *image_num_or_name;
2487         WIMStruct *wim;
2488         int image;
2489         int ret;
2490
2491         for_opt(c, delete_options) {
2492                 switch (c) {
2493                 case IMAGEX_CHECK_OPTION:
2494                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2495                         /* fall-through */
2496                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2497                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2498                         break;
2499                 case IMAGEX_SOFT_OPTION:
2500                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2501                         break;
2502                 case IMAGEX_UNSAFE_COMPACT_OPTION:
2503                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2504                         break;
2505                 default:
2506                         goto out_usage;
2507                 }
2508         }
2509         argc -= optind;
2510         argv += optind;
2511
2512         if (argc != 2) {
2513                 if (argc < 1)
2514                         imagex_error(T("Must specify a WIM file"));
2515                 if (argc < 2)
2516                         imagex_error(T("Must specify an image"));
2517                 goto out_usage;
2518         }
2519         wimfile = argv[0];
2520         image_num_or_name = argv[1];
2521
2522         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2523                                             imagex_progress_func, NULL);
2524         if (ret)
2525                 goto out;
2526
2527         image = wimlib_resolve_image(wim, image_num_or_name);
2528
2529         ret = verify_image_exists(image, image_num_or_name, wimfile);
2530         if (ret)
2531                 goto out_wimlib_free;
2532
2533         ret = wimlib_delete_image(wim, image);
2534         if (ret) {
2535                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2536                              wimfile);
2537                 goto out_wimlib_free;
2538         }
2539
2540         ret = wimlib_overwrite(wim, write_flags, 0);
2541         if (ret) {
2542                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2543                                "deleted"), wimfile);
2544         }
2545 out_wimlib_free:
2546         wimlib_free(wim);
2547 out:
2548         return ret;
2549
2550 out_usage:
2551         usage(CMD_DELETE, stderr);
2552         ret = -1;
2553         goto out;
2554 }
2555
2556 struct print_dentry_options {
2557         bool detailed;
2558 };
2559
2560 static void
2561 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2562 {
2563         tprintf(T("%"TS"\n"), dentry->full_path);
2564 }
2565
2566 static const struct {
2567         uint32_t flag;
2568         const tchar *name;
2569 } file_attr_flags[] = {
2570         {WIMLIB_FILE_ATTRIBUTE_READONLY,            T("READONLY")},
2571         {WIMLIB_FILE_ATTRIBUTE_HIDDEN,              T("HIDDEN")},
2572         {WIMLIB_FILE_ATTRIBUTE_SYSTEM,              T("SYSTEM")},
2573         {WIMLIB_FILE_ATTRIBUTE_DIRECTORY,           T("DIRECTORY")},
2574         {WIMLIB_FILE_ATTRIBUTE_ARCHIVE,             T("ARCHIVE")},
2575         {WIMLIB_FILE_ATTRIBUTE_DEVICE,              T("DEVICE")},
2576         {WIMLIB_FILE_ATTRIBUTE_NORMAL,              T("NORMAL")},
2577         {WIMLIB_FILE_ATTRIBUTE_TEMPORARY,           T("TEMPORARY")},
2578         {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE,         T("SPARSE_FILE")},
2579         {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT,       T("REPARSE_POINT")},
2580         {WIMLIB_FILE_ATTRIBUTE_COMPRESSED,          T("COMPRESSED")},
2581         {WIMLIB_FILE_ATTRIBUTE_OFFLINE,             T("OFFLINE")},
2582         {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2583         {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED,           T("ENCRYPTED")},
2584         {WIMLIB_FILE_ATTRIBUTE_VIRTUAL,             T("VIRTUAL")},
2585 };
2586
2587 #define TIMESTR_MAX 100
2588
2589 static void
2590 print_time(const tchar *type, const struct wimlib_timespec *wts,
2591            int32_t high_part)
2592 {
2593         tchar timestr[TIMESTR_MAX];
2594         time_t t;
2595         struct tm tm;
2596
2597         if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2598                 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2599         else
2600                 t = wts->tv_sec;
2601
2602         gmtime_r(&t, &tm);
2603         tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2604         timestr[TIMESTR_MAX - 1] = '\0';
2605
2606         tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2607 }
2608
2609 static void print_byte_field(const uint8_t field[], size_t len)
2610 {
2611         while (len--)
2612                 tprintf(T("%02hhx"), *field++);
2613 }
2614
2615 static void
2616 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2617 {
2618         tchar attr_string[256];
2619         tchar *p;
2620
2621         tputs(T("WIM Information:"));
2622         tputs(T("----------------"));
2623         tprintf(T("Path:           %"TS"\n"), wimfile);
2624         tprintf(T("GUID:           0x"));
2625         print_byte_field(info->guid, sizeof(info->guid));
2626         tputchar(T('\n'));
2627         tprintf(T("Version:        %u\n"), info->wim_version);
2628         tprintf(T("Image Count:    %d\n"), info->image_count);
2629         tprintf(T("Compression:    %"TS"\n"),
2630                 wimlib_get_compression_type_string(info->compression_type));
2631         tprintf(T("Chunk Size:     %"PRIu32" bytes\n"),
2632                 info->chunk_size);
2633         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2634         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2635         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2636
2637         attr_string[0] = T('\0');
2638
2639         if (info->pipable)
2640                 tstrcat(attr_string, T("Pipable, "));
2641
2642         if (info->has_integrity_table)
2643                 tstrcat(attr_string, T("Integrity info, "));
2644
2645         if (info->has_rpfix)
2646                 tstrcat(attr_string, T("Relative path junction, "));
2647
2648         if (info->resource_only)
2649                 tstrcat(attr_string, T("Resource only, "));
2650
2651         if (info->metadata_only)
2652                 tstrcat(attr_string, T("Metadata only, "));
2653
2654         if (info->is_marked_readonly)
2655                 tstrcat(attr_string, T("Readonly, "));
2656
2657         p = tstrchr(attr_string, T('\0'));
2658         if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2659                 p[-2] = T('\0');
2660
2661         tprintf(T("Attributes:     %"TS"\n\n"), attr_string);
2662 }
2663
2664 static int
2665 print_resource(const struct wimlib_resource_entry *resource,
2666                void *_ignore)
2667 {
2668         tprintf(T("Hash              = 0x"));
2669         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2670         tputchar(T('\n'));
2671
2672         if (!resource->is_missing) {
2673                 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2674                         resource->uncompressed_size);
2675                 if (resource->packed) {
2676                         tprintf(T("Solid resource    = %"PRIu64" => %"PRIu64" "
2677                                   "bytes @ offset %"PRIu64"\n"),
2678                                 resource->raw_resource_uncompressed_size,
2679                                 resource->raw_resource_compressed_size,
2680                                 resource->raw_resource_offset_in_wim);
2681
2682                         tprintf(T("Solid offset      = %"PRIu64" bytes\n"),
2683                                 resource->offset);
2684                 } else {
2685                         tprintf(T("Compressed size   = %"PRIu64" bytes\n"),
2686                                 resource->compressed_size);
2687
2688                         tprintf(T("Offset in WIM     = %"PRIu64" bytes\n"),
2689                                 resource->offset);
2690                 }
2691
2692                 tprintf(T("Part Number       = %u\n"), resource->part_number);
2693                 tprintf(T("Reference Count   = %u\n"), resource->reference_count);
2694
2695                 tprintf(T("Flags             = "));
2696                 if (resource->is_compressed)
2697                         tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2698                 if (resource->is_metadata)
2699                         tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2700                 if (resource->is_free)
2701                         tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2702                 if (resource->is_spanned)
2703                         tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2704                 if (resource->packed)
2705                         tprintf(T("WIM_RESHDR_FLAG_SOLID  "));
2706                 tputchar(T('\n'));
2707         }
2708         tputchar(T('\n'));
2709         return 0;
2710 }
2711
2712 static void
2713 print_blobs(WIMStruct *wim)
2714 {
2715         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2716 }
2717
2718 #ifndef __WIN32__
2719 static void
2720 default_print_security_descriptor(const uint8_t *sd, size_t size)
2721 {
2722         tprintf(T("Security Descriptor = "));
2723         print_byte_field(sd, size);
2724         tputchar(T('\n'));
2725 }
2726 #endif
2727
2728 static bool
2729 is_null_guid(const uint8_t *guid)
2730 {
2731         static const uint8_t null_guid[WIMLIB_GUID_LEN];
2732
2733         return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2734 }
2735
2736 static void
2737 print_guid(const tchar *label, const uint8_t *guid)
2738 {
2739         if (is_null_guid(guid))
2740                 return;
2741         tprintf(T("%-20"TS"= 0x"), label);
2742         print_byte_field(guid, WIMLIB_GUID_LEN);
2743         tputchar(T('\n'));
2744 }
2745
2746 static void
2747 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2748 {
2749         tprintf(T(
2750 "----------------------------------------------------------------------------\n"));
2751         tprintf(T("Full Path           = \"%"TS"\"\n"), dentry->full_path);
2752         if (dentry->dos_name)
2753                 tprintf(T("Short Name          = \"%"TS"\"\n"), dentry->dos_name);
2754         tprintf(T("Attributes          = 0x%08x\n"), dentry->attributes);
2755         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2756                 if (file_attr_flags[i].flag & dentry->attributes)
2757                         tprintf(T("    FILE_ATTRIBUTE_%"TS" is set\n"),
2758                                 file_attr_flags[i].name);
2759
2760         if (dentry->security_descriptor) {
2761                 print_security_descriptor(dentry->security_descriptor,
2762                                           dentry->security_descriptor_size);
2763         }
2764
2765         print_time(T("Creation Time"),
2766                    &dentry->creation_time, dentry->creation_time_high);
2767         print_time(T("Last Write Time"),
2768                    &dentry->last_write_time, dentry->last_write_time_high);
2769         print_time(T("Last Access Time"),
2770                    &dentry->last_access_time, dentry->last_access_time_high);
2771
2772
2773         if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2774                 tprintf(T("Reparse Tag         = 0x%"PRIx32"\n"), dentry->reparse_tag);
2775
2776         tprintf(T("Link Group ID       = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2777         tprintf(T("Link Count          = %"PRIu32"\n"), dentry->num_links);
2778
2779         if (dentry->unix_mode != 0) {
2780                 tprintf(T("UNIX Data           = uid:%"PRIu32" gid:%"PRIu32" "
2781                           "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2782                         dentry->unix_uid, dentry->unix_gid,
2783                         dentry->unix_mode, dentry->unix_rdev);
2784         }
2785
2786         if (!is_null_guid(dentry->object_id.object_id)) {
2787                 print_guid(T("Object ID"), dentry->object_id.object_id);
2788                 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2789                 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2790                 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2791         }
2792
2793         for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2794                 if (dentry->streams[i].stream_name) {
2795                         tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2796                                 dentry->streams[i].stream_name);
2797                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2798                         tprintf(T("\tRaw encrypted data stream:\n"));
2799                 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2800                         tprintf(T("\tReparse point stream:\n"));
2801                 } else {
2802                         tprintf(T("\tUnnamed data stream:\n"));
2803                 }
2804                 print_resource(&dentry->streams[i].resource, NULL);
2805         }
2806 }
2807
2808 static int
2809 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2810 {
2811         const struct print_dentry_options *options = _options;
2812         if (!options->detailed)
2813                 print_dentry_full_path(dentry);
2814         else
2815                 print_dentry_detailed(dentry);
2816         return 0;
2817 }
2818
2819 /* Print the files contained in an image(s) in a WIM file. */
2820 static int
2821 imagex_dir(int argc, tchar **argv, int cmd)
2822 {
2823         const tchar *wimfile;
2824         WIMStruct *wim = NULL;
2825         int image;
2826         int ret;
2827         const tchar *path = WIMLIB_WIM_ROOT_PATH;
2828         int c;
2829         struct print_dentry_options options = {
2830                 .detailed = false,
2831         };
2832         int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2833
2834         STRING_LIST(refglobs);
2835
2836         for_opt(c, dir_options) {
2837                 switch (c) {
2838                 case IMAGEX_PATH_OPTION:
2839                         path = optarg;
2840                         break;
2841                 case IMAGEX_DETAILED_OPTION:
2842                         options.detailed = true;
2843                         break;
2844                 case IMAGEX_ONE_FILE_ONLY_OPTION:
2845                         iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2846                         break;
2847                 case IMAGEX_REF_OPTION:
2848                         ret = string_list_append(&refglobs, optarg);
2849                         if (ret)
2850                                 goto out_free_refglobs;
2851                         break;
2852                 default:
2853                         goto out_usage;
2854                 }
2855         }
2856         argc -= optind;
2857         argv += optind;
2858
2859         if (argc < 1) {
2860                 imagex_error(T("Must specify a WIM file"));
2861                 goto out_usage;
2862         }
2863         if (argc > 2) {
2864                 imagex_error(T("Too many arguments"));
2865                 goto out_usage;
2866         }
2867
2868         wimfile = argv[0];
2869         ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2870                                             imagex_progress_func, NULL);
2871         if (ret)
2872                 goto out_free_refglobs;
2873
2874         if (argc >= 2) {
2875                 image = wimlib_resolve_image(wim, argv[1]);
2876                 ret = verify_image_exists(image, argv[1], wimfile);
2877                 if (ret)
2878                         goto out_wimlib_free;
2879         } else {
2880                 /* No image specified; default to image 1, but only if the WIM
2881                  * contains exactly one image.  */
2882
2883                 struct wimlib_wim_info info;
2884
2885                 wimlib_get_wim_info(wim, &info);
2886                 if (info.image_count != 1) {
2887                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2888                                        "select one (or all)."),
2889                                      wimfile, info.image_count);
2890                         wimlib_free(wim);
2891                         goto out_usage;
2892                 }
2893                 image = 1;
2894         }
2895
2896         if (refglobs.num_strings) {
2897                 ret = wim_reference_globs(wim, &refglobs, 0);
2898                 if (ret)
2899                         goto out_wimlib_free;
2900         }
2901
2902         ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2903                                       print_dentry, &options);
2904         if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2905                 struct wimlib_wim_info info;
2906
2907                 wimlib_get_wim_info(wim, &info);
2908                 do_metadata_not_found_warning(wimfile, &info);
2909         }
2910 out_wimlib_free:
2911         wimlib_free(wim);
2912 out_free_refglobs:
2913         string_list_destroy(&refglobs);
2914         return ret;
2915
2916 out_usage:
2917         usage(CMD_DIR, stderr);
2918         ret = -1;
2919         goto out_free_refglobs;
2920 }
2921
2922 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2923  * WIM file. */
2924 static int
2925 imagex_export(int argc, tchar **argv, int cmd)
2926 {
2927         int c;
2928         int open_flags = 0;
2929         int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2930         int write_flags = 0;
2931         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2932         const tchar *src_wimfile;
2933         const tchar *src_image_num_or_name;
2934         const tchar *dest_wimfile;
2935         int dest_wim_fd;
2936         const tchar *dest_name;
2937         const tchar *dest_desc;
2938         WIMStruct *src_wim;
2939         struct wimlib_wim_info src_info;
2940         WIMStruct *dest_wim;
2941         int ret;
2942         int image;
2943         struct stat stbuf;
2944         bool wim_is_new;
2945         STRING_LIST(refglobs);
2946         unsigned num_threads = 0;
2947         uint32_t chunk_size = UINT32_MAX;
2948         uint32_t solid_chunk_size = UINT32_MAX;
2949         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2950
2951         for_opt(c, export_options) {
2952                 switch (c) {
2953                 case IMAGEX_BOOT_OPTION:
2954                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2955                         break;
2956                 case IMAGEX_CHECK_OPTION:
2957                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2958                         /* fall-through */
2959                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2960                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2961                         break;
2962                 case IMAGEX_NOCHECK_OPTION:
2963                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2964                         break;
2965                 case IMAGEX_COMPRESS_OPTION:
2966                         compression_type = get_compression_type(optarg, false);
2967                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2968                                 goto out_err;
2969                         break;
2970                 case IMAGEX_RECOMPRESS_OPTION:
2971                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2972                         break;
2973                 case IMAGEX_SOLID_OPTION:
2974                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2975                         break;
2976                 case IMAGEX_NO_SOLID_SORT_OPTION:
2977                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2978                         break;
2979                 case IMAGEX_CHUNK_SIZE_OPTION:
2980                         chunk_size = parse_chunk_size(optarg);
2981                         if (chunk_size == UINT32_MAX)
2982                                 goto out_err;
2983                         break;
2984                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2985                         solid_chunk_size = parse_chunk_size(optarg);
2986                         if (solid_chunk_size == UINT32_MAX)
2987                                 goto out_err;
2988                         break;
2989                 case IMAGEX_SOLID_COMPRESS_OPTION:
2990                         solid_ctype = get_compression_type(optarg, true);
2991                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2992                                 goto out_err;
2993                         break;
2994                 case IMAGEX_REF_OPTION:
2995                         ret = string_list_append(&refglobs, optarg);
2996                         if (ret)
2997                                 goto out_free_refglobs;
2998                         break;
2999                 case IMAGEX_THREADS_OPTION:
3000                         num_threads = parse_num_threads(optarg);
3001                         if (num_threads == UINT_MAX)
3002                                 goto out_err;
3003                         break;
3004                 case IMAGEX_REBUILD_OPTION:
3005                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3006                         break;
3007                 case IMAGEX_PIPABLE_OPTION:
3008                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3009                         break;
3010                 case IMAGEX_NOT_PIPABLE_OPTION:
3011                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3012                         break;
3013                 case IMAGEX_WIMBOOT_OPTION:
3014                         export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
3015                         break;
3016                 case IMAGEX_UNSAFE_COMPACT_OPTION:
3017                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3018                         break;
3019                 default:
3020                         goto out_usage;
3021                 }
3022         }
3023         argc -= optind;
3024         argv += optind;
3025         if (argc < 3 || argc > 5)
3026                 goto out_usage;
3027         src_wimfile           = argv[0];
3028         src_image_num_or_name = argv[1];
3029         dest_wimfile          = argv[2];
3030         dest_name             = (argc >= 4) ? argv[3] : NULL;
3031         dest_desc             = (argc >= 5) ? argv[4] : NULL;
3032         ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
3033                                             imagex_progress_func, NULL);
3034         if (ret)
3035                 goto out_free_refglobs;
3036
3037         wimlib_get_wim_info(src_wim, &src_info);
3038
3039         /* Determine if the destination is an existing file or not.  If so, we
3040          * try to append the exported image(s) to it; otherwise, we create a new
3041          * WIM containing the exported image(s).  Furthermore, determine if we
3042          * need to write a pipable WIM directly to standard output.  */
3043
3044         if (tstrcmp(dest_wimfile, T("-")) == 0) {
3045         #if 0
3046                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
3047                         imagex_error("Can't write a non-pipable WIM to "
3048                                      "standard output!  Specify --pipable\n"
3049                                      "       if you want to create a pipable WIM "
3050                                      "(but read the docs first).");
3051                         ret = -1;
3052                         goto out_free_src_wim;
3053                 }
3054         #else
3055                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3056         #endif
3057                 dest_wimfile = NULL;
3058                 dest_wim_fd = STDOUT_FILENO;
3059                 imagex_output_to_stderr();
3060                 set_fd_to_binary_mode(dest_wim_fd);
3061         }
3062         errno = ENOENT;
3063         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
3064                 wim_is_new = false;
3065                 /* Destination file exists. */
3066
3067                 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
3068                         imagex_error(T("\"%"TS"\" is not a regular file "
3069                                        "or block device"), dest_wimfile);
3070                         ret = -1;
3071                         goto out_free_src_wim;
3072                 }
3073                 ret = wimlib_open_wim_with_progress(dest_wimfile,
3074                                                     open_flags |
3075                                                         WIMLIB_OPEN_FLAG_WRITE_ACCESS,
3076                                                     &dest_wim,
3077                                                     imagex_progress_func,
3078                                                     NULL);
3079                 if (ret)
3080                         goto out_free_src_wim;
3081
3082                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3083                         /* The user specified a compression type, but we're
3084                          * exporting to an existing WIM.  Make sure the
3085                          * specified compression type is the same as the
3086                          * compression type of the existing destination WIM. */
3087                         struct wimlib_wim_info dest_info;
3088
3089                         wimlib_get_wim_info(dest_wim, &dest_info);
3090                         if (compression_type != dest_info.compression_type) {
3091                                 imagex_error(T("Cannot specify a compression type that is "
3092                                                "not the same as that used in the "
3093                                                "destination WIM"));
3094                                 ret = -1;
3095                                 goto out_free_dest_wim;
3096                         }
3097                 }
3098         } else {
3099                 wim_is_new = true;
3100
3101                 if (errno != ENOENT) {
3102                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3103                                                 dest_wimfile);
3104                         ret = -1;
3105                         goto out_free_src_wim;
3106                 }
3107
3108                 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3109                         imagex_error(T("'--unsafe-compact' is only valid when "
3110                                        "exporting to an existing WIM file!"));
3111                         ret = -1;
3112                         goto out_free_src_wim;
3113                 }
3114
3115                 /* dest_wimfile is not an existing file, so create a new WIM. */
3116
3117                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3118                         /* The user did not specify a compression type; default
3119                          * to that of the source WIM, unless --solid or
3120                          * --wimboot was specified.   */
3121
3122                         if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3123                                 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3124                         else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3125                                 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3126                         else
3127                                 compression_type = src_info.compression_type;
3128                 }
3129                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3130                 if (ret)
3131                         goto out_free_src_wim;
3132
3133                 wimlib_register_progress_function(dest_wim,
3134                                                   imagex_progress_func, NULL);
3135
3136                 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3137                     && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3138                 {
3139                         /* For --wimboot export, use small XPRESS chunks.  */
3140                         wimlib_set_output_chunk_size(dest_wim, 4096);
3141                 } else if (compression_type == src_info.compression_type &&
3142                            chunk_size == UINT32_MAX)
3143                 {
3144                         /* Use same chunk size if compression type is the same.  */
3145                         wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3146                 }
3147         }
3148
3149         if (chunk_size != UINT32_MAX) {
3150                 /* Set destination chunk size.  */
3151                 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3152                 if (ret)
3153                         goto out_free_dest_wim;
3154         }
3155         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3156                 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3157                 if (ret)
3158                         goto out_free_dest_wim;
3159         }
3160         if (solid_chunk_size != UINT32_MAX) {
3161                 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3162                 if (ret)
3163                         goto out_free_dest_wim;
3164         }
3165
3166         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3167         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3168         if (ret)
3169                 goto out_free_dest_wim;
3170
3171         if (refglobs.num_strings) {
3172                 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3173                 if (ret)
3174                         goto out_free_dest_wim;
3175         }
3176
3177         if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3178             image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3179         {
3180                 imagex_error(T("--boot specified for all-images export, but source WIM "
3181                                "has no bootable image."));
3182                 ret = -1;
3183                 goto out_free_dest_wim;
3184         }
3185
3186         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3187                                   dest_desc, export_flags);
3188         if (ret) {
3189                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3190                         do_resource_not_found_warning(src_wimfile,
3191                                                       &src_info, &refglobs);
3192                 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3193                         do_metadata_not_found_warning(src_wimfile, &src_info);
3194                 }
3195                 goto out_free_dest_wim;
3196         }
3197
3198         if (!wim_is_new)
3199                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3200         else if (dest_wimfile)
3201                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3202                                    write_flags, num_threads);
3203         else
3204                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3205                                          WIMLIB_ALL_IMAGES, write_flags,
3206                                          num_threads);
3207 out_free_dest_wim:
3208         wimlib_free(dest_wim);
3209 out_free_src_wim:
3210         wimlib_free(src_wim);
3211 out_free_refglobs:
3212         string_list_destroy(&refglobs);
3213         return ret;
3214
3215 out_usage:
3216         usage(CMD_EXPORT, stderr);
3217 out_err:
3218         ret = -1;
3219         goto out_free_refglobs;
3220 }
3221
3222 /* Extract files or directories from a WIM image */
3223 static int
3224 imagex_extract(int argc, tchar **argv, int cmd)
3225 {
3226         int c;
3227         int open_flags = 0;
3228         int image;
3229         WIMStruct *wim;
3230         int ret;
3231         const tchar *wimfile;
3232         const tchar *image_num_or_name;
3233         tchar *dest_dir = T(".");
3234         int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3235                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3236                             WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3237         int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3238
3239         STRING_LIST(refglobs);
3240
3241         tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3242
3243         for_opt(c, extract_options) {
3244                 switch (c) {
3245                 case IMAGEX_CHECK_OPTION:
3246                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3247                         break;
3248                 case IMAGEX_VERBOSE_OPTION:
3249                         /* No longer does anything.  */
3250                         break;
3251                 case IMAGEX_REF_OPTION:
3252                         ret = string_list_append(&refglobs, optarg);
3253                         if (ret)
3254                                 goto out_free_refglobs;
3255                         break;
3256                 case IMAGEX_UNIX_DATA_OPTION:
3257                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3258                         break;
3259                 case IMAGEX_NO_ACLS_OPTION:
3260                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3261                         break;
3262                 case IMAGEX_STRICT_ACLS_OPTION:
3263                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3264                         break;
3265                 case IMAGEX_NO_ATTRIBUTES_OPTION:
3266                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3267                         break;
3268                 case IMAGEX_DEST_DIR_OPTION:
3269                         dest_dir = optarg;
3270                         break;
3271                 case IMAGEX_TO_STDOUT_OPTION:
3272                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3273                         imagex_suppress_output();
3274                         set_fd_to_binary_mode(STDOUT_FILENO);
3275                         break;
3276                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3277                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3278                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3279                         break;
3280                 case IMAGEX_NO_GLOBS_OPTION:
3281                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3282                         break;
3283                 case IMAGEX_NULLGLOB_OPTION:
3284                         extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3285                         break;
3286                 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3287                         notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3288                         break;
3289                 case IMAGEX_WIMBOOT_OPTION:
3290                         extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3291                         break;
3292                 case IMAGEX_COMPACT_OPTION:
3293                         ret = set_compact_mode(optarg, &extract_flags);
3294                         if (ret)
3295                                 goto out_free_refglobs;
3296                         break;
3297                 default:
3298                         goto out_usage;
3299                 }
3300         }
3301         argc -= optind;
3302         argv += optind;
3303
3304         if (argc < 2)
3305                 goto out_usage;
3306
3307         if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3308                                WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3309         {
3310                 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3311                 goto out_err;
3312         }
3313
3314         wimfile = argv[0];
3315         image_num_or_name = argv[1];
3316
3317         argc -= 2;
3318         argv += 2;
3319
3320         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3321                                             imagex_progress_func, NULL);
3322         if (ret)
3323                 goto out_free_refglobs;
3324
3325         image = wimlib_resolve_image(wim, image_num_or_name);
3326         ret = verify_image_exists_and_is_single(image,
3327                                                 image_num_or_name,
3328                                                 wimfile);
3329         if (ret)
3330                 goto out_wimlib_free;
3331
3332         if (refglobs.num_strings) {
3333                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3334                 if (ret)
3335                         goto out_wimlib_free;
3336         }
3337
3338         if (argc == 0) {
3339                 argv = &root_path;
3340                 argc = 1;
3341                 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3342         }
3343
3344         while (argc != 0 && ret == 0) {
3345                 int num_paths;
3346
3347                 for (num_paths = 0;
3348                      num_paths < argc && argv[num_paths][0] != T('@');
3349                      num_paths++)
3350                         ;
3351
3352                 if (num_paths) {
3353                         ret = wimlib_extract_paths(wim, image, dest_dir,
3354                                                    (const tchar **)argv,
3355                                                    num_paths,
3356                                                    extract_flags | notlist_extract_flags);
3357                         argc -= num_paths;
3358                         argv += num_paths;
3359                 } else {
3360                         const tchar *listfile = argv[0] + 1;
3361
3362                         if (!tstrcmp(listfile, T("-"))) {
3363                                 tputs(T("Reading pathlist file from standard input..."));
3364                                 listfile = NULL;
3365                         }
3366
3367                         ret = wimlib_extract_pathlist(wim, image, dest_dir,
3368                                                       listfile, extract_flags);
3369                         argc--;
3370                         argv++;
3371                 }
3372         }
3373
3374         if (ret == 0) {
3375                 imagex_printf(T("Done extracting files.\n"));
3376         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3377                 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3378                                       WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3379                         == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3380                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3381                 {
3382                         tfprintf(stderr,
3383                                  T("Note: You can use the '--nullglob' "
3384                                    "option to ignore missing files.\n"));
3385                 }
3386                 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3387                                    "files and directories\n"
3388                                    "      are in the WIM image.\n"),
3389                                 get_cmd_string(CMD_DIR, false));
3390         } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3391                 struct wimlib_wim_info info;
3392
3393                 wimlib_get_wim_info(wim, &info);
3394                 do_resource_not_found_warning(wimfile, &info, &refglobs);
3395         } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3396                 struct wimlib_wim_info info;
3397
3398                 wimlib_get_wim_info(wim, &info);
3399                 do_metadata_not_found_warning(wimfile, &info);
3400         }
3401 out_wimlib_free:
3402         wimlib_free(wim);
3403 out_free_refglobs:
3404         string_list_destroy(&refglobs);
3405         return ret;
3406
3407 out_usage:
3408         usage(CMD_EXTRACT, stderr);
3409 out_err:
3410         ret = -1;
3411         goto out_free_refglobs;
3412 }
3413
3414 /* Prints information about a WIM file; also can mark an image as bootable,
3415  * change the name of an image, or change the description of an image. */
3416 static int
3417 imagex_info(int argc, tchar **argv, int cmd)
3418 {
3419         int c;
3420         bool boot         = false;
3421         bool header       = false;
3422         bool blobs        = false;
3423         bool xml          = false;
3424         bool short_header = true;
3425         const tchar *xml_out_file = NULL;
3426         const tchar *wimfile;
3427         const tchar *image_num_or_name;
3428         STRING_LIST(image_properties);
3429         WIMStruct *wim;
3430         int image;
3431         int ret;
3432         int open_flags = 0;
3433         int write_flags = 0;
3434         struct wimlib_wim_info info;
3435
3436         for_opt(c, info_options) {
3437                 switch (c) {
3438                 case IMAGEX_BOOT_OPTION:
3439                         boot = true;
3440                         break;
3441                 case IMAGEX_CHECK_OPTION:
3442                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3443                         /* fall-through */
3444                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3445                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3446                         break;
3447                 case IMAGEX_NOCHECK_OPTION:
3448                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3449                         break;
3450                 case IMAGEX_HEADER_OPTION:
3451                         header = true;
3452                         short_header = false;
3453                         break;
3454                 case IMAGEX_BLOBS_OPTION:
3455                         blobs = true;
3456                         short_header = false;
3457                         break;
3458                 case IMAGEX_XML_OPTION:
3459                         xml = true;
3460                         short_header = false;
3461                         break;
3462                 case IMAGEX_EXTRACT_XML_OPTION:
3463                         xml_out_file = optarg;
3464                         short_header = false;
3465                         break;
3466                 case IMAGEX_IMAGE_PROPERTY_OPTION:
3467                         ret = append_image_property_argument(&image_properties);
3468                         if (ret)
3469                                 goto out;
3470                         break;
3471                 default:
3472                         goto out_usage;
3473                 }
3474         }
3475
3476         argc -= optind;
3477         argv += optind;
3478         if (argc < 1 || argc > 4)
3479                 goto out_usage;
3480
3481         wimfile           = argv[0];
3482         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3483
3484         if (argc >= 3) {
3485                 /* NEW_NAME */
3486                 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3487                 tsprintf(p, T("NAME=%"TS), argv[2]);
3488                 ret = string_list_append(&image_properties, p);
3489                 if (ret)
3490                         goto out;
3491         }
3492
3493         if (argc >= 4) {
3494                 /* NEW_DESC */
3495                 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3496                 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3497                 ret = string_list_append(&image_properties, p);
3498                 if (ret)
3499                         goto out;
3500         }
3501
3502         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3503                                             imagex_progress_func, NULL);
3504         if (ret)
3505                 goto out;
3506
3507         wimlib_get_wim_info(wim, &info);
3508
3509         image = wimlib_resolve_image(wim, image_num_or_name);
3510         ret = WIMLIB_ERR_INVALID_IMAGE;
3511         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3512                 verify_image_exists(image, image_num_or_name, wimfile);
3513                 if (boot) {
3514                         imagex_error(T("If you would like to set the boot "
3515                                        "index to 0, specify image \"0\" with "
3516                                        "the --boot flag."));
3517                 }
3518                 goto out_wimlib_free;
3519         }
3520
3521         if (boot && info.image_count == 0) {
3522                 imagex_error(T("--boot is meaningless on a WIM with no images"));
3523                 goto out_wimlib_free;
3524         }
3525
3526         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3527                 if (boot) {
3528                         imagex_error(T("Cannot specify the --boot flag "
3529                                        "without specifying a specific "
3530                                        "image in a multi-image WIM"));
3531                         goto out_wimlib_free;
3532                 }
3533                 if (image_properties.num_strings) {
3534                         imagex_error(T("Can't change image properties without "
3535                                        "specifying a specific image in a "
3536                                        "multi-image WIM"));
3537                         goto out_wimlib_free;
3538                 }
3539         }
3540
3541         /* Operations that print information are separated from operations that
3542          * recreate the WIM file. */
3543         if (!image_properties.num_strings && !boot) {
3544
3545                 /* Read-only operations */
3546
3547                 if (image == WIMLIB_NO_IMAGE) {
3548                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3549                                      image_num_or_name, wimfile);
3550                         goto out_wimlib_free;
3551                 }
3552
3553                 if (image == WIMLIB_ALL_IMAGES && short_header)
3554                         print_wim_information(wimfile, &info);
3555
3556                 if (header)
3557                         wimlib_print_header(wim);
3558
3559                 if (blobs) {
3560                         if (info.total_parts != 1) {
3561                                 tfprintf(stderr, T("Warning: Only showing the blobs "
3562                                                    "for part %d of a %d-part WIM.\n"),
3563                                          info.part_number, info.total_parts);
3564                         }
3565                         print_blobs(wim);
3566                 }
3567
3568                 if (xml) {
3569                         ret = wimlib_extract_xml_data(wim, stdout);
3570                         if (ret)
3571                                 goto out_wimlib_free;
3572                 }
3573
3574                 if (xml_out_file) {
3575                         FILE *fp;
3576
3577                         fp = tfopen(xml_out_file, T("wb"));
3578                         if (!fp) {
3579                                 imagex_error_with_errno(T("Failed to open the "
3580                                                           "file \"%"TS"\" for "
3581                                                           "writing"),
3582                                                         xml_out_file);
3583                                 ret = -1;
3584                                 goto out_wimlib_free;
3585                         }
3586                         ret = wimlib_extract_xml_data(wim, fp);
3587                         if (fclose(fp)) {
3588                                 imagex_error(T("Failed to close the file "
3589                                                "\"%"TS"\""),
3590                                              xml_out_file);
3591                                 ret = -1;
3592                         }
3593                         if (ret)
3594                                 goto out_wimlib_free;
3595                 }
3596
3597                 if (short_header)
3598                         wimlib_print_available_images(wim, image);
3599
3600                 ret = 0;
3601         } else {
3602                 /* Modification operations */
3603                 bool any_property_changes;
3604
3605                 if (image == WIMLIB_ALL_IMAGES)
3606                         image = 1;
3607
3608                 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3609                         imagex_error(T("Cannot change image properties "
3610                                        "when using image 0"));
3611                         ret = -1;
3612                         goto out_wimlib_free;
3613                 }
3614
3615                 if (boot) {
3616                         if (image == info.boot_index) {
3617                                 imagex_printf(T("Image %d is already marked as "
3618                                           "bootable.\n"), image);
3619                                 boot = false;
3620                         } else {
3621                                 imagex_printf(T("Marking image %d as bootable.\n"),
3622                                         image);
3623                                 info.boot_index = image;
3624                                 ret = wimlib_set_wim_info(wim, &info,
3625                                                           WIMLIB_CHANGE_BOOT_INDEX);
3626                                 if (ret)
3627                                         goto out_wimlib_free;
3628                         }
3629                 }
3630
3631                 ret = apply_image_properties(&image_properties, wim, image,
3632                                              &any_property_changes);
3633                 if (ret)
3634                         goto out_wimlib_free;
3635
3636                 /* Only call wimlib_overwrite() if something actually needs to
3637                  * be changed.  */
3638                 if (boot || any_property_changes ||
3639                     ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
3640                      !info.has_integrity_table) ||
3641                     ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
3642                      info.has_integrity_table))
3643                 {
3644                         ret = wimlib_overwrite(wim, write_flags, 1);
3645                 } else {
3646                         imagex_printf(T("The file \"%"TS"\" was not modified "
3647                                         "because nothing needed to be done.\n"),
3648                                       wimfile);
3649                         ret = 0;
3650                 }
3651         }
3652 out_wimlib_free:
3653         wimlib_free(wim);
3654 out:
3655         string_list_destroy(&image_properties);
3656         return ret;
3657
3658 out_usage:
3659         usage(CMD_INFO, stderr);
3660         ret = -1;
3661         goto out;
3662 }
3663
3664 /* Join split WIMs into one part WIM */
3665 static int
3666 imagex_join(int argc, tchar **argv, int cmd)
3667 {
3668         int c;
3669         int swm_open_flags = 0;
3670         int wim_write_flags = 0;
3671         const tchar *output_path;
3672         int ret;
3673
3674         for_opt(c, join_options) {
3675                 switch (c) {
3676                 case IMAGEX_CHECK_OPTION:
3677                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3678                         /* fall-through */
3679                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3680                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3681                         break;
3682                 default:
3683                         goto out_usage;
3684                 }
3685         }
3686         argc -= optind;
3687         argv += optind;
3688
3689         if (argc < 2) {
3690                 imagex_error(T("Must specify one or more split WIM (.swm) "
3691                                "parts to join"));
3692                 goto out_usage;
3693         }
3694         output_path = argv[0];
3695         ret = wimlib_join_with_progress((const tchar * const *)++argv,
3696                                         --argc,
3697                                         output_path,
3698                                         swm_open_flags,
3699                                         wim_write_flags,
3700                                         imagex_progress_func,
3701                                         NULL);
3702 out:
3703         return ret;
3704
3705 out_usage:
3706         usage(CMD_JOIN, stderr);
3707         ret = -1;
3708         goto out;
3709 }
3710
3711 #if WIM_MOUNTING_SUPPORTED
3712
3713 /* Mounts a WIM image.  */
3714 static int
3715 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3716 {
3717         int c;
3718         int mount_flags = 0;
3719         int open_flags = 0;
3720         const tchar *staging_dir = NULL;
3721         const tchar *wimfile;
3722         const tchar *dir;
3723         WIMStruct *wim;
3724         struct wimlib_wim_info info;
3725         int image;
3726         int ret;
3727
3728         STRING_LIST(refglobs);
3729
3730         if (cmd == CMD_MOUNTRW) {
3731                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3732                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3733         }
3734
3735         for_opt(c, mount_options) {
3736                 switch (c) {
3737                 case IMAGEX_ALLOW_OTHER_OPTION:
3738                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3739                         break;
3740                 case IMAGEX_CHECK_OPTION:
3741                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3742                         break;
3743                 case IMAGEX_DEBUG_OPTION:
3744                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3745                         break;
3746                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3747                         if (!tstrcasecmp(optarg, T("none")))
3748                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3749                         else if (!tstrcasecmp(optarg, T("xattr")))
3750                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3751                         else if (!tstrcasecmp(optarg, T("windows")))
3752                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3753                         else {
3754                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3755                                              optarg);
3756                                 goto out_usage;
3757                         }
3758                         break;
3759                 case IMAGEX_REF_OPTION:
3760                         ret = string_list_append(&refglobs, optarg);
3761                         if (ret)
3762                                 goto out_free_refglobs;
3763                         break;
3764                 case IMAGEX_STAGING_DIR_OPTION:
3765                         staging_dir = optarg;
3766                         break;
3767                 case IMAGEX_UNIX_DATA_OPTION:
3768                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3769                         break;
3770                 default:
3771                         goto out_usage;
3772                 }
3773         }
3774         argc -= optind;
3775         argv += optind;
3776         if (argc != 2 && argc != 3)
3777                 goto out_usage;
3778
3779         wimfile = argv[0];
3780
3781         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3782                                             imagex_progress_func, NULL);
3783         if (ret)
3784                 goto out_free_refglobs;
3785
3786         wimlib_get_wim_info(wim, &info);
3787
3788         if (argc >= 3) {
3789                 /* Image explicitly specified.  */
3790                 image = wimlib_resolve_image(wim, argv[1]);
3791                 dir = argv[2];
3792                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3793                 if (ret)
3794                         goto out_free_wim;
3795         } else {
3796                 /* No image specified; default to image 1, but only if the WIM
3797                  * contains exactly one image.  */
3798
3799                 if (info.image_count != 1) {
3800                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3801                                        "select one."), wimfile, info.image_count);
3802                         wimlib_free(wim);
3803                         goto out_usage;
3804                 }
3805                 image = 1;
3806                 dir = argv[1];
3807         }
3808
3809         if (refglobs.num_strings) {
3810                 ret = wim_reference_globs(wim, &refglobs, open_flags);
3811                 if (ret)
3812                         goto out_free_wim;
3813         }
3814
3815         ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3816         if (ret) {
3817                 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3818                         do_metadata_not_found_warning(wimfile, &info);
3819                 } else {
3820                         imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3821                                        "on \"%"TS"\""),
3822                                      image, wimfile, dir);
3823                 }
3824         }
3825 out_free_wim:
3826         wimlib_free(wim);
3827 out_free_refglobs:
3828         string_list_destroy(&refglobs);
3829         return ret;
3830
3831 out_usage:
3832         usage(cmd, stderr);
3833         ret = -1;
3834         goto out_free_refglobs;
3835 }
3836 #endif /* WIM_MOUNTING_SUPPORTED */
3837
3838 /* Rebuild a WIM file */
3839 static int
3840 imagex_optimize(int argc, tchar **argv, int cmd)
3841 {
3842         int c;
3843         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3844         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3845         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3846         uint32_t chunk_size = UINT32_MAX;
3847         uint32_t solid_chunk_size = UINT32_MAX;
3848         int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3849         int ret;
3850         WIMStruct *wim;
3851         const tchar *wimfile;
3852         off_t old_size;
3853         off_t new_size;
3854         unsigned num_threads = 0;
3855
3856         for_opt(c, optimize_options) {
3857                 switch (c) {
3858                 case IMAGEX_CHECK_OPTION:
3859                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3860                         /* fall-through */
3861                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3862                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3863                         break;
3864                 case IMAGEX_NOCHECK_OPTION:
3865                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3866                         break;
3867                 case IMAGEX_COMPRESS_OPTION:
3868                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3869                         compression_type = get_compression_type(optarg, false);
3870                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3871                                 goto out_err;
3872                         break;
3873                 case IMAGEX_RECOMPRESS_OPTION:
3874                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3875                         break;
3876                 case IMAGEX_CHUNK_SIZE_OPTION:
3877                         chunk_size = parse_chunk_size(optarg);
3878                         if (chunk_size == UINT32_MAX)
3879                                 goto out_err;
3880                         break;
3881                 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3882                         solid_chunk_size = parse_chunk_size(optarg);
3883                         if (solid_chunk_size == UINT32_MAX)
3884                                 goto out_err;
3885                         break;
3886                 case IMAGEX_SOLID_COMPRESS_OPTION:
3887                         solid_ctype = get_compression_type(optarg, true);
3888                         if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3889                                 goto out_err;
3890                         break;
3891                 case IMAGEX_SOLID_OPTION:
3892                         write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3893                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3894                         break;
3895                 case IMAGEX_NO_SOLID_SORT_OPTION:
3896                         write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3897                         break;
3898                 case IMAGEX_THREADS_OPTION:
3899                         num_threads = parse_num_threads(optarg);
3900                         if (num_threads == UINT_MAX)
3901                                 goto out_err;
3902                         break;
3903                 case IMAGEX_PIPABLE_OPTION:
3904                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3905                         break;
3906                 case IMAGEX_NOT_PIPABLE_OPTION:
3907                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3908                         break;
3909                 case IMAGEX_UNSAFE_COMPACT_OPTION:
3910                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3911                         break;
3912                 default:
3913                         goto out_usage;
3914                 }
3915         }
3916         argc -= optind;
3917         argv += optind;
3918
3919         if (argc != 1)
3920                 goto out_usage;
3921
3922         wimfile = argv[0];
3923
3924         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3925                                             imagex_progress_func, NULL);
3926         if (ret)
3927                 goto out;
3928
3929         if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3930                 /* Change compression type.  */
3931                 ret = wimlib_set_output_compression_type(wim, compression_type);
3932                 if (ret)
3933                         goto out_wimlib_free;
3934         }
3935
3936         if (chunk_size != UINT32_MAX) {
3937                 /* Change chunk size.  */
3938                 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3939                 if (ret)
3940                         goto out_wimlib_free;
3941         }
3942         if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3943                 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3944                 if (ret)
3945                         goto out_wimlib_free;
3946         }
3947         if (solid_chunk_size != UINT32_MAX) {
3948                 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3949                 if (ret)
3950                         goto out_wimlib_free;
3951         }
3952
3953         old_size = file_get_size(wimfile);
3954         tprintf(T("\"%"TS"\" original size: "), wimfile);
3955         if (old_size == -1)
3956                 tputs(T("Unknown"));
3957         else
3958                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3959
3960         ret = wimlib_overwrite(wim, write_flags, num_threads);
3961         if (ret) {
3962                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3963                 goto out_wimlib_free;
3964         }
3965
3966         new_size = file_get_size(wimfile);
3967         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3968         if (new_size == -1)
3969                 tputs(T("Unknown"));
3970         else
3971                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3972
3973         tfputs(T("Space saved: "), stdout);
3974         if (new_size != -1 && old_size != -1) {
3975                 tprintf(T("%lld KiB\n"),
3976                        ((long long)old_size - (long long)new_size) >> 10);
3977         } else {
3978                 tputs(T("Unknown"));
3979         }
3980         ret = 0;
3981 out_wimlib_free:
3982         wimlib_free(wim);
3983 out:
3984         return ret;
3985
3986 out_usage:
3987         usage(CMD_OPTIMIZE, stderr);
3988 out_err:
3989         ret = -1;
3990         goto out;
3991 }
3992
3993 /* Split a WIM into a spanned set */
3994 static int
3995 imagex_split(int argc, tchar **argv, int cmd)
3996 {
3997         int c;
3998         int open_flags = 0;
3999         int write_flags = 0;
4000         unsigned long part_size;
4001         tchar *tmp;
4002         int ret;
4003         WIMStruct *wim;
4004
4005         for_opt(c, split_options) {
4006                 switch (c) {
4007                 case IMAGEX_CHECK_OPTION:
4008                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4009                         /* fall-through */
4010                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4011                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4012                         break;
4013                 default:
4014                         goto out_usage;
4015                 }
4016         }
4017         argc -= optind;
4018         argv += optind;
4019
4020         if (argc != 3)
4021                 goto out_usage;
4022
4023         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
4024         if (tmp == argv[2] || *tmp) {
4025                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
4026                 imagex_error(T("The part size must be an integer or "
4027                                "floating-point number of megabytes."));
4028                 goto out_err;
4029         }
4030         ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
4031                                             imagex_progress_func, NULL);
4032         if (ret)
4033                 goto out;
4034
4035         ret = wimlib_split(wim, argv[1], part_size, write_flags);
4036         tprintf(T("\nFinished splitting \"%"TS"\"\n"), argv[0]);
4037         wimlib_free(wim);
4038 out:
4039         return ret;
4040
4041 out_usage:
4042         usage(CMD_SPLIT, stderr);
4043 out_err:
4044         ret = -1;
4045         goto out;
4046 }
4047
4048 #if WIM_MOUNTING_SUPPORTED
4049 /* Unmounts a mounted WIM image. */
4050 static int
4051 imagex_unmount(int argc, tchar **argv, int cmd)
4052 {
4053         int c;
4054         int unmount_flags = 0;
4055         int ret;
4056
4057         for_opt(c, unmount_options) {
4058                 switch (c) {
4059                 case IMAGEX_COMMIT_OPTION:
4060                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
4061                         break;
4062                 case IMAGEX_CHECK_OPTION:
4063                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
4064                         break;
4065                 case IMAGEX_REBUILD_OPTION:
4066                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
4067                         break;
4068                 case IMAGEX_LAZY_OPTION:
4069                 case IMAGEX_FORCE_OPTION:
4070                         /* Now, unmount is lazy by default.  However, committing
4071                          * the image will fail with
4072                          * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
4073                          * file descriptors on the WIM image.  The
4074                          * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
4075                          * descriptors to be closed.  */
4076                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4077                         break;
4078                 case IMAGEX_NEW_IMAGE_OPTION:
4079                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4080                         break;
4081                 default:
4082                         goto out_usage;
4083                 }
4084         }
4085         argc -= optind;
4086         argv += optind;
4087         if (argc != 1)
4088                 goto out_usage;
4089
4090         if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4091                 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4092                         imagex_error(T("--new-image is meaningless "
4093                                        "without --commit also specified!"));
4094                         goto out_err;
4095                 }
4096         }
4097
4098         ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4099                                                  imagex_progress_func, NULL);
4100         if (ret) {
4101                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4102                 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4103                         imagex_printf(T(
4104                                 "\tNote: Use --commit --force to force changes "
4105                                         "to be committed, regardless\n"
4106                                 "\t      of open files.\n"));
4107                 }
4108         }
4109 out:
4110         return ret;
4111
4112 out_usage:
4113         usage(CMD_UNMOUNT, stderr);
4114 out_err:
4115         ret = -1;
4116         goto out;
4117 }
4118 #endif /* WIM_MOUNTING_SUPPORTED */
4119
4120 /*
4121  * Add, delete, or rename files in a WIM image.
4122  */
4123 static int
4124 imagex_update(int argc, tchar **argv, int cmd)
4125 {
4126         const tchar *wimfile;
4127         int image;
4128         WIMStruct *wim;
4129         int ret;
4130         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4131         int write_flags = 0;
4132         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4133         int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4134                                 WIMLIB_ADD_FLAG_VERBOSE |
4135                                 WIMLIB_ADD_FLAG_WINCONFIG;
4136         int default_delete_flags = 0;
4137         unsigned num_threads = 0;
4138         int c;
4139         tchar *cmd_file_contents;
4140         size_t cmd_file_nchars;
4141         struct wimlib_update_command *cmds;
4142         size_t num_cmds;
4143         tchar *command_str = NULL;
4144         tchar *config_file = NULL;
4145         tchar *wimboot_config = NULL;
4146
4147         for_opt(c, update_options) {
4148                 switch (c) {
4149                 /* Generic or write options */
4150                 case IMAGEX_THREADS_OPTION:
4151                         num_threads = parse_num_threads(optarg);
4152                         if (num_threads == UINT_MAX)
4153                                 goto out_err;
4154                         break;
4155                 case IMAGEX_CHECK_OPTION:
4156                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4157                         /* fall-through */
4158                 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4159                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4160                         break;
4161                 case IMAGEX_REBUILD_OPTION:
4162                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4163                         break;
4164                 case IMAGEX_COMMAND_OPTION:
4165                         if (command_str) {
4166                                 imagex_error(T("--command may only be specified "
4167                                                "one time.  Please provide\n"
4168                                                "       the update commands "
4169                                                "on standard input instead."));
4170                                 goto out_err;
4171                         }
4172                         command_str = tstrdup(optarg);
4173                         if (!command_str) {
4174                                 imagex_error(T("Out of memory!"));
4175                                 goto out_err;
4176                         }
4177                         break;
4178                 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4179                         wimboot_config = optarg;
4180                         break;
4181                 /* Default delete options */
4182                 case IMAGEX_FORCE_OPTION:
4183                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4184                         break;
4185                 case IMAGEX_RECURSIVE_OPTION:
4186                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4187                         break;
4188
4189                 /* Global add option */
4190                 case IMAGEX_CONFIG_OPTION:
4191                         default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4192                         config_file = optarg;
4193                         break;
4194
4195                 /* Default add options */
4196                 case IMAGEX_VERBOSE_OPTION:
4197                         /* No longer does anything.  */
4198                         break;
4199                 case IMAGEX_DEREFERENCE_OPTION:
4200                         default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4201                         break;
4202                 case IMAGEX_UNIX_DATA_OPTION:
4203                         default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4204                         break;
4205                 case IMAGEX_NO_ACLS_OPTION:
4206                         default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4207                         break;
4208                 case IMAGEX_STRICT_ACLS_OPTION:
4209                         default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4210                         break;
4211                 case IMAGEX_NO_REPLACE_OPTION:
4212                         default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4213                         break;
4214                 case IMAGEX_UNSAFE_COMPACT_OPTION:
4215                         write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4216                         break;
4217                 default:
4218                         goto out_usage;
4219                 }
4220         }
4221         argv += optind;
4222         argc -= optind;
4223
4224         if (argc != 1 && argc != 2)
4225                 goto out_usage;
4226         wimfile = argv[0];
4227
4228         ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4229                                             imagex_progress_func, NULL);
4230         if (ret)
4231                 goto out_free_command_str;
4232
4233         if (argc >= 2) {
4234                 /* Image explicitly specified.  */
4235                 image = wimlib_resolve_image(wim, argv[1]);
4236                 ret = verify_image_exists_and_is_single(image, argv[1],
4237                                                         wimfile);
4238                 if (ret)
4239                         goto out_wimlib_free;
4240         } else {
4241                 /* No image specified; default to image 1, but only if the WIM
4242                  * contains exactly one image.  */
4243                 struct wimlib_wim_info info;
4244
4245                 wimlib_get_wim_info(wim, &info);
4246                 if (info.image_count != 1) {
4247                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4248                                      wimfile, info.image_count);
4249                         wimlib_free(wim);
4250                         goto out_usage;
4251                 }
4252                 image = 1;
4253         }
4254
4255         /* Read update commands from standard input, or the command string if
4256          * specified.  */
4257         if (command_str) {
4258                 cmd_file_contents = NULL;
4259                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4260                                                  &num_cmds);
4261                 if (!cmds) {
4262                         ret = -1;
4263                         goto out_free_cmd_file_contents;
4264                 }
4265         } else if (!wimboot_config) {
4266                 if (isatty(STDIN_FILENO)) {
4267                         tputs(T("Reading update commands from standard input..."));
4268                         recommend_man_page(CMD_UPDATE, stdout);
4269                 }
4270                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4271                 if (!cmd_file_contents) {
4272                         ret = -1;
4273                         goto out_wimlib_free;
4274                 }
4275
4276                 /* Parse the update commands */
4277                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4278                                                  &num_cmds);
4279                 if (!cmds) {
4280                         ret = -1;
4281                         goto out_free_cmd_file_contents;
4282                 }
4283         } else {
4284                 cmd_file_contents = NULL;
4285                 cmds = NULL;
4286                 num_cmds = 0;
4287         }
4288
4289         /* Set default flags and capture config on the update commands */
4290         for (size_t i = 0; i < num_cmds; i++) {
4291                 switch (cmds[i].op) {
4292                 case WIMLIB_UPDATE_OP_ADD:
4293                         cmds[i].add.add_flags |= default_add_flags;
4294                         cmds[i].add.config_file = config_file;
4295                         break;
4296                 case WIMLIB_UPDATE_OP_DELETE:
4297                         cmds[i].delete_.delete_flags |= default_delete_flags;
4298                         break;
4299                 default:
4300                         break;
4301                 }
4302         }
4303
4304         /* Execute the update commands */
4305         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4306         if (ret)
4307                 goto out_free_cmds;
4308
4309         if (wimboot_config) {
4310                 /* --wimboot-config=FILE is short for an
4311                  * "add FILE /Windows/System32/WimBootCompress.ini" command.
4312                  */
4313                 struct wimlib_update_command cmd;
4314
4315                 cmd.op = WIMLIB_UPDATE_OP_ADD;
4316                 cmd.add.fs_source_path = wimboot_config;
4317                 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4318                 cmd.add.config_file = NULL;
4319                 cmd.add.add_flags = 0;
4320
4321                 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4322                 if (ret)
4323                         goto out_free_cmds;
4324         }
4325
4326         /* Overwrite the updated WIM */
4327         ret = wimlib_overwrite(wim, write_flags, num_threads);
4328 out_free_cmds:
4329         free(cmds);
4330 out_free_cmd_file_contents:
4331         free(cmd_file_contents);
4332 out_wimlib_free:
4333         wimlib_free(wim);
4334 out_free_command_str:
4335         free(command_str);
4336         return ret;
4337
4338 out_usage:
4339         usage(CMD_UPDATE, stderr);
4340 out_err:
4341         ret = -1;
4342         goto out_free_command_str;
4343 }
4344
4345 /* Verify a WIM file.  */
4346 static int
4347 imagex_verify(int argc, tchar **argv, int cmd)
4348 {
4349         int ret;
4350         const tchar *wimfile;
4351         WIMStruct *wim;
4352         int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4353         int verify_flags = 0;
4354         STRING_LIST(refglobs);
4355         int c;
4356
4357         for_opt(c, verify_options) {
4358                 switch (c) {
4359                 case IMAGEX_REF_OPTION:
4360                         ret = string_list_append(&refglobs, optarg);
4361                         if (ret)
4362                                 goto out_free_refglobs;
4363                         break;
4364                 case IMAGEX_NOCHECK_OPTION:
4365                         open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4366                         break;
4367                 default:
4368                         goto out_usage;
4369                 }
4370         }
4371
4372         argv += optind;
4373         argc -= optind;
4374
4375         if (argc != 1) {
4376                 if (argc == 0)
4377                         imagex_error(T("Must specify a WIM file!"));
4378                 else
4379                         imagex_error(T("At most one WIM file can be specified!"));
4380                 goto out_usage;
4381         }
4382
4383         wimfile = argv[0];
4384
4385         ret = wimlib_open_wim_with_progress(wimfile,
4386                                             open_flags,
4387                                             &wim,
4388                                             imagex_progress_func,
4389                                             NULL);
4390         if (ret)
4391                 goto out_free_refglobs;
4392
4393         ret = wim_reference_globs(wim, &refglobs, open_flags);
4394         if (ret)
4395                 goto out_wimlib_free;
4396
4397         ret = wimlib_verify_wim(wim, verify_flags);
4398         if (ret) {
4399                 tputc(T('\n'), stderr);
4400                 imagex_error(T("\"%"TS"\" failed verification!"),
4401                              wimfile);
4402                 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4403                     refglobs.num_strings == 0)
4404                 {
4405                         imagex_printf(T("Note: if this WIM file is not standalone, "
4406                                         "use the --ref option to specify the other parts.\n"));
4407                 }
4408         } else {
4409                 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4410                               wimfile);
4411         }
4412
4413 out_wimlib_free:
4414         wimlib_free(wim);
4415 out_free_refglobs:
4416         string_list_destroy(&refglobs);
4417         return ret;
4418
4419 out_usage:
4420         usage(CMD_VERIFY, stderr);
4421         ret = -1;
4422         goto out_free_refglobs;
4423 }
4424
4425 struct imagex_command {
4426         const tchar *name;
4427         int (*func)(int argc, tchar **argv, int cmd);
4428 };
4429
4430 static const struct imagex_command imagex_commands[] = {
4431         [CMD_APPEND]   = {T("append"),   imagex_capture_or_append},
4432         [CMD_APPLY]    = {T("apply"),    imagex_apply},
4433         [CMD_CAPTURE]  = {T("capture"),  imagex_capture_or_append},
4434         [CMD_DELETE]   = {T("delete"),   imagex_delete},
4435         [CMD_DIR ]     = {T("dir"),      imagex_dir},
4436         [CMD_EXPORT]   = {T("export"),   imagex_export},
4437         [CMD_EXTRACT]  = {T("extract"),  imagex_extract},
4438         [CMD_INFO]     = {T("info"),     imagex_info},
4439         [CMD_JOIN]     = {T("join"),     imagex_join},
4440 #if WIM_MOUNTING_SUPPORTED
4441         [CMD_MOUNT]    = {T("mount"),    imagex_mount_rw_or_ro},
4442         [CMD_MOUNTRW]  = {T("mountrw"),  imagex_mount_rw_or_ro},
4443 #endif
4444         [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4445         [CMD_SPLIT]    = {T("split"),    imagex_split},
4446 #if WIM_MOUNTING_SUPPORTED
4447         [CMD_UNMOUNT]  = {T("unmount"),  imagex_unmount},
4448 #endif
4449         [CMD_UPDATE]   = {T("update"),   imagex_update},
4450         [CMD_VERIFY]   = {T("verify"),   imagex_verify},
4451 };
4452
4453 #ifdef __WIN32__
4454
4455    /* Can be a directory or source list file.  But source list file is probably
4456     * a rare use case, so just say directory.  */
4457 #  define SOURCE_STR T("DIRECTORY")
4458
4459    /* Can only be a directory  */
4460 #  define TARGET_STR T("DIRECTORY")
4461
4462 #else
4463    /* Can be a directory, NTFS volume, or source list file. */
4464 #  define SOURCE_STR T("SOURCE")
4465
4466    /* Can be a directory or NTFS volume.  */
4467 #  define TARGET_STR T("TARGET")
4468
4469 #endif
4470
4471 static const tchar * const usage_strings[] = {
4472 [CMD_APPEND] =
4473 T(
4474 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4475 "                    [--boot] [--check] [--nocheck] [--config=FILE]\n"
4476 "                    [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4477 "                    [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4478 "                    [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4479 "                    [--dereference] [--snapshot] [--create]\n"
4480 ),
4481 [CMD_APPLY] =
4482 T(
4483 "    %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4484 "                    [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4485 "                    [--no-attributes] [--rpfix] [--norpfix]\n"
4486 "                    [--include-invalid-names] [--wimboot] [--unix-data]\n"
4487 "                    [--compact=FORMAT]\n"
4488 ),
4489 [CMD_CAPTURE] =
4490 T(
4491 "    %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4492 "                    [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4493 "                    [--config=FILE] [--threads=NUM_THREADS]\n"
4494 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4495 "                    [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4496 "                    [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4497 "                    [--snapshot]\n"
4498 ),
4499 [CMD_DELETE] =
4500 T(
4501 "    %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4502 ),
4503 [CMD_DIR] =
4504 T(
4505 "    %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4506 ),
4507 [CMD_EXPORT] =
4508 T(
4509 "    %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4510 "                        [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4511 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4512 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4513 "                    [--wimboot] [--solid]\n"
4514 ),
4515 [CMD_EXTRACT] =
4516 T(
4517 "    %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4518 "                    [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4519 "                    [--to-stdout] [--no-acls] [--strict-acls]\n"
4520 "                    [--no-attributes] [--include-invalid-names]\n"
4521 "                    [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4522 ),
4523 [CMD_INFO] =
4524 T(
4525 "    %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4526 "                    [--boot] [--check] [--nocheck] [--xml]\n"
4527 "                    [--extract-xml FILE] [--header] [--blobs]\n"
4528 "                    [--image-property NAME=VALUE]\n"
4529 ),
4530 [CMD_JOIN] =
4531 T(
4532 "    %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4533 ),
4534 #if WIM_MOUNTING_SUPPORTED
4535 [CMD_MOUNT] =
4536 T(
4537 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4538 "                    [--check] [--streams-interface=INTERFACE]\n"
4539 "                    [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4540 ),
4541 [CMD_MOUNTRW] =
4542 T(
4543 "    %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4544 "                    [--check] [--streams-interface=INTERFACE]\n"
4545 "                    [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4546 ),
4547 #endif
4548 [CMD_OPTIMIZE] =
4549 T(
4550 "    %"TS" WIMFILE\n"
4551 "                    [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4552 "                    [--check] [--nocheck] [--solid]\n"
4553 "\n"
4554 ),
4555 [CMD_SPLIT] =
4556 T(
4557 "    %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4558 ),
4559 #if WIM_MOUNTING_SUPPORTED
4560 [CMD_UNMOUNT] =
4561 T(
4562 "    %"TS" DIRECTORY\n"
4563 "                    [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4564 ),
4565 #endif
4566 [CMD_UPDATE] =
4567 T(
4568 "    %"TS" WIMFILE [IMAGE]\n"
4569 "                    [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4570 "                    [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4571 "                    [--command=STRING] [--wimboot-config=FILE]\n"
4572 "                    [< CMDFILE]\n"
4573 ),
4574 [CMD_VERIFY] =
4575 T(
4576 "    %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4577 ),
4578 };
4579
4580 static const tchar *invocation_name;
4581 static int invocation_cmd = CMD_NONE;
4582
4583 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4584 {
4585         static tchar buf[50];
4586
4587         if (cmd == CMD_NONE)
4588                 return T("wimlib-imagex");
4589
4590         if (only_short_form || invocation_cmd != CMD_NONE) {
4591                 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4592         } else {
4593                 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4594                          imagex_commands[cmd].name);
4595         }
4596         return buf;
4597 }
4598
4599 static void
4600 version(void)
4601 {
4602         static const tchar * const fmt =
4603         T(
4604 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4605 "Copyright (C) 2012-2018 Eric Biggers\n"
4606 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4607 "This is free software: you are free to change and redistribute it.\n"
4608 "There is NO WARRANTY, to the extent permitted by law.\n"
4609 "\n"
4610 "Report bugs to "PACKAGE_BUGREPORT".\n"
4611         );
4612         tfprintf(stdout, fmt, wimlib_get_version_string());
4613 }
4614
4615 static void
4616 do_common_options(int *argc_p, tchar **argv, int cmd)
4617 {
4618         int argc = *argc_p;
4619         int i;
4620         const tchar *p;
4621
4622         for (i = 1; i < argc; i++) {
4623                 p = argv[i];
4624                 if (p[0] == T('-') && p[1] == T('-')) {
4625                         p += 2;
4626                         if (!tstrcmp(p, T("help"))) {
4627                                 if (cmd == CMD_NONE)
4628                                         usage_all(stdout);
4629                                 else
4630                                         usage(cmd, stdout);
4631                                 exit(0);
4632                         } else if (!tstrcmp(p, T("version"))) {
4633                                 version();
4634                                 exit(0);
4635                         } else if (!tstrcmp(p, T("quiet"))) {
4636                                 imagex_suppress_output();
4637                                 memmove(&argv[i], &argv[i + 1],
4638                                         (argc - i) * sizeof(argv[i]));
4639                                 argc--;
4640                                 i--;
4641                         } else if (!*p) /* reached "--", no more options */
4642                                 break;
4643                 }
4644         }
4645
4646         *argc_p = argc;
4647 }
4648
4649 static void
4650 print_usage_string(int cmd, FILE *fp)
4651 {
4652         tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4653 }
4654
4655 static void
4656 recommend_man_page(int cmd, FILE *fp)
4657 {
4658         const tchar *format_str;
4659 #ifdef __WIN32__
4660         format_str = T("Some uncommon options are not listed;\n"
4661                        "See %"TS".pdf in the doc directory for more details.\n");
4662 #else
4663         format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4664 #endif
4665         tfprintf(fp, format_str, get_cmd_string(cmd, true));
4666 }
4667
4668 static void
4669 usage(int cmd, FILE *fp)
4670 {
4671         tfprintf(fp, T("Usage:\n"));
4672         print_usage_string(cmd, fp);
4673         tfprintf(fp, T("\n"));
4674         recommend_man_page(cmd, fp);
4675 }
4676
4677 static void
4678 usage_all(FILE *fp)
4679 {
4680         tfprintf(fp, T("Usage:\n"));
4681         for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4682                 print_usage_string(cmd, fp);
4683                 tfprintf(fp, T("\n"));
4684         }
4685         static const tchar * const extra =
4686         T(
4687 "    %"TS" --help\n"
4688 "    %"TS" --version\n"
4689 "\n"
4690         );
4691         tfprintf(fp, extra, invocation_name, invocation_name);
4692         tfprintf(fp,
4693                  T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4694                    "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4695                    "For some commands IMAGE may be \"all\".\n"
4696                    "\n"));
4697         recommend_man_page(CMD_NONE, fp);
4698 }
4699
4700 #ifdef __WIN32__
4701 extern int wmain(int argc, wchar_t **argv);
4702 #define main wmain
4703 #endif
4704
4705 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
4706  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4707  * something else), while on Windows the command arguments will be UTF-16LE
4708  * encoded 'wchar_t' strings. */
4709 int
4710 main(int argc, tchar **argv)
4711 {
4712         int ret;
4713         int init_flags = 0;
4714         int cmd;
4715
4716         imagex_info_file = stdout;
4717         invocation_name = tbasename(argv[0]);
4718
4719         {
4720                 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4721                 if (igcase != NULL) {
4722                         if (!tstrcmp(igcase, T("no")) ||
4723                             !tstrcmp(igcase, T("0")))
4724                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4725                         else if (!tstrcmp(igcase, T("yes")) ||
4726                                  !tstrcmp(igcase, T("1")))
4727                                 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4728                         else {
4729                                 fprintf(stderr,
4730                                         "WARNING: Ignoring unknown setting of "
4731                                         "WIMLIB_IMAGEX_IGNORE_CASE\n");
4732                         }
4733                 }
4734         }
4735
4736         /* Allow being invoked as wimCOMMAND (e.g. wimapply).  */
4737         cmd = CMD_NONE;
4738         if (!tstrncmp(invocation_name, T("wim"), 3) &&
4739             tstrcmp(invocation_name, T("wimlib-imagex"))) {
4740                 for (int i = 0; i < CMD_MAX; i++) {
4741                         if (!tstrcmp(invocation_name + 3,
4742                                      imagex_commands[i].name))
4743                         {
4744                                 invocation_cmd = i;
4745                                 cmd = i;
4746                                 break;
4747                         }
4748                 }
4749         }
4750
4751         /* Unless already known from the invocation name, determine which
4752          * command was specified.  */
4753         if (cmd == CMD_NONE) {
4754                 if (argc < 2) {
4755                         imagex_error(T("No command specified!\n"));
4756                         usage_all(stderr);
4757                         exit(2);
4758                 }
4759                 for (int i = 0; i < CMD_MAX; i++) {
4760                         if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4761                                 cmd = i;
4762                                 break;
4763                         }
4764                 }
4765                 if (cmd != CMD_NONE) {
4766                         argc--;
4767                         argv++;
4768                 }
4769         }
4770
4771         /* Handle common options.  May exit early (for --help or --version).  */
4772         do_common_options(&argc, argv, cmd);
4773
4774         /* Bail if a valid command was not specified.  */
4775         if (cmd == CMD_NONE) {
4776                 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4777                 usage_all(stderr);
4778                 exit(2);
4779         }
4780
4781         /* Enable warning and error messages in wimlib to be more user-friendly.
4782          * */
4783         wimlib_set_print_errors(true);
4784
4785         /* Initialize wimlib.  */
4786         ret = wimlib_global_init(init_flags);
4787         if (ret)
4788                 goto out_check_status;
4789
4790         /* Call the command handler function.  */
4791         ret = imagex_commands[cmd].func(argc, argv, cmd);
4792
4793         /* Check for error writing to standard output, especially since for some
4794          * commands, writing to standard output is part of the program's actual
4795          * behavior and not just for informational purposes.  */
4796         if (ferror(stdout) || fclose(stdout)) {
4797                 imagex_error_with_errno(T("error writing to standard output"));
4798                 if (ret == 0)
4799                         ret = -1;
4800         }
4801 out_check_status:
4802         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
4803          * itself (not by wimlib).  0 indicates success.  > 0 indicates a wimlib
4804          * error code from which an error message can be printed.  */
4805         if (ret > 0) {
4806                 imagex_error(T("Exiting with error code %d:\n"
4807                                "       %"TS"."), ret,
4808                              wimlib_get_error_string(ret));
4809                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4810                         imagex_error_with_errno(T("errno"));
4811         }
4812         /* Make wimlib free any resources it's holding (although this is not
4813          * strictly necessary because the process is ending anyway).  */
4814         wimlib_global_cleanup();
4815         return ret;
4816 }