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