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