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