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