2b2cf48d3d2ae0080876a72e4e379510cd27c64e
[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  * Copyright (C) 2012 Eric Biggers
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22
23 #include "wimlib.h"
24 #include "config.h"
25 #include <getopt.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/stat.h>
31
32 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
33
34 #define swap(a, b) ({ typeof(a) __a = (a); typeof(b) __b = (b); \
35                                 a = __b; b = __a; })
36
37 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (char**)argv, "", \
38                                 opts, NULL)) != -1)
39
40 enum imagex_op_type {
41         APPEND,
42         APPLY,
43         CAPTURE,
44         DELETE,
45         DIR,
46         EXPORT,
47         INFO,
48         MOUNT,
49         MOUNTRW,
50         UNMOUNT,
51 };
52
53 static const char *path_basename(const char *path)
54 {
55         const char *p = path;
56         while (*p)
57                 p++;
58         p--;
59
60         /* Trailing slashes. */
61         while ((p != path - 1) && *p == '/')
62                 p--;
63
64         while ((p != path - 1) && *p != '/')
65                 p--;
66
67         return p + 1;
68 }
69
70
71 static const char *usage_strings[] = {
72 [APPEND] = 
73 "    imagex append DIRECTORY WIMFILE [\"IMAGE_NAME\"] [\"DESCRIPTION\"] [--boot]\n"
74 "        [--check] [--config CONFIG_FILE] [--flags EDITIONID] [--verify]\n",
75 [APPLY] = 
76 "    imagex apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all] DIRECTORY [--check]\n"
77 "        [--verify] [--hardlink] [--symlink] [--verbose]\n",
78 [CAPTURE] = 
79 "    imagex capture DIRECTORY WIMFILE [\"IMAGE_NAME\"] [\"DESCRIPTION\"]\n"
80 "        [--boot] [--check] [--compress[=TYPE]] [--config CONFIG_FILE]\n"
81 "        [--flags \"EditionID\"] [--norpfix] [--verify] [--verbose]\n",
82 [DELETE] = 
83 "    imagex delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check]\n",
84 [DIR] = 
85 "    imagex dir WIMFILE (IMAGE_NUM | IMAGE_NAME | \"all\")\n",
86 [EXPORT] = 
87 "    imagex export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
88 "        DEST_WIMFILE [\"DEST_IMAGE_NAME\"] [\"DEST_IMAGE_DESCRIPTION\"]\n"
89 "        [--boot] [--check] [--compress[=TYPE]]\n",
90 [INFO] = 
91 "    imagex info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
92 "        [NEW_DESC] [--boot] [--check] [--header] [--lookup-table]\n"
93 "        [--xml] [--extract-xml FILE] [--metadata]\n",
94 [MOUNT] = 
95 "    imagex mount WIMFILE (IMAGE_NUM | IMAGE_NAME) DIRECTORY\n"
96 "        [--check] [--debug]\n",
97 [MOUNTRW] = 
98 "    imagex mountrw WIMFILE [IMAGE_NUM | IMAGE_NAME] DIRECTORY\n"
99 "        [--check] [--debug]\n",
100 [UNMOUNT] = 
101 "    imagex unmount DIRECTORY [--commit] [--check]\n",
102 };
103
104 static const struct option common_options[] = {
105         {"help", 0, NULL, 'h'},
106         {"version", 0, NULL, 'v'},
107         {NULL, 0, NULL, 0},
108 };
109
110 static const struct option append_options[] = {
111         {"boot",   no_argument,       NULL, 'b'},
112         {"check",  no_argument,       NULL, 'c'},
113         {"config", required_argument, NULL, 'C'},
114         {"flags",    required_argument, NULL, 'f'},
115         {"verify", no_argument,       NULL, 'V'},
116         {NULL, 0, NULL, 0},
117 };
118 static const struct option apply_options[] = {
119         {"check",    no_argument,       NULL, 'c'},
120         {"verify",   no_argument,       NULL, 'V'},
121         {"hardlink", no_argument,       NULL, 'h'},
122         {"symlink",  no_argument,       NULL, 's'},
123         {"verbose",  no_argument,       NULL, 'v'},
124         {NULL, 0, NULL, 0},
125 };
126 static const struct option capture_options[] = {
127         {"boot",     no_argument,       NULL, 'b'},
128         {"check",    no_argument,       NULL, 'c'},
129         {"compress", optional_argument, NULL, 'x'},
130         {"config",   required_argument, NULL, 'C'},
131         {"flags",    required_argument, NULL, 'f'},
132         {"norpfix",  no_argument,       NULL, 'n'},
133         {"verify",   no_argument,       NULL, 'V'},
134         {"verbose",   no_argument,       NULL, 'v'},
135         {NULL, 0, NULL, 0},
136 };
137 static const struct option delete_options[] = {
138         {"check", no_argument, NULL, 'c'},
139         {NULL, 0, NULL, 0},
140 };
141
142 static const struct option export_options[] = {
143         {"boot",       no_argument, NULL, 'b'},
144         {"check",      no_argument , NULL, 'c'},
145         {"compress",   optional_argument, NULL, 'x'},
146         {NULL, 0, NULL, 0},
147 };
148
149 static const struct option info_options[] = {
150         {"boot",         no_argument, NULL, 'b'},
151         {"check",        no_argument, NULL, 'c'},
152         {"header",       no_argument, NULL, 'h'},
153         {"lookup-table", no_argument, NULL, 'l'},
154         {"xml",          no_argument, NULL, 'x'},
155         {"extract-xml",  required_argument, NULL, 'X'},
156         {"metadata",     no_argument, NULL, 'm'},
157         {NULL, 0, NULL, 0},
158 };
159 static const struct option mount_options[] = {
160         {"check", no_argument, NULL, 'c'},
161         {"debug", no_argument, NULL, 'd'},
162         {NULL, 0, NULL, 0},
163 };
164
165 static const struct option unmount_options[] = {
166         {"commit", no_argument, NULL, 'c'},
167         {"check", no_argument, NULL, 'C'},
168         {NULL, 0, NULL, 0},
169 };
170
171
172 /* Print formatted error message to stderr. */
173 static void imagex_error(const char *format, ...)
174 {
175         va_list va;
176         va_start(va, format);
177         fputs("ERROR: ", stderr);
178         vfprintf(stderr, format, va);
179         va_end(va);
180 }
181
182
183 static inline void version()
184 {
185         static const char *s = 
186         "imagex (" PACKAGE ") " PACKAGE_VERSION "\n"
187         "Copyright (C) 2012 Eric Biggers\n"
188         "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
189         "This is free software: you are free to change and redistribute it.\n"
190         "There is NO WARRANTY, to the extent permitted by law.\n"
191         "\n"
192         "Report bugs to "PACKAGE_BUGREPORT".\n";
193         fputs(s, stdout);
194 }
195
196 static inline void usage(int cmd)
197 {
198         puts("IMAGEX: Usage:");
199         fputs(usage_strings[cmd], stdout);
200 }
201
202 static void usage_all()
203 {
204         puts("IMAGEX: Usage:");
205         for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
206                 fputs(usage_strings[i], stdout);
207         static const char *extra = 
208 "    imagex --help\n"
209 "    imagex --version\n"
210 "\n"
211 "    The compression TYPE may be \"maximum\", \"fast\", or \"none\".\n"
212         ;
213         fputs(extra, stdout);
214 }
215
216 static int verify_image_exists(int image)
217 {
218         if (image == WIM_NO_IMAGE) {
219                 imagex_error("Not a valid image!\n");
220                 return WIMLIB_ERR_INVALID_IMAGE;
221         }
222         return 0;
223 }
224
225 static int verify_image_is_single(int image)
226 {
227         if (image == WIM_ALL_IMAGES) {
228                 imagex_error("Cannot specify all images for this action!\n");
229                 return WIMLIB_ERR_INVALID_IMAGE;
230         }
231         return 0;
232 }
233
234 static int verify_image_exists_and_is_single(int image)
235 {
236         int ret;
237         ret = verify_image_exists(image);
238         if (ret == 0)
239                 ret = verify_image_is_single(image);
240         return ret;
241 }
242
243 static int get_compression_type(const char *optarg)
244 {
245         if (!optarg)
246                 return WIM_COMPRESSION_TYPE_LZX;
247         if (strcasecmp(optarg, "maximum") == 0 || strcasecmp(optarg, "lzx") == 0)
248                 return WIM_COMPRESSION_TYPE_LZX;
249         else if (strcasecmp(optarg, "fast") == 0 || strcasecmp(optarg, "xpress") == 0)
250                 return WIM_COMPRESSION_TYPE_XPRESS;
251         else if (strcasecmp(optarg, "none") == 0)
252                 return WIM_COMPRESSION_TYPE_NONE;
253         else {
254                 imagex_error("Invalid compression type `%s'! Must be "
255                                 "\"maximum\", \"fast\", or \"none\".\n", 
256                                 optarg);
257                 return WIM_COMPRESSION_TYPE_INVALID;
258         }
259 }
260
261 static int imagex_append(int argc, const char **argv)
262 {
263         int c;
264         const char *flags_element = NULL;
265         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
266         int add_image_flags = 0;
267         int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
268         const char *dir;
269         const char *wimfile;
270         const char *name;
271         const char *desc;
272         WIMStruct *w;
273         int ret;
274
275         for_opt(c, append_options) {
276                 switch (c) {
277                 case 'b':
278                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
279                         break;
280                 case 'c':
281                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
282                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
283                         break;
284                 case 'C':
285                         /*config = optarg;*/
286                         break;
287                 case 'f':
288                         flags_element = optarg;
289                         break;
290                 case 'V':
291                         /* verify */
292                         break;
293                 default:
294                         usage(APPEND);
295                         return -1;
296                 }
297         }
298         argc -= optind;
299         argv += optind;
300         if (argc < 2 || argc > 4) {
301                 usage(APPEND);
302                 return -1;
303         }
304         dir     = argv[0];
305         wimfile = argv[1];
306         name    = (argc >= 3) ? argv[2] : path_basename(dir);
307         desc    = (argc >= 4) ? argv[3] : NULL;
308
309         ret = wimlib_open_wim(wimfile, open_flags, &w);
310         if (ret != 0)
311                 return ret;
312
313         ret = wimlib_add_image(w, dir, name, desc, 
314                                flags_element, add_image_flags);
315         if (ret != 0)
316                 goto done;
317         ret = wimlib_overwrite(w, write_flags);
318 done:
319         wimlib_free(w);
320         return ret;
321 }
322
323 /* Extract one image, or all images, from a WIM file into a directory. */
324 static int imagex_apply(int argc, const char **argv)
325 {
326         int c;
327         int link_type = WIM_LINK_TYPE_NONE;
328         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
329         bool verbose = false;
330         int image;
331         int num_images;
332         WIMStruct *w;
333         int ret;
334         const char *wimfile;
335         const char *dir;
336         const char *image_num_or_name;
337
338         for_opt(c, apply_options) {
339                 switch (c) {
340                 case 'c':
341                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
342                         break;
343                 case 'V':
344                         /* verify */
345                         break;
346                 case 'h':
347                         link_type = WIM_LINK_TYPE_HARD;
348                         break;
349                 case 's':
350                         link_type = WIM_LINK_TYPE_SYMBOLIC;
351                         break;
352                 case 'v':
353                         verbose = true;
354                         break;
355                 default:
356                         usage(APPLY);
357                         return -1;
358                 }
359         }
360         argc -= optind;
361         argv += optind;
362         if (argc != 2 && argc != 3) {
363                 usage(APPLY);
364                 return -1;
365         }
366
367         wimfile = argv[0];
368         if (argc == 2) {
369                 image_num_or_name =  "1";
370                 dir = argv[1];
371         } else {
372                 image_num_or_name = argv[1];
373                 dir = argv[2];
374         }
375
376         ret = wimlib_open_wim(wimfile, open_flags, &w);
377         if (ret != 0)
378                 return ret;
379
380         image = wimlib_resolve_image(w, image_num_or_name);
381         ret = verify_image_exists(image);
382         if (ret != 0)
383                 goto done;
384
385         num_images = wimlib_get_num_images(w);
386         if (argc == 2 && num_images != 1) {
387                 imagex_error("`%s' contains %d images; Please select one "
388                                 "(or all).\n", wimfile, num_images);
389                 usage(APPLY);
390                 ret = -1;
391                 goto done;
392         }
393         ret = wimlib_set_output_dir(w, dir);
394         if (ret != 0)
395                 goto done;
396         wimlib_set_verbose(w, verbose);
397         wimlib_set_link_type(w, link_type);
398         ret = wimlib_extract_image(w, image);
399 done:
400         wimlib_free(w);
401         return ret;
402 }
403
404
405 /* Create a WIM file from a directory. */
406 static int imagex_capture(int argc, const char **argv)
407 {
408         int c;
409         int add_image_flags = 0;
410         int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
411         int compression_type = WIM_COMPRESSION_TYPE_NONE;
412         const char *flags_element    = NULL;
413         bool verbose         = false;
414         const char *dir;
415         const char *wimfile;
416         const char *name;
417         const char *desc;
418         WIMStruct *w;
419         int ret;
420
421         for_opt(c, capture_options) {
422                 switch (c) {
423                 case 'b':
424                         add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
425                         break;
426                 case 'c':
427                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
428                         break;
429                 case 'x':
430                         compression_type = get_compression_type(optarg);
431                         if (compression_type == WIM_COMPRESSION_TYPE_INVALID)
432                                 return -1;
433                         break;
434                 case 'C':
435                         /*config = optarg;*/
436                         break;
437                 case 'f':
438                         flags_element = optarg;
439                         break;
440                 case 'n':
441                         /*norpfix = true;*/
442                         break;
443                 case 'v':
444                         verbose = true;
445                         break;
446                 case 'V':
447                         /*verify = true;*/
448                         break;
449                 default:
450                         usage(CAPTURE);
451                         return -1;
452                 }
453         }
454
455         argc -= optind;
456         argv += optind;
457         if (argc < 2 || argc > 4) {
458                 usage(CAPTURE);
459                 return -1;
460         }
461         dir     = argv[0];
462         wimfile = argv[1];
463         name    = (argc >= 3) ? argv[2] : dir;
464         desc    = (argc >= 4) ? argv[3] : NULL;
465
466         ret = wimlib_create_new_wim(compression_type, &w);
467         if (ret != 0)
468                 return ret;
469
470         wimlib_set_verbose(w, verbose);
471
472         ret = wimlib_add_image(w, dir, name, desc, flags_element, 
473                                add_image_flags);
474         if (ret != 0) {
475                 imagex_error("Failed to add the image `%s'!\n", dir);
476                 goto done;
477         }
478
479         ret = wimlib_write(w, wimfile, WIM_ALL_IMAGES, write_flags);
480         if (ret != 0)
481                 imagex_error("Failed to write the WIM file `%s'!\n", wimfile);
482 done:
483         wimlib_free(w);
484         return ret;
485 }
486
487 /* Remove image(s) from a WIM. */
488 static int imagex_delete(int argc, const char **argv)
489 {
490         int c;
491         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
492         int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
493         const char *wimfile;
494         const char *image_num_or_name;
495         WIMStruct *w;
496         int image;
497         int ret;
498
499         for_opt(c, delete_options) {
500                 switch (c) {
501                 case 'c':
502                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
503                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
504                         break;
505                 default:
506                         usage(DELETE);
507                         return -1;
508                 }
509         }
510         argc -= optind;
511         argv += optind;
512
513         if (argc != 2) {
514                 if (argc < 1)
515                         imagex_error("Must specify a WIM file!\n");
516                 if (argc < 2)
517                         imagex_error("Must specify an image!\n");
518                 usage(DELETE);
519                 return -1;
520         }
521         wimfile = argv[0];
522         image_num_or_name = argv[1];
523
524         ret = wimlib_open_wim(wimfile, open_flags, &w);
525         if (ret != 0)
526                 return ret;
527
528         image = wimlib_resolve_image(w, image_num_or_name);
529
530         ret = verify_image_exists(image);
531         if (ret != 0)
532                 goto done;
533
534         ret = wimlib_delete_image(w, image);
535         if (ret != 0) {
536                 imagex_error("Failed to delete image from `%s'!\n",
537                                                 wimfile);
538                 goto done;
539         }
540
541         ret = wimlib_overwrite(w, write_flags);
542         if (ret != 0) {
543                 imagex_error("Failed to write the file `%s' with image "
544                                 "deleted!\n", wimfile);
545         }
546 done:
547         wimlib_free(w);
548         return ret;
549 }
550
551 /* Print the files contained in an image(s) in a WIM file. */
552 static int imagex_dir(int argc, const char **argv)
553 {
554         const char *wimfile;
555         WIMStruct *w;
556         int image;
557         int ret;
558         int num_images;
559
560         if (argc < 2) {
561                 imagex_error("Must specify a WIM file!\n");
562                 usage(DIR);
563                 return -1;
564         }
565         if (argc > 3) {
566                 imagex_error("Too many arguments!\n");
567                 usage(DIR);
568                 return -1;
569         }
570
571         wimfile = argv[1];
572         ret = wimlib_open_wim(wimfile, 0, &w);
573         if (ret != 0)
574                 return ret;
575
576         if (argc == 3) {
577                 image = wimlib_resolve_image(w, argv[2]);
578                 ret = verify_image_exists(image);
579                 if (ret != 0)
580                         goto done;
581         } else {
582                 /* Image was not specified.  If the WIM only contains one image,
583                  * choose that one; otherwise, print an error. */
584                 num_images = wimlib_get_num_images(w);
585                 if (num_images != 1) {
586                         imagex_error("The file `%s' contains %d images; Please "
587                                         "select one.\n", wimfile, num_images);
588                         usage(DIR);
589                         ret = -1;
590                         goto done;
591                 }
592                 image = 1;
593         }
594
595         ret = wimlib_print_files(w, image);
596 done:
597         wimlib_free(w);
598         return ret;
599 }
600
601 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
602  * WIM file. */
603 static int imagex_export(int argc, const char **argv)
604 {
605         int c;
606         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
607         int export_flags = 0;
608         int write_flags = WIMLIB_WRITE_FLAG_SHOW_PROGRESS;
609         int compression_type = WIM_COMPRESSION_TYPE_NONE;
610         bool compression_type_specified = false;
611         const char *src_wimfile;
612         const char *src_image_num_or_name;
613         const char *dest_wimfile;
614         const char *dest_name;
615         const char *dest_desc;
616         WIMStruct *src_w = NULL;
617         WIMStruct *dest_w = NULL;
618         int ret;
619         int image;
620         struct stat stbuf;
621         bool wim_is_new;
622
623         for_opt(c, export_options) {
624                 switch (c) {
625                 case 'b':
626                         export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
627                         break;
628                 case 'c':
629                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
630                         write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
631                         break;
632                 case 'x':
633                         compression_type = get_compression_type(optarg);
634                         if (compression_type == WIM_COMPRESSION_TYPE_INVALID)
635                                 return -1;
636                         compression_type_specified = true;
637                         break;
638                 default:
639                         usage(EXPORT);
640                         return -1;
641                 }
642         }
643         argc -= optind;
644         argv += optind;
645         if (argc < 3 || argc > 5) {
646                 usage(EXPORT);
647                 return -1;
648         }
649         src_wimfile           = argv[0];
650         src_image_num_or_name = argv[1];
651         dest_wimfile          = argv[2];
652         dest_name             = (argc >= 4) ? argv[3] : NULL;
653         dest_desc             = (argc >= 5) ? argv[4] : NULL;
654         ret = wimlib_open_wim(src_wimfile, open_flags, &src_w);
655         if (ret != 0)
656                 return ret;
657
658         /* Determine if the destination is an existing file or not.  
659          * If so, we try to append the exported image(s) to it; otherwise, we
660          * create a new WIM containing the exported image(s). */
661         if (stat(dest_wimfile, &stbuf) == 0) {
662                 wim_is_new = false;
663                 /* Destination file exists. */
664                 if (!S_ISREG(stbuf.st_mode)) {
665                         imagex_error("`%s' is not a regular file!\n",
666                                         dest_wimfile);
667                         goto done;
668                 }
669                 ret = wimlib_open_wim(dest_wimfile, open_flags, &dest_w);
670                 if (ret != 0)
671                         goto done;
672
673                 if (compression_type_specified && compression_type != 
674                                 wimlib_get_compression_type(dest_w)) {
675                         imagex_error("Cannot specify a compression type that is "
676                                         "not the same as that used in the "
677                                         "destination WIM!\n");
678                         goto done;
679                 }
680                 compression_type = wimlib_get_compression_type(dest_w);
681         } else {
682                 wim_is_new = true;
683                 /* dest_wimfile is not an existing file, so create a new WIM. */
684                 if (errno == ENOENT) {
685                         ret = wimlib_create_new_wim(compression_type, &dest_w);
686                         if (ret != 0)
687                                 goto done;
688                 } else {
689                         imagex_error("Cannot stat file `%s': %m\n",
690                                                 dest_wimfile);
691                         goto done;
692                 }
693         }
694
695         image = wimlib_resolve_image(src_w, src_image_num_or_name);
696         ret = verify_image_exists(image);
697         if (ret != 0)
698                 goto done;
699
700         ret = wimlib_export_image(src_w, image, dest_w, dest_name, dest_desc, 
701                                   export_flags);
702         if (ret != 0)
703                 goto done;
704
705
706         if (wim_is_new)
707                 ret = wimlib_write(dest_w, dest_wimfile, WIM_ALL_IMAGES, 
708                                    write_flags);
709         else
710                 ret = wimlib_overwrite(dest_w, write_flags);
711 done:
712         wimlib_free(src_w);
713         wimlib_free(dest_w);
714         return ret;
715 }
716
717 /* Prints information about a WIM file; also can mark an image as bootable,
718  * change the name of an image, or change the description of an image. */
719 static int imagex_info(int argc, const char **argv)
720 {
721         int c;
722         bool boot         = false;
723         bool check        = false;
724         bool header       = false;
725         bool lookup_table = false;
726         bool xml          = false;
727         bool metadata     = false;
728         bool short_header = true;
729         const char *xml_out_file = NULL;
730         const char *wimfile;
731         const char *image_num_or_name = "all";
732         const char *new_name = NULL;
733         const char *new_desc = NULL;
734         WIMStruct *w;
735         FILE *fp;
736         int image;
737         int ret;
738
739         for_opt(c, info_options) {
740                 switch (c) {
741                 case 'b':
742                         boot = true;
743                         break;
744                 case 'c':
745                         check = true;
746                         break;
747                 case 'h':
748                         header = true;
749                         short_header = false;
750                         break;
751                 case 'l':
752                         lookup_table = true;
753                         short_header = false;
754                         break;
755                 case 'x':
756                         xml = true;
757                         short_header = false;
758                         break;
759                 case 'X':
760                         xml_out_file = optarg;
761                         short_header = false;
762                         break;
763                 case 'm':
764                         metadata = true;
765                         short_header = false;
766                         break;
767                 default:
768                         usage(INFO);
769                         return -1;
770                 }
771         }
772
773         argc -= optind;
774         argv += optind;
775         if (argc == 0 || argc > 4) {
776                 usage(INFO);
777                 return -1;
778         }
779         wimfile = argv[0];
780         if (argc > 1) {
781                 image_num_or_name = argv[1];
782                 if (argc > 2) {
783                         new_name = argv[2];
784                         if (argc > 3) {
785                                 new_desc = argv[3];
786                         } 
787                 }
788         }
789
790         ret = wimlib_open_wim(wimfile, 
791                               check ? 
792                                 WIMLIB_OPEN_FLAG_CHECK_INTEGRITY |
793                                         WIMLIB_OPEN_FLAG_SHOW_PROGRESS
794                                 : 0, 
795                               &w);
796         if (ret != 0)
797                 return ret;
798
799         image = wimlib_resolve_image(w, image_num_or_name);
800         if (image == WIM_NO_IMAGE && strcmp(image_num_or_name, "0") != 0) {
801                 imagex_error("The image `%s' does not exist!\n", 
802                                                 image_num_or_name);
803                 if (boot)
804                         imagex_error("If you would like to set the boot "
805                                         "index to 0, specify image \"0\" with "
806                                         "the --boot flag.\n");
807                 ret = WIMLIB_ERR_INVALID_IMAGE;
808                 goto done;
809         }
810
811         if (image == WIM_ALL_IMAGES && wimlib_get_num_images(w) > 1) {
812                 if (boot) {
813                         imagex_error("Cannot specify the --boot flag "
814                                         "without specifying a specific "
815                                         "image in a multi-image WIM!\n");
816                         ret = WIMLIB_ERR_INVALID_IMAGE;
817                         goto done;
818                 }
819                 if (new_name) {
820                         imagex_error("Cannot specify the NEW_NAME "
821                                         "without specifying a specific "
822                                         "image in a multi-image WIM!\n");
823                         ret = WIMLIB_ERR_INVALID_IMAGE;
824                         goto done;
825                 }
826         }
827
828         /* Operations that print information are separated from operations that
829          * recreate the WIM file. */
830         if (!new_name && !boot) {
831
832                 if (image == WIM_NO_IMAGE) {
833                         imagex_error("`%s' is not a valid image!\n", 
834                                         image_num_or_name);
835                         ret = WIMLIB_ERR_INVALID_IMAGE;
836                         goto done;
837                 }
838
839                 if (image == WIM_ALL_IMAGES && short_header)
840                         wimlib_print_wim_information(w);
841
842                 if (header)
843                         wimlib_print_header(w);
844
845                 if (lookup_table)
846                         wimlib_print_lookup_table(w);
847
848                 if (xml) {
849                         ret = wimlib_extract_xml_data(w, stdout);
850                         if (ret != 0)
851                                 goto done;
852                 }
853
854                 if (xml_out_file) {
855                         fp = fopen(xml_out_file, "wb");
856                         if (!fp) {
857                                 imagex_error("Failed to open the file `%s' for "
858                                                 "writing: %m\n", xml_out_file);
859                                 goto done;
860                         }
861                         ret = wimlib_extract_xml_data(w, fp);
862                         if (fclose(fp) != 0) {
863                                 imagex_error("Failed to close the "
864                                                 "file `%s': %m\n",
865                                                 xml_out_file);
866                                 goto done;
867                         }
868
869                         if (ret != 0)
870                                 goto done;
871                 }
872
873                 if (short_header)
874                         wimlib_print_available_images(w, image);
875
876                 if (metadata) {
877                         ret = wimlib_print_metadata(w, image);
878                         if (ret != 0)
879                                 goto done;
880                 }
881         } else {
882                 if (image == WIM_ALL_IMAGES)
883                         image = 1;
884
885                 if (image == WIM_NO_IMAGE && new_name) {
886                         imagex_error("Cannot specify new_name (`%s') when "
887                                         "using image 0!\n");
888                         return -1;
889                 }
890
891                 if (boot) {
892                         if (image == wimlib_get_boot_idx(w)) {
893                                 printf("Image %d is already marked as "
894                                                 "bootable.\n", image);
895                                 boot = false;
896                         } else {
897                                 printf("Marking image %d as bootable.\n", 
898                                                                 image);
899                                 wimlib_set_boot_idx(w, image);
900                         }
901                 }
902                 if (new_name) {
903                         if (strcmp(wimlib_get_image_name(w, image), 
904                                                 new_name) == 0) {
905                                 printf("Image %d is already named \"%s\".\n",
906                                                 image, new_name);
907                                 new_name = NULL;
908                         } else {
909                                 printf("Changing the name of image %d to \"%s\".\n",
910                                                 image, new_name);
911                                 ret = wimlib_set_image_name(w, image, new_name);
912                                 if (ret != 0)
913                                         goto done;
914                         }
915                 }
916                 if (new_desc) {
917                         const char *old_desc;
918                         old_desc = wimlib_get_image_description(w, image);
919                         if (old_desc && strcmp(old_desc, new_desc) == 0) {
920                                 printf("The description of image %d is already "
921                                                 "\"%s\".\n", image, new_desc);
922                                 new_desc = NULL;
923                         } else {
924                                 printf("Changing the description of image %d "
925                                                 "to \"%s\".\n", image, new_desc);
926                                 ret = wimlib_set_image_descripton(w, image, 
927                                                                   new_desc);
928                                 if (ret != 0)
929                                         goto done;
930                         }
931                 }
932
933                 /* Only call wimlib_overwrite_xml_and_header() if something
934                  * actually needs to be changed. */
935                 if (boot || new_name || new_desc || 
936                                 check != wimlib_has_integrity_table(w)) {
937
938                         ret = wimlib_overwrite_xml_and_header(w, check ? 
939                                         WIMLIB_WRITE_FLAG_CHECK_INTEGRITY | 
940                                         WIMLIB_WRITE_FLAG_SHOW_PROGRESS : 0);
941                 } else {
942                         printf("The file `%s' was not modified because nothing "
943                                         "needed to be done.\n", wimfile);
944                         ret = 0;
945                 }
946         }
947
948 done:
949         wimlib_free(w);
950         return ret;
951 }
952
953 /* Mounts an image using a FUSE mount. */
954 static int imagex_mount_rw_or_ro(int argc, const char **argv)
955 {
956         bool ro;
957         int c;
958         int mount_flags = 0;
959         int open_flags = WIMLIB_OPEN_FLAG_SHOW_PROGRESS;
960         const char *wimfile;
961         const char *dir;
962         WIMStruct *w;
963         int image;
964         int num_images;
965         int ret;
966
967         if (strcmp(argv[0], "mountrw") == 0)
968                 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
969         for_opt(c, mount_options) {
970                 switch (c) {
971                 case 'c':
972                         open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
973                         break;
974                 case 'd':
975                         mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
976                         break;
977                 default:
978                         usage(ro ? MOUNT : MOUNTRW);
979                         return -1;
980                 }
981         }
982         argc -= optind;
983         argv += optind;
984         if (argc != 2 && argc != 3) {
985                 usage(ro ? MOUNT : MOUNTRW);
986                 return -1;
987         }
988
989         wimfile = argv[0];
990
991         ret = wimlib_open_wim(wimfile, open_flags, &w);
992         if (ret != 0)
993                 return ret;
994
995         if (argc == 2) {
996                 image = 1;
997                 num_images = wimlib_get_num_images(w);
998                 if (num_images != 1) {
999                         imagex_error("The file `%s' contains %d images; Please "
1000                                         "select one.\n", wimfile, num_images);
1001                         usage(ro ? MOUNT : MOUNTRW);
1002                         ret = WIMLIB_ERR_INVALID_IMAGE;
1003                         goto done;
1004                 }
1005                 dir = argv[1];
1006         } else {
1007                 image = wimlib_resolve_image(w, argv[1]);
1008                 dir = argv[2];
1009         }
1010
1011         ret = verify_image_exists_and_is_single(image);
1012         if (ret != 0)
1013                 goto done;
1014
1015         ret = wimlib_mount(w, image, dir, mount_flags);
1016         if (ret != 0) {
1017                 imagex_error("Failed to mount image %d from `%s' on `%s'!\n",
1018                                 image, wimfile, dir);
1019
1020         }
1021 done:
1022         wimlib_free(w);
1023         return ret;
1024 }
1025
1026 /* Unmounts an image. */
1027 static int imagex_unmount(int argc, const char **argv)
1028 {
1029         int c;
1030         int unmount_flags = 0;
1031         int ret;
1032
1033         for_opt(c, unmount_options) {
1034                 switch (c) {
1035                 case 'c':
1036                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
1037                         break;
1038                 case 'C':
1039                         unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
1040                         break;
1041                 default:
1042                         usage(UNMOUNT);
1043                         return -1;
1044                 }
1045         }
1046         argc -= optind;
1047         argv += optind;
1048         if (argc != 1) {
1049                 usage(UNMOUNT);
1050                 return -1;
1051         }
1052
1053         ret = wimlib_unmount(argv[0], unmount_flags);
1054         if (ret != 0)
1055                 imagex_error("Failed to unmount `%s'!\n", argv[0]);
1056         return ret;
1057 }
1058
1059 struct imagex_command {
1060         const char *name;
1061         int (*func)(int , const char **);
1062         int cmd;
1063 };
1064
1065 static struct imagex_command imagex_commands[] = {
1066         {"append",  imagex_append,         APPEND},
1067         {"apply",   imagex_apply,          APPLY},
1068         {"capture", imagex_capture,        CAPTURE},
1069         {"delete",  imagex_delete,         DELETE},
1070         {"dir",     imagex_dir,            DIR},
1071         {"export",  imagex_export,         EXPORT},
1072         {"info",    imagex_info,           INFO},
1073         {"mount",   imagex_mount_rw_or_ro, MOUNT},
1074         {"mountrw", imagex_mount_rw_or_ro, MOUNTRW},
1075         {"unmount", imagex_unmount,        UNMOUNT},
1076 };
1077
1078 #define for_imagex_command(p) for (p = &imagex_commands[0]; \
1079                 p != &imagex_commands[ARRAY_LEN(imagex_commands)]; p++)
1080
1081 static void help_or_version(int argc, const char **argv)
1082 {
1083         int i;
1084         const char *p;
1085         struct imagex_command *cmd;
1086
1087         for (i = 1; i < argc; i++) {
1088                 p = argv[i];
1089                 if (*p == '-')
1090                         p++;
1091                 else
1092                         continue;
1093                 if (*p == '-')
1094                         p++;
1095                 if (strcmp(p, "help") == 0 || (*p == '?' && *(p + 1) == '\0')) {
1096                         for_imagex_command(cmd) {
1097                                 if (strcmp(cmd->name, argv[1]) == 0) {
1098                                         usage(cmd->cmd);
1099                                         exit(0);
1100                                 }
1101                         }
1102                         usage_all();
1103                         exit(0);
1104                 }
1105                 if (strcmp(p, "version") == 0) {
1106                         version();
1107                         exit(0);
1108                 }
1109         }
1110 }
1111
1112
1113 int main(int argc, const char **argv)
1114 {
1115         struct imagex_command *cmd;
1116         int ret;
1117
1118         if (argc < 2) {
1119                 imagex_error("No command specified!\n");
1120                 usage_all();
1121                 return 1;
1122         }
1123
1124         help_or_version(argc, argv);
1125         argc--;
1126         argv++;
1127
1128         wimlib_set_print_errors(true);
1129
1130         for_imagex_command(cmd) {
1131                 if (strcmp(cmd->name, *argv) == 0) {
1132                         ret = cmd->func(argc, argv);
1133                         if (ret > 0) {
1134                                 imagex_error("Exiting with error code %d:\n"
1135                                                 "       %s.\n", ret, 
1136                                                 wimlib_get_error_string(ret));
1137                         }
1138                         return ret;
1139                 }
1140         }
1141
1142         imagex_error("Unrecognized command: `%s'\n", argv[0]);
1143         usage_all();
1144         return 1;
1145 }