]> wimlib.net Git - wimlib/blob - programs/imagex.c
Merge branch with pipable WIM support
[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 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 #ifdef __WIN32__
50 #  include "imagex-win32.h"
51 #  define tbasename     win32_wbasename
52 #  define tglob         win32_wglob
53 #  define OS_PREFERRED_PATH_SEPARATOR L'\\'
54 #  define OS_PREFERRED_PATH_SEPARATOR_STRING L"\\"
55 #else /* __WIN32__ */
56 #  include <glob.h>
57 #  include <getopt.h>
58 #  include <langinfo.h>
59 #  define tbasename     basename
60 #  define tglob         glob
61 #  define OS_PREFERRED_PATH_SEPARATOR '/'
62 #  define OS_PREFERRED_PATH_SEPARATOR_STRING "/"
63 static inline void set_fd_to_binary_mode(int fd)
64 {
65 }
66 #endif /* !__WIN32 */
67
68
69 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
70
71 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
72                                 opts, NULL)) != -1)
73
74 enum imagex_op_type {
75         APPEND = 0,
76         APPLY,
77         CAPTURE,
78         DELETE,
79         DIR,
80         EXPORT,
81         EXTRACT,
82         INFO,
83         JOIN,
84         MOUNT,
85         MOUNTRW,
86         OPTIMIZE,
87         SPLIT,
88         UNMOUNT,
89         UPDATE,
90 };
91
92 static void usage(int cmd_type);
93 static void usage_all(void);
94
95 static bool imagex_be_quiet = false;
96 static FILE *imagex_info_file;
97
98 #define imagex_printf(format, ...) \
99                 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
100
101
102 static const tchar *usage_strings[] = {
103 [APPEND] =
104 T(
105 IMAGEX_PROGNAME" append (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
106 "                    [DESCRIPTION] [--boot] [--check] [--nocheck]\n"
107 "                    [--flags EDITION_ID] [--verbose] [--dereference]\n"
108 "                    [--config=FILE] [--threads=NUM_THREADS] [--rebuild]\n"
109 "                    [--unix-data] [--source-list] [--no-acls]\n"
110 "                    [--strict-acls] [--rpfix] [--norpfix] [--pipable]\n"
111 "                    [--not-pipable]\n"
112 ),
113 [APPLY] =
114 T(
115 IMAGEX_PROGNAME" apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n"
116 "                    (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n"
117 "                    [--symlink] [--verbose] [--ref=\"GLOB\"] [--unix-data]\n"
118 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
119 "                    [--include-invalid-names]\n"
120 ),
121 [CAPTURE] =
122 T(
123 IMAGEX_PROGNAME" capture (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
124 "                    [DESCRIPTION] [--boot] [--check] [--nocheck]\n"
125 "                    [--compress=TYPE] [--flags EDITION_ID] [--verbose]\n"
126 "                    [--dereference] [--config=FILE]\n"
127 "                    [--threads=NUM_THREADS] [--unix-data] [--source-list]\n"
128 "                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
129 "                    [--pipable] [--not-pipable]\n"
130 ),
131 [DELETE] =
132 T(
133 IMAGEX_PROGNAME" delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check]\n"
134 "                    [--soft]\n"
135 ),
136 [DIR] =
137 T(
138 IMAGEX_PROGNAME" dir WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--path=PATH]\n"
139 ),
140 [EXPORT] =
141 T(
142 IMAGEX_PROGNAME" export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
143 "                    DEST_WIMFILE [DEST_IMAGE_NAME] [DEST_IMAGE_DESCRIPTION]\n"
144 "                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
145 "                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
146 "                    [--pipable] [--not-pipable]\n"
147 ),
148 [EXTRACT] =
149 T(
150 IMAGEX_PROGNAME" extract WIMFILE (IMAGE_NUM | IMAGE_NAME) [PATH...]\n"
151 "                    [--check] [--ref=\"GLOB\"] [--verbose] [--unix-data]\n"
152 "                    [--no-acls] [--strict-acls] [--to-stdout] [--dest-dir=DIR]\n"
153 "                    [--include-invalid-names]\n"
154 ),
155 [INFO] =
156 T(
157 IMAGEX_PROGNAME" info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
158 "                    [NEW_DESC] [--boot] [--check] [--nocheck] [--header]\n"
159 "                    [--lookup-table] [--xml] [--extract-xml FILE]\n"
160 "                    [--metadata]\n"
161 ),
162 [JOIN] =
163 T(
164 IMAGEX_PROGNAME" join [--check] WIMFILE SPLIT_WIM...\n"
165 ),
166 [MOUNT] =
167 T(
168 IMAGEX_PROGNAME" mount WIMFILE (IMAGE_NUM | IMAGE_NAME) DIRECTORY\n"
169 "                    [--check] [--debug] [--streams-interface=INTERFACE]\n"
170 "                    [--ref=\"GLOB\"] [--unix-data] [--allow-other]\n"
171 ),
172 [MOUNTRW] =
173 T(
174 IMAGEX_PROGNAME" mountrw WIMFILE [IMAGE_NUM | IMAGE_NAME] DIRECTORY\n"
175 "                    [--check] [--debug] [--streams-interface=INTERFACE]\n"
176 "                    [--staging-dir=DIR] [--unix-data] [--allow-other]\n"
177 ),
178 [OPTIMIZE] =
179 T(
180 IMAGEX_PROGNAME" optimize WIMFILE [--check] [--nocheck] [--recompress]\n"
181 "                    [--threads=NUM_THREADS] [--pipable] [--not-pipable]\n"
182 ),
183 [SPLIT] =
184 T(
185 IMAGEX_PROGNAME" split WIMFILE SPLIT_WIMFILE PART_SIZE_MB [--check]\n"
186 ),
187 [UNMOUNT] =
188 T(
189 IMAGEX_PROGNAME" unmount DIRECTORY [--commit] [--check] [--rebuild] [--lazy]\n"
190 ),
191 [UPDATE] =
192 T(
193 IMAGEX_PROGNAME" update WIMFILE [IMAGE_NUM | IMAGE_NAME] [--check] [--rebuild]\n"
194 "                    [--threads=NUM_THREADS] [DEFAULT_ADD_OPTIONS]\n"
195 "                    [DEFAULT_DELETE_OPTIONS] [--command=STRING] [< CMDFILE]\n"
196 ),
197 };
198
199
200 static void
201 recommend_man_page(const tchar *cmd_name)
202 {
203         const tchar *format_str;
204 #ifdef __WIN32__
205         format_str = T("See "IMAGEX_PROGNAME"%"TS"%"TS".pdf in the "
206                        "doc directory for more details.\n");
207 #else
208         format_str = T("Try `man "IMAGEX_PROGNAME"%"TS"%"TS"' "
209                        "for more details.\n");
210 #endif
211         tprintf(format_str, *cmd_name ? T("-") : T(""), cmd_name);
212 }
213
214 enum {
215         IMAGEX_ALLOW_OTHER_OPTION,
216         IMAGEX_BOOT_OPTION,
217         IMAGEX_CHECK_OPTION,
218         IMAGEX_COMMAND_OPTION,
219         IMAGEX_COMMIT_OPTION,
220         IMAGEX_COMPRESS_OPTION,
221         IMAGEX_CONFIG_OPTION,
222         IMAGEX_DEBUG_OPTION,
223         IMAGEX_DEREFERENCE_OPTION,
224         IMAGEX_DEST_DIR_OPTION,
225         IMAGEX_EXTRACT_XML_OPTION,
226         IMAGEX_FLAGS_OPTION,
227         IMAGEX_FORCE_OPTION,
228         IMAGEX_HARDLINK_OPTION,
229         IMAGEX_HEADER_OPTION,
230         IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
231         IMAGEX_LAZY_OPTION,
232         IMAGEX_LOOKUP_TABLE_OPTION,
233         IMAGEX_METADATA_OPTION,
234         IMAGEX_NORPFIX_OPTION,
235         IMAGEX_NOCHECK_OPTION,
236         IMAGEX_NO_ACLS_OPTION,
237         IMAGEX_NOT_PIPABLE_OPTION,
238         IMAGEX_PATH_OPTION,
239         IMAGEX_PIPABLE_OPTION,
240         IMAGEX_REBUILD_OPTION,
241         IMAGEX_RECOMPRESS_OPTION,
242         IMAGEX_RECURSIVE_OPTION,
243         IMAGEX_REF_OPTION,
244         IMAGEX_RPFIX_OPTION,
245         IMAGEX_SOFT_OPTION,
246         IMAGEX_SOURCE_LIST_OPTION,
247         IMAGEX_STAGING_DIR_OPTION,
248         IMAGEX_STREAMS_INTERFACE_OPTION,
249         IMAGEX_STRICT_ACLS_OPTION,
250         IMAGEX_SYMLINK_OPTION,
251         IMAGEX_THREADS_OPTION,
252         IMAGEX_TO_STDOUT_OPTION,
253         IMAGEX_UNIX_DATA_OPTION,
254         IMAGEX_VERBOSE_OPTION,
255         IMAGEX_XML_OPTION,
256 };
257
258 static const struct option apply_options[] = {
259         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
260         {T("hardlink"),    no_argument,       NULL, IMAGEX_HARDLINK_OPTION},
261         {T("symlink"),     no_argument,       NULL, IMAGEX_SYMLINK_OPTION},
262         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
263         {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
264         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
265         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
266         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
267         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
268         {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
269         {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
270         {T("include-invalid-names"), no_argument,       NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
271         {NULL, 0, NULL, 0},
272 };
273 static const struct option capture_or_append_options[] = {
274         {T("boot"),        no_argument,       NULL, IMAGEX_BOOT_OPTION},
275         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
276         {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
277         {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
278         {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
279         {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
280         {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
281         {T("flags"),       required_argument, NULL, IMAGEX_FLAGS_OPTION},
282         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
283         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
284         {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
285         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
286         {T("source-list"), no_argument,       NULL, IMAGEX_SOURCE_LIST_OPTION},
287         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
288         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
289         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
290         {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
291         {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
292         {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
293         {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
294         {NULL, 0, NULL, 0},
295 };
296 static const struct option delete_options[] = {
297         {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
298         {T("soft"),  no_argument, NULL, IMAGEX_SOFT_OPTION},
299         {NULL, 0, NULL, 0},
300 };
301
302 static const struct option dir_options[] = {
303         {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
304         {NULL, 0, NULL, 0},
305 };
306
307 static const struct option export_options[] = {
308         {T("boot"),        no_argument,       NULL, IMAGEX_BOOT_OPTION},
309         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
310         {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
311         {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
312         {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
313         {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
314         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
315         {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
316         {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
317         {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
318         {NULL, 0, NULL, 0},
319 };
320
321 static const struct option extract_options[] = {
322         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
323         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
324         {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
325         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
326         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
327         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
328         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
329         {T("dest-dir"),    required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
330         {T("to-stdout"),   no_argument,       NULL, IMAGEX_TO_STDOUT_OPTION},
331         {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
332         {NULL, 0, NULL, 0},
333 };
334
335 static const struct option info_options[] = {
336         {T("boot"),         no_argument,       NULL, IMAGEX_BOOT_OPTION},
337         {T("check"),        no_argument,       NULL, IMAGEX_CHECK_OPTION},
338         {T("nocheck"),      no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
339         {T("no-check"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
340         {T("extract-xml"),  required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
341         {T("header"),       no_argument,       NULL, IMAGEX_HEADER_OPTION},
342         {T("lookup-table"), no_argument,       NULL, IMAGEX_LOOKUP_TABLE_OPTION},
343         {T("metadata"),     no_argument,       NULL, IMAGEX_METADATA_OPTION},
344         {T("xml"),          no_argument,       NULL, IMAGEX_XML_OPTION},
345         {NULL, 0, NULL, 0},
346 };
347
348 static const struct option join_options[] = {
349         {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
350         {NULL, 0, NULL, 0},
351 };
352
353 static const struct option mount_options[] = {
354         {T("check"),             no_argument,       NULL, IMAGEX_CHECK_OPTION},
355         {T("debug"),             no_argument,       NULL, IMAGEX_DEBUG_OPTION},
356         {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
357         {T("ref"),               required_argument, NULL, IMAGEX_REF_OPTION},
358         {T("staging-dir"),       required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
359         {T("unix-data"),         no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
360         {T("allow-other"),       no_argument,       NULL, IMAGEX_ALLOW_OTHER_OPTION},
361         {NULL, 0, NULL, 0},
362 };
363
364 static const struct option optimize_options[] = {
365         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
366         {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
367         {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
368         {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
369         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
370         {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
371         {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
372         {NULL, 0, NULL, 0},
373 };
374
375 static const struct option split_options[] = {
376         {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
377         {NULL, 0, NULL, 0},
378 };
379
380 static const struct option unmount_options[] = {
381         {T("commit"),  no_argument, NULL, IMAGEX_COMMIT_OPTION},
382         {T("check"),   no_argument, NULL, IMAGEX_CHECK_OPTION},
383         {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
384         {T("lazy"),    no_argument, NULL, IMAGEX_LAZY_OPTION},
385         {NULL, 0, NULL, 0},
386 };
387
388 static const struct option update_options[] = {
389         /* Careful: some of the options here set the defaults for update
390          * commands, but the flags given to an actual update command (and not to
391          * `imagex update' itself are also handled in
392          * update_command_add_option().  */
393         {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
394         {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
395         {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
396         {T("command"),     required_argument, NULL, IMAGEX_COMMAND_OPTION},
397
398         /* Default delete options */
399         {T("force"),       no_argument,       NULL, IMAGEX_FORCE_OPTION},
400         {T("recursive"),   no_argument,       NULL, IMAGEX_RECURSIVE_OPTION},
401
402         /* Global add option */
403         {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
404
405         /* Default add options */
406         {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
407         {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
408         {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
409         {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
410         {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
411         {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
412
413         {NULL, 0, NULL, 0},
414 };
415
416 #if 0
417 #       define _format_attribute(type, format_str, args_start) \
418                         __attribute__((format(type, format_str, args_start)))
419 #else
420 #       define _format_attribute(type, format_str, args_start)
421 #endif
422
423 /* Print formatted error message to stderr. */
424 static void _format_attribute(printf, 1, 2)
425 imagex_error(const tchar *format, ...)
426 {
427         va_list va;
428         va_start(va, format);
429         tfputs(T("ERROR: "), stderr);
430         tvfprintf(stderr, format, va);
431         tputc(T('\n'), stderr);
432         va_end(va);
433 }
434
435 /* Print formatted error message to stderr. */
436 static void _format_attribute(printf, 1, 2)
437 imagex_error_with_errno(const tchar *format, ...)
438 {
439         int errno_save = errno;
440         va_list va;
441         va_start(va, format);
442         tfputs(T("ERROR: "), stderr);
443         tvfprintf(stderr, format, va);
444         tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
445         va_end(va);
446 }
447
448 static int
449 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
450 {
451         if (image == WIMLIB_NO_IMAGE) {
452                 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
453                              "       Please specify a 1-based image index or "
454                              "image name.\n"
455                              "       You may use `"IMAGEX_PROGNAME" info' to list the images "
456                              "contained in a WIM."),
457                              image_name, wim_name);
458                 return -1;
459         }
460         return 0;
461 }
462
463 static int
464 verify_image_is_single(int image)
465 {
466         if (image == WIMLIB_ALL_IMAGES) {
467                 imagex_error(T("Cannot specify all images for this action!"));
468                 return -1;
469         }
470         return 0;
471 }
472
473 static int
474 verify_image_exists_and_is_single(int image, const tchar *image_name,
475                                   const tchar *wim_name)
476 {
477         int ret;
478         ret = verify_image_exists(image, image_name, wim_name);
479         if (ret == 0)
480                 ret = verify_image_is_single(image);
481         return ret;
482 }
483
484 /* Parse the argument to --compress */
485 static int
486 get_compression_type(const tchar *optarg)
487 {
488         if (!tstrcasecmp(optarg, T("maximum")) || !tstrcasecmp(optarg, T("lzx")))
489                 return WIMLIB_COMPRESSION_TYPE_LZX;
490         else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
491                 return WIMLIB_COMPRESSION_TYPE_XPRESS;
492         else if (!tstrcasecmp(optarg, T("none")))
493                 return WIMLIB_COMPRESSION_TYPE_NONE;
494         else {
495                 imagex_error(T("Invalid compression type \"%"TS"\"! Must be "
496                              "\"maximum\", \"fast\", or \"none\"."), optarg);
497                 return WIMLIB_COMPRESSION_TYPE_INVALID;
498         }
499 }
500
501 /* Returns the size of a file given its name, or -1 if the file does not exist
502  * or its size cannot be determined.  */
503 static off_t
504 file_get_size(const tchar *filename)
505 {
506         struct stat st;
507         if (tstat(filename, &st) == 0)
508                 return st.st_size;
509         else
510                 return (off_t)-1;
511 }
512
513 tchar pat_ntfs_log[]                  = T("/$ntfs.log");
514 tchar pat_hiberfil_sys[]              = T("/hiberfil.sys");
515 tchar pat_pagefile_sys[]              = T("/pagefile.sys");
516 tchar pat_system_volume_information[] = T("/System Volume Information");
517 tchar pat_recycler[]                  = T("/RECYCLER");
518 tchar pat_windows_csc[]               = T("/Windows/CSC");
519
520 tchar *default_pats[] = {
521         pat_ntfs_log,
522         pat_hiberfil_sys,
523         pat_pagefile_sys,
524         pat_system_volume_information,
525         pat_recycler,
526         pat_windows_csc,
527 };
528
529 static struct wimlib_capture_config default_capture_config = {
530         .exclusion_pats = {
531                 .num_pats = sizeof(default_pats) / sizeof(default_pats[0]),
532                 .pats = default_pats,
533         },
534 };
535
536 enum {
537         PARSE_STRING_SUCCESS = 0,
538         PARSE_STRING_FAILURE = 1,
539         PARSE_STRING_NONE = 2,
540 };
541
542 /*
543  * Parses a string token from an array of characters.
544  *
545  * Tokens are either whitespace-delimited, or double or single-quoted.
546  *
547  * @line_p:  Pointer to the pointer to the line of data.  Will be updated
548  *           to point past the string token iff the return value is
549  *           PARSE_STRING_SUCCESS.  If *len_p > 0, (*line_p)[*len_p - 1] must
550  *           be '\0'.
551  *
552  * @len_p:   @len_p initially stores the length of the line of data, which may
553  *           be 0, and it will be updated to the number of bytes remaining in
554  *           the line iff the return value is PARSE_STRING_SUCCESS.
555  *
556  * @fn_ret:  Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
557  *           parsed string token will be returned here.
558  *
559  * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
560  *          PARSE_STRING_FAILURE if the data was invalid due to a missing
561  *          closing quote; or PARSE_STRING_NONE if the line ended before the
562  *          beginning of a string token was found.
563  */
564 static int
565 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
566 {
567         size_t len = *len_p;
568         tchar *line = *line_p;
569         tchar *fn;
570         tchar quote_char;
571
572         /* Skip leading whitespace */
573         for (;;) {
574                 if (len == 0)
575                         return PARSE_STRING_NONE;
576                 if (!istspace(*line) && *line != T('\0'))
577                         break;
578                 line++;
579                 len--;
580         }
581         quote_char = *line;
582         if (quote_char == T('"') || quote_char == T('\'')) {
583                 /* Quoted string */
584                 line++;
585                 len--;
586                 fn = line;
587                 line = tmemchr(line, quote_char, len);
588                 if (!line) {
589                         imagex_error(T("Missing closing quote: %"TS), fn - 1);
590                         return PARSE_STRING_FAILURE;
591                 }
592         } else {
593                 /* Unquoted string.  Go until whitespace.  Line is terminated
594                  * by '\0', so no need to check 'len'. */
595                 fn = line;
596                 do {
597                         line++;
598                 } while (!istspace(*line) && *line != T('\0'));
599         }
600         *line = T('\0');
601         len -= line - fn;
602         *len_p = len;
603         *line_p = line;
604         *fn_ret = fn;
605         return PARSE_STRING_SUCCESS;
606 }
607
608 /* Parses a line of data (not an empty line or comment) in the source list file
609  * format.  (See the man page for 'wimlib-imagex capture' for details on this
610  * format and the meaning.)
611  *
612  * @line:  Line of data to be parsed.  line[len - 1] must be '\0', unless
613  *         len == 0.  The data in @line will be modified by this function call.
614  *
615  * @len:   Length of the line of data.
616  *
617  * @source:  On success, the capture source and target described by the line is
618  *           written into this destination.  Note that it will contain pointers
619  *           to data in the @line array.
620  *
621  * Returns true if the line was valid; false otherwise.  */
622 static bool
623 parse_source_list_line(tchar *line, size_t len,
624                        struct wimlib_capture_source *source)
625 {
626         /* SOURCE [DEST] */
627         int ret;
628         ret = parse_string(&line, &len, &source->fs_source_path);
629         if (ret != PARSE_STRING_SUCCESS)
630                 return false;
631         ret = parse_string(&line, &len, &source->wim_target_path);
632         if (ret == PARSE_STRING_NONE)
633                 source->wim_target_path = source->fs_source_path;
634         return ret != PARSE_STRING_FAILURE;
635 }
636
637 /* Returns %true if the given line of length @len > 0 is a comment or empty line
638  * in the source list file format. */
639 static bool
640 is_comment_line(const tchar *line, size_t len)
641 {
642         for (;;) {
643                 if (*line == T('#'))
644                         return true;
645                 if (!istspace(*line) && *line != T('\0'))
646                         return false;
647                 ++line;
648                 --len;
649                 if (len == 0)
650                         return true;
651         }
652 }
653
654 static ssize_t
655 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
656 {
657         ssize_t nlines = 0;
658         tchar *contents = *contents_p;
659         size_t nchars = *nchars_p;
660         size_t i;
661
662         for (i = 0; i < nchars; i++)
663                 if (contents[i] == T('\n'))
664                         nlines++;
665
666         /* Handle last line not terminated by a newline */
667         if (nchars != 0 && contents[nchars - 1] != T('\n')) {
668                 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
669                 if (!contents) {
670                         imagex_error(T("Out of memory!"));
671                         return -1;
672                 }
673                 contents[nchars] = T('\n');
674                 *contents_p = contents;
675                 nchars++;
676                 nlines++;
677         }
678         *nchars_p = nchars;
679         return nlines;
680 }
681
682 /* Parses a file in the source list format.  (See the man page for
683  * 'wimlib-imagex capture' for details on this format and the meaning.)
684  *
685  * @source_list_contents:  Contents of the source list file.  Note that this
686  *                         buffer will be modified to save memory allocations,
687  *                         and cannot be freed until the returned array of
688  *                         wimlib_capture_source's has also been freed.
689  *
690  * @source_list_nbytes:    Number of bytes of data in the @source_list_contents
691  *                         buffer.
692  *
693  * @nsources_ret:          On success, the length of the returned array is
694  *                         returned here.
695  *
696  * Returns:   An array of `struct wimlib_capture_source's that can be passed to
697  * the wimlib_add_image_multisource() function to specify how a WIM image is to
698  * be created.  */
699 static struct wimlib_capture_source *
700 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
701                   size_t *nsources_ret)
702 {
703         ssize_t nlines;
704         tchar *p;
705         struct wimlib_capture_source *sources;
706         size_t i, j;
707
708         nlines = text_file_count_lines(source_list_contents_p,
709                                        &source_list_nchars);
710         if (nlines < 0)
711                 return NULL;
712
713         /* Always allocate at least 1 slot, just in case the implementation of
714          * calloc() returns NULL if 0 bytes are requested. */
715         sources = calloc(nlines ?: 1, sizeof(*sources));
716         if (!sources) {
717                 imagex_error(T("out of memory"));
718                 return NULL;
719         }
720         p = *source_list_contents_p;
721         j = 0;
722         for (i = 0; i < nlines; i++) {
723                 /* XXX: Could use rawmemchr() here instead, but it may not be
724                  * available on all platforms. */
725                 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
726                 size_t len = endp - p + 1;
727                 *endp = T('\0');
728                 if (!is_comment_line(p, len)) {
729                         if (!parse_source_list_line(p, len, &sources[j++])) {
730                                 free(sources);
731                                 return NULL;
732                         }
733                 }
734                 p = endp + 1;
735
736         }
737         *nsources_ret = j;
738         return sources;
739 }
740
741
742 enum capture_config_section {
743         CAPTURE_CONFIG_NO_SECTION,
744         CAPTURE_CONFIG_EXCLUSION_SECTION,
745         CAPTURE_CONFIG_EXCLUSION_EXCEPTION_SECTION,
746         CAPTURE_CONFIG_IGNORE_SECTION,
747 };
748
749 enum {
750         CAPTURE_CONFIG_INVALID_SECTION,
751         CAPTURE_CONFIG_CHANGED_SECTION,
752         CAPTURE_CONFIG_SAME_SECTION,
753 };
754
755 static int
756 check_config_section(tchar *line, size_t len,
757                      enum capture_config_section *cur_section)
758 {
759         while (istspace(*line))
760                 line++;
761
762         if (*line != T('['))
763                 return CAPTURE_CONFIG_SAME_SECTION;
764
765         line++;
766         tchar *endbrace = tstrrchr(line, T(']'));
767         if (!endbrace)
768                 return CAPTURE_CONFIG_SAME_SECTION;
769
770         if (!tmemcmp(line, T("ExclusionList"), endbrace - line)) {
771                 *cur_section = CAPTURE_CONFIG_EXCLUSION_SECTION;
772         } else if (!tmemcmp(line, T("ExclusionException"), endbrace - line)) {
773                 *cur_section = CAPTURE_CONFIG_EXCLUSION_EXCEPTION_SECTION;
774         } else if (!tmemcmp(line, T("CompressionExclusionList"), endbrace - line)) {
775                 *cur_section = CAPTURE_CONFIG_IGNORE_SECTION;
776                 tfputs(T("WARNING: Ignoring [CompressionExclusionList] section "
777                          "of capture config file\n"),
778                        stderr);
779         } else if (!tmemcmp(line, T("AlignmentList"), endbrace - line)) {
780                 *cur_section = CAPTURE_CONFIG_IGNORE_SECTION;
781                 tfputs(T("WARNING: Ignoring [AlignmentList] section "
782                          "of capture config file\n"),
783                        stderr);
784         } else {
785                 imagex_error(T("Invalid capture config file section \"%"TS"\""),
786                              line - 1);
787                 return CAPTURE_CONFIG_INVALID_SECTION;
788         }
789         return CAPTURE_CONFIG_CHANGED_SECTION;
790 }
791
792
793 static bool
794 pattern_list_add_pattern(struct wimlib_pattern_list *pat_list,
795                          tchar *pat)
796 {
797         if (pat_list->num_pats == pat_list->num_allocated_pats) {
798                 tchar **pats;
799                 size_t num_allocated_pats = pat_list->num_pats + 8;
800
801                 pats = realloc(pat_list->pats,
802                                num_allocated_pats * sizeof(pat_list->pats[0]));
803                 if (!pats) {
804                         imagex_error(T("Out of memory!"));
805                         return false;
806                 }
807                 pat_list->pats = pats;
808                 pat_list->num_allocated_pats = num_allocated_pats;
809         }
810         pat_list->pats[pat_list->num_pats++] = pat;
811         return true;
812 }
813
814 static bool
815 parse_capture_config_line(tchar *line, size_t len,
816                           enum capture_config_section *cur_section,
817                           struct wimlib_capture_config *config)
818 {
819         tchar *filename;
820         int ret;
821
822         ret = check_config_section(line, len, cur_section);
823         if (ret == CAPTURE_CONFIG_INVALID_SECTION)
824                 return false;
825         if (ret == CAPTURE_CONFIG_CHANGED_SECTION)
826                 return true;
827
828         switch (*cur_section) {
829         case CAPTURE_CONFIG_NO_SECTION:
830                 imagex_error(T("Line \"%"TS"\" is not in a section "
831                                "(such as [ExclusionList]"), line);
832                 return false;
833         case CAPTURE_CONFIG_EXCLUSION_SECTION:
834                 if (parse_string(&line, &len, &filename) != PARSE_STRING_SUCCESS)
835                         return false;
836                 return pattern_list_add_pattern(&config->exclusion_pats,
837                                                 filename);
838         case CAPTURE_CONFIG_EXCLUSION_EXCEPTION_SECTION:
839                 if (parse_string(&line, &len, &filename) != PARSE_STRING_SUCCESS)
840                         return false;
841                 return pattern_list_add_pattern(&config->exclusion_exception_pats,
842                                                 filename);
843         case CAPTURE_CONFIG_IGNORE_SECTION:
844                 return true;
845         }
846         return false;
847 }
848
849 static int
850 parse_capture_config(tchar **contents_p, size_t nchars,
851                      struct wimlib_capture_config *config)
852 {
853         ssize_t nlines;
854         tchar *p;
855         size_t i;
856         enum capture_config_section cur_section;
857
858         memset(config, 0, sizeof(*config));
859
860         nlines = text_file_count_lines(contents_p, &nchars);
861         if (nlines < 0)
862                 return -1;
863
864         cur_section = CAPTURE_CONFIG_NO_SECTION;
865         p = *contents_p;
866         for (i = 0; i < nlines; i++) {
867                 tchar *endp = tmemchr(p, T('\n'), nchars);
868                 size_t len = endp - p + 1;
869                 *endp = T('\0');
870                 if (!is_comment_line(p, len))
871                         if (!parse_capture_config_line(p, len, &cur_section, config))
872                                 return -1;
873                 p = endp + 1;
874
875         }
876         return 0;
877 }
878
879 /* Reads the contents of a file into memory. */
880 static char *
881 file_get_contents(const tchar *filename, size_t *len_ret)
882 {
883         struct stat stbuf;
884         void *buf = NULL;
885         size_t len;
886         FILE *fp;
887
888         if (tstat(filename, &stbuf) != 0) {
889                 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
890                 goto out;
891         }
892         len = stbuf.st_size;
893
894         fp = tfopen(filename, T("rb"));
895         if (!fp) {
896                 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
897                 goto out;
898         }
899
900         buf = malloc(len ? len : 1);
901         if (!buf) {
902                 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
903                                "contents of file \"%"TS"\""), len, filename);
904                 goto out_fclose;
905         }
906         if (fread(buf, 1, len, fp) != len) {
907                 imagex_error_with_errno(T("Failed to read %zu bytes from the "
908                                           "file \"%"TS"\""), len, filename);
909                 goto out_free_buf;
910         }
911         *len_ret = len;
912         goto out_fclose;
913 out_free_buf:
914         free(buf);
915         buf = NULL;
916 out_fclose:
917         fclose(fp);
918 out:
919         return buf;
920 }
921
922 /* Read standard input until EOF and return the full contents in a malloc()ed
923  * buffer and the number of bytes of data in @len_ret.  Returns NULL on read
924  * error. */
925 static char *
926 stdin_get_contents(size_t *len_ret)
927 {
928         /* stdin can, of course, be a pipe or other non-seekable file, so the
929          * total length of the data cannot be pre-determined */
930         char *buf = NULL;
931         size_t newlen = 1024;
932         size_t pos = 0;
933         size_t inc = 1024;
934         for (;;) {
935                 char *p = realloc(buf, newlen);
936                 size_t bytes_read, bytes_to_read;
937                 if (!p) {
938                         imagex_error(T("out of memory while reading stdin"));
939                         break;
940                 }
941                 buf = p;
942                 bytes_to_read = newlen - pos;
943                 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
944                 pos += bytes_read;
945                 if (bytes_read != bytes_to_read) {
946                         if (feof(stdin)) {
947                                 *len_ret = pos;
948                                 return buf;
949                         } else {
950                                 imagex_error_with_errno(T("error reading stdin"));
951                                 break;
952                         }
953                 }
954                 newlen += inc;
955                 inc *= 3;
956                 inc /= 2;
957         }
958         free(buf);
959         return NULL;
960 }
961
962
963 static tchar *
964 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
965 {
966 #ifndef __WIN32__
967         /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
968          * */
969         *num_tchars_ret = num_bytes;
970         return text;
971 #else /* !__WIN32__ */
972         /* On Windows, translate the text to UTF-16LE */
973         wchar_t *text_wstr;
974         size_t num_wchars;
975
976         if (num_bytes >= 2 &&
977             ((text[0] == 0xff && text[1] == 0xfe) ||
978              (text[0] <= 0x7f && text[1] == 0x00)))
979         {
980                 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
981                  * with something that looks like an ASCII character encoded as
982                  * a UTF-16LE code unit.  Assume the file is encoded as
983                  * UTF-16LE.  This is not a 100% reliable check. */
984                 num_wchars = num_bytes / 2;
985                 text_wstr = (wchar_t*)text;
986         } else {
987                 /* File does not look like UTF-16LE.  Assume it is encoded in
988                  * the current Windows code page.  I think these are always
989                  * ASCII-compatible, so any so-called "plain-text" (ASCII) files
990                  * should work as expected. */
991                 text_wstr = win32_mbs_to_wcs(text,
992                                              num_bytes,
993                                              &num_wchars);
994                 free(text);
995         }
996         *num_tchars_ret = num_wchars;
997         return text_wstr;
998 #endif /* __WIN32__ */
999 }
1000
1001 static tchar *
1002 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1003 {
1004         char *contents;
1005         size_t num_bytes;
1006
1007         contents = file_get_contents(filename, &num_bytes);
1008         if (!contents)
1009                 return NULL;
1010         return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1011 }
1012
1013 static tchar *
1014 stdin_get_text_contents(size_t *num_tchars_ret)
1015 {
1016         char *contents;
1017         size_t num_bytes;
1018
1019         contents = stdin_get_contents(&num_bytes);
1020         if (!contents)
1021                 return NULL;
1022         return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1023 }
1024
1025 #define TO_PERCENT(numerator, denominator) \
1026         (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1027
1028 /* Given an enumerated value for WIM compression type, return a descriptive
1029  * string. */
1030 static const tchar *
1031 get_data_type(int ctype)
1032 {
1033         switch (ctype) {
1034         case WIMLIB_COMPRESSION_TYPE_NONE:
1035                 return T("uncompressed");
1036         case WIMLIB_COMPRESSION_TYPE_LZX:
1037                 return T("LZX-compressed");
1038         case WIMLIB_COMPRESSION_TYPE_XPRESS:
1039                 return T("XPRESS-compressed");
1040         }
1041         return NULL;
1042 }
1043
1044 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1045 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1046 #define KIBIBYTE_MIN_NBYTES 10000ULL
1047
1048 static unsigned
1049 get_unit(uint64_t total_bytes, const tchar **name_ret)
1050 {
1051         if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1052                 *name_ret = T("GiB");
1053                 return 30;
1054         } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1055                 *name_ret = T("MiB");
1056                 return 20;
1057         } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1058                 *name_ret = T("KiB");
1059                 return 10;
1060         } else {
1061                 *name_ret = T("bytes");
1062                 return 0;
1063         }
1064 }
1065
1066 /* Progress callback function passed to various wimlib functions. */
1067 static int
1068 imagex_progress_func(enum wimlib_progress_msg msg,
1069                      const union wimlib_progress_info *info)
1070 {
1071         unsigned percent_done;
1072         unsigned unit_shift;
1073         const tchar *unit_name;
1074         if (imagex_be_quiet)
1075                 return 0;
1076         switch (msg) {
1077         case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1078                 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1079                 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1080                                           info->write_streams.total_bytes);
1081                 if (info->write_streams.completed_streams == 0) {
1082                         const tchar *data_type;
1083
1084                         data_type = get_data_type(info->write_streams.compression_type);
1085                         imagex_printf(T("Writing %"TS" data using %u thread%"TS"\n"),
1086                                 data_type, info->write_streams.num_threads,
1087                                 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1088                 }
1089                 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1090                         "written (%u%% done)"),
1091                         info->write_streams.completed_bytes >> unit_shift,
1092                         unit_name,
1093                         info->write_streams.total_bytes >> unit_shift,
1094                         unit_name,
1095                         percent_done);
1096                 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1097                         imagex_printf(T("\n"));
1098                 break;
1099         case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1100                 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1101                 if (*info->scan.wim_target_path) {
1102                         imagex_printf(T(" (loading as WIM path: "
1103                                   "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\")...\n"),
1104                                info->scan.wim_target_path);
1105                 } else {
1106                         imagex_printf(T(" (loading as root of WIM image)...\n"));
1107                 }
1108                 break;
1109         case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1110                 if (info->scan.excluded)
1111                         imagex_printf(T("Excluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1112                 else
1113                         imagex_printf(T("Scanning \"%"TS"\"\n"), info->scan.cur_path);
1114                 break;
1115         /*case WIMLIB_PROGRESS_MSG_SCAN_END:*/
1116                 /*break;*/
1117         case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1118                 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1119                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1120                                           info->integrity.total_bytes);
1121                 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1122                         "of %"PRIu64" %"TS" (%u%%) done"),
1123                         info->integrity.filename,
1124                         info->integrity.completed_bytes >> unit_shift,
1125                         unit_name,
1126                         info->integrity.total_bytes >> unit_shift,
1127                         unit_name,
1128                         percent_done);
1129                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1130                         imagex_printf(T("\n"));
1131                 break;
1132         case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1133                 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1134                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1135                                           info->integrity.total_bytes);
1136                 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1137                           "of %"PRIu64" %"TS" (%u%%) done"),
1138                         info->integrity.completed_bytes >> unit_shift,
1139                         unit_name,
1140                         info->integrity.total_bytes >> unit_shift,
1141                         unit_name,
1142                         percent_done);
1143                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1144                         imagex_printf(T("\n"));
1145                 break;
1146         case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1147                 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1148                           "to %"TS" \"%"TS"\"\n"),
1149                         info->extract.image,
1150                         info->extract.image_name,
1151                         info->extract.wimfile_name,
1152                         ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1153                          T("NTFS volume") : T("directory")),
1154                         info->extract.target);
1155                 break;
1156         case WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN:
1157                 imagex_printf(T("Extracting "
1158                           "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\" from image %d (\"%"TS"\") "
1159                           "in \"%"TS"\" to \"%"TS"\"\n"),
1160                         info->extract.extract_root_wim_source_path,
1161                         info->extract.image,
1162                         info->extract.image_name,
1163                         info->extract.wimfile_name,
1164                         info->extract.target);
1165                 break;
1166         /*case WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN:*/
1167                 /*imagex_printf(T("Applying directory structure to %"TS"\n"),*/
1168                         /*info->extract.target);*/
1169                 /*break;*/
1170         case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1171                 percent_done = TO_PERCENT(info->extract.completed_bytes,
1172                                           info->extract.total_bytes);
1173                 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1174                 imagex_printf(T("\rExtracting files: "
1175                           "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1176                         info->extract.completed_bytes >> unit_shift,
1177                         unit_name,
1178                         info->extract.total_bytes >> unit_shift,
1179                         unit_name,
1180                         percent_done);
1181                 if (info->extract.completed_bytes >= info->extract.total_bytes)
1182                         imagex_printf(T("\n"));
1183                 break;
1184         case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS:
1185                 if (info->extract.extract_root_wim_source_path[0] == T('\0'))
1186                         imagex_printf(T("Setting timestamps on all extracted files...\n"));
1187                 break;
1188         case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END:
1189                 if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
1190                         imagex_printf(T("Unmounting NTFS volume \"%"TS"\"...\n"),
1191                                 info->extract.target);
1192                 }
1193                 break;
1194         case WIMLIB_PROGRESS_MSG_JOIN_STREAMS:
1195                 percent_done = TO_PERCENT(info->join.completed_bytes,
1196                                           info->join.total_bytes);
1197                 unit_shift = get_unit(info->join.total_bytes, &unit_name);
1198                 imagex_printf(T("Writing resources from part %u of %u: "
1199                           "%"PRIu64 " %"TS" of %"PRIu64" %"TS" (%u%%) written\n"),
1200                         (info->join.completed_parts == info->join.total_parts) ?
1201                         info->join.completed_parts : info->join.completed_parts + 1,
1202                         info->join.total_parts,
1203                         info->join.completed_bytes >> unit_shift,
1204                         unit_name,
1205                         info->join.total_bytes >> unit_shift,
1206                         unit_name,
1207                         percent_done);
1208                 break;
1209         case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1210                 percent_done = TO_PERCENT(info->split.completed_bytes,
1211                                           info->split.total_bytes);
1212                 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1213                 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1214                           "%"PRIu64" %"TS" (%u%%) written\n"),
1215                         info->split.part_name,
1216                         info->split.cur_part_number,
1217                         info->split.total_parts,
1218                         info->split.completed_bytes >> unit_shift,
1219                         unit_name,
1220                         info->split.total_bytes >> unit_shift,
1221                         unit_name,
1222                         percent_done);
1223                 break;
1224         case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1225                 if (info->split.completed_bytes == info->split.total_bytes) {
1226                         imagex_printf(T("Finished writing part %u of %u WIM parts\n"),
1227                                 info->split.cur_part_number,
1228                                 info->split.total_parts);
1229                 }
1230                 break;
1231         case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1232                 switch (info->update.command->op) {
1233                 case WIMLIB_UPDATE_OP_DELETE:
1234                         imagex_printf(T("Deleted WIM path "
1235                                   "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"),
1236                                 info->update.command->delete.wim_path);
1237                         break;
1238                 case WIMLIB_UPDATE_OP_RENAME:
1239                         imagex_printf(T("Renamed WIM path "
1240                                   "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\" => "
1241                                   "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"),
1242                                 info->update.command->rename.wim_source_path,
1243                                 info->update.command->rename.wim_target_path);
1244                         break;
1245                 case WIMLIB_UPDATE_OP_ADD:
1246                 default:
1247                         break;
1248                 }
1249                 break;
1250         default:
1251                 break;
1252         }
1253         fflush(stdout);
1254         return 0;
1255 }
1256
1257 /* Open all the split WIM parts that correspond to a file glob.
1258  *
1259  * @first_part specifies the first part of the split WIM and it may be either
1260  * included or omitted from the glob. */
1261 static int
1262 open_swms_from_glob(const tchar *swm_glob,
1263                     const tchar *first_part,
1264                     int open_flags,
1265                     WIMStruct ***additional_swms_ret,
1266                     unsigned *num_additional_swms_ret)
1267 {
1268         unsigned num_additional_swms = 0;
1269         WIMStruct **additional_swms = NULL;
1270         glob_t globbuf;
1271         int ret;
1272
1273         /* Warning: glob() is replaced in Windows native builds */
1274         ret = tglob(swm_glob, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf);
1275         if (ret) {
1276                 if (ret == GLOB_NOMATCH) {
1277                         imagex_error(T("Found no files for glob \"%"TS"\""),
1278                                      swm_glob);
1279                 } else {
1280                         imagex_error_with_errno(T("Failed to process glob \"%"TS"\""),
1281                                                 swm_glob);
1282                 }
1283                 ret = -1;
1284                 goto out;
1285         }
1286         num_additional_swms = globbuf.gl_pathc;
1287         additional_swms = calloc(num_additional_swms, sizeof(additional_swms[0]));
1288         if (!additional_swms) {
1289                 imagex_error(T("Out of memory"));
1290                 ret = -1;
1291                 goto out_globfree;
1292         }
1293         unsigned offset = 0;
1294         for (unsigned i = 0; i < num_additional_swms; i++) {
1295                 if (tstrcmp(globbuf.gl_pathv[i], first_part) == 0) {
1296                         offset++;
1297                         continue;
1298                 }
1299                 ret = wimlib_open_wim(globbuf.gl_pathv[i],
1300                                       open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK,
1301                                       &additional_swms[i - offset],
1302                                       imagex_progress_func);
1303                 if (ret)
1304                         goto out_close_swms;
1305         }
1306         *additional_swms_ret = additional_swms;
1307         *num_additional_swms_ret = num_additional_swms - offset;
1308         ret = 0;
1309         goto out_globfree;
1310 out_close_swms:
1311         for (unsigned i = 0; i < num_additional_swms; i++)
1312                 wimlib_free(additional_swms[i]);
1313         free(additional_swms);
1314 out_globfree:
1315         globfree(&globbuf);
1316 out:
1317         return ret;
1318 }
1319
1320
1321 static unsigned
1322 parse_num_threads(const tchar *optarg)
1323 {
1324         tchar *tmp;
1325         unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1326         if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1327                 imagex_error(T("Number of threads must be a non-negative integer!"));
1328                 return UINT_MAX;
1329         } else {
1330                 return ul_nthreads;
1331         }
1332 }
1333
1334 /*
1335  * Parse an option passed to an update command.
1336  *
1337  * @op:         One of WIMLIB_UPDATE_OP_* that indicates the command being
1338  *              parsed.
1339  *
1340  * @option:     Text string for the option (beginning with --)
1341  *
1342  * @cmd:        `struct wimlib_update_command' that is being constructed for
1343  *              this command.
1344  *
1345  * Returns true if the option was recognized; false if not.
1346  */
1347 static bool
1348 update_command_add_option(int op, const tchar *option,
1349                           struct wimlib_update_command *cmd)
1350 {
1351         bool recognized = true;
1352         switch (op) {
1353         case WIMLIB_UPDATE_OP_ADD:
1354                 if (!tstrcmp(option, T("--verbose")))
1355                         cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1356                 else if (!tstrcmp(option, T("--unix-data")))
1357                         cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1358                 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1359                         cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1360                 else if (!tstrcmp(option, T("--strict-acls")))
1361                         cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1362                 else if (!tstrcmp(option, T("--dereference")))
1363                         cmd->add.add_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1364                 else
1365                         recognized = false;
1366                 break;
1367         case WIMLIB_UPDATE_OP_DELETE:
1368                 if (!tstrcmp(option, T("--force")))
1369                         cmd->delete.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1370                 else if (!tstrcmp(option, T("--recursive")))
1371                         cmd->delete.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1372                 else
1373                         recognized = false;
1374                 break;
1375         default:
1376                 recognized = false;
1377                 break;
1378         }
1379         return recognized;
1380 }
1381
1382 /* How many nonoption arguments each `imagex update' command expects */
1383 static const unsigned update_command_num_nonoptions[] = {
1384         [WIMLIB_UPDATE_OP_ADD] = 2,
1385         [WIMLIB_UPDATE_OP_DELETE] = 1,
1386         [WIMLIB_UPDATE_OP_RENAME] = 2,
1387 };
1388
1389 static void
1390 update_command_add_nonoption(int op, const tchar *nonoption,
1391                              struct wimlib_update_command *cmd,
1392                              unsigned num_nonoptions)
1393 {
1394         switch (op) {
1395         case WIMLIB_UPDATE_OP_ADD:
1396                 if (num_nonoptions == 0)
1397                         cmd->add.fs_source_path = (tchar*)nonoption;
1398                 else
1399                         cmd->add.wim_target_path = (tchar*)nonoption;
1400                 break;
1401         case WIMLIB_UPDATE_OP_DELETE:
1402                 cmd->delete.wim_path = (tchar*)nonoption;
1403                 break;
1404         case WIMLIB_UPDATE_OP_RENAME:
1405                 if (num_nonoptions == 0)
1406                         cmd->rename.wim_source_path = (tchar*)nonoption;
1407                 else
1408                         cmd->rename.wim_target_path = (tchar*)nonoption;
1409                 break;
1410         }
1411 }
1412
1413 /*
1414  * Parse a command passed on stdin to `imagex update'.
1415  *
1416  * @line:       Text of the command.
1417  * @len:        Length of the line, including a null terminator
1418  *              at line[len - 1].
1419  *
1420  * @command:    A `struct wimlib_update_command' to fill in from the parsed
1421  *              line.
1422  *
1423  * @line_number: Line number of the command, for diagnostics.
1424  *
1425  * Returns true on success; returns false on parse error.
1426  */
1427 static bool
1428 parse_update_command(tchar *line, size_t len,
1429                      struct wimlib_update_command *command,
1430                      size_t line_number)
1431 {
1432         int ret;
1433         tchar *command_name;
1434         int op;
1435         size_t num_nonoptions;
1436
1437         /* Get the command name ("add", "delete", "rename") */
1438         ret = parse_string(&line, &len, &command_name);
1439         if (ret != PARSE_STRING_SUCCESS)
1440                 return false;
1441
1442         if (!tstrcasecmp(command_name, T("add"))) {
1443                 op = WIMLIB_UPDATE_OP_ADD;
1444         } else if (!tstrcasecmp(command_name, T("delete"))) {
1445                 op = WIMLIB_UPDATE_OP_DELETE;
1446         } else if (!tstrcasecmp(command_name, T("rename"))) {
1447                 op = WIMLIB_UPDATE_OP_RENAME;
1448         } else {
1449                 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1450                              command_name, line_number);
1451                 return false;
1452         }
1453         command->op = op;
1454
1455         /* Parse additional options and non-options as needed */
1456         num_nonoptions = 0;
1457         for (;;) {
1458                 tchar *next_string;
1459
1460                 ret = parse_string(&line, &len, &next_string);
1461                 if (ret == PARSE_STRING_NONE) /* End of line */
1462                         break;
1463                 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1464                         return false;
1465                 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1466                         /* Option */
1467                         if (!update_command_add_option(op, next_string, command))
1468                         {
1469                                 imagex_error(T("Unrecognized option \"%"TS"\" to "
1470                                                "update command \"%"TS"\" on line %zu"),
1471                                              next_string, command_name, line_number);
1472
1473                                 return false;
1474                         }
1475                 } else {
1476                         /* Nonoption */
1477                         if (num_nonoptions == update_command_num_nonoptions[op])
1478                         {
1479                                 imagex_error(T("Unexpected argument \"%"TS"\" in "
1480                                                "update command on line %zu\n"
1481                                                "       (The \"%"TS"\" command only "
1482                                                "takes %zu nonoption arguments!)\n"),
1483                                              next_string, line_number,
1484                                              command_name, num_nonoptions);
1485                                 return false;
1486                         }
1487                         update_command_add_nonoption(op, next_string,
1488                                                      command, num_nonoptions);
1489                         num_nonoptions++;
1490                 }
1491         }
1492
1493         if (num_nonoptions != update_command_num_nonoptions[op]) {
1494                 imagex_error(T("Not enough arguments to update command "
1495                                "\"%"TS"\" on line %zu"), command_name, line_number);
1496                 return false;
1497         }
1498         return true;
1499 }
1500
1501 static struct wimlib_update_command *
1502 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1503                           size_t *num_cmds_ret)
1504 {
1505         ssize_t nlines;
1506         tchar *p;
1507         struct wimlib_update_command *cmds;
1508         size_t i, j;
1509
1510         nlines = text_file_count_lines(cmd_file_contents_p,
1511                                        &cmd_file_nchars);
1512         if (nlines < 0)
1513                 return NULL;
1514
1515         /* Always allocate at least 1 slot, just in case the implementation of
1516          * calloc() returns NULL if 0 bytes are requested. */
1517         cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1518         if (!cmds) {
1519                 imagex_error(T("out of memory"));
1520                 return NULL;
1521         }
1522         p = *cmd_file_contents_p;
1523         j = 0;
1524         for (i = 0; i < nlines; i++) {
1525                 /* XXX: Could use rawmemchr() here instead, but it may not be
1526                  * available on all platforms. */
1527                 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1528                 size_t len = endp - p + 1;
1529                 *endp = T('\0');
1530                 if (!is_comment_line(p, len)) {
1531                         if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1532                                 free(cmds);
1533                                 return NULL;
1534                         }
1535                 }
1536                 p = endp + 1;
1537         }
1538         *num_cmds_ret = j;
1539         return cmds;
1540 }
1541
1542 /* Apply one image, or all images, from a WIM file into a directory, OR apply
1543  * one image from a WIM file to a NTFS volume.  */
1544 static int
1545 imagex_apply(int argc, tchar **argv)
1546 {
1547         int c;
1548         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1549         int image;
1550         WIMStruct *wim;
1551         int ret;
1552         const tchar *wimfile;
1553         const tchar *target;
1554         const tchar *image_num_or_name;
1555         int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
1556
1557         const tchar *swm_glob = NULL;
1558         WIMStruct **additional_swms;
1559         unsigned num_additional_swms;
1560
1561         for_opt(c, apply_options) {
1562                 switch (c) {
1563                 case IMAGEX_CHECK_OPTION:
1564                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1565                         break;
1566                 case IMAGEX_HARDLINK_OPTION:
1567                         extract_flags |= WIMLIB_EXTRACT_FLAG_HARDLINK;
1568                         break;
1569                 case IMAGEX_SYMLINK_OPTION:
1570                         extract_flags |= WIMLIB_EXTRACT_FLAG_SYMLINK;
1571                         break;
1572                 case IMAGEX_VERBOSE_OPTION:
1573                         extract_flags |= WIMLIB_EXTRACT_FLAG_VERBOSE;
1574                         break;
1575                 case IMAGEX_REF_OPTION:
1576                         swm_glob = optarg;
1577                         break;
1578                 case IMAGEX_UNIX_DATA_OPTION:
1579                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1580                         break;
1581                 case IMAGEX_NO_ACLS_OPTION:
1582                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1583                         break;
1584                 case IMAGEX_STRICT_ACLS_OPTION:
1585                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1586                         break;
1587                 case IMAGEX_NORPFIX_OPTION:
1588                         extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1589                         break;
1590                 case IMAGEX_RPFIX_OPTION:
1591                         extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1592                         break;
1593                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1594                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1595                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1596                         break;
1597                 default:
1598                         goto out_usage;
1599                 }
1600         }
1601         argc -= optind;
1602         argv += optind;
1603         if (argc != 2 && argc != 3)
1604                 goto out_usage;
1605
1606         wimfile = argv[0];
1607
1608         if (!tstrcmp(wimfile, T("-"))) {
1609                 /* Attempt to apply pipable WIM from standard input.  */
1610                 if (argc < 3) {
1611                         imagex_error(T("Imagex index or name must be explicitly "
1612                                        "specified when applying pipable WIM on "
1613                                        "standard input."));
1614                         goto out_usage;
1615                 }
1616                 image_num_or_name = argv[1];
1617                 target = argv[2];
1618                 wim = NULL;
1619                 num_additional_swms = 0;
1620                 additional_swms = NULL;
1621         } else {
1622                 ret = wimlib_open_wim(wimfile, open_flags, &wim,
1623                                       imagex_progress_func);
1624                 if (ret)
1625                         goto out;
1626
1627                 if (argc >= 3) {
1628                         /* Image explicitly specified.  */
1629                         image_num_or_name = argv[1];
1630                         image = wimlib_resolve_image(wim, image_num_or_name);
1631                         ret = verify_image_exists(image, image_num_or_name, wimfile);
1632                         if (ret)
1633                                 goto out_wimlib_free;
1634                         target = argv[2];
1635                 } else {
1636                         /* No image specified; default to image 1, but only if the WIM
1637                          * contains exactly one image.  */
1638                         struct wimlib_wim_info info;
1639
1640                         wimlib_get_wim_info(wim, &info);
1641                         if (info.image_count != 1) {
1642                                 imagex_error(T("\"%"TS"\" contains %d images; "
1643                                                "Please select one (or all)."),
1644                                              wimfile, info.image_count);
1645                                 wimlib_free(wim);
1646                                 goto out_usage;
1647                         }
1648                         image = 1;
1649                         target = argv[1];
1650                 }
1651
1652                 if (swm_glob) {
1653                         ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
1654                                                   &additional_swms,
1655                                                   &num_additional_swms);
1656                         if (ret)
1657                                 goto out_wimlib_free;
1658                 } else {
1659                         additional_swms = NULL;
1660                         num_additional_swms = 0;
1661                 }
1662         }
1663
1664 #ifndef __WIN32__
1665         {
1666                 /* Interpret a regular file or block device target as a NTFS
1667                  * volume.  */
1668                 struct stat stbuf;
1669
1670                 if (tstat(target, &stbuf)) {
1671                         if (errno != ENOENT) {
1672                                 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1673                                                         target);
1674                                 ret = -1;
1675                                 goto out_free_swms;
1676                         }
1677                 } else {
1678                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1679                                 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1680                 }
1681         }
1682 #endif
1683
1684 #ifdef __WIN32__
1685         win32_acquire_restore_privileges();
1686 #endif
1687         if (wim) {
1688                 ret = wimlib_extract_image(wim, image, target, extract_flags,
1689                                            additional_swms, num_additional_swms,
1690                                            imagex_progress_func);
1691         } else {
1692                 set_fd_to_binary_mode(STDIN_FILENO);
1693                 ret = wimlib_extract_image_from_pipe(STDIN_FILENO,
1694                                                      image_num_or_name,
1695                                                      target, extract_flags,
1696                                                      imagex_progress_func);
1697         }
1698         if (ret == 0)
1699                 imagex_printf(T("Done applying WIM image.\n"));
1700 #ifdef __WIN32__
1701         win32_release_restore_privileges();
1702 #endif
1703 out_free_swms:
1704         for (unsigned i = 0; i < num_additional_swms; i++)
1705                 wimlib_free(additional_swms[i]);
1706         free(additional_swms);
1707 out_wimlib_free:
1708         wimlib_free(wim);
1709 out:
1710         return ret;
1711
1712 out_usage:
1713         usage(APPLY);
1714         ret = -1;
1715         goto out;
1716 }
1717
1718 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1719  * directory trees.  'wimlib-imagex capture': create a new WIM file containing
1720  * the desired image.  'wimlib-imagex append': add a new image to an existing
1721  * WIM file. */
1722 static int
1723 imagex_capture_or_append(int argc, tchar **argv)
1724 {
1725         int c;
1726         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
1727         int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE;
1728         int write_flags = 0;
1729         int compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1730         const tchar *wimfile;
1731         int wim_fd;
1732         const tchar *name;
1733         const tchar *desc;
1734         const tchar *flags_element = NULL;
1735         WIMStruct *wim;
1736         int ret;
1737         int cmd = tstrcmp(argv[0], T("append")) ? CAPTURE : APPEND;
1738         unsigned num_threads = 0;
1739
1740         tchar *source;
1741         tchar *source_copy;
1742
1743         const tchar *config_file = NULL;
1744         tchar *config_str;
1745         struct wimlib_capture_config *config;
1746
1747         bool source_list = false;
1748         size_t source_list_nchars;
1749         tchar *source_list_contents;
1750         bool capture_sources_malloced;
1751         struct wimlib_capture_source *capture_sources;
1752         size_t num_sources;
1753         bool name_defaulted;
1754
1755         for_opt(c, capture_or_append_options) {
1756                 switch (c) {
1757                 case IMAGEX_BOOT_OPTION:
1758                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
1759                         break;
1760                 case IMAGEX_CHECK_OPTION:
1761                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1762                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1763                         break;
1764                 case IMAGEX_NOCHECK_OPTION:
1765                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1766                         break;
1767                 case IMAGEX_CONFIG_OPTION:
1768                         config_file = optarg;
1769                         break;
1770                 case IMAGEX_COMPRESS_OPTION:
1771                         compression_type = get_compression_type(optarg);
1772                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1773                                 goto out_err;
1774                         break;
1775                 case IMAGEX_FLAGS_OPTION:
1776                         flags_element = optarg;
1777                         break;
1778                 case IMAGEX_DEREFERENCE_OPTION:
1779                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1780                         break;
1781                 case IMAGEX_VERBOSE_OPTION:
1782                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1783                         break;
1784                 case IMAGEX_THREADS_OPTION:
1785                         num_threads = parse_num_threads(optarg);
1786                         if (num_threads == UINT_MAX)
1787                                 goto out_err;
1788                         break;
1789                 case IMAGEX_REBUILD_OPTION:
1790                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1791                         break;
1792                 case IMAGEX_UNIX_DATA_OPTION:
1793                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1794                         break;
1795                 case IMAGEX_SOURCE_LIST_OPTION:
1796                         source_list = true;
1797                         break;
1798                 case IMAGEX_NO_ACLS_OPTION:
1799                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1800                         break;
1801                 case IMAGEX_STRICT_ACLS_OPTION:
1802                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1803                         break;
1804                 case IMAGEX_RPFIX_OPTION:
1805                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_RPFIX;
1806                         break;
1807                 case IMAGEX_NORPFIX_OPTION:
1808                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NORPFIX;
1809                         break;
1810                 case IMAGEX_PIPABLE_OPTION:
1811                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1812                         break;
1813                 case IMAGEX_NOT_PIPABLE_OPTION:
1814                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1815                         break;
1816                 default:
1817                         goto out_usage;
1818                 }
1819         }
1820         argc -= optind;
1821         argv += optind;
1822
1823         if (argc < 2 || argc > 4)
1824                 goto out_usage;
1825
1826         source = argv[0];
1827         wimfile = argv[1];
1828
1829         if (!tstrcmp(wimfile, T("-"))) {
1830         #if 0
1831                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1832                         imagex_error("Can't write a non-pipable WIM to "
1833                                      "standard output!  Specify --pipable\n"
1834                                      "       if you want to create a pipable WIM "
1835                                      "(but read the docs first).");
1836                         goto out_err;
1837                 }
1838         #else
1839                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1840         #endif
1841                 if (cmd == APPEND) {
1842                         imagex_error(T("Using standard output for append does "
1843                                        "not make sense."));
1844                         goto out_err;
1845                 }
1846                 wim_fd = STDOUT_FILENO;
1847                 wimfile = NULL;
1848                 imagex_info_file = stderr;
1849         }
1850
1851         if (argc >= 3) {
1852                 name = argv[2];
1853                 name_defaulted = false;
1854         } else {
1855                 /* Set default name to SOURCE argument, omitting any directory
1856                  * prefixes and trailing slashes.  This requires making a copy
1857                  * of @source.  Leave some free characters at the end in case we
1858                  * append a number to keep the name unique. */
1859                 size_t source_name_len;
1860
1861                 source_name_len = tstrlen(source);
1862                 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1863                 name = tbasename(tstrcpy(source_copy, source));
1864                 name_defaulted = true;
1865         }
1866         /* Image description defaults to NULL if not given. */
1867         if (argc >= 4)
1868                 desc = argv[3];
1869         else
1870                 desc = NULL;
1871
1872         if (source_list) {
1873                 /* Set up capture sources in source list mode */
1874                 if (source[0] == T('-') && source[1] == T('\0')) {
1875                         source_list_contents = stdin_get_text_contents(&source_list_nchars);
1876                 } else {
1877                         source_list_contents = file_get_text_contents(source,
1878                                                                       &source_list_nchars);
1879                 }
1880                 if (!source_list_contents)
1881                         goto out_err;
1882
1883                 capture_sources = parse_source_list(&source_list_contents,
1884                                                     source_list_nchars,
1885                                                     &num_sources);
1886                 if (!capture_sources) {
1887                         ret = -1;
1888                         goto out_free_source_list_contents;
1889                 }
1890                 capture_sources_malloced = true;
1891         } else {
1892                 /* Set up capture source in non-source-list mode (could be
1893                  * either "normal" mode or "NTFS mode"--- see the man page). */
1894                 capture_sources = alloca(sizeof(struct wimlib_capture_source));
1895                 capture_sources[0].fs_source_path = source;
1896                 capture_sources[0].wim_target_path = NULL;
1897                 capture_sources[0].reserved = 0;
1898                 num_sources = 1;
1899                 capture_sources_malloced = false;
1900                 source_list_contents = NULL;
1901         }
1902
1903         if (config_file) {
1904                 size_t config_len;
1905
1906                 config_str = file_get_text_contents(config_file, &config_len);
1907                 if (!config_str) {
1908                         ret = -1;
1909                         goto out_free_capture_sources;
1910                 }
1911
1912                 config = alloca(sizeof(*config));
1913                 ret = parse_capture_config(&config_str, config_len, config);
1914                 if (ret)
1915                         goto out_free_config;
1916         } else {
1917                 config = &default_capture_config;
1918         }
1919
1920         if (cmd == APPEND)
1921                 ret = wimlib_open_wim(wimfile, open_flags, &wim,
1922                                       imagex_progress_func);
1923         else
1924                 ret = wimlib_create_new_wim(compression_type, &wim);
1925         if (ret)
1926                 goto out_free_config;
1927
1928 #ifndef __WIN32__
1929         if (!source_list) {
1930                 struct stat stbuf;
1931
1932                 if (tstat(source, &stbuf) == 0) {
1933                         if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
1934                                 imagex_printf(T("Capturing WIM image from NTFS "
1935                                           "filesystem on \"%"TS"\"\n"), source);
1936                                 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
1937                         }
1938                 } else {
1939                         if (errno != ENOENT) {
1940                                 imagex_error_with_errno(T("Failed to stat "
1941                                                           "\"%"TS"\""), source);
1942                                 ret = -1;
1943                                 goto out_wimlib_free;
1944                         }
1945                 }
1946         }
1947 #endif
1948
1949         if (cmd == APPEND && name_defaulted) {
1950                 /* If the user did not specify an image name, and the basename
1951                  * of the source already exists as an image name in the WIM
1952                  * file, append a suffix to make it unique. */
1953                 unsigned long conflict_idx;
1954                 tchar *name_end = tstrchr(name, T('\0'));
1955                 for (conflict_idx = 1;
1956                      wimlib_image_name_in_use(wim, name);
1957                      conflict_idx++)
1958                 {
1959                         tsprintf(name_end, T(" (%lu)"), conflict_idx);
1960                 }
1961         }
1962 #ifdef __WIN32__
1963         win32_acquire_capture_privileges();
1964 #endif
1965
1966         ret = wimlib_add_image_multisource(wim,
1967                                            capture_sources,
1968                                            num_sources,
1969                                            name,
1970                                            config,
1971                                            add_image_flags,
1972                                            imagex_progress_func);
1973         if (ret)
1974                 goto out_release_privs;
1975
1976         if (desc || flags_element) {
1977                 /* User provided <DESCRIPTION> or <FLAGS> element.  Get the
1978                  * index of the image we just added, then use it to call the
1979                  * appropriate functions.  */
1980                 struct wimlib_wim_info info;
1981
1982                 wimlib_get_wim_info(wim, &info);
1983
1984                 if (desc) {
1985                         ret = wimlib_set_image_descripton(wim,
1986                                                           info.image_count,
1987                                                           desc);
1988                         if (ret)
1989                                 goto out_release_privs;
1990                 }
1991
1992                 if (flags_element) {
1993                         ret = wimlib_set_image_flags(wim, info.image_count,
1994                                                      flags_element);
1995                         if (ret)
1996                                 goto out_release_privs;
1997                 }
1998         }
1999
2000         /* Write the new WIM or overwrite the existing WIM with the new image
2001          * appended.  */
2002         if (cmd == APPEND) {
2003                 ret = wimlib_overwrite(wim, write_flags, num_threads,
2004                                        imagex_progress_func);
2005         } else if (wimfile) {
2006                 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2007                                    write_flags, num_threads,
2008                                    imagex_progress_func);
2009         } else {
2010                 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2011                                          write_flags, num_threads,
2012                                          imagex_progress_func);
2013         }
2014 out_release_privs:
2015 #ifdef __WIN32__
2016         win32_release_capture_privileges();
2017 #endif
2018 out_wimlib_free:
2019         wimlib_free(wim);
2020 out_free_config:
2021         if (config != &default_capture_config) {
2022                 free(config->exclusion_pats.pats);
2023                 free(config->exclusion_exception_pats.pats);
2024                 free(config_str);
2025         }
2026 out_free_capture_sources:
2027         if (capture_sources_malloced)
2028                 free(capture_sources);
2029 out_free_source_list_contents:
2030         free(source_list_contents);
2031 out:
2032         return ret;
2033
2034 out_usage:
2035         usage(cmd);
2036 out_err:
2037         ret = -1;
2038         goto out;
2039 }
2040
2041 /* Remove image(s) from a WIM. */
2042 static int
2043 imagex_delete(int argc, tchar **argv)
2044 {
2045         int c;
2046         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2047         int write_flags = 0;
2048         const tchar *wimfile;
2049         const tchar *image_num_or_name;
2050         WIMStruct *wim;
2051         int image;
2052         int ret;
2053
2054         for_opt(c, delete_options) {
2055                 switch (c) {
2056                 case IMAGEX_CHECK_OPTION:
2057                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2058                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2059                         break;
2060                 case IMAGEX_SOFT_OPTION:
2061                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2062                         break;
2063                 default:
2064                         goto out_usage;
2065                 }
2066         }
2067         argc -= optind;
2068         argv += optind;
2069
2070         if (argc != 2) {
2071                 if (argc < 1)
2072                         imagex_error(T("Must specify a WIM file"));
2073                 if (argc < 2)
2074                         imagex_error(T("Must specify an image"));
2075                 goto out_usage;
2076         }
2077         wimfile = argv[0];
2078         image_num_or_name = argv[1];
2079
2080         ret = wimlib_open_wim(wimfile, open_flags, &wim,
2081                               imagex_progress_func);
2082         if (ret)
2083                 goto out;
2084
2085         image = wimlib_resolve_image(wim, image_num_or_name);
2086
2087         ret = verify_image_exists(image, image_num_or_name, wimfile);
2088         if (ret)
2089                 goto out_wimlib_free;
2090
2091         ret = wimlib_delete_image(wim, image);
2092         if (ret) {
2093                 imagex_error(T("Failed to delete image from \"%"TS"\""),
2094                              wimfile);
2095                 goto out_wimlib_free;
2096         }
2097
2098         ret = wimlib_overwrite(wim, write_flags, 0, imagex_progress_func);
2099         if (ret) {
2100                 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2101                                "deleted"), wimfile);
2102         }
2103 out_wimlib_free:
2104         wimlib_free(wim);
2105 out:
2106         return ret;
2107
2108 out_usage:
2109         usage(DELETE);
2110         ret = -1;
2111         goto out;
2112 }
2113
2114 static int
2115 print_full_path(const struct wimlib_dir_entry *wdentry, void *_ignore)
2116 {
2117         int ret = tprintf(T("%"TS"\n"), wdentry->full_path);
2118         return (ret >= 0) ? 0 : -1;
2119 }
2120
2121 /* Print the files contained in an image(s) in a WIM file. */
2122 static int
2123 imagex_dir(int argc, tchar **argv)
2124 {
2125         const tchar *wimfile;
2126         WIMStruct *wim = NULL;
2127         int image;
2128         int ret;
2129         const tchar *path = T("");
2130         int c;
2131
2132         for_opt(c, dir_options) {
2133                 switch (c) {
2134                 case IMAGEX_PATH_OPTION:
2135                         path = optarg;
2136                         break;
2137                 default:
2138                         goto out_usage;
2139                 }
2140         }
2141         argc -= optind;
2142         argv += optind;
2143
2144         if (argc < 1) {
2145                 imagex_error(T("Must specify a WIM file"));
2146                 goto out_usage;
2147         }
2148         if (argc > 2) {
2149                 imagex_error(T("Too many arguments"));
2150                 goto out_usage;
2151         }
2152
2153         wimfile = argv[0];
2154         ret = wimlib_open_wim(wimfile, WIMLIB_OPEN_FLAG_SPLIT_OK, &wim,
2155                               imagex_progress_func);
2156         if (ret)
2157                 goto out;
2158
2159         if (argc >= 2) {
2160                 image = wimlib_resolve_image(wim, argv[1]);
2161                 ret = verify_image_exists(image, argv[1], wimfile);
2162                 if (ret)
2163                         goto out_wimlib_free;
2164         } else {
2165                 /* No image specified; default to image 1, but only if the WIM
2166                  * contains exactly one image.  */
2167
2168                 struct wimlib_wim_info info;
2169
2170                 wimlib_get_wim_info(wim, &info);
2171                 if (info.image_count != 1) {
2172                         imagex_error(T("\"%"TS"\" contains %d images; Please "
2173                                        "select one (or all)."),
2174                                      wimfile, info.image_count);
2175                         wimlib_free(wim);
2176                         goto out_usage;
2177                 }
2178                 image = 1;
2179         }
2180
2181         ret = wimlib_iterate_dir_tree(wim, image, path,
2182                                       WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE,
2183                                       print_full_path, NULL);
2184 out_wimlib_free:
2185         wimlib_free(wim);
2186 out:
2187         return ret;
2188
2189 out_usage:
2190         usage(DIR);
2191         ret = -1;
2192         goto out;
2193 }
2194
2195 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2196  * WIM file. */
2197 static int
2198 imagex_export(int argc, tchar **argv)
2199 {
2200         int c;
2201         int open_flags = 0;
2202         int export_flags = 0;
2203         int write_flags = 0;
2204         int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2205         const tchar *src_wimfile;
2206         const tchar *src_image_num_or_name;
2207         const tchar *dest_wimfile;
2208         int dest_wim_fd;
2209         const tchar *dest_name;
2210         const tchar *dest_desc;
2211         WIMStruct *src_wim;
2212         WIMStruct *dest_wim;
2213         int ret;
2214         int image;
2215         struct stat stbuf;
2216         bool wim_is_new;
2217         const tchar *swm_glob = NULL;
2218         WIMStruct **additional_swms;
2219         unsigned num_additional_swms;
2220         unsigned num_threads = 0;
2221
2222         for_opt(c, export_options) {
2223                 switch (c) {
2224                 case IMAGEX_BOOT_OPTION:
2225                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2226                         break;
2227                 case IMAGEX_CHECK_OPTION:
2228                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2229                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2230                         break;
2231                 case IMAGEX_NOCHECK_OPTION:
2232                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2233                         break;
2234                 case IMAGEX_COMPRESS_OPTION:
2235                         compression_type = get_compression_type(optarg);
2236                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2237                                 goto out_err;
2238                         break;
2239                 case IMAGEX_REF_OPTION:
2240                         swm_glob = optarg;
2241                         break;
2242                 case IMAGEX_THREADS_OPTION:
2243                         num_threads = parse_num_threads(optarg);
2244                         if (num_threads == UINT_MAX)
2245                                 goto out_err;
2246                         break;
2247                 case IMAGEX_REBUILD_OPTION:
2248                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2249                         break;
2250                 case IMAGEX_PIPABLE_OPTION:
2251                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2252                         break;
2253                 case IMAGEX_NOT_PIPABLE_OPTION:
2254                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2255                         break;
2256                 default:
2257                         goto out_usage;
2258                 }
2259         }
2260         argc -= optind;
2261         argv += optind;
2262         if (argc < 3 || argc > 5)
2263                 goto out_usage;
2264         src_wimfile           = argv[0];
2265         src_image_num_or_name = argv[1];
2266         dest_wimfile          = argv[2];
2267         dest_name             = (argc >= 4) ? argv[3] : NULL;
2268         dest_desc             = (argc >= 5) ? argv[4] : NULL;
2269         ret = wimlib_open_wim(src_wimfile,
2270                               open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK, &src_wim,
2271                               imagex_progress_func);
2272         if (ret)
2273                 goto out;
2274
2275         /* Determine if the destination is an existing file or not.  If so, we
2276          * try to append the exported image(s) to it; otherwise, we create a new
2277          * WIM containing the exported image(s).  Furthermore, determine if we
2278          * need to write a pipable WIM directly to standard output.  */
2279
2280         if (tstrcmp(dest_wimfile, T("-")) == 0) {
2281         #if 0
2282                 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2283                         imagex_error("Can't write a non-pipable WIM to "
2284                                      "standard output!  Specify --pipable\n"
2285                                      "       if you want to create a pipable WIM "
2286                                      "(but read the docs first).");
2287                         ret = -1;
2288                         goto out_free_src_wim;
2289                 }
2290         #else
2291                 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2292         #endif
2293                 dest_wimfile = NULL;
2294                 dest_wim_fd = STDOUT_FILENO;
2295                 imagex_info_file = stderr;
2296         }
2297         errno = ENOENT;
2298         if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2299                 wim_is_new = false;
2300                 /* Destination file exists. */
2301
2302                 if (!S_ISREG(stbuf.st_mode)) {
2303                         imagex_error(T("\"%"TS"\" is not a regular file"),
2304                                      dest_wimfile);
2305                         ret = -1;
2306                         goto out_free_src_wim;
2307                 }
2308                 ret = wimlib_open_wim(dest_wimfile, open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2309                                       &dest_wim, imagex_progress_func);
2310                 if (ret)
2311                         goto out_free_src_wim;
2312
2313                 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2314                         /* The user specified a compression type, but we're
2315                          * exporting to an existing WIM.  Make sure the
2316                          * specified compression type is the same as the
2317                          * compression type of the existing destination WIM. */
2318                         struct wimlib_wim_info dest_info;
2319
2320                         wimlib_get_wim_info(dest_wim, &dest_info);
2321                         if (compression_type != dest_info.compression_type) {
2322                                 imagex_error(T("Cannot specify a compression type that is "
2323                                                "not the same as that used in the "
2324                                                "destination WIM"));
2325                                 ret = -1;
2326                                 goto out_free_dest_wim;
2327                         }
2328                 }
2329         } else {
2330                 wim_is_new = true;
2331
2332                 if (errno != ENOENT) {
2333                         imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2334                                                 dest_wimfile);
2335                         ret = -1;
2336                         goto out_free_src_wim;
2337                 }
2338
2339                 /* dest_wimfile is not an existing file, so create a new WIM. */
2340
2341                 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2342                         /* The user did not specify a compression type; default
2343                          * to that of the source WIM.  */
2344
2345                         struct wimlib_wim_info src_info;
2346
2347                         wimlib_get_wim_info(src_wim, &src_info);
2348                         compression_type = src_info.compression_type;
2349                 }
2350                 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2351                 if (ret)
2352                         goto out_free_src_wim;
2353         }
2354
2355         image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2356         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2357         if (ret)
2358                 goto out_free_dest_wim;
2359
2360         if (swm_glob) {
2361                 ret = open_swms_from_glob(swm_glob, src_wimfile, open_flags,
2362                                           &additional_swms,
2363                                           &num_additional_swms);
2364                 if (ret)
2365                         goto out_free_dest_wim;
2366         } else {
2367                 additional_swms = NULL;
2368                 num_additional_swms = 0;
2369         }
2370
2371         ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2372                                   dest_desc, export_flags, additional_swms,
2373                                   num_additional_swms, imagex_progress_func);
2374         if (ret)
2375                 goto out_free_swms;
2376
2377         if (!wim_is_new)
2378                 ret = wimlib_overwrite(dest_wim, write_flags, num_threads,
2379                                        imagex_progress_func);
2380         else if (dest_wimfile)
2381                 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2382                                    write_flags, num_threads,
2383                                    imagex_progress_func);
2384         else
2385                 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2386                                          WIMLIB_ALL_IMAGES, write_flags,
2387                                          num_threads, imagex_progress_func);
2388         if (ret)
2389                 imagex_error(T("Export failed."));
2390 out_free_swms:
2391         for (unsigned i = 0; i < num_additional_swms; i++)
2392                 wimlib_free(additional_swms[i]);
2393         free(additional_swms);
2394 out_free_dest_wim:
2395         wimlib_free(dest_wim);
2396 out_free_src_wim:
2397         wimlib_free(src_wim);
2398 out:
2399         return ret;
2400
2401 out_usage:
2402         usage(EXPORT);
2403 out_err:
2404         ret = -1;
2405         goto out;
2406 }
2407
2408 static bool
2409 is_root_wim_path(const tchar *path)
2410 {
2411         const tchar *p;
2412         for (p = path; *p; p++)
2413                 if (*p != T('\\') && *p != T('/'))
2414                         return false;
2415         return true;
2416 }
2417
2418 static void
2419 free_extract_commands(struct wimlib_extract_command *cmds, size_t num_cmds,
2420                       const tchar *dest_dir)
2421 {
2422         for (size_t i = 0; i < num_cmds; i++)
2423                 if (cmds[i].fs_dest_path != dest_dir)
2424                         free(cmds[i].fs_dest_path);
2425         free(cmds);
2426 }
2427
2428 static struct wimlib_extract_command *
2429 prepare_extract_commands(tchar **paths, unsigned num_paths,
2430                          int extract_flags, tchar *dest_dir,
2431                          size_t *num_cmds_ret)
2432 {
2433         struct wimlib_extract_command *cmds;
2434         size_t num_cmds;
2435         tchar *emptystr = T("");
2436
2437         if (num_paths == 0) {
2438                 num_paths = 1;
2439                 paths = &emptystr;
2440         }
2441         num_cmds = num_paths;
2442         cmds = calloc(num_cmds, sizeof(cmds[0]));
2443         if (!cmds) {
2444                 imagex_error(T("Out of memory!"));
2445                 return NULL;
2446         }
2447
2448         for (size_t i = 0; i < num_cmds; i++) {
2449                 cmds[i].extract_flags = extract_flags;
2450                 cmds[i].wim_source_path = paths[i];
2451                 if (is_root_wim_path(paths[i])) {
2452                         cmds[i].fs_dest_path = dest_dir;
2453                 } else {
2454                         size_t len = tstrlen(dest_dir) + 1 + tstrlen(paths[i]);
2455                         cmds[i].fs_dest_path = malloc((len + 1) * sizeof(tchar));
2456                         if (!cmds[i].fs_dest_path) {
2457                                 free_extract_commands(cmds, num_cmds, dest_dir);
2458                                 return NULL;
2459                         }
2460                         tsprintf(cmds[i].fs_dest_path,
2461                                  T("%"TS""OS_PREFERRED_PATH_SEPARATOR_STRING"%"TS),
2462                                  dest_dir, tbasename(paths[i]));
2463                 }
2464         }
2465         *num_cmds_ret = num_cmds;
2466         return cmds;
2467 }
2468
2469 /* Extract files or directories from a WIM image */
2470 static int
2471 imagex_extract(int argc, tchar **argv)
2472 {
2473         int c;
2474         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
2475         int image;
2476         WIMStruct *wim;
2477         int ret;
2478         const tchar *wimfile;
2479         const tchar *image_num_or_name;
2480         tchar *dest_dir = T(".");
2481         int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL | WIMLIB_EXTRACT_FLAG_NORPFIX;
2482
2483         const tchar *swm_glob = NULL;
2484         WIMStruct **additional_swms;
2485         unsigned num_additional_swms;
2486
2487         struct wimlib_extract_command *cmds;
2488         size_t num_cmds;
2489
2490         for_opt(c, extract_options) {
2491                 switch (c) {
2492                 case IMAGEX_CHECK_OPTION:
2493                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2494                         break;
2495                 case IMAGEX_VERBOSE_OPTION:
2496                         extract_flags |= WIMLIB_EXTRACT_FLAG_VERBOSE;
2497                         break;
2498                 case IMAGEX_REF_OPTION:
2499                         swm_glob = optarg;
2500                         break;
2501                 case IMAGEX_UNIX_DATA_OPTION:
2502                         extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2503                         break;
2504                 case IMAGEX_NO_ACLS_OPTION:
2505                         extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2506                         break;
2507                 case IMAGEX_STRICT_ACLS_OPTION:
2508                         extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2509                         break;
2510                 case IMAGEX_DEST_DIR_OPTION:
2511                         dest_dir = optarg;
2512                         break;
2513                 case IMAGEX_TO_STDOUT_OPTION:
2514                         extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2515                         imagex_info_file = stderr;
2516                         imagex_be_quiet = true;
2517                         break;
2518                 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2519                         extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2520                         extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2521                         break;
2522                 default:
2523                         goto out_usage;
2524                 }
2525         }
2526         argc -= optind;
2527         argv += optind;
2528
2529         if (argc < 2)
2530                 goto out_usage;
2531
2532         wimfile = argv[0];
2533         image_num_or_name = argv[1];
2534
2535         argc -= 2;
2536         argv += 2;
2537
2538         cmds = prepare_extract_commands(argv, argc, extract_flags, dest_dir,
2539                                         &num_cmds);
2540         if (!cmds)
2541                 goto out_err;
2542
2543         ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
2544         if (ret)
2545                 goto out_free_cmds;
2546
2547         image = wimlib_resolve_image(wim, image_num_or_name);
2548         ret = verify_image_exists_and_is_single(image,
2549                                                 image_num_or_name,
2550                                                 wimfile);
2551         if (ret)
2552                 goto out_wimlib_free;
2553
2554         if (swm_glob) {
2555                 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
2556                                           &additional_swms,
2557                                           &num_additional_swms);
2558                 if (ret)
2559                         goto out_wimlib_free;
2560         } else {
2561                 additional_swms = NULL;
2562                 num_additional_swms = 0;
2563         }
2564
2565 #ifdef __WIN32__
2566         win32_acquire_restore_privileges();
2567 #endif
2568
2569         ret = wimlib_extract_files(wim, image, cmds, num_cmds, 0,
2570                                    additional_swms, num_additional_swms,
2571                                    imagex_progress_func);
2572         if (ret == 0) {
2573                 if (!imagex_be_quiet)
2574                         imagex_printf(T("Done extracting files.\n"));
2575         } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
2576                 tfprintf(stderr, T("Note: You can use `"IMAGEX_PROGNAME" dir' to see what "
2577                                    "files and directories\n"
2578                                    "      are in the WIM image.\n"));
2579         }
2580 #ifdef __WIN32__
2581         win32_release_restore_privileges();
2582 #endif
2583         for (unsigned i = 0; i < num_additional_swms; i++)
2584                 wimlib_free(additional_swms[i]);
2585         free(additional_swms);
2586 out_wimlib_free:
2587         wimlib_free(wim);
2588 out_free_cmds:
2589         free_extract_commands(cmds, num_cmds, dest_dir);
2590 out:
2591         return ret;
2592
2593 out_usage:
2594         usage(EXTRACT);
2595 out_err:
2596         ret = -1;
2597         goto out;
2598 }
2599
2600 static void print_byte_field(const uint8_t field[], size_t len)
2601 {
2602         while (len--)
2603                 tprintf(T("%02hhx"), *field++);
2604 }
2605
2606 static void
2607 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2608 {
2609         tputs(T("WIM Information:"));
2610         tputs(T("----------------"));
2611         tprintf(T("Path:           %"TS"\n"), wimfile);
2612         tprintf(T("GUID:           0x"));
2613         print_byte_field(info->guid, sizeof(info->guid));
2614         tputchar(T('\n'));
2615         tprintf(T("Image Count:    %d\n"), info->image_count);
2616         tprintf(T("Compression:    %"TS"\n"),
2617                 wimlib_get_compression_type_string(info->compression_type));
2618         tprintf(T("Part Number:    %d/%d\n"), info->part_number, info->total_parts);
2619         tprintf(T("Boot Index:     %d\n"), info->boot_index);
2620         tprintf(T("Size:           %"PRIu64" bytes\n"), info->total_bytes);
2621         tprintf(T("Integrity Info: %"TS"\n"),
2622                 info->has_integrity_table ? T("yes") : T("no"));
2623         tprintf(T("Relative path junction: %"TS"\n"),
2624                 info->has_rpfix ? T("yes") : T("no"));
2625         tprintf(T("Pipable:        %"TS"\n"),
2626                 info->pipable ? T("yes") : T("no"));
2627         tputchar(T('\n'));
2628 }
2629
2630 static int
2631 print_resource(const struct wimlib_resource_entry *resource,
2632                void *_ignore)
2633 {
2634
2635         tprintf(T("Uncompressed size   = %"PRIu64" bytes\n"),
2636                 resource->uncompressed_size);
2637
2638         tprintf(T("Compressed size     = %"PRIu64" bytes\n"),
2639                 resource->compressed_size);
2640
2641         tprintf(T("Offset              = %"PRIu64" bytes\n"),
2642                 resource->offset);
2643
2644
2645         tprintf(T("Part Number         = %u\n"), resource->part_number);
2646         tprintf(T("Reference Count     = %u\n"), resource->reference_count);
2647
2648         tprintf(T("Hash                = 0x"));
2649         print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2650         tputchar(T('\n'));
2651
2652         tprintf(T("Flags               = "));
2653         if (resource->is_compressed)
2654                 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED  "));
2655         if (resource->is_metadata)
2656                 tprintf(T("WIM_RESHDR_FLAG_METADATA  "));
2657         if (resource->is_free)
2658                 tprintf(T("WIM_RESHDR_FLAG_FREE  "));
2659         if (resource->is_spanned)
2660                 tprintf(T("WIM_RESHDR_FLAG_SPANNED  "));
2661         tputchar(T('\n'));
2662         tputchar(T('\n'));
2663         return 0;
2664 }
2665
2666 static void
2667 print_lookup_table(WIMStruct *wim)
2668 {
2669         wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2670 }
2671
2672 /* Prints information about a WIM file; also can mark an image as bootable,
2673  * change the name of an image, or change the description of an image. */
2674 static int
2675 imagex_info(int argc, tchar **argv)
2676 {
2677         int c;
2678         bool boot         = false;
2679         bool check        = false;
2680         bool nocheck      = false;
2681         bool header       = false;
2682         bool lookup_table = false;
2683         bool xml          = false;
2684         bool metadata     = false;
2685         bool short_header = true;
2686         const tchar *xml_out_file = NULL;
2687         const tchar *wimfile;
2688         const tchar *image_num_or_name;
2689         const tchar *new_name;
2690         const tchar *new_desc;
2691         WIMStruct *wim;
2692         int image;
2693         int ret;
2694         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
2695         struct wimlib_wim_info info;
2696
2697         for_opt(c, info_options) {
2698                 switch (c) {
2699                 case IMAGEX_BOOT_OPTION:
2700                         boot = true;
2701                         break;
2702                 case IMAGEX_CHECK_OPTION:
2703                         check = true;
2704                         break;
2705                 case IMAGEX_NOCHECK_OPTION:
2706                         nocheck = true;
2707                         break;
2708                 case IMAGEX_HEADER_OPTION:
2709                         header = true;
2710                         short_header = false;
2711                         break;
2712                 case IMAGEX_LOOKUP_TABLE_OPTION:
2713                         lookup_table = true;
2714                         short_header = false;
2715                         break;
2716                 case IMAGEX_XML_OPTION:
2717                         xml = true;
2718                         short_header = false;
2719                         break;
2720                 case IMAGEX_EXTRACT_XML_OPTION:
2721                         xml_out_file = optarg;
2722                         short_header = false;
2723                         break;
2724                 case IMAGEX_METADATA_OPTION:
2725                         metadata = true;
2726                         short_header = false;
2727                         break;
2728                 default:
2729                         goto out_usage;
2730                 }
2731         }
2732
2733         argc -= optind;
2734         argv += optind;
2735         if (argc < 1 || argc > 4)
2736                 goto out_usage;
2737
2738         wimfile           = argv[0];
2739         image_num_or_name = (argc >= 2) ? argv[1] : T("all");
2740         new_name          = (argc >= 3) ? argv[2] : NULL;
2741         new_desc          = (argc >= 4) ? argv[3] : NULL;
2742
2743         if (check && nocheck) {
2744                 imagex_error(T("Can't specify both --check and --nocheck"));
2745                 goto out_err;
2746         }
2747
2748         if (check)
2749                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2750
2751         ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
2752         if (ret)
2753                 goto out;
2754
2755         wimlib_get_wim_info(wim, &info);
2756
2757         image = wimlib_resolve_image(wim, image_num_or_name);
2758         ret = WIMLIB_ERR_INVALID_IMAGE;
2759         if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
2760                 imagex_error(T("The image \"%"TS"\" does not exist in \"%"TS"\""),
2761                              image_num_or_name, wimfile);
2762                 if (boot) {
2763                         imagex_error(T("If you would like to set the boot "
2764                                        "index to 0, specify image \"0\" with "
2765                                        "the --boot flag."));
2766                 }
2767                 goto out_wimlib_free;
2768         }
2769
2770         if (boot && info.image_count == 0) {
2771                 imagex_error(T("--boot is meaningless on a WIM with no images"));
2772                 goto out_wimlib_free;
2773         }
2774
2775         if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
2776                 if (boot) {
2777                         imagex_error(T("Cannot specify the --boot flag "
2778                                        "without specifying a specific "
2779                                        "image in a multi-image WIM"));
2780                         goto out_wimlib_free;
2781                 }
2782                 if (new_name) {
2783                         imagex_error(T("Cannot specify the NEW_NAME "
2784                                        "without specifying a specific "
2785                                        "image in a multi-image WIM"));
2786                         goto out_wimlib_free;
2787                 }
2788         }
2789
2790         /* Operations that print information are separated from operations that
2791          * recreate the WIM file. */
2792         if (!new_name && !boot) {
2793
2794                 /* Read-only operations */
2795
2796                 if (image == WIMLIB_NO_IMAGE) {
2797                         imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
2798                                      image_num_or_name, wimfile);
2799                         goto out_wimlib_free;
2800                 }
2801
2802                 if (image == WIMLIB_ALL_IMAGES && short_header)
2803                         print_wim_information(wimfile, &info);
2804
2805                 if (header)
2806                         wimlib_print_header(wim);
2807
2808                 if (lookup_table) {
2809                         if (info.total_parts != 1) {
2810                                 tfprintf(stderr, T("Warning: Only showing the lookup table "
2811                                                    "for part %d of a %d-part WIM.\n"),
2812                                          info.part_number, info.total_parts);
2813                         }
2814                         print_lookup_table(wim);
2815                 }
2816
2817                 if (xml) {
2818                         ret = wimlib_extract_xml_data(wim, stdout);
2819                         if (ret)
2820                                 goto out_wimlib_free;
2821                 }
2822
2823                 if (xml_out_file) {
2824                         FILE *fp;
2825
2826                         fp = tfopen(xml_out_file, T("wb"));
2827                         if (!fp) {
2828                                 imagex_error_with_errno(T("Failed to open the "
2829                                                           "file \"%"TS"\" for "
2830                                                           "writing"),
2831                                                         xml_out_file);
2832                                 ret = -1;
2833                                 goto out_wimlib_free;
2834                         }
2835                         ret = wimlib_extract_xml_data(wim, fp);
2836                         if (fclose(fp)) {
2837                                 imagex_error(T("Failed to close the file "
2838                                                "\"%"TS"\""),
2839                                              xml_out_file);
2840                                 ret = -1;
2841                         }
2842                         if (ret)
2843                                 goto out_wimlib_free;
2844                 }
2845
2846                 if (short_header)
2847                         wimlib_print_available_images(wim, image);
2848
2849                 if (metadata) {
2850                         ret = wimlib_print_metadata(wim, image);
2851                         if (ret)
2852                                 goto out_wimlib_free;
2853                 }
2854                 ret = 0;
2855         } else {
2856
2857                 /* Modification operations */
2858
2859                 if (image == WIMLIB_ALL_IMAGES)
2860                         image = 1;
2861
2862                 if (image == WIMLIB_NO_IMAGE && new_name) {
2863                         imagex_error(T("Cannot specify new_name (\"%"TS"\") "
2864                                        "when using image 0"), new_name);
2865                         ret = -1;
2866                         goto out_wimlib_free;
2867                 }
2868
2869                 if (boot) {
2870                         if (image == info.boot_index) {
2871                                 imagex_printf(T("Image %d is already marked as "
2872                                           "bootable.\n"), image);
2873                                 boot = false;
2874                         } else {
2875                                 imagex_printf(T("Marking image %d as bootable.\n"),
2876                                         image);
2877                                 info.boot_index = image;
2878                                 ret = wimlib_set_wim_info(wim, &info,
2879                                                           WIMLIB_CHANGE_BOOT_INDEX);
2880                                 if (ret)
2881                                         goto out_wimlib_free;
2882                         }
2883                 }
2884                 if (new_name) {
2885                         if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
2886                         {
2887                                 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
2888                                         image, new_name);
2889                                 new_name = NULL;
2890                         } else {
2891                                 imagex_printf(T("Changing the name of image %d to "
2892                                           "\"%"TS"\".\n"), image, new_name);
2893                                 ret = wimlib_set_image_name(wim, image, new_name);
2894                                 if (ret)
2895                                         goto out_wimlib_free;
2896                         }
2897                 }
2898                 if (new_desc) {
2899                         const tchar *old_desc;
2900                         old_desc = wimlib_get_image_description(wim, image);
2901                         if (old_desc && !tstrcmp(old_desc, new_desc)) {
2902                                 imagex_printf(T("The description of image %d is already "
2903                                           "\"%"TS"\".\n"), image, new_desc);
2904                                 new_desc = NULL;
2905                         } else {
2906                                 imagex_printf(T("Changing the description of image %d "
2907                                           "to \"%"TS"\".\n"), image, new_desc);
2908                                 ret = wimlib_set_image_descripton(wim, image,
2909                                                                   new_desc);
2910                                 if (ret)
2911                                         goto out_wimlib_free;
2912                         }
2913                 }
2914
2915                 /* Only call wimlib_overwrite() if something actually needs to
2916                  * be changed.  */
2917                 if (boot || new_name || new_desc ||
2918                     (check && !info.has_integrity_table) ||
2919                     (nocheck && info.has_integrity_table))
2920                 {
2921                         int write_flags = 0;
2922
2923                         if (check)
2924                                 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2925                         if (nocheck)
2926                                 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2927                         ret = wimlib_overwrite(wim, write_flags, 1,
2928                                                imagex_progress_func);
2929                 } else {
2930                         imagex_printf(T("The file \"%"TS"\" was not modified "
2931                                         "because nothing needed to be done.\n"),
2932                                       wimfile);
2933                         ret = 0;
2934                 }
2935         }
2936 out_wimlib_free:
2937         wimlib_free(wim);
2938 out:
2939         return ret;
2940
2941 out_usage:
2942         usage(INFO);
2943 out_err:
2944         ret = -1;
2945         goto out;
2946 }
2947
2948 /* Join split WIMs into one part WIM */
2949 static int
2950 imagex_join(int argc, tchar **argv)
2951 {
2952         int c;
2953         int swm_open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
2954         int wim_write_flags = 0;
2955         const tchar *output_path;
2956         int ret;
2957
2958         for_opt(c, join_options) {
2959                 switch (c) {
2960                 case IMAGEX_CHECK_OPTION:
2961                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2962                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2963                         break;
2964                 default:
2965                         goto out_usage;
2966                 }
2967         }
2968         argc -= optind;
2969         argv += optind;
2970
2971         if (argc < 2) {
2972                 imagex_error(T("Must specify one or more split WIM (.swm) "
2973                                "parts to join"));
2974                 goto out_usage;
2975         }
2976         output_path = argv[0];
2977         ret = wimlib_join((const tchar * const *)++argv,
2978                           --argc,
2979                           output_path,
2980                           swm_open_flags,
2981                           wim_write_flags,
2982                           imagex_progress_func);
2983 out:
2984         return ret;
2985
2986 out_usage:
2987         usage(JOIN);
2988         ret = -1;
2989         goto out;
2990 }
2991
2992 /* Mounts an image using a FUSE mount. */
2993 static int
2994 imagex_mount_rw_or_ro(int argc, tchar **argv)
2995 {
2996         int c;
2997         int mount_flags = 0;
2998         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
2999         const tchar *swm_glob = NULL;
3000         const tchar *staging_dir = NULL;
3001         const tchar *wimfile;
3002         const tchar *dir;
3003         WIMStruct *wim;
3004         int image;
3005         int ret;
3006         WIMStruct **additional_swms;
3007         unsigned num_additional_swms;
3008
3009         if (!tstrcmp(argv[0], T("mountrw"))) {
3010                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3011                 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3012         }
3013
3014         for_opt(c, mount_options) {
3015                 switch (c) {
3016                 case IMAGEX_ALLOW_OTHER_OPTION:
3017                         mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3018                         break;
3019                 case IMAGEX_CHECK_OPTION:
3020                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3021                         break;
3022                 case IMAGEX_DEBUG_OPTION:
3023                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3024                         break;
3025                 case IMAGEX_STREAMS_INTERFACE_OPTION:
3026                         if (!tstrcasecmp(optarg, T("none")))
3027                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3028                         else if (!tstrcasecmp(optarg, T("xattr")))
3029                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3030                         else if (!tstrcasecmp(optarg, T("windows")))
3031                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3032                         else {
3033                                 imagex_error(T("Unknown stream interface \"%"TS"\""),
3034                                              optarg);
3035                                 goto out_usage;
3036                         }
3037                         break;
3038                 case IMAGEX_REF_OPTION:
3039                         swm_glob = optarg;
3040                         break;
3041                 case IMAGEX_STAGING_DIR_OPTION:
3042                         staging_dir = optarg;
3043                         break;
3044                 case IMAGEX_UNIX_DATA_OPTION:
3045                         mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3046                         break;
3047                 default:
3048                         goto out_usage;
3049                 }
3050         }
3051         argc -= optind;
3052         argv += optind;
3053         if (argc != 2 && argc != 3)
3054                 goto out_usage;
3055
3056         wimfile = argv[0];
3057
3058         ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
3059         if (ret)
3060                 goto out;
3061
3062         if (argc >= 3) {
3063                 /* Image explicitly specified.  */
3064                 image = wimlib_resolve_image(wim, argv[1]);
3065                 dir = argv[2];
3066                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3067                 if (ret)
3068                         goto out_free_wim;
3069         } else {
3070                 /* No image specified; default to image 1, but only if the WIM
3071                  * contains exactly one image.  */
3072                 struct wimlib_wim_info info;
3073
3074                 wimlib_get_wim_info(wim, &info);
3075                 if (info.image_count != 1) {
3076                         imagex_error(T("\"%"TS"\" contains %d images; Please "
3077                                        "select one."), wimfile, info.image_count);
3078                         wimlib_free(wim);
3079                         goto out_usage;
3080                 }
3081                 image = 1;
3082                 dir = argv[1];
3083         }
3084
3085         if (swm_glob) {
3086                 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
3087                                           &additional_swms,
3088                                           &num_additional_swms);
3089                 if (ret)
3090                         goto out_free_wim;
3091         } else {
3092                 additional_swms = NULL;
3093                 num_additional_swms = 0;
3094         }
3095
3096         ret = wimlib_mount_image(wim, image, dir, mount_flags, additional_swms,
3097                                  num_additional_swms, staging_dir);
3098         if (ret) {
3099                 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3100                                "on \"%"TS"\""),
3101                              image, wimfile, dir);
3102         }
3103         for (unsigned i = 0; i < num_additional_swms; i++)
3104                 wimlib_free(additional_swms[i]);
3105         free(additional_swms);
3106 out_free_wim:
3107         wimlib_free(wim);
3108 out:
3109         return ret;
3110
3111 out_usage:
3112         usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
3113                         ? MOUNTRW : MOUNT);
3114         ret = -1;
3115         goto out;
3116 }
3117
3118 /* Rebuild a WIM file */
3119 static int
3120 imagex_optimize(int argc, tchar **argv)
3121 {
3122         int c;
3123         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3124         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3125         int ret;
3126         WIMStruct *wim;
3127         const tchar *wimfile;
3128         off_t old_size;
3129         off_t new_size;
3130         unsigned num_threads = 0;
3131
3132         for_opt(c, optimize_options) {
3133                 switch (c) {
3134                 case IMAGEX_CHECK_OPTION:
3135                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3136                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3137                         break;
3138                 case IMAGEX_NOCHECK_OPTION:
3139                         write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3140                         break;
3141                 case IMAGEX_RECOMPRESS_OPTION:
3142                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3143                         break;
3144                 case IMAGEX_THREADS_OPTION:
3145                         num_threads = parse_num_threads(optarg);
3146                         if (num_threads == UINT_MAX)
3147                                 goto out_err;
3148                         break;
3149                 case IMAGEX_PIPABLE_OPTION:
3150                         write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3151                         break;
3152                 case IMAGEX_NOT_PIPABLE_OPTION:
3153                         write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3154                         break;
3155                 default:
3156                         goto out_usage;
3157                 }
3158         }
3159         argc -= optind;
3160         argv += optind;
3161
3162         if (argc != 1)
3163                 goto out_usage;
3164
3165         wimfile = argv[0];
3166
3167         ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
3168         if (ret)
3169                 goto out;
3170
3171         old_size = file_get_size(wimfile);
3172         tprintf(T("\"%"TS"\" original size: "), wimfile);
3173         if (old_size == -1)
3174                 tputs(T("Unknown"));
3175         else
3176                 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3177
3178         ret = wimlib_overwrite(wim, write_flags, num_threads,
3179                                imagex_progress_func);
3180         if (ret) {
3181                 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3182                 goto out_wimlib_free;
3183         }
3184
3185         new_size = file_get_size(wimfile);
3186         tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3187         if (new_size == -1)
3188                 tputs(T("Unknown"));
3189         else
3190                 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3191
3192         tfputs(T("Space saved: "), stdout);
3193         if (new_size != -1 && old_size != -1) {
3194                 tprintf(T("%lld KiB\n"),
3195                        ((long long)old_size - (long long)new_size) >> 10);
3196         } else {
3197                 tputs(T("Unknown"));
3198         }
3199         ret = 0;
3200 out_wimlib_free:
3201         wimlib_free(wim);
3202 out:
3203         return ret;
3204
3205 out_usage:
3206         usage(OPTIMIZE);
3207 out_err:
3208         ret = -1;
3209         goto out;
3210 }
3211
3212 /* Split a WIM into a spanned set */
3213 static int
3214 imagex_split(int argc, tchar **argv)
3215 {
3216         int c;
3217         int open_flags = 0;
3218         int write_flags = 0;
3219         unsigned long part_size;
3220         tchar *tmp;
3221         int ret;
3222         WIMStruct *wim;
3223
3224         for_opt(c, split_options) {
3225                 switch (c) {
3226                 case IMAGEX_CHECK_OPTION:
3227                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3228                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3229                         break;
3230                 default:
3231                         goto out_usage;
3232                 }
3233         }
3234         argc -= optind;
3235         argv += optind;
3236
3237         if (argc != 3)
3238                 goto out_usage;
3239
3240         part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3241         if (tmp == argv[2] || *tmp) {
3242                 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3243                 imagex_error(T("The part size must be an integer or "
3244                                "floating-point number of megabytes."));
3245                 goto out_err;
3246         }
3247         ret = wimlib_open_wim(argv[0], open_flags, &wim, imagex_progress_func);
3248         if (ret)
3249                 goto out;
3250
3251         ret = wimlib_split(wim, argv[1], part_size, write_flags, imagex_progress_func);
3252         wimlib_free(wim);
3253 out:
3254         return ret;
3255
3256 out_usage:
3257         usage(SPLIT);
3258 out_err:
3259         ret = -1;
3260         goto out;
3261 }
3262
3263 /* Unmounts a mounted WIM image. */
3264 static int
3265 imagex_unmount(int argc, tchar **argv)
3266 {
3267         int c;
3268         int unmount_flags = 0;
3269         int ret;
3270
3271         for_opt(c, unmount_options) {
3272                 switch (c) {
3273                 case IMAGEX_COMMIT_OPTION:
3274                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3275                         break;
3276                 case IMAGEX_CHECK_OPTION:
3277                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3278                         break;
3279                 case IMAGEX_REBUILD_OPTION:
3280                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3281                         break;
3282                 case IMAGEX_LAZY_OPTION:
3283                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_LAZY;
3284                         break;
3285                 default:
3286                         goto out_usage;
3287                 }
3288         }
3289         argc -= optind;
3290         argv += optind;
3291         if (argc != 1)
3292                 goto out_usage;
3293
3294         ret = wimlib_unmount_image(argv[0], unmount_flags,
3295                                    imagex_progress_func);
3296         if (ret)
3297                 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3298 out:
3299         return ret;
3300
3301 out_usage:
3302         usage(UNMOUNT);
3303         ret = -1;
3304         goto out;
3305 }
3306
3307 /*
3308  * Add, delete, or rename files in a WIM image.
3309  */
3310 static int
3311 imagex_update(int argc, tchar **argv)
3312 {
3313         const tchar *wimfile;
3314         int image;
3315         WIMStruct *wim;
3316         int ret;
3317         int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3318         int write_flags = 0;
3319         int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3320         int default_add_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE;
3321         int default_delete_flags = 0;
3322         unsigned num_threads = 0;
3323         int c;
3324         tchar *cmd_file_contents;
3325         size_t cmd_file_nchars;
3326         struct wimlib_update_command *cmds;
3327         size_t num_cmds;
3328         tchar *command_str = NULL;
3329
3330         const tchar *config_file = NULL;
3331         tchar *config_str;
3332         struct wimlib_capture_config *config;
3333
3334         for_opt(c, update_options) {
3335                 switch (c) {
3336                 /* Generic or write options */
3337                 case IMAGEX_THREADS_OPTION:
3338                         num_threads = parse_num_threads(optarg);
3339                         if (num_threads == UINT_MAX)
3340                                 goto out_err;
3341                         break;
3342                 case IMAGEX_CHECK_OPTION:
3343                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3344                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3345                         break;
3346                 case IMAGEX_REBUILD_OPTION:
3347                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3348                         break;
3349                 case IMAGEX_COMMAND_OPTION:
3350                         if (command_str) {
3351                                 imagex_error(T("--command may only be specified "
3352                                                "one time.  Please provide\n"
3353                                                "       the update commands "
3354                                                "on standard input instead."));
3355                                 goto out_err;
3356                         }
3357                         command_str = tstrdup(optarg);
3358                         if (!command_str) {
3359                                 imagex_error(T("Out of memory!"));
3360                                 goto out_err;
3361                         }
3362                         break;
3363                 /* Default delete options */
3364                 case IMAGEX_FORCE_OPTION:
3365                         default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3366                         break;
3367                 case IMAGEX_RECURSIVE_OPTION:
3368                         default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3369                         break;
3370
3371                 /* Global add option */
3372                 case IMAGEX_CONFIG_OPTION:
3373                         config_file = optarg;
3374                         break;
3375
3376                 /* Default add options */
3377                 case IMAGEX_VERBOSE_OPTION:
3378                         default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
3379                         break;
3380                 case IMAGEX_DEREFERENCE_OPTION:
3381                         default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
3382                         break;
3383                 case IMAGEX_UNIX_DATA_OPTION:
3384                         default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
3385                         break;
3386                 case IMAGEX_NO_ACLS_OPTION:
3387                         default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
3388                         break;
3389                 case IMAGEX_STRICT_ACLS_OPTION:
3390                         default_add_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
3391                         break;
3392                 default:
3393                         goto out_usage;
3394                 }
3395         }
3396         argv += optind;
3397         argc -= optind;
3398
3399         if (argc != 1 && argc != 2)
3400                 goto out_usage;
3401         wimfile = argv[0];
3402
3403         ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
3404         if (ret)
3405                 goto out_free_command_str;
3406
3407         if (argc >= 2) {
3408                 /* Image explicitly specified.  */
3409                 image = wimlib_resolve_image(wim, argv[1]);
3410                 ret = verify_image_exists_and_is_single(image, argv[1],
3411                                                         wimfile);
3412                 if (ret)
3413                         goto out_wimlib_free;
3414         } else {
3415                 /* No image specified; default to image 1, but only if the WIM
3416                  * contains exactly one image.  */
3417                 struct wimlib_wim_info info;
3418
3419                 wimlib_get_wim_info(wim, &info);
3420                 if (info.image_count != 1) {
3421                         imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3422                                      wimfile, info.image_count);
3423                         wimlib_free(wim);
3424                         goto out_usage;
3425                 }
3426                 image = 1;
3427         }
3428
3429         /* Parse capture configuration file if specified */
3430         if (config_file) {
3431                 size_t config_len;
3432
3433                 config_str = file_get_text_contents(config_file, &config_len);
3434                 if (!config_str) {
3435                         ret = -1;
3436                         goto out_wimlib_free;
3437                 }
3438
3439                 config = alloca(sizeof(*config));
3440                 ret = parse_capture_config(&config_str, config_len, config);
3441                 if (ret)
3442                         goto out_free_config;
3443         } else {
3444                 config = &default_capture_config;
3445         }
3446
3447         /* Read update commands from standard input, or the command string if
3448          * specified.  */
3449         if (command_str) {
3450                 cmd_file_contents = NULL;
3451                 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3452                                                  &num_cmds);
3453         } else {
3454                 if (isatty(STDIN_FILENO)) {
3455                         tputs(T("Reading update commands from standard input..."));
3456                         recommend_man_page(T("update"));
3457                 }
3458                 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3459                 if (!cmd_file_contents) {
3460                         ret = -1;
3461                         goto out_free_config;
3462                 }
3463
3464                 /* Parse the update commands */
3465                 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3466                                                  &num_cmds);
3467         }
3468         if (!cmds) {
3469                 ret = -1;
3470                 goto out_free_cmd_file_contents;
3471         }
3472
3473         /* Set default flags and capture config on the update commands */
3474         bool have_add_command = false;
3475         for (size_t i = 0; i < num_cmds; i++) {
3476                 switch (cmds[i].op) {
3477                 case WIMLIB_UPDATE_OP_ADD:
3478                         cmds[i].add.add_flags |= default_add_flags;
3479                         cmds[i].add.config = config;
3480                         have_add_command = true;
3481                         break;
3482                 case WIMLIB_UPDATE_OP_DELETE:
3483                         cmds[i].delete.delete_flags |= default_delete_flags;
3484                         break;
3485                 default:
3486                         break;
3487                 }
3488         }
3489
3490 #ifdef __WIN32__
3491         if (have_add_command)
3492                 win32_acquire_capture_privileges();
3493 #endif
3494
3495         /* Execute the update commands */
3496         ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags,
3497                                   imagex_progress_func);
3498         if (ret)
3499                 goto out_release_privs;
3500
3501         /* Overwrite the updated WIM */
3502         ret = wimlib_overwrite(wim, write_flags, num_threads,
3503                                imagex_progress_func);
3504 out_release_privs:
3505 #ifdef __WIN32__
3506         if (have_add_command)
3507                 win32_release_capture_privileges();
3508 #endif
3509         free(cmds);
3510 out_free_cmd_file_contents:
3511         free(cmd_file_contents);
3512 out_free_config:
3513         if (config != &default_capture_config) {
3514                 free(config->exclusion_pats.pats);
3515                 free(config->exclusion_exception_pats.pats);
3516                 free(config_str);
3517         }
3518 out_wimlib_free:
3519         wimlib_free(wim);
3520 out_free_command_str:
3521         free(command_str);
3522         return ret;
3523
3524 out_usage:
3525         usage(UPDATE);
3526 out_err:
3527         ret = -1;
3528         goto out_free_command_str;
3529 }
3530
3531 struct imagex_command {
3532         const tchar *name;
3533         int (*func)(int , tchar **);
3534         int cmd;
3535 };
3536
3537
3538 #define for_imagex_command(p) for (p = &imagex_commands[0]; \
3539                 p != &imagex_commands[ARRAY_LEN(imagex_commands)]; p++)
3540
3541 static const struct imagex_command imagex_commands[] = {
3542         {T("append"),  imagex_capture_or_append, APPEND},
3543         {T("apply"),   imagex_apply,             APPLY},
3544         {T("capture"), imagex_capture_or_append, CAPTURE},
3545         {T("delete"),  imagex_delete,            DELETE},
3546         {T("dir"),     imagex_dir,               DIR},
3547         {T("export"),  imagex_export,            EXPORT},
3548         {T("extract"), imagex_extract,           EXTRACT},
3549         {T("info"),    imagex_info,              INFO},
3550         {T("join"),    imagex_join,              JOIN},
3551         {T("mount"),   imagex_mount_rw_or_ro,    MOUNT},
3552         {T("mountrw"), imagex_mount_rw_or_ro,    MOUNTRW},
3553         {T("optimize"),imagex_optimize,          OPTIMIZE},
3554         {T("split"),   imagex_split,             SPLIT},
3555         {T("unmount"), imagex_unmount,           UNMOUNT},
3556         {T("update"),  imagex_update,            UPDATE},
3557 };
3558
3559 static void
3560 version(void)
3561 {
3562         static const tchar *s =
3563         T(
3564 IMAGEX_PROGNAME " (" PACKAGE ") " PACKAGE_VERSION "\n"
3565 "Copyright (C) 2012, 2013 Eric Biggers\n"
3566 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
3567 "This is free software: you are free to change and redistribute it.\n"
3568 "There is NO WARRANTY, to the extent permitted by law.\n"
3569 "\n"
3570 "Report bugs to "PACKAGE_BUGREPORT".\n"
3571         );
3572         tfputs(s, stdout);
3573 }
3574
3575
3576 static void
3577 help_or_version(int argc, tchar **argv)
3578 {
3579         int i;
3580         const tchar *p;
3581         const struct imagex_command *cmd;
3582
3583         for (i = 1; i < argc; i++) {
3584                 p = argv[i];
3585                 if (*p == T('-'))
3586                         p++;
3587                 else
3588                         continue;
3589                 if (*p == T('-'))
3590                         p++;
3591                 if (!tstrcmp(p, T("help"))) {
3592                         for_imagex_command(cmd) {
3593                                 if (!tstrcmp(cmd->name, argv[1])) {
3594                                         usage(cmd->cmd);
3595                                         exit(0);
3596                                 }
3597                         }
3598                         usage_all();
3599                         exit(0);
3600                 }
3601                 if (!tstrcmp(p, T("version"))) {
3602                         version();
3603                         exit(0);
3604                 }
3605         }
3606 }
3607
3608
3609 static void
3610 usage(int cmd_type)
3611 {
3612         const struct imagex_command *cmd;
3613         tprintf(T("Usage:\n%"TS), usage_strings[cmd_type]);
3614         for_imagex_command(cmd) {
3615                 if (cmd->cmd == cmd_type) {
3616                         tputc(T('\n'), stdout);
3617                         recommend_man_page(cmd->name);
3618                 }
3619         }
3620 }
3621
3622 static void
3623 usage_all(void)
3624 {
3625         tfputs(T("Usage:\n"), stdout);
3626         for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
3627                 tprintf(T("    %"TS"\n"), usage_strings[i]);
3628         static const tchar *extra =
3629         T(
3630 "    "IMAGEX_PROGNAME" --help\n"
3631 "    "IMAGEX_PROGNAME" --version\n"
3632 "\n"
3633 "    The compression TYPE may be \"maximum\", \"fast\", or \"none\".\n"
3634 "\n"
3635         );
3636         tfputs(extra, stdout);
3637         recommend_man_page(T(""));
3638 }
3639
3640 /* Entry point for wimlib's ImageX implementation.  On UNIX the command
3641  * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
3642  * something else), while an Windows the command arguments will be UTF-16LE
3643  * encoded 'wchar_t' strings. */
3644 int
3645 #ifdef __WIN32__
3646 wmain(int argc, wchar_t **argv, wchar_t **envp)
3647 #else
3648 main(int argc, char **argv)
3649 #endif
3650 {
3651         const struct imagex_command *cmd;
3652         int ret;
3653         int init_flags = 0;
3654
3655         imagex_info_file = stdout;
3656
3657 #ifndef __WIN32__
3658         if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
3659                 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
3660         } else {
3661                 char *codeset;
3662
3663                 setlocale(LC_ALL, "");
3664                 codeset = nl_langinfo(CODESET);
3665                 if (!strstr(codeset, "UTF-8") &&
3666                     !strstr(codeset, "UTF8") &&
3667                     !strstr(codeset, "utf-8") &&
3668                     !strstr(codeset, "utf8"))
3669                 {
3670                         fputs(
3671 "WARNING: Running "IMAGEX_PROGNAME" in a UTF-8 locale is recommended!\n"
3672 "         Maybe try: `export LANG=en_US.UTF-8'?\n"
3673 "         Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
3674 "         to any value to force wimlib to use UTF-8.\n",
3675                         stderr);
3676
3677                 }
3678         }
3679 #endif /* !__WIN32__ */
3680
3681         if (argc < 2) {
3682                 imagex_error(T("No command specified"));
3683                 usage_all();
3684                 ret = 2;
3685                 goto out;
3686         }
3687
3688         /* Handle --help and --version for all commands.  Note that this will
3689          * not return if either of these arguments are present. */
3690         help_or_version(argc, argv);
3691         argc--;
3692         argv++;
3693
3694         /* The user may like to see more informative error messages. */
3695         wimlib_set_print_errors(true);
3696
3697         /* Do any initializations that the library needs */
3698         ret = wimlib_global_init(init_flags);
3699         if (ret)
3700                 goto out_check_status;
3701
3702         /* Search for the function to handle the ImageX subcommand. */
3703         for_imagex_command(cmd) {
3704                 if (!tstrcmp(cmd->name, *argv)) {
3705                         ret = cmd->func(argc, argv);
3706                         goto out_check_write_error;
3707                 }
3708         }
3709
3710         imagex_error(T("Unrecognized command: `%"TS"'"), argv[0]);
3711         usage_all();
3712         ret = 2;
3713         goto out_cleanup;
3714 out_check_write_error:
3715         /* For 'wimlib-imagex info' and 'wimlib-imagex dir', data printed to
3716          * standard output is part of the program's actual behavior and not just
3717          * for informational purposes, so we should set a failure exit status if
3718          * there was a write error. */
3719         if (cmd == &imagex_commands[INFO] || cmd == &imagex_commands[DIR]) {
3720                 if (ferror(stdout) || fclose(stdout)) {
3721                         imagex_error_with_errno(T("error writing to standard output"));
3722                         if (ret == 0)
3723                                 ret = -1;
3724                 }
3725         }
3726 out_check_status:
3727         /* Exit status (ret):  -1 indicates an error found by 'wimlib-imagex'
3728          * outside of the wimlib library code.  0 indicates success.  > 0
3729          * indicates a wimlib error code from which an error message can be
3730          * printed. */
3731         if (ret > 0) {
3732                 imagex_error(T("Exiting with error code %d:\n"
3733                                "       %"TS"."), ret,
3734                              wimlib_get_error_string(ret));
3735                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
3736                         imagex_error_with_errno(T("errno"));
3737         }
3738 out_cleanup:
3739         /* Make the library free any resources it's holding (not strictly
3740          * necessary because the process is ending anyway). */
3741         wimlib_global_cleanup();
3742 out:
3743         return ret;
3744 }