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