266dc0d45070080297b09e0910cf50fd66254174
[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 == WIMLIB_ERR_REOPEN)
627                 ret = 0;
628         if (ret != 0)
629                 imagex_error("Failed to write the WIM file `%s'", wimfile);
630 out:
631         wimlib_free(w);
632         free(config_str);
633         return ret;
634 }
635
636 /* Remove image(s) from a WIM. */
637 static int imagex_delete(int argc, const char **argv)
638 {
639         int c;
640         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
641         int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
642         const char *wimfile;
643         const char *image_num_or_name;
644         WIMStruct *w;
645         int image;
646         int ret;
647
648         for_opt(c, delete_options) {
649                 switch (c) {
650                 case 'c':
651                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
652                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
653                         break;
654                 default:
655                         usage(DELETE);
656                         return -1;
657                 }
658         }
659         argc -= optind;
660         argv += optind;
661
662         if (argc != 2) {
663                 if (argc < 1)
664                         imagex_error("Must specify a WIM file");
665                 if (argc < 2)
666                         imagex_error("Must specify an image");
667                 usage(DELETE);
668                 return -1;
669         }
670         wimfile = argv[0];
671         image_num_or_name = argv[1];
672
673         ret = file_writable(wimfile);
674         if (ret != 0)
675                 return ret;
676
677         ret = wimlib_open_wim(wimfile, open_flags, &w);
678         if (ret != 0)
679                 return ret;
680
681         image = wimlib_resolve_image(w, image_num_or_name);
682
683         ret = verify_image_exists(image, image_num_or_name, wimfile);
684         if (ret != 0)
685                 goto out;
686
687         ret = wimlib_delete_image(w, image);
688         if (ret != 0) {
689                 imagex_error("Failed to delete image from `%s'", wimfile);
690                 goto out;
691         }
692
693         ret = wimlib_overwrite(w, write_flags, 0);
694         if (ret == WIMLIB_ERR_REOPEN)
695                 ret = 0;
696         if (ret != 0) {
697                 imagex_error("Failed to write the file `%s' with image "
698                              "deleted", wimfile);
699         }
700 out:
701         wimlib_free(w);
702         return ret;
703 }
704
705 /* Print the files contained in an image(s) in a WIM file. */
706 static int imagex_dir(int argc, const char **argv)
707 {
708         const char *wimfile;
709         WIMStruct *w;
710         int image;
711         int ret;
712         int num_images;
713
714         if (argc < 2) {
715                 imagex_error("Must specify a WIM file");
716                 usage(DIR);
717                 return -1;
718         }
719         if (argc > 3) {
720                 imagex_error("Too many arguments");
721                 usage(DIR);
722                 return -1;
723         }
724
725         wimfile = argv[1];
726         ret = wimlib_open_wim(wimfile, WIMLIB_OPEN_FLAG_SPLIT_OK, &w);
727         if (ret != 0)
728                 return ret;
729
730         if (argc == 3) {
731                 image = wimlib_resolve_image(w, argv[2]);
732                 ret = verify_image_exists(image, argv[2], wimfile);
733                 if (ret != 0)
734                         goto out;
735         } else {
736                 /* Image was not specified.  If the WIM only contains one image,
737                  * choose that one; otherwise, print an error. */
738                 num_images = wimlib_get_num_images(w);
739                 if (num_images != 1) {
740                         imagex_error("The file `%s' contains %d images; Please "
741                                      "select one.", wimfile, num_images);
742                         usage(DIR);
743                         ret = -1;
744                         goto out;
745                 }
746                 image = 1;
747         }
748
749         ret = wimlib_print_files(w, image);
750 out:
751         wimlib_free(w);
752         return ret;
753 }
754
755 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
756  * WIM file. */
757 static int imagex_export(int argc, const char **argv)
758 {
759         int c;
760         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
761         int export_flags = 0;
762         int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
763         int compression_type;
764         bool compression_type_specified = false;
765         const char *src_wimfile;
766         const char *src_image_num_or_name;
767         const char *dest_wimfile;
768         const char *dest_name;
769         const char *dest_desc;
770         WIMStruct *src_w = NULL;
771         WIMStruct *dest_w = NULL;
772         int ret;
773         int image;
774         struct stat stbuf;
775         bool wim_is_new;
776         const char *swm_glob = NULL;
777         WIMStruct **additional_swms = NULL;
778         unsigned num_additional_swms = 0;
779         unsigned num_threads = 0;
780
781         for_opt(c, export_options) {
782                 switch (c) {
783                 case 'b':
784                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
785                         break;
786                 case 'c':
787                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
788                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
789                         break;
790                 case 'x':
791                         compression_type = get_compression_type(optarg);
792                         if (compression_type == WIM_COMPRESSION_TYPE_INVALID)
793                                 return -1;
794                         compression_type_specified = true;
795                         break;
796                 case 'r':
797                         swm_glob = optarg;
798                         break;
799                 case 't':
800                         num_threads = parse_num_threads(optarg);
801                         if (num_threads == UINT_MAX)
802                                 return -1;
803                         break;
804                 default:
805                         usage(EXPORT);
806                         return -1;
807                 }
808         }
809         argc -= optind;
810         argv += optind;
811         if (argc < 3 || argc > 5) {
812                 usage(EXPORT);
813                 return -1;
814         }
815         src_wimfile           = argv[0];
816         src_image_num_or_name = argv[1];
817         dest_wimfile          = argv[2];
818         dest_name             = (argc >= 4) ? argv[3] : NULL;
819         dest_desc             = (argc >= 5) ? argv[4] : NULL;
820         ret = wimlib_open_wim(src_wimfile,
821                               open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK, &src_w);
822         if (ret != 0)
823                 return ret;
824
825         /* Determine if the destination is an existing file or not.
826          * If so, we try to append the exported image(s) to it; otherwise, we
827          * create a new WIM containing the exported image(s). */
828         if (stat(dest_wimfile, &stbuf) == 0) {
829                 int dest_ctype;
830
831                 wim_is_new = false;
832                 /* Destination file exists. */
833                 if (!S_ISREG(stbuf.st_mode) && !S_ISLNK(stbuf.st_mode)) {
834                         imagex_error("`%s' is not a regular file",
835                                         dest_wimfile);
836                         ret = -1;
837                         goto out;
838                 }
839                 ret = wimlib_open_wim(dest_wimfile, open_flags, &dest_w);
840                 if (ret != 0)
841                         goto out;
842
843                 ret = file_writable(dest_wimfile);
844                 if (ret != 0)
845                         return ret;
846
847                 dest_ctype = wimlib_get_compression_type(dest_w);
848                 if (compression_type_specified
849                     && compression_type != dest_ctype)
850                 {
851                         imagex_error("Cannot specify a compression type that is "
852                                      "not the same as that used in the "
853                                      "destination WIM");
854                         ret = -1;
855                         goto out;
856                 }
857                 compression_type = dest_ctype;
858         } else {
859                 wim_is_new = true;
860                 /* dest_wimfile is not an existing file, so create a new WIM. */
861                 if (!compression_type_specified)
862                         compression_type = wimlib_get_compression_type(src_w);
863                 if (errno == ENOENT) {
864                         ret = wimlib_create_new_wim(compression_type, &dest_w);
865                         if (ret != 0)
866                                 goto out;
867                 } else {
868                         imagex_error_with_errno("Cannot stat file `%s'",
869                                                 dest_wimfile);
870                         ret = -1;
871                         goto out;
872                 }
873         }
874
875         image = wimlib_resolve_image(src_w, src_image_num_or_name);
876         ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
877         if (ret != 0)
878                 goto out;
879
880         if (swm_glob) {
881                 ret = open_swms_from_glob(swm_glob, src_wimfile, open_flags,
882                                           &additional_swms,
883                                           &num_additional_swms);
884                 if (ret != 0)
885                         goto out;
886         }
887
888         ret = wimlib_export_image(src_w, image, dest_w, dest_name, dest_desc,
889                                   export_flags, additional_swms,
890                                   num_additional_swms);
891         if (ret != 0)
892                 goto out;
893
894
895         if (wim_is_new)
896                 ret = wimlib_write(dest_w, dest_wimfile, WIM_ALL_IMAGES,
897                                    write_flags, num_threads);
898         else
899                 ret = wimlib_overwrite(dest_w, write_flags, num_threads);
900 out:
901         if (ret == WIMLIB_ERR_REOPEN)
902                 ret = 0;
903         wimlib_free(src_w);
904         wimlib_free(dest_w);
905         if (additional_swms) {
906                 for (unsigned i = 0; i < num_additional_swms; i++)
907                         wimlib_free(additional_swms[i]);
908                 free(additional_swms);
909         }
910         return ret;
911 }
912
913 /* Prints information about a WIM file; also can mark an image as bootable,
914  * change the name of an image, or change the description of an image. */
915 static int imagex_info(int argc, const char **argv)
916 {
917         int c;
918         bool boot         = false;
919         bool check        = false;
920         bool header       = false;
921         bool lookup_table = false;
922         bool xml          = false;
923         bool metadata     = false;
924         bool short_header = true;
925         const char *xml_out_file = NULL;
926         const char *wimfile;
927         const char *image_num_or_name = "all";
928         const char *new_name = NULL;
929         const char *new_desc = NULL;
930         WIMStruct *w;
931         FILE *fp;
932         int image;
933         int ret;
934         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS |
935                          WIMLIB_OPEN_FLAG_SPLIT_OK;
936         int part_number;
937         int total_parts;
938
939         for_opt(c, info_options) {
940                 switch (c) {
941                 case 'b':
942                         boot = true;
943                         break;
944                 case 'c':
945                         check = true;
946                         break;
947                 case 'h':
948                         header = true;
949                         short_header = false;
950                         break;
951                 case 'l':
952                         lookup_table = true;
953                         short_header = false;
954                         break;
955                 case 'x':
956                         xml = true;
957                         short_header = false;
958                         break;
959                 case 'X':
960                         xml_out_file = optarg;
961                         short_header = false;
962                         break;
963                 case 'm':
964                         metadata = true;
965                         short_header = false;
966                         break;
967                 default:
968                         usage(INFO);
969                         return -1;
970                 }
971         }
972
973         argc -= optind;
974         argv += optind;
975         if (argc == 0 || argc > 4) {
976                 usage(INFO);
977                 return -1;
978         }
979         wimfile = argv[0];
980         if (argc > 1) {
981                 image_num_or_name = argv[1];
982                 if (argc > 2) {
983                         new_name = argv[2];
984                         if (argc > 3) {
985                                 new_desc = argv[3];
986                         }
987                 }
988         }
989
990         if (check)
991                 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
992
993         ret = wimlib_open_wim(wimfile, open_flags, &w);
994         if (ret != 0)
995                 return ret;
996
997         part_number = wimlib_get_part_number(w, &total_parts);
998
999         image = wimlib_resolve_image(w, image_num_or_name);
1000         if (image == WIM_NO_IMAGE && strcmp(image_num_or_name, "0") != 0) {
1001                 imagex_error("The image `%s' does not exist",
1002                                                 image_num_or_name);
1003                 if (boot)
1004                         imagex_error("If you would like to set the boot "
1005                                      "index to 0, specify image \"0\" with "
1006                                      "the --boot flag.");
1007                 ret = WIMLIB_ERR_INVALID_IMAGE;
1008                 goto out;
1009         }
1010
1011         if (image == WIM_ALL_IMAGES && wimlib_get_num_images(w) > 1) {
1012                 if (boot) {
1013                         imagex_error("Cannot specify the --boot flag "
1014                                      "without specifying a specific "
1015                                      "image in a multi-image WIM");
1016                         ret = WIMLIB_ERR_INVALID_IMAGE;
1017                         goto out;
1018                 }
1019                 if (new_name) {
1020                         imagex_error("Cannot specify the NEW_NAME "
1021                                      "without specifying a specific "
1022                                      "image in a multi-image WIM");
1023                         ret = WIMLIB_ERR_INVALID_IMAGE;
1024                         goto out;
1025                 }
1026         }
1027
1028         /* Operations that print information are separated from operations that
1029          * recreate the WIM file. */
1030         if (!new_name && !boot) {
1031
1032                 /* Read-only operations */
1033
1034                 if (image == WIM_NO_IMAGE) {
1035                         imagex_error("`%s' is not a valid image",
1036                                      image_num_or_name);
1037                         ret = WIMLIB_ERR_INVALID_IMAGE;
1038                         goto out;
1039                 }
1040
1041                 if (image == WIM_ALL_IMAGES && short_header)
1042                         wimlib_print_wim_information(w);
1043
1044                 if (header)
1045                         wimlib_print_header(w);
1046
1047                 if (lookup_table) {
1048                         if (total_parts != 1) {
1049                                 printf("Warning: Only showing the lookup table "
1050                                        "for part %d of a %d-part WIM.\n",
1051                                        part_number, total_parts);
1052                         }
1053                         wimlib_print_lookup_table(w);
1054                 }
1055
1056                 if (xml) {
1057                         ret = wimlib_extract_xml_data(w, stdout);
1058                         if (ret != 0)
1059                                 goto out;
1060                 }
1061
1062                 if (xml_out_file) {
1063                         fp = fopen(xml_out_file, "wb");
1064                         if (!fp) {
1065                                 imagex_error_with_errno("Failed to open the "
1066                                                         "file `%s' for "
1067                                                         "writing ",
1068                                                         xml_out_file);
1069                                 goto out;
1070                         }
1071                         ret = wimlib_extract_xml_data(w, fp);
1072                         if (fclose(fp) != 0) {
1073                                 imagex_error("Failed to close the file `%s'",
1074                                              xml_out_file);
1075                                 goto out;
1076                         }
1077
1078                         if (ret != 0)
1079                                 goto out;
1080                 }
1081
1082                 if (short_header)
1083                         wimlib_print_available_images(w, image);
1084
1085                 if (metadata) {
1086                         ret = wimlib_print_metadata(w, image);
1087                         if (ret != 0)
1088                                 goto out;
1089                 }
1090         } else {
1091
1092                 /* Modification operations */
1093                 if (total_parts != 1) {
1094                         imagex_error("Modifying a split WIM is not supported.");
1095                         return -1;
1096                 }
1097                 if (image == WIM_ALL_IMAGES)
1098                         image = 1;
1099
1100                 if (image == WIM_NO_IMAGE && new_name) {
1101                         imagex_error("Cannot specify new_name (`%s') when "
1102                                      "using image 0", new_name);
1103                         return -1;
1104                 }
1105
1106                 if (boot) {
1107                         if (image == wimlib_get_boot_idx(w)) {
1108                                 printf("Image %d is already marked as "
1109                                        "bootable.\n", image);
1110                                 boot = false;
1111                         } else {
1112                                 printf("Marking image %d as bootable.\n",
1113                                        image);
1114                                 wimlib_set_boot_idx(w, image);
1115                         }
1116                 }
1117                 if (new_name) {
1118                         if (strcmp(wimlib_get_image_name(w, image),
1119                                                 new_name) == 0) {
1120                                 printf("Image %d is already named \"%s\".\n",
1121                                        image, new_name);
1122                                 new_name = NULL;
1123                         } else {
1124                                 printf("Changing the name of image %d to "
1125                                        "\"%s\".\n", image, new_name);
1126                                 ret = wimlib_set_image_name(w, image, new_name);
1127                                 if (ret != 0)
1128                                         goto out;
1129                         }
1130                 }
1131                 if (new_desc) {
1132                         const char *old_desc;
1133                         old_desc = wimlib_get_image_description(w, image);
1134                         if (old_desc && strcmp(old_desc, new_desc) == 0) {
1135                                 printf("The description of image %d is already "
1136                                        "\"%s\".\n", image, new_desc);
1137                                 new_desc = NULL;
1138                         } else {
1139                                 printf("Changing the description of image %d "
1140                                        "to \"%s\".\n", image, new_desc);
1141                                 ret = wimlib_set_image_descripton(w, image,
1142                                                                   new_desc);
1143                                 if (ret != 0)
1144                                         goto out;
1145                         }
1146                 }
1147
1148                 /* Only call wimlib_overwrite() if something actually needs to
1149                  * be changed. */
1150                 if (boot || new_name || new_desc ||
1151                                 check != wimlib_has_integrity_table(w)) {
1152
1153                         ret = file_writable(wimfile);
1154                         if (ret != 0)
1155                                 return ret;
1156
1157                         int write_flags;
1158                         if (check) {
1159                                 write_flags = WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
1160                                               WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
1161                         } else {
1162                                 write_flags = 0;
1163                         }
1164
1165                         ret = wimlib_overwrite(w, write_flags, 1);
1166                         if (ret == WIMLIB_ERR_REOPEN)
1167                                 ret = 0;
1168                 } else {
1169                         printf("The file `%s' was not modified because nothing "
1170                                         "needed to be done.\n", wimfile);
1171                         ret = 0;
1172                 }
1173         }
1174 out:
1175         wimlib_free(w);
1176         return ret;
1177 }
1178
1179 /* Join split WIMs into one part WIM */
1180 static int imagex_join(int argc, const char **argv)
1181 {
1182         int c;
1183         int flags = WIMLIB_OPEN_FLAG_SPLIT_OK | WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
1184         const char *output_path;
1185
1186         for_opt(c, join_options) {
1187                 switch (c) {
1188                 case 'c':
1189                         flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1190                         break;
1191                 default:
1192                         goto err;
1193                 }
1194         }
1195         argc -= optind;
1196         argv += optind;
1197
1198         if (argc < 2) {
1199                 imagex_error("Must specify at least one split WIM (.swm) parts "
1200                              "to join");
1201                 goto err;
1202         }
1203         output_path = argv[0];
1204         return wimlib_join(++argv, --argc, output_path, flags);
1205 err:
1206         usage(JOIN);
1207         return -1;
1208 }
1209
1210 /* Mounts an image using a FUSE mount. */
1211 static int imagex_mount_rw_or_ro(int argc, const char **argv)
1212 {
1213         int c;
1214         int mount_flags = 0;
1215         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS |
1216                          WIMLIB_OPEN_FLAG_SPLIT_OK;
1217         const char *wimfile;
1218         const char *dir;
1219         WIMStruct *w;
1220         int image;
1221         int num_images;
1222         int ret;
1223         const char *swm_glob = NULL;
1224         WIMStruct **additional_swms = NULL;
1225         unsigned num_additional_swms = 0;
1226
1227         if (strcmp(argv[0], "mountrw") == 0)
1228                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
1229
1230         for_opt(c, mount_options) {
1231                 switch (c) {
1232                 case 'c':
1233                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1234                         break;
1235                 case 'd':
1236                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
1237                         break;
1238                 case 's':
1239                         if (strcasecmp(optarg, "none") == 0)
1240                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
1241                         else if (strcasecmp(optarg, "xattr") == 0)
1242                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
1243                         else if (strcasecmp(optarg, "windows") == 0)
1244                                 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
1245                         else {
1246                                 imagex_error("Unknown stream interface \"%s\"", optarg);
1247                                 goto mount_usage;
1248                         }
1249                         break;
1250                 case 'r':
1251                         swm_glob = optarg;
1252                         break;
1253                 default:
1254                         goto mount_usage;
1255                 }
1256         }
1257         argc -= optind;
1258         argv += optind;
1259         if (argc != 2 && argc != 3)
1260                 goto mount_usage;
1261
1262         wimfile = argv[0];
1263
1264         ret = wimlib_open_wim(wimfile, open_flags, &w);
1265         if (ret != 0)
1266                 return ret;
1267
1268         if (swm_glob) {
1269                 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
1270                                           &additional_swms,
1271                                           &num_additional_swms);
1272                 if (ret != 0)
1273                         goto out;
1274         }
1275
1276         if (argc == 2) {
1277                 image = 1;
1278                 num_images = wimlib_get_num_images(w);
1279                 if (num_images != 1) {
1280                         imagex_error("The file `%s' contains %d images; Please "
1281                                      "select one.", wimfile, num_images);
1282                         usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
1283                                         ? MOUNTRW : MOUNT);
1284                         ret = -1;
1285                         goto out;
1286                 }
1287                 dir = argv[1];
1288         } else {
1289                 image = wimlib_resolve_image(w, argv[1]);
1290                 dir = argv[2];
1291                 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
1292                 if (ret != 0)
1293                         goto out;
1294         }
1295
1296         if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1297                 ret = file_writable(wimfile);
1298                 if (ret != 0)
1299                         return ret;
1300         }
1301
1302         ret = wimlib_mount(w, image, dir, mount_flags, additional_swms,
1303                            num_additional_swms);
1304         if (ret != 0) {
1305                 imagex_error("Failed to mount image %d from `%s' on `%s'",
1306                              image, wimfile, dir);
1307
1308         }
1309 out:
1310         wimlib_free(w);
1311         if (additional_swms) {
1312                 for (unsigned i = 0; i < num_additional_swms; i++)
1313                         wimlib_free(additional_swms[i]);
1314                 free(additional_swms);
1315         }
1316         return ret;
1317 mount_usage:
1318         usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
1319                         ? MOUNTRW : MOUNT);
1320         return -1;
1321 }
1322
1323 /* Split a WIM into a spanned set */
1324 static int imagex_split(int argc, const char **argv)
1325 {
1326         int c;
1327         int flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
1328         unsigned long part_size;
1329         char *tmp;
1330
1331         for_opt(c, split_options) {
1332                 switch (c) {
1333                 case 'c':
1334                         flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1335                         break;
1336                 default:
1337                         usage(SPLIT);
1338                         return -1;
1339                 }
1340         }
1341         argc -= optind;
1342         argv += optind;
1343
1344         if (argc != 3) {
1345                 usage(SPLIT);
1346                 return -1;
1347         }
1348         part_size = strtod(argv[2], &tmp) * (1 << 20);
1349         if (tmp == argv[2] || *tmp) {
1350                 imagex_error("Invalid part size \"%s\"", argv[2]);
1351                 imagex_error("The part size must be an integer or floating-point number of megabytes.");
1352                 return -1;
1353         }
1354         return wimlib_split(argv[0], argv[1], part_size, flags);
1355 }
1356
1357 /* Unmounts an image. */
1358 static int imagex_unmount(int argc, const char **argv)
1359 {
1360         int c;
1361         int unmount_flags = 0;
1362         int ret;
1363
1364         for_opt(c, unmount_options) {
1365                 switch (c) {
1366                 case 'c':
1367                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
1368                         break;
1369                 case 'C':
1370                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
1371                         break;
1372                 default:
1373                         usage(UNMOUNT);
1374                         return -1;
1375                 }
1376         }
1377         argc -= optind;
1378         argv += optind;
1379         if (argc != 1) {
1380                 usage(UNMOUNT);
1381                 return -1;
1382         }
1383
1384         ret = wimlib_unmount(argv[0], unmount_flags);
1385         if (ret != 0)
1386                 imagex_error("Failed to unmount `%s'", argv[0]);
1387         return ret;
1388 }
1389
1390 struct imagex_command {
1391         const char *name;
1392         int (*func)(int , const char **);
1393         int cmd;
1394 };
1395
1396
1397 #define for_imagex_command(p) for (p = &imagex_commands[0]; \
1398                 p != &imagex_commands[ARRAY_LEN(imagex_commands)]; p++)
1399
1400 static const struct imagex_command imagex_commands[] = {
1401         {"append",  imagex_capture_or_append, APPEND},
1402         {"apply",   imagex_apply,             APPLY},
1403         {"capture", imagex_capture_or_append, CAPTURE},
1404         {"delete",  imagex_delete,            DELETE},
1405         {"dir",     imagex_dir,               DIR},
1406         {"export",  imagex_export,            EXPORT},
1407         {"info",    imagex_info,              INFO},
1408         {"join",    imagex_join,              JOIN},
1409         {"mount",   imagex_mount_rw_or_ro,    MOUNT},
1410         {"mountrw", imagex_mount_rw_or_ro,    MOUNTRW},
1411         {"split",   imagex_split,             SPLIT},
1412         {"unmount", imagex_unmount,           UNMOUNT},
1413 };
1414
1415 static void version()
1416 {
1417         static const char *s =
1418         "imagex (" PACKAGE ") " PACKAGE_VERSION "\n"
1419         "Copyright (C) 2012 Eric Biggers\n"
1420         "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
1421         "This is free software: you are free to change and redistribute it.\n"
1422         "There is NO WARRANTY, to the extent permitted by law.\n"
1423         "\n"
1424         "Report bugs to "PACKAGE_BUGREPORT".\n";
1425         fputs(s, stdout);
1426 }
1427
1428
1429 static void help_or_version(int argc, const char **argv)
1430 {
1431         int i;
1432         const char *p;
1433         const struct imagex_command *cmd;
1434
1435         for (i = 1; i < argc; i++) {
1436                 p = argv[i];
1437                 if (*p == '-')
1438                         p++;
1439                 else
1440                         continue;
1441                 if (*p == '-')
1442                         p++;
1443                 if (strcmp(p, "help") == 0) {
1444                         for_imagex_command(cmd) {
1445                                 if (strcmp(cmd->name, argv[1]) == 0) {
1446                                         usage(cmd->cmd);
1447                                         exit(0);
1448                                 }
1449                         }
1450                         usage_all();
1451                         exit(0);
1452                 }
1453                 if (strcmp(p, "version") == 0) {
1454                         version();
1455                         exit(0);
1456                 }
1457         }
1458 }
1459
1460
1461 static void usage(int cmd_type)
1462 {
1463         const struct imagex_command *cmd;
1464         printf("Usage: %s", usage_strings[cmd_type]);
1465         for_imagex_command(cmd) {
1466                 if (cmd->cmd == cmd_type)
1467                         printf("\nTry `man imagex-%s' for more details.\n",
1468                                cmd->name);
1469         }
1470 }
1471
1472 static void usage_all()
1473 {
1474         puts("IMAGEX: Usage:");
1475         for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
1476                 printf("    %s", usage_strings[i]);
1477         static const char *extra =
1478 "    imagex --help\n"
1479 "    imagex --version\n"
1480 "\n"
1481 "    The compression TYPE may be \"maximum\", \"fast\", or \"none\".\n"
1482 "\n"
1483 "    Try `man imagex' for more information.\n"
1484         ;
1485         fputs(extra, stdout);
1486 }
1487
1488
1489 int main(int argc, const char **argv)
1490 {
1491         const struct imagex_command *cmd;
1492         int ret;
1493
1494         if (argc < 2) {
1495                 imagex_error("No command specified");
1496                 usage_all();
1497                 return 1;
1498         }
1499
1500         help_or_version(argc, argv);
1501         argc--;
1502         argv++;
1503
1504         wimlib_set_print_errors(true);
1505
1506         for_imagex_command(cmd) {
1507                 if (strcmp(cmd->name, *argv) == 0) {
1508                         ret = cmd->func(argc, argv);
1509                         if (ret > 0) {
1510                                 imagex_error("Exiting with error code %d:\n"
1511                                              "       %s.", ret,
1512                                              wimlib_get_error_string(ret));
1513                                 if (ret == WIMLIB_ERR_NTFS_3G)
1514                                         imagex_error_with_errno("errno");
1515                         }
1516                         return ret;
1517                 }
1518         }
1519
1520         imagex_error("Unrecognized command: `%s'", argv[0]);
1521         usage_all();
1522         return 1;
1523 }