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