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