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