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