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