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