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