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