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