5ab9aeba8ce96425e51141909011079de5470bef
[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 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
27 #include "wimlib.h"
28
29 #include <errno.h>
30 #include <getopt.h>
31 #include <glob.h>
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
41 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
42
43 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (char**)argv, "", \
44                                 opts, NULL)) != -1)
45
46 enum imagex_op_type {
47         APPEND,
48         APPLY,
49         CAPTURE,
50         DELETE,
51         DIR,
52         EXPORT,
53         INFO,
54         JOIN,
55         MOUNT,
56         MOUNTRW,
57         OPTIMIZE,
58         SPLIT,
59         UNMOUNT,
60 };
61
62 static void usage(int cmd_type);
63 static void usage_all();
64
65 static const char *usage_strings[] = {
66 [APPEND] =
67 "imagex append (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
68 "                     [DESCRIPTION] [--boot] [--check] [--flags EDITION_ID]\n"
69 "                     [--verbose] [--dereference] [--config=FILE]\n"
70 "                     [--threads=NUM_THREADS] [--rebuild]\n",
71 [APPLY] =
72 "imagex apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n"
73 "                    (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n"
74 "                    [--symlink] [--verbose] [--ref=\"GLOB\"]\n",
75 [CAPTURE] =
76 "imagex capture (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
77 "                      [DESCRIPTION] [--boot] [--check] [--compress=TYPE]\n"
78 "                      [--flags EDITION_ID] [--verbose] [--dereference]\n"
79 "                      [--config=FILE] [--threads=NUM_THREADS]\n",
80 [DELETE] =
81 "imagex delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check] [--soft]\n",
82 [DIR] =
83 "imagex dir WIMFILE (IMAGE_NUM | IMAGE_NAME | all)\n",
84 [EXPORT] =
85 "imagex export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
86 "              DEST_WIMFILE [DEST_IMAGE_NAME] [DEST_IMAGE_DESCRIPTION]\n"
87 "              [--boot] [--check] [--compress=TYPE] [--ref=\"GLOB\"]\n"
88 "              [--threads=NUM_THREADS] [--rebuild]\n",
89 [INFO] =
90 "imagex info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
91 "                   [NEW_DESC] [--boot] [--check] [--header] [--lookup-table]\n"
92 "                   [--xml] [--extract-xml FILE] [--metadata]\n",
93 [JOIN] =
94 "imagex join [--check] WIMFILE SPLIT_WIM...\n",
95 [MOUNT] =
96 "imagex mount WIMFILE (IMAGE_NUM | IMAGE_NAME) DIRECTORY\n"
97 "                    [--check] [--debug] [--streams-interface=INTERFACE]\n"
98 "                    [--ref=\"GLOB\"]\n",
99 [MOUNTRW] =
100 "imagex mountrw WIMFILE [IMAGE_NUM | IMAGE_NAME] DIRECTORY\n"
101 "                      [--check] [--debug] [--streams-interface=INTERFACE]\n"
102 "                      [--staging-dir=DIR]\n",
103 [OPTIMIZE] =
104 "imagex optimize WIMFILE [--check] [--recompress] [--compress=TYPE]\n",
105 [SPLIT] =
106 "imagex split WIMFILE SPLIT_WIMFILE PART_SIZE_MB [--check]\n",
107 [UNMOUNT] =
108 "imagex unmount DIRECTORY [--commit] [--check] [--rebuild]\n",
109 };
110
111 static const struct option common_options[] = {
112         {"help", 0, NULL, 'h'},
113         {"version", 0, NULL, 'v'},
114         {NULL, 0, NULL, 0},
115 };
116
117 static const struct option apply_options[] = {
118         {"check",    no_argument,       NULL, 'c'},
119         {"hardlink", no_argument,       NULL, 'h'},
120         {"symlink",  no_argument,       NULL, 's'},
121         {"verbose",  no_argument,       NULL, 'v'},
122         {"ref",      required_argument, NULL, 'r'},
123         {NULL, 0, NULL, 0},
124 };
125 static const struct option capture_or_append_options[] = {
126         {"boot",        no_argument,       NULL, 'b'},
127         {"check",       no_argument,       NULL, 'c'},
128         {"compress",    required_argument, NULL, 'x'},
129         {"config",      required_argument, NULL, 'C'},
130         {"dereference", no_argument,       NULL, 'L'},
131         {"flags",       required_argument, NULL, 'f'},
132         {"verbose",     no_argument,       NULL, 'v'},
133         {"threads",     required_argument, NULL, 't'},
134         {"rebuild",     no_argument,       NULL, 'R'},
135         {NULL, 0, NULL, 0},
136 };
137 static const struct option delete_options[] = {
138         {"check", no_argument, NULL, 'c'},
139         {"soft",  no_argument, NULL, 's'},
140         {NULL, 0, NULL, 0},
141 };
142
143 static const struct option export_options[] = {
144         {"boot",       no_argument,       NULL, 'b'},
145         {"check",      no_argument,       NULL, 'c'},
146         {"compress",   required_argument, NULL, 'x'},
147         {"ref",        required_argument, NULL, 'r'},
148         {"threads",    required_argument, NULL, 't'},
149         {"rebuild",    no_argument,       NULL, 'R'},
150         {NULL, 0, NULL, 0},
151 };
152
153 static const struct option info_options[] = {
154         {"boot",         no_argument, NULL, 'b'},
155         {"check",        no_argument, NULL, 'c'},
156         {"extract-xml",  required_argument, NULL, 'X'},
157         {"header",       no_argument, NULL, 'h'},
158         {"lookup-table", no_argument, NULL, 'l'},
159         {"metadata",     no_argument, NULL, 'm'},
160         {"xml",          no_argument, NULL, 'x'},
161         {NULL, 0, NULL, 0},
162 };
163
164 static const struct option join_options[] = {
165         {"check", no_argument, NULL, 'c'},
166         {NULL, 0, NULL, 0},
167 };
168
169 static const struct option mount_options[] = {
170         {"check",             no_argument,       NULL, 'c'},
171         {"debug",             no_argument,       NULL, 'd'},
172         {"streams-interface", required_argument, NULL, 's'},
173         {"ref",               required_argument, NULL, 'r'},
174         {"staging-dir",       required_argument, NULL, 'D'},
175         {NULL, 0, NULL, 0},
176 };
177
178 static const struct option optimize_options[] = {
179         {"check",      no_argument, NULL, 'c'},
180         {"recompress", no_argument, NULL, 'r'},
181         {NULL, 0, NULL, 0},
182 };
183
184 static const struct option split_options[] = {
185         {"check", no_argument, NULL, 'c'},
186         {NULL, 0, NULL, 0},
187 };
188
189 static const struct option unmount_options[] = {
190         {"commit",  no_argument, NULL, 'c'},
191         {"check",   no_argument, NULL, 'C'},
192         {"rebuild", no_argument, NULL, 'R'},
193         {NULL, 0, NULL, 0},
194 };
195
196
197
198 /* Print formatted error message to stderr. */
199 static void imagex_error(const char *format, ...)
200 {
201         va_list va;
202         va_start(va, format);
203         fputs("ERROR: ", stderr);
204         vfprintf(stderr, format, va);
205         putc('\n', stderr);
206         va_end(va);
207 }
208
209 /* Print formatted error message to stderr. */
210 static void imagex_error_with_errno(const char *format, ...)
211 {
212         int errno_save = errno;
213         va_list va;
214         va_start(va, format);
215         fputs("ERROR: ", stderr);
216         vfprintf(stderr, format, va);
217         fprintf(stderr, ": %s\n", strerror(errno_save));
218         va_end(va);
219 }
220
221 static int verify_image_exists(int image, const char *image_name,
222                                const char *wim_name)
223 {
224         if (image == WIMLIB_NO_IMAGE) {
225                 imagex_error("\"%s\" is not a valid image in `%s'!\n"
226                              "       Please specify a 1-based imagex index or "
227                              "image name.\n"
228                              "       You may use `imagex info' to list the images "
229                              "contained in a WIM.",
230                              image_name, wim_name);
231                 return -1;
232         }
233         return 0;
234 }
235
236 static int verify_image_is_single(int image)
237 {
238         if (image == WIMLIB_ALL_IMAGES) {
239                 imagex_error("Cannot specify all images for this action!");
240                 return -1;
241         }
242         return 0;
243 }
244
245 static int verify_image_exists_and_is_single(int image, const char *image_name,
246                                              const char *wim_name)
247 {
248         int ret;
249         ret = verify_image_exists(image, image_name, wim_name);
250         if (ret == 0)
251                 ret = verify_image_is_single(image);
252         return ret;
253 }
254
255 static int get_compression_type(const char *optarg)
256 {
257         if (strcasecmp(optarg, "maximum") == 0 || strcasecmp(optarg, "lzx") == 0)
258                 return WIMLIB_COMPRESSION_TYPE_LZX;
259         else if (strcasecmp(optarg, "fast") == 0 || strcasecmp(optarg, "xpress") == 0)
260                 return WIMLIB_COMPRESSION_TYPE_XPRESS;
261         else if (strcasecmp(optarg, "none") == 0)
262                 return WIMLIB_COMPRESSION_TYPE_NONE;
263         else {
264                 imagex_error("Invalid compression type `%s'! Must be "
265                              "\"maximum\", \"fast\", or \"none\".", optarg);
266                 return WIMLIB_COMPRESSION_TYPE_INVALID;
267         }
268 }
269
270 static off_t file_get_size(const char *filename)
271 {
272         struct stat st;
273         if (stat(filename, &st) == 0)
274                 return st.st_size;
275         else
276                 return (off_t)-1;
277 }
278
279 static char *file_get_contents(const char *filename, size_t *len_ret)
280 {
281         struct stat stbuf;
282         char *buf;
283         size_t len;
284         FILE *fp;
285
286         if (stat(filename, &stbuf) != 0) {
287                 imagex_error_with_errno("Failed to stat the file `%s'", filename);
288                 return NULL;
289         }
290         len = stbuf.st_size;
291
292         fp = fopen(filename, "rb");
293         if (!fp) {
294                 imagex_error_with_errno("Failed to open the file `%s'", filename);
295                 return NULL;
296         }
297
298         buf = malloc(len);
299         if (!buf) {
300                 imagex_error("Failed to allocate buffer of %zu bytes to hold "
301                              "contents of file `%s'", len, filename);
302                 goto out_fclose;
303         }
304         if (fread(buf, 1, len, fp) != len) {
305                 imagex_error_with_errno("Failed to read %lu bytes from the "
306                                         "file `%s'", len, filename);
307                 goto out_free_buf;
308         }
309         *len_ret = len;
310         return buf;
311 out_free_buf:
312         free(buf);
313 out_fclose:
314         fclose(fp);
315         return NULL;
316 }
317
318 static int file_writable(const char *path)
319 {
320         int ret;
321         ret = access(path, F_OK | W_OK);
322         if (ret != 0)
323                 imagex_error_with_errno("Can't modify `%s'", path);
324         return ret;
325 }
326
327 #define TO_PERCENT(numerator, denominator) \
328         (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
329
330 static const char *get_data_type(int ctype)
331 {
332         switch (ctype) {
333         case WIMLIB_COMPRESSION_TYPE_NONE:
334                 return "uncompressed";
335         case WIMLIB_COMPRESSION_TYPE_LZX:
336                 return "LZX-compressed";
337         case WIMLIB_COMPRESSION_TYPE_XPRESS:
338                 return "XPRESS-compressed";
339         }
340         return NULL;
341 }
342
343 static int imagex_progress_func(enum wimlib_progress_msg msg,
344                                 const union wimlib_progress_info *info)
345 {
346         unsigned percent_done;
347         switch (msg) {
348         case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
349                 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
350                                           info->write_streams.total_bytes);
351                 if (info->write_streams.completed_streams == 0) {
352                         const char *data_type;
353
354                         data_type = get_data_type(info->write_streams.compression_type);
355                         printf("Writing %s data using %u thread%s\n",
356                                data_type, info->write_streams.num_threads,
357                                (info->write_streams.num_threads == 1) ? "" : "s");
358                 }
359                 printf("\r%"PRIu64" MiB of %"PRIu64" MiB (uncompressed) "
360                        "written (%u%% done)",
361                        info->write_streams.completed_bytes >> 20,
362                        info->write_streams.total_bytes >> 20,
363                        percent_done);
364                 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
365                         putchar('\n');
366                 break;
367         case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
368                 printf("Scanning `%s'...\n", info->scan.source);
369                 break;
370         case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
371                 if (info->scan.excluded)
372                         printf("Excluding `%s' from capture\n", info->scan.cur_path);
373                 else
374                         printf("Scanning `%s'\n", info->scan.cur_path);
375                 break;
376         /*case WIMLIB_PROGRESS_MSG_SCAN_END:*/
377                 /*break;*/
378         case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
379                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
380                                           info->integrity.total_bytes);
381                 printf("\rVerifying integrity of `%s': %"PRIu64" MiB "
382                        "of %"PRIu64" MiB (%u%%) done",
383                        info->integrity.filename,
384                        info->integrity.completed_bytes >> 20,
385                        info->integrity.total_bytes >> 20,
386                        percent_done);
387                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
388                         putchar('\n');
389                 break;
390         case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
391                 percent_done = TO_PERCENT(info->integrity.completed_bytes,
392                                           info->integrity.total_bytes);
393                 printf("\rCalculating integrity table for WIM: %"PRIu64" MiB "
394                        "of %"PRIu64" MiB (%u%%) done",
395                        info->integrity.completed_bytes >> 20,
396                        info->integrity.total_bytes >> 20,
397                        percent_done);
398                 if (info->integrity.completed_bytes == info->integrity.total_bytes)
399                         putchar('\n');
400                 break;
401         case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
402                 printf("Applying image %d (%s) from `%s' to %s `%s'\n",
403                        info->extract.image,
404                        info->extract.image_name,
405                        info->extract.wimfile_name,
406                        ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
407                                 "NTFS volume" : "directory"),
408                        info->extract.target);
409                 break;
410         /*case WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN:*/
411                 /*printf("Applying directory structure to %s\n",*/
412                        /*info->extract.target);*/
413                 /*break;*/
414         case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
415                 percent_done = TO_PERCENT(info->extract.completed_bytes,
416                                           info->extract.total_bytes);
417                 printf("\rExtracting files: "
418                        "%"PRIu64" MiB of %"PRIu64" MiB (%u%%) done",
419                        info->extract.completed_bytes >> 20,
420                        info->extract.total_bytes >> 20,
421                        percent_done);
422                 if (info->extract.completed_bytes >= info->extract.total_bytes)
423                         putchar('\n');
424                 break;
425         case WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY:
426                 puts(info->extract.cur_path);
427                 break;
428         case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS:
429                 printf("Setting timestamps on all extracted files...\n");
430                 break;
431         case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END:
432                 if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
433                         printf("Unmounting NTFS volume `%s'...\n",
434                                info->extract.target);
435                 }
436                 break;
437         case WIMLIB_PROGRESS_MSG_JOIN_STREAMS:
438                 percent_done = TO_PERCENT(info->join.completed_bytes,
439                                           info->join.total_bytes);
440                 printf("Writing resources from part %u of %u: "
441                        "%"PRIu64 " MiB of %"PRIu64" MiB (%u%%) written\n",
442                        (info->join.completed_parts == info->join.total_parts) ?
443                                 info->join.completed_parts : info->join.completed_parts + 1,
444                        info->join.total_parts,
445                        info->join.completed_bytes >> 20,
446                        info->join.total_bytes >> 20,
447                        percent_done);
448                 break;
449         case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
450                 percent_done = TO_PERCENT(info->split.completed_bytes,
451                                           info->split.total_bytes);
452                 printf("Writing `%s': %"PRIu64" MiB of %"PRIu64" MiB (%u%%) written\n",
453                        info->split.part_name,
454                        info->split.completed_bytes >> 20,
455                        info->split.total_bytes >> 20,
456                        percent_done);
457                 break;
458         case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
459                 if (info->split.completed_bytes == info->split.total_bytes) {
460                         printf("Finished writing %u split WIM parts\n",
461                                info->split.cur_part_number);
462                 }
463                 break;
464         default:
465                 break;
466         }
467         fflush(stdout);
468         return 0;
469 }
470
471
472 static int open_swms_from_glob(const char *swm_glob,
473                                const char *first_part,
474                                int open_flags,
475                                WIMStruct ***additional_swms_ret,
476                                unsigned *num_additional_swms_ret)
477 {
478         unsigned num_additional_swms = 0;
479         WIMStruct **additional_swms = NULL;
480         glob_t globbuf;
481         int ret;
482
483         ret = glob(swm_glob, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf);
484         if (ret != 0) {
485                 if (ret == GLOB_NOMATCH) {
486                         imagex_error("Found no files for glob \"%s\"",
487                                      swm_glob);
488                 } else {
489                         imagex_error_with_errno("Failed to process glob "
490                                                 "\"%s\"", swm_glob);
491                 }
492                 ret = -1;
493                 goto out;
494         }
495         num_additional_swms = globbuf.gl_pathc;
496         additional_swms = calloc(num_additional_swms, sizeof(additional_swms[0]));
497         if (!additional_swms) {
498                 imagex_error("Out of memory");
499                 ret = -1;
500                 goto out_globfree;
501         }
502         unsigned offset = 0;
503         for (unsigned i = 0; i < num_additional_swms; i++) {
504                 if (strcmp(globbuf.gl_pathv[i], first_part) == 0) {
505                         offset++;
506                         continue;
507                 }
508                 ret = wimlib_open_wim(globbuf.gl_pathv[i],
509                                       open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK,
510                                       &additional_swms[i - offset],
511                                       imagex_progress_func);
512                 if (ret != 0)
513                         goto out_close_swms;
514         }
515         *additional_swms_ret = additional_swms;
516         *num_additional_swms_ret = num_additional_swms - offset;
517         ret = 0;
518         goto out_globfree;
519 out_close_swms:
520         for (unsigned i = 0; i < num_additional_swms; i++)
521                 wimlib_free(additional_swms[i]);
522         free(additional_swms);
523 out_globfree:
524         globfree(&globbuf);
525 out:
526         return ret;
527 }
528
529
530 static unsigned parse_num_threads(const char *optarg)
531 {
532         char *tmp;
533         unsigned nthreads = strtoul(optarg, &tmp, 10);
534         if (nthreads == UINT_MAX || *tmp || tmp == optarg) {
535                 imagex_error("Number of threads must be a non-negative integer!");
536                 return UINT_MAX;
537         } else {
538                 return nthreads;
539         }
540 }
541
542
543 /* Extract one image, or all images, from a WIM file into a directory. */
544 static int imagex_apply(int argc, const char **argv)
545 {
546         int c;
547         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
548         int image;
549         int num_images;
550         WIMStruct *w;
551         int ret;
552         const char *wimfile;
553         const char *target;
554         const char *image_num_or_name;
555         int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
556
557         const char *swm_glob = NULL;
558         WIMStruct **additional_swms = NULL;
559         unsigned num_additional_swms = 0;
560
561         for_opt(c, apply_options) {
562                 switch (c) {
563                 case 'c':
564                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
565                         break;
566                 case 'h':
567                         extract_flags |= WIMLIB_EXTRACT_FLAG_HARDLINK;
568                         break;
569                 case 's':
570                         extract_flags |= WIMLIB_EXTRACT_FLAG_SYMLINK;
571                         break;
572                 case 'v':
573                         extract_flags |= WIMLIB_EXTRACT_FLAG_VERBOSE;
574                         break;
575                 case 'r':
576                         swm_glob = optarg;
577                         break;
578                 default:
579                         usage(APPLY);
580                         return -1;
581                 }
582         }
583         argc -= optind;
584         argv += optind;
585         if (argc != 2 && argc != 3) {
586                 usage(APPLY);
587                 return -1;
588         }
589
590         wimfile = argv[0];
591         if (argc == 2) {
592                 image_num_or_name = "1";
593                 target = argv[1];
594         } else {
595                 image_num_or_name = argv[1];
596                 target = argv[2];
597         }
598
599         ret = wimlib_open_wim(wimfile, open_flags, &w, imagex_progress_func);
600         if (ret != 0)
601                 return ret;
602
603         image = wimlib_resolve_image(w, image_num_or_name);
604         ret = verify_image_exists(image, image_num_or_name, wimfile);
605         if (ret != 0)
606                 goto out;
607
608         num_images = wimlib_get_num_images(w);
609         if (argc == 2 && num_images != 1) {
610                 imagex_error("`%s' contains %d images; Please select one "
611                              "(or all)", wimfile, num_images);
612                 usage(APPLY);
613                 ret = -1;
614                 goto out;
615         }
616
617         if (swm_glob) {
618                 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
619                                           &additional_swms,
620                                           &num_additional_swms);
621                 if (ret != 0)
622                         goto out;
623         }
624
625         struct stat stbuf;
626
627         ret = stat(target, &stbuf);
628         if (ret == 0) {
629                 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
630                         extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
631         } else {
632                 if (errno != ENOENT) {
633                         imagex_error_with_errno("Failed to stat `%s'", target);
634                         ret = -1;
635                         goto out;
636                 }
637         }
638
639         ret = wimlib_extract_image(w, image, target, extract_flags,
640                                    additional_swms, num_additional_swms,
641                                    imagex_progress_func);
642         if (ret == 0)
643                 printf("Done applying WIM image.\n");
644 out:
645         wimlib_free(w);
646         if (additional_swms) {
647                 for (unsigned i = 0; i < num_additional_swms; i++)
648                         wimlib_free(additional_swms[i]);
649                 free(additional_swms);
650         }
651         return ret;
652 }
653
654 static int imagex_capture_or_append(int argc, const char **argv)
655 {
656         int c;
657         int open_flags = 0;
658         int add_image_flags = 0;
659         int write_flags = 0;
660         int compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
661         const char *source;
662         const char *wimfile;
663         const char *name;
664         const char *desc;
665         const char *flags_element = NULL;
666         const char *config_file = NULL;
667         char *config_str = NULL;
668         size_t config_len = 0;
669         WIMStruct *w = NULL;
670         int ret;
671         int cur_image;
672         char *default_name;
673         int cmd = strcmp(argv[0], "append") ? CAPTURE : APPEND;
674         unsigned num_threads = 0;
675
676         for_opt(c, capture_or_append_options) {
677                 switch (c) {
678                 case 'b':
679                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
680                         break;
681                 case 'c':
682                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
683                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
684                         break;
685                 case 'C':
686                         config_file = optarg;
687                         break;
688                 case 'x':
689                         compression_type = get_compression_type(optarg);
690                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
691                                 return -1;
692                         break;
693                 case 'f':
694                         flags_element = optarg;
695                         break;
696                 case 'L':
697                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
698                         break;
699                 case 'v':
700                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
701                         break;
702                 case 't':
703                         num_threads = parse_num_threads(optarg);
704                         if (num_threads == UINT_MAX)
705                                 return -1;
706                         break;
707                 case 'R':
708                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
709                         break;
710                 default:
711                         usage(cmd);
712                         return -1;
713                 }
714         }
715         argc -= optind;
716         argv += optind;
717         if (argc < 2 || argc > 4) {
718                 usage(cmd);
719                 return -1;
720         }
721         source = argv[0];
722         wimfile = argv[1];
723
724         char source_copy[strlen(source) + 1];
725         memcpy(source_copy, source, strlen(source) + 1);
726         default_name = basename(source_copy);
727
728         name = (argc >= 3) ? argv[2] : default_name;
729         desc = (argc >= 4) ? argv[3] : NULL;
730
731         if (config_file) {
732                 config_str = file_get_contents(config_file, &config_len);
733                 if (!config_str)
734                         return -1;
735         }
736
737         if (cmd == APPEND)
738                 ret = wimlib_open_wim(wimfile, open_flags, &w,
739                                       imagex_progress_func);
740         else
741                 ret = wimlib_create_new_wim(compression_type, &w);
742         if (ret != 0)
743                 goto out;
744
745         struct stat stbuf;
746
747         ret = stat(source, &stbuf);
748         if (ret == 0) {
749                 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
750                         printf("Capturing WIM image from NTFS filesystem on `%s'\n",
751                                source);
752                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
753                 }
754         } else {
755                 if (errno != ENOENT) {
756                         imagex_error_with_errno("Failed to stat `%s'", source);
757                         ret = -1;
758                         goto out;
759                 }
760         }
761
762         ret = wimlib_add_image(w, source, name, config_str, config_len,
763                                add_image_flags, imagex_progress_func);
764
765         if (ret != 0)
766                 goto out;
767         cur_image = wimlib_get_num_images(w);
768         if (desc) {
769                 ret = wimlib_set_image_descripton(w, cur_image, desc);
770                 if (ret != 0)
771                         goto out;
772         }
773         if (flags_element) {
774                 ret = wimlib_set_image_flags(w, cur_image, flags_element);
775                 if (ret != 0)
776                         goto out;
777         }
778         if (cmd == APPEND) {
779                 ret = wimlib_overwrite(w, write_flags, num_threads,
780                                        imagex_progress_func);
781         } else {
782                 ret = wimlib_write(w, wimfile, WIMLIB_ALL_IMAGES, write_flags,
783                                    num_threads, imagex_progress_func);
784         }
785         if (ret == WIMLIB_ERR_REOPEN)
786                 ret = 0;
787         if (ret != 0)
788                 imagex_error("Failed to write the WIM file `%s'", wimfile);
789 out:
790         wimlib_free(w);
791         free(config_str);
792         return ret;
793 }
794
795 /* Remove image(s) from a WIM. */
796 static int imagex_delete(int argc, const char **argv)
797 {
798         int c;
799         int open_flags = 0;
800         int write_flags = 0;
801         const char *wimfile;
802         const char *image_num_or_name;
803         WIMStruct *w;
804         int image;
805         int ret;
806
807         for_opt(c, delete_options) {
808                 switch (c) {
809                 case 'c':
810                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
811                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
812                         break;
813                 case 's':
814                         write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
815                         break;
816                 default:
817                         usage(DELETE);
818                         return -1;
819                 }
820         }
821         argc -= optind;
822         argv += optind;
823
824         if (argc != 2) {
825                 if (argc < 1)
826                         imagex_error("Must specify a WIM file");
827                 if (argc < 2)
828                         imagex_error("Must specify an image");
829                 usage(DELETE);
830                 return -1;
831         }
832         wimfile = argv[0];
833         image_num_or_name = argv[1];
834
835         ret = file_writable(wimfile);
836         if (ret != 0)
837                 return ret;
838
839         ret = wimlib_open_wim(wimfile, open_flags, &w,
840                               imagex_progress_func);
841         if (ret != 0)
842                 return ret;
843
844         image = wimlib_resolve_image(w, image_num_or_name);
845
846         ret = verify_image_exists(image, image_num_or_name, wimfile);
847         if (ret != 0)
848                 goto out;
849
850         ret = wimlib_delete_image(w, image);
851         if (ret != 0) {
852                 imagex_error("Failed to delete image from `%s'", wimfile);
853                 goto out;
854         }
855
856         ret = wimlib_overwrite(w, write_flags, 0, imagex_progress_func);
857         if (ret == WIMLIB_ERR_REOPEN)
858                 ret = 0;
859         if (ret != 0) {
860                 imagex_error("Failed to write the file `%s' with image "
861                              "deleted", wimfile);
862         }
863 out:
864         wimlib_free(w);
865         return ret;
866 }
867
868 /* Print the files contained in an image(s) in a WIM file. */
869 static int imagex_dir(int argc, const char **argv)
870 {
871         const char *wimfile;
872         WIMStruct *w;
873         int image;
874         int ret;
875         int num_images;
876
877         if (argc < 2) {
878                 imagex_error("Must specify a WIM file");
879                 usage(DIR);
880                 return -1;
881         }
882         if (argc > 3) {
883                 imagex_error("Too many arguments");
884                 usage(DIR);
885                 return -1;
886         }
887
888         wimfile = argv[1];
889         ret = wimlib_open_wim(wimfile, WIMLIB_OPEN_FLAG_SPLIT_OK, &w,
890                               imagex_progress_func);
891         if (ret != 0)
892                 return ret;
893
894         if (argc == 3) {
895                 image = wimlib_resolve_image(w, argv[2]);
896                 ret = verify_image_exists(image, argv[2], wimfile);
897                 if (ret != 0)
898                         goto out;
899         } else {
900                 /* Image was not specified.  If the WIM only contains one image,
901                  * choose that one; otherwise, print an error. */
902                 num_images = wimlib_get_num_images(w);
903                 if (num_images != 1) {
904                         imagex_error("The file `%s' contains %d images; Please "
905                                      "select one.", wimfile, num_images);
906                         usage(DIR);
907                         ret = -1;
908                         goto out;
909                 }
910                 image = 1;
911         }
912
913         ret = wimlib_print_files(w, image);
914 out:
915         wimlib_free(w);
916         return ret;
917 }
918
919 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
920  * WIM file. */
921 static int imagex_export(int argc, const char **argv)
922 {
923         int c;
924         int open_flags = 0;
925         int export_flags = 0;
926         int write_flags = 0;
927         int compression_type = WIMLIB_COMPRESSION_TYPE_NONE;
928         bool compression_type_specified = false;
929         const char *src_wimfile;
930         const char *src_image_num_or_name;
931         const char *dest_wimfile;
932         const char *dest_name;
933         const char *dest_desc;
934         WIMStruct *src_w = NULL;
935         WIMStruct *dest_w = NULL;
936         int ret;
937         int image;
938         struct stat stbuf;
939         bool wim_is_new;
940         const char *swm_glob = NULL;
941         WIMStruct **additional_swms = NULL;
942         unsigned num_additional_swms = 0;
943         unsigned num_threads = 0;
944
945         for_opt(c, export_options) {
946                 switch (c) {
947                 case 'b':
948                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
949                         break;
950                 case 'c':
951                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
952                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
953                         break;
954                 case 'x':
955                         compression_type = get_compression_type(optarg);
956                         if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
957                                 return -1;
958                         compression_type_specified = true;
959                         break;
960                 case 'r':
961                         swm_glob = optarg;
962                         break;
963                 case 't':
964                         num_threads = parse_num_threads(optarg);
965                         if (num_threads == UINT_MAX)
966                                 return -1;
967                         break;
968                 case 'R':
969                         write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
970                         break;
971                 default:
972                         usage(EXPORT);
973                         return -1;
974                 }
975         }
976         argc -= optind;
977         argv += optind;
978         if (argc < 3 || argc > 5) {
979                 usage(EXPORT);
980                 return -1;
981         }
982         src_wimfile           = argv[0];
983         src_image_num_or_name = argv[1];
984         dest_wimfile          = argv[2];
985         dest_name             = (argc >= 4) ? argv[3] : NULL;
986         dest_desc             = (argc >= 5) ? argv[4] : NULL;
987         ret = wimlib_open_wim(src_wimfile,
988                               open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK, &src_w,
989                               imagex_progress_func);
990         if (ret != 0)
991                 return ret;
992
993         /* Determine if the destination is an existing file or not.
994          * If so, we try to append the exported image(s) to it; otherwise, we
995          * create a new WIM containing the exported image(s). */
996         if (stat(dest_wimfile, &stbuf) == 0) {
997                 int dest_ctype;
998
999                 wim_is_new = false;
1000                 /* Destination file exists. */
1001                 if (!S_ISREG(stbuf.st_mode) && !S_ISLNK(stbuf.st_mode)) {
1002                         imagex_error("`%s' is not a regular file",
1003                                         dest_wimfile);
1004                         ret = -1;
1005                         goto out;
1006                 }
1007                 ret = wimlib_open_wim(dest_wimfile, open_flags, &dest_w,
1008                                       imagex_progress_func);
1009                 if (ret != 0)
1010                         goto out;
1011
1012                 ret = file_writable(dest_wimfile);
1013                 if (ret != 0)
1014                         goto out;
1015
1016                 dest_ctype = wimlib_get_compression_type(dest_w);
1017                 if (compression_type_specified
1018                     && compression_type != dest_ctype)
1019                 {
1020                         imagex_error("Cannot specify a compression type that is "
1021                                      "not the same as that used in the "
1022                                      "destination WIM");
1023                         ret = -1;
1024                         goto out;
1025                 }
1026                 compression_type = dest_ctype;
1027         } else {
1028                 wim_is_new = true;
1029                 /* dest_wimfile is not an existing file, so create a new WIM. */
1030                 if (!compression_type_specified)
1031                         compression_type = wimlib_get_compression_type(src_w);
1032                 if (errno == ENOENT) {
1033                         ret = wimlib_create_new_wim(compression_type, &dest_w);
1034                         if (ret != 0)
1035                                 goto out;
1036                 } else {
1037                         imagex_error_with_errno("Cannot stat file `%s'",
1038                                                 dest_wimfile);
1039                         ret = -1;
1040                         goto out;
1041                 }
1042         }
1043
1044         image = wimlib_resolve_image(src_w, src_image_num_or_name);
1045         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
1046         if (ret != 0)
1047                 goto out;
1048
1049         if (swm_glob) {
1050                 ret = open_swms_from_glob(swm_glob, src_wimfile, open_flags,
1051                                           &additional_swms,
1052                                           &num_additional_swms);
1053                 if (ret != 0)
1054                         goto out;
1055         }
1056
1057         ret = wimlib_export_image(src_w, image, dest_w, dest_name, dest_desc,
1058                                   export_flags, additional_swms,
1059                                   num_additional_swms, imagex_progress_func);
1060         if (ret != 0)
1061                 goto out;
1062
1063
1064         if (wim_is_new)
1065                 ret = wimlib_write(dest_w, dest_wimfile, WIMLIB_ALL_IMAGES,
1066                                    write_flags, num_threads,
1067                                    imagex_progress_func);
1068         else
1069                 ret = wimlib_overwrite(dest_w, write_flags, num_threads,
1070                                        imagex_progress_func);
1071 out:
1072         if (ret == WIMLIB_ERR_REOPEN)
1073                 ret = 0;
1074         wimlib_free(src_w);
1075         wimlib_free(dest_w);
1076         if (additional_swms) {
1077                 for (unsigned i = 0; i < num_additional_swms; i++)
1078                         wimlib_free(additional_swms[i]);
1079                 free(additional_swms);
1080         }
1081         return ret;
1082 }
1083
1084 /* Prints information about a WIM file; also can mark an image as bootable,
1085  * change the name of an image, or change the description of an image. */
1086 static int imagex_info(int argc, const char **argv)
1087 {
1088         int c;
1089         bool boot         = false;
1090         bool check        = false;
1091         bool header       = false;
1092         bool lookup_table = false;
1093         bool xml          = false;
1094         bool metadata     = false;
1095         bool short_header = true;
1096         const char *xml_out_file = NULL;
1097         const char *wimfile;
1098         const char *image_num_or_name = "all";
1099         const char *new_name = NULL;
1100         const char *new_desc = NULL;
1101         WIMStruct *w;
1102         FILE *fp;
1103         int image;
1104         int ret;
1105         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1106         int part_number;
1107         int total_parts;
1108         int num_images;
1109
1110         for_opt(c, info_options) {
1111                 switch (c) {
1112                 case 'b':
1113                         boot = true;
1114                         break;
1115                 case 'c':
1116                         check = true;
1117                         break;
1118                 case 'h':
1119                         header = true;
1120                         short_header = false;
1121                         break;
1122                 case 'l':
1123                         lookup_table = true;
1124                         short_header = false;
1125                         break;
1126                 case 'x':
1127                         xml = true;
1128                         short_header = false;
1129                         break;
1130                 case 'X':
1131                         xml_out_file = optarg;
1132                         short_header = false;
1133                         break;
1134                 case 'm':
1135                         metadata = true;
1136                         short_header = false;
1137                         break;
1138                 default:
1139                         usage(INFO);
1140                         return -1;
1141                 }
1142         }
1143
1144         argc -= optind;
1145         argv += optind;
1146         if (argc == 0 || argc > 4) {
1147                 usage(INFO);
1148                 return -1;
1149         }
1150         wimfile = argv[0];
1151         if (argc > 1) {
1152                 image_num_or_name = argv[1];
1153                 if (argc > 2) {
1154                         new_name = argv[2];
1155                         if (argc > 3) {
1156                                 new_desc = argv[3];
1157                         }
1158                 }
1159         }
1160
1161         if (check)
1162                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1163
1164         ret = wimlib_open_wim(wimfile, open_flags, &w,
1165                               imagex_progress_func);
1166         if (ret != 0)
1167                 return ret;
1168
1169         part_number = wimlib_get_part_number(w, &total_parts);
1170
1171         image = wimlib_resolve_image(w, image_num_or_name);
1172         if (image == WIMLIB_NO_IMAGE && strcmp(image_num_or_name, "0") != 0) {
1173                 imagex_error("The image `%s' does not exist",
1174                              image_num_or_name);
1175                 if (boot)
1176                         imagex_error("If you would like to set the boot "
1177                                      "index to 0, specify image \"0\" with "
1178                                      "the --boot flag.");
1179                 ret = WIMLIB_ERR_INVALID_IMAGE;
1180                 goto out;
1181         }
1182
1183         num_images = wimlib_get_num_images(w);
1184
1185         if (num_images == 0) {
1186                 if (boot) {
1187                         imagex_error("--boot is meaningless on a WIM with no "
1188                                      "images");
1189                         ret = WIMLIB_ERR_INVALID_IMAGE;
1190                         goto out;
1191                 }
1192         }
1193
1194         if (image == WIMLIB_ALL_IMAGES && num_images > 1) {
1195                 if (boot) {
1196                         imagex_error("Cannot specify the --boot flag "
1197                                      "without specifying a specific "
1198                                      "image in a multi-image WIM");
1199                         ret = WIMLIB_ERR_INVALID_IMAGE;
1200                         goto out;
1201                 }
1202                 if (new_name) {
1203                         imagex_error("Cannot specify the NEW_NAME "
1204                                      "without specifying a specific "
1205                                      "image in a multi-image WIM");
1206                         ret = WIMLIB_ERR_INVALID_IMAGE;
1207                         goto out;
1208                 }
1209         }
1210
1211         /* Operations that print information are separated from operations that
1212          * recreate the WIM file. */
1213         if (!new_name && !boot) {
1214
1215                 /* Read-only operations */
1216
1217                 if (image == WIMLIB_NO_IMAGE) {
1218                         imagex_error("`%s' is not a valid image",
1219                                      image_num_or_name);
1220                         ret = WIMLIB_ERR_INVALID_IMAGE;
1221                         goto out;
1222                 }
1223
1224                 if (image == WIMLIB_ALL_IMAGES && short_header)
1225                         wimlib_print_wim_information(w);
1226
1227                 if (header)
1228                         wimlib_print_header(w);
1229
1230                 if (lookup_table) {
1231                         if (total_parts != 1) {
1232                                 printf("Warning: Only showing the lookup table "
1233                                        "for part %d of a %d-part WIM.\n",
1234                                        part_number, total_parts);
1235                         }
1236                         wimlib_print_lookup_table(w);
1237                 }
1238
1239                 if (xml) {
1240                         ret = wimlib_extract_xml_data(w, stdout);
1241                         if (ret != 0)
1242                                 goto out;
1243                 }
1244
1245                 if (xml_out_file) {
1246                         fp = fopen(xml_out_file, "wb");
1247                         if (!fp) {
1248                                 imagex_error_with_errno("Failed to open the "
1249                                                         "file `%s' for "
1250                                                         "writing ",
1251                                                         xml_out_file);
1252                                 ret = -1;
1253                                 goto out;
1254                         }
1255                         ret = wimlib_extract_xml_data(w, fp);
1256                         if (fclose(fp) != 0) {
1257                                 imagex_error("Failed to close the file `%s'",
1258                                              xml_out_file);
1259                                 ret = -1;
1260                         }
1261
1262                         if (ret != 0)
1263                                 goto out;
1264                 }
1265
1266                 if (short_header)
1267                         wimlib_print_available_images(w, image);
1268
1269                 if (metadata) {
1270                         ret = wimlib_print_metadata(w, image);
1271                         if (ret != 0)
1272                                 goto out;
1273                 }
1274         } else {
1275
1276                 /* Modification operations */
1277                 if (total_parts != 1) {
1278                         imagex_error("Modifying a split WIM is not supported.");
1279                         ret = -1;
1280                         goto out;
1281                 }
1282                 if (image == WIMLIB_ALL_IMAGES)
1283                         image = 1;
1284
1285                 if (image == WIMLIB_NO_IMAGE && new_name) {
1286                         imagex_error("Cannot specify new_name (`%s') when "
1287                                      "using image 0", new_name);
1288                         ret = -1;
1289                         goto out;
1290                 }
1291
1292                 if (boot) {
1293                         if (image == wimlib_get_boot_idx(w)) {
1294                                 printf("Image %d is already marked as "
1295                                        "bootable.\n", image);
1296                                 boot = false;
1297                         } else {
1298                                 printf("Marking image %d as bootable.\n",
1299                                        image);
1300                                 wimlib_set_boot_idx(w, image);
1301                         }
1302                 }
1303                 if (new_name) {
1304                         if (strcmp(wimlib_get_image_name(w, image),
1305                                                 new_name) == 0) {
1306                                 printf("Image %d is already named \"%s\".\n",
1307                                        image, new_name);
1308                                 new_name = NULL;
1309                         } else {
1310                                 printf("Changing the name of image %d to "
1311                                        "\"%s\".\n", image, new_name);
1312                                 ret = wimlib_set_image_name(w, image, new_name);
1313                                 if (ret != 0)
1314                                         goto out;
1315                         }
1316                 }
1317                 if (new_desc) {
1318                         const char *old_desc;
1319                         old_desc = wimlib_get_image_description(w, image);
1320                         if (old_desc && strcmp(old_desc, new_desc) == 0) {
1321                                 printf("The description of image %d is already "
1322                                        "\"%s\".\n", image, new_desc);
1323                                 new_desc = NULL;
1324                         } else {
1325                                 printf("Changing the description of image %d "
1326                                        "to \"%s\".\n", image, new_desc);
1327                                 ret = wimlib_set_image_descripton(w, image,
1328                                                                   new_desc);
1329                                 if (ret != 0)
1330                                         goto out;
1331                         }
1332                 }
1333
1334                 /* Only call wimlib_overwrite() if something actually needs to
1335                  * be changed. */
1336                 if (boot || new_name || new_desc ||
1337                     (check && !wimlib_has_integrity_table(w)))
1338                 {
1339                         int write_flags;
1340
1341                         ret = file_writable(wimfile);
1342                         if (ret != 0)
1343                                 return ret;
1344
1345                         if (check)
1346                                 write_flags = WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1347                         else
1348                                 write_flags = 0;
1349
1350                         ret = wimlib_overwrite(w, write_flags, 1,
1351                                                imagex_progress_func);
1352                         if (ret == WIMLIB_ERR_REOPEN)
1353                                 ret = 0;
1354                 } else {
1355                         printf("The file `%s' was not modified because nothing "
1356                                "needed to be done.\n", wimfile);
1357                         ret = 0;
1358                 }
1359         }
1360 out:
1361         wimlib_free(w);
1362         return ret;
1363 }
1364
1365 /* Join split WIMs into one part WIM */
1366 static int imagex_join(int argc, const char **argv)
1367 {
1368         int c;
1369         int swm_open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1370         int wim_write_flags = 0;
1371         const char *output_path;
1372
1373         for_opt(c, join_options) {
1374                 switch (c) {
1375                 case 'c':
1376                         swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1377                         wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1378                         break;
1379                 default:
1380                         goto err;
1381                 }
1382         }
1383         argc -= optind;
1384         argv += optind;
1385
1386         if (argc < 2) {
1387                 imagex_error("Must specify one or more split WIM (.swm) parts "
1388                              "to join");
1389                 goto err;
1390         }
1391         output_path = argv[0];
1392         return wimlib_join(++argv, --argc, output_path, swm_open_flags,
1393                            wim_write_flags, imagex_progress_func);
1394 err:
1395         usage(JOIN);
1396         return -1;
1397 }
1398
1399 /* Mounts an image using a FUSE mount. */
1400 static int imagex_mount_rw_or_ro(int argc, const char **argv)
1401 {
1402         int c;
1403         int mount_flags = 0;
1404         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1405         const char *wimfile;
1406         const char *dir;
1407         WIMStruct *w;
1408         int image;
1409         int num_images;
1410         int ret;
1411         const char *swm_glob = NULL;
1412         WIMStruct **additional_swms = NULL;
1413         unsigned num_additional_swms = 0;
1414         const char *staging_dir = NULL;
1415
1416         if (strcmp(argv[0], "mountrw") == 0)
1417                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
1418
1419         for_opt(c, mount_options) {
1420                 switch (c) {
1421                 case 'c':
1422                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1423                         break;
1424                 case 'd':
1425                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
1426                         break;
1427                 case 's':
1428                         if (strcasecmp(optarg, "none") == 0)
1429                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
1430                         else if (strcasecmp(optarg, "xattr") == 0)
1431                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
1432                         else if (strcasecmp(optarg, "windows") == 0)
1433                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
1434                         else {
1435                                 imagex_error("Unknown stream interface \"%s\"", optarg);
1436                                 goto mount_usage;
1437                         }
1438                         break;
1439                 case 'r':
1440                         swm_glob = optarg;
1441                         break;
1442                 case 'D':
1443                         staging_dir = optarg;
1444                         break;
1445                 default:
1446                         goto mount_usage;
1447                 }
1448         }
1449         argc -= optind;
1450         argv += optind;
1451         if (argc != 2 && argc != 3)
1452                 goto mount_usage;
1453
1454         wimfile = argv[0];
1455
1456         ret = wimlib_open_wim(wimfile, open_flags, &w,
1457                               imagex_progress_func);
1458         if (ret != 0)
1459                 return ret;
1460
1461         if (swm_glob) {
1462                 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
1463                                           &additional_swms,
1464                                           &num_additional_swms);
1465                 if (ret != 0)
1466                         goto out;
1467         }
1468
1469         if (argc == 2) {
1470                 image = 1;
1471                 num_images = wimlib_get_num_images(w);
1472                 if (num_images != 1) {
1473                         imagex_error("The file `%s' contains %d images; Please "
1474                                      "select one.", wimfile, num_images);
1475                         usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
1476                                         ? MOUNTRW : MOUNT);
1477                         ret = -1;
1478                         goto out;
1479                 }
1480                 dir = argv[1];
1481         } else {
1482                 image = wimlib_resolve_image(w, argv[1]);
1483                 dir = argv[2];
1484                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
1485                 if (ret != 0)
1486                         goto out;
1487         }
1488
1489         if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1490                 ret = file_writable(wimfile);
1491                 if (ret != 0)
1492                         goto out;
1493         }
1494
1495         ret = wimlib_mount_image(w, image, dir, mount_flags, additional_swms,
1496                                  num_additional_swms, staging_dir);
1497         if (ret != 0) {
1498                 imagex_error("Failed to mount image %d from `%s' on `%s'",
1499                              image, wimfile, dir);
1500
1501         }
1502 out:
1503         wimlib_free(w);
1504         if (additional_swms) {
1505                 for (unsigned i = 0; i < num_additional_swms; i++)
1506                         wimlib_free(additional_swms[i]);
1507                 free(additional_swms);
1508         }
1509         return ret;
1510 mount_usage:
1511         usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
1512                         ? MOUNTRW : MOUNT);
1513         return -1;
1514 }
1515
1516 static int imagex_optimize(int argc, const char **argv)
1517 {
1518         int c;
1519         int open_flags = 0;
1520         int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
1521         int ret;
1522         WIMStruct *w;
1523         const char *wimfile;
1524         off_t old_size;
1525         off_t new_size;
1526
1527         for_opt(c, optimize_options) {
1528                 switch (c) {
1529                 case 'c':
1530                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1531                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1532                         break;
1533                 case 'r':
1534                         write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
1535                         break;
1536                 default:
1537                         usage(OPTIMIZE);
1538                         return -1;
1539                 }
1540         }
1541         argc -= optind;
1542         argv += optind;
1543
1544         if (argc != 1) {
1545                 usage(OPTIMIZE);
1546                 return -1;
1547         }
1548
1549         wimfile = argv[0];
1550
1551         ret = wimlib_open_wim(wimfile, open_flags, &w,
1552                               imagex_progress_func);
1553         if (ret != 0)
1554                 return ret;
1555
1556         old_size = file_get_size(argv[0]);
1557         printf("`%s' original size: ", wimfile);
1558         if (old_size == -1)
1559                 puts("Unknown");
1560         else
1561                 printf("%"PRIu64" KiB\n", old_size >> 10);
1562
1563         ret = wimlib_overwrite(w, write_flags, 0, imagex_progress_func);
1564
1565         if (ret == 0) {
1566                 new_size = file_get_size(argv[0]);
1567                 printf("`%s' optimized size: ", wimfile);
1568                 if (new_size == -1)
1569                         puts("Unknown");
1570                 else
1571                         printf("%"PRIu64" KiB\n", new_size >> 10);
1572
1573                 fputs("Space saved: ", stdout);
1574                 if (new_size != -1 && old_size != -1) {
1575                         printf("%lld KiB\n",
1576                                ((long long)old_size - (long long)new_size) >> 10);
1577                 } else {
1578                         puts("Unknown");
1579                 }
1580         }
1581
1582         wimlib_free(w);
1583         return ret;
1584 }
1585
1586 /* Split a WIM into a spanned set */
1587 static int imagex_split(int argc, const char **argv)
1588 {
1589         int c;
1590         int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1591         int write_flags = 0;
1592         unsigned long part_size;
1593         char *tmp;
1594         int ret;
1595         WIMStruct *w;
1596
1597         for_opt(c, split_options) {
1598                 switch (c) {
1599                 case 'c':
1600                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1601                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1602                         break;
1603                 default:
1604                         usage(SPLIT);
1605                         return -1;
1606                 }
1607         }
1608         argc -= optind;
1609         argv += optind;
1610
1611         if (argc != 3) {
1612                 usage(SPLIT);
1613                 return -1;
1614         }
1615         part_size = strtod(argv[2], &tmp) * (1 << 20);
1616         if (tmp == argv[2] || *tmp) {
1617                 imagex_error("Invalid part size \"%s\"", argv[2]);
1618                 imagex_error("The part size must be an integer or floating-point number of megabytes.");
1619                 return -1;
1620         }
1621         ret = wimlib_open_wim(argv[0], open_flags, &w, imagex_progress_func);
1622         if (ret != 0)
1623                 return ret;
1624         ret = wimlib_split(w, argv[1], part_size, write_flags, imagex_progress_func);
1625         wimlib_free(w);
1626         return ret;
1627 }
1628
1629 /* Unmounts an image. */
1630 static int imagex_unmount(int argc, const char **argv)
1631 {
1632         int c;
1633         int unmount_flags = 0;
1634         int ret;
1635
1636         for_opt(c, unmount_options) {
1637                 switch (c) {
1638                 case 'c':
1639                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
1640                         break;
1641                 case 'C':
1642                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
1643                         break;
1644                 case 'R':
1645                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
1646                         break;
1647                 default:
1648                         usage(UNMOUNT);
1649                         return -1;
1650                 }
1651         }
1652         argc -= optind;
1653         argv += optind;
1654         if (argc != 1) {
1655                 usage(UNMOUNT);
1656                 return -1;
1657         }
1658
1659         ret = wimlib_unmount_image(argv[0], unmount_flags,
1660                                    imagex_progress_func);
1661         if (ret != 0)
1662                 imagex_error("Failed to unmount `%s'", argv[0]);
1663         return ret;
1664 }
1665
1666 struct imagex_command {
1667         const char *name;
1668         int (*func)(int , const char **);
1669         int cmd;
1670 };
1671
1672
1673 #define for_imagex_command(p) for (p = &imagex_commands[0]; \
1674                 p != &imagex_commands[ARRAY_LEN(imagex_commands)]; p++)
1675
1676 static const struct imagex_command imagex_commands[] = {
1677         {"append",  imagex_capture_or_append, APPEND},
1678         {"apply",   imagex_apply,             APPLY},
1679         {"capture", imagex_capture_or_append, CAPTURE},
1680         {"delete",  imagex_delete,            DELETE},
1681         {"dir",     imagex_dir,               DIR},
1682         {"export",  imagex_export,            EXPORT},
1683         {"info",    imagex_info,              INFO},
1684         {"join",    imagex_join,              JOIN},
1685         {"mount",   imagex_mount_rw_or_ro,    MOUNT},
1686         {"mountrw", imagex_mount_rw_or_ro,    MOUNTRW},
1687         {"optimize",imagex_optimize,          OPTIMIZE},
1688         {"split",   imagex_split,             SPLIT},
1689         {"unmount", imagex_unmount,           UNMOUNT},
1690 };
1691
1692 static void version()
1693 {
1694         static const char *s =
1695         "imagex (" PACKAGE ") " PACKAGE_VERSION "\n"
1696         "Copyright (C) 2012 Eric Biggers\n"
1697         "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
1698         "This is free software: you are free to change and redistribute it.\n"
1699         "There is NO WARRANTY, to the extent permitted by law.\n"
1700         "\n"
1701         "Report bugs to "PACKAGE_BUGREPORT".\n";
1702         fputs(s, stdout);
1703 }
1704
1705
1706 static void help_or_version(int argc, const char **argv)
1707 {
1708         int i;
1709         const char *p;
1710         const struct imagex_command *cmd;
1711
1712         for (i = 1; i < argc; i++) {
1713                 p = argv[i];
1714                 if (*p == '-')
1715                         p++;
1716                 else
1717                         continue;
1718                 if (*p == '-')
1719                         p++;
1720                 if (strcmp(p, "help") == 0) {
1721                         for_imagex_command(cmd) {
1722                                 if (strcmp(cmd->name, argv[1]) == 0) {
1723                                         usage(cmd->cmd);
1724                                         exit(0);
1725                                 }
1726                         }
1727                         usage_all();
1728                         exit(0);
1729                 }
1730                 if (strcmp(p, "version") == 0) {
1731                         version();
1732                         exit(0);
1733                 }
1734         }
1735 }
1736
1737
1738 static void usage(int cmd_type)
1739 {
1740         const struct imagex_command *cmd;
1741         printf("Usage: %s", usage_strings[cmd_type]);
1742         for_imagex_command(cmd) {
1743                 if (cmd->cmd == cmd_type)
1744                         printf("\nTry `man imagex-%s' for more details.\n",
1745                                cmd->name);
1746         }
1747 }
1748
1749 static void usage_all()
1750 {
1751         puts("IMAGEX: Usage:");
1752         for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
1753                 printf("    %s", usage_strings[i]);
1754         static const char *extra =
1755 "    imagex --help\n"
1756 "    imagex --version\n"
1757 "\n"
1758 "    The compression TYPE may be \"maximum\", \"fast\", or \"none\".\n"
1759 "\n"
1760 "    Try `man imagex' for more information.\n"
1761         ;
1762         fputs(extra, stdout);
1763 }
1764
1765
1766 int main(int argc, const char **argv)
1767 {
1768         const struct imagex_command *cmd;
1769         int ret;
1770
1771         if (argc < 2) {
1772                 imagex_error("No command specified");
1773                 usage_all();
1774                 return 1;
1775         }
1776
1777         help_or_version(argc, argv);
1778         argc--;
1779         argv++;
1780
1781         wimlib_set_print_errors(true);
1782
1783         for_imagex_command(cmd) {
1784                 if (strcmp(cmd->name, *argv) == 0) {
1785                         ret = cmd->func(argc, argv);
1786                         if (ret > 0) {
1787                                 imagex_error("Exiting with error code %d:\n"
1788                                              "       %s.", ret,
1789                                              wimlib_get_error_string(ret));
1790                                 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
1791                                         imagex_error_with_errno("errno");
1792                         }
1793                         return ret;
1794                 }
1795         }
1796
1797         imagex_error("Unrecognized command: `%s'", argv[0]);
1798         usage_all();
1799         return 1;
1800 }