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