4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013 Eric Biggers
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.
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.
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/>.
26 #include "wimlib_tchar.h"
47 # include "imagex-win32.h"
48 # define tbasename win32_wbasename
49 # define tglob win32_wglob
53 # include <langinfo.h>
54 # define tbasename basename
59 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
61 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
80 static void usage(int cmd_type);
81 static void usage_all();
84 static const tchar *usage_strings[] = {
87 IMAGEX_PROGNAME" append (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
88 " [DESCRIPTION] [--boot] [--check] [--flags EDITION_ID]\n"
89 " [--verbose] [--dereference] [--config=FILE]\n"
90 " [--threads=NUM_THREADS] [--rebuild] [--unix-data]\n"
91 " [--source-list] [--no-acls] [--strict-acls]\n"
95 IMAGEX_PROGNAME" apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n"
96 " (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n"
97 " [--symlink] [--verbose] [--ref=\"GLOB\"] [--unix-data]\n"
98 " [--no-acls] [--strict-acls]\n"
102 IMAGEX_PROGNAME" capture (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
103 " [DESCRIPTION] [--boot] [--check] [--compress=TYPE]\n"
104 " [--flags EDITION_ID] [--verbose] [--dereference]\n"
105 " [--config=FILE] [--threads=NUM_THREADS] [--unix-data]\n"
106 " [--source-list] [--no-acls] [--strict-acls]\n"
110 IMAGEX_PROGNAME" delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check] [--soft]\n"
114 IMAGEX_PROGNAME" dir WIMFILE (IMAGE_NUM | IMAGE_NAME | all)\n"
118 IMAGEX_PROGNAME" export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
119 " DEST_WIMFILE [DEST_IMAGE_NAME] [DEST_IMAGE_DESCRIPTION]\n"
120 " [--boot] [--check] [--compress=TYPE] [--ref=\"GLOB\"]\n"
121 " [--threads=NUM_THREADS] [--rebuild]\n"
125 IMAGEX_PROGNAME" info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
126 " [NEW_DESC] [--boot] [--check] [--header] [--lookup-table]\n"
127 " [--xml] [--extract-xml FILE] [--metadata]\n"
131 IMAGEX_PROGNAME" join [--check] WIMFILE SPLIT_WIM...\n"
135 IMAGEX_PROGNAME" mount WIMFILE (IMAGE_NUM | IMAGE_NAME) DIRECTORY\n"
136 " [--check] [--debug] [--streams-interface=INTERFACE]\n"
137 " [--ref=\"GLOB\"] [--unix-data] [--allow-other]\n"
141 IMAGEX_PROGNAME" mountrw WIMFILE [IMAGE_NUM | IMAGE_NAME] DIRECTORY\n"
142 " [--check] [--debug] [--streams-interface=INTERFACE]\n"
143 " [--staging-dir=DIR] [--unix-data] [--allow-other]\n"
147 IMAGEX_PROGNAME" optimize WIMFILE [--check] [--recompress]\n"
151 IMAGEX_PROGNAME" split WIMFILE SPLIT_WIMFILE PART_SIZE_MB [--check]\n"
155 IMAGEX_PROGNAME" unmount DIRECTORY [--commit] [--check] [--rebuild]\n"
159 static const struct option apply_options[] = {
160 {T("check"), no_argument, NULL, 'c'},
161 {T("hardlink"), no_argument, NULL, 'h'},
162 {T("symlink"), no_argument, NULL, 's'},
163 {T("verbose"), no_argument, NULL, 'v'},
164 {T("ref"), required_argument, NULL, 'r'},
165 {T("unix-data"), no_argument, NULL, 'U'},
166 {T("noacls"), no_argument, NULL, 'N'},
167 {T("no-acls"), no_argument, NULL, 'N'},
168 {T("strict-acls"), no_argument, NULL, 'A'},
171 static const struct option capture_or_append_options[] = {
172 {T("boot"), no_argument, NULL, 'b'},
173 {T("check"), no_argument, NULL, 'c'},
174 {T("compress"), required_argument, NULL, 'x'},
175 {T("config"), required_argument, NULL, 'C'},
176 {T("dereference"), no_argument, NULL, 'L'},
177 {T("flags"), required_argument, NULL, 'f'},
178 {T("verbose"), no_argument, NULL, 'v'},
179 {T("threads"), required_argument, NULL, 't'},
180 {T("rebuild"), no_argument, NULL, 'R'},
181 {T("unix-data"), no_argument, NULL, 'U'},
182 {T("source-list"), no_argument, NULL, 'S'},
183 {T("noacls"), no_argument, NULL, 'N'},
184 {T("no-acls"), no_argument, NULL, 'N'},
185 {T("strict-acls"), no_argument, NULL, 'A'},
188 static const struct option delete_options[] = {
189 {T("check"), no_argument, NULL, 'c'},
190 {T("soft"), no_argument, NULL, 's'},
194 static const struct option export_options[] = {
195 {T("boot"), no_argument, NULL, 'b'},
196 {T("check"), no_argument, NULL, 'c'},
197 {T("compress"), required_argument, NULL, 'x'},
198 {T("ref"), required_argument, NULL, 'r'},
199 {T("threads"), required_argument, NULL, 't'},
200 {T("rebuild"), no_argument, NULL, 'R'},
204 static const struct option info_options[] = {
205 {T("boot"), no_argument, NULL, 'b'},
206 {T("check"), no_argument, NULL, 'c'},
207 {T("extract-xml"), required_argument, NULL, 'X'},
208 {T("header"), no_argument, NULL, 'h'},
209 {T("lookup-table"), no_argument, NULL, 'l'},
210 {T("metadata"), no_argument, NULL, 'm'},
211 {T("xml"), no_argument, NULL, 'x'},
215 static const struct option join_options[] = {
216 {T("check"), no_argument, NULL, 'c'},
220 static const struct option mount_options[] = {
221 {T("check"), no_argument, NULL, 'c'},
222 {T("debug"), no_argument, NULL, 'd'},
223 {T("streams-interface"), required_argument, NULL, 's'},
224 {T("ref"), required_argument, NULL, 'r'},
225 {T("staging-dir"), required_argument, NULL, 'D'},
226 {T("unix-data"), no_argument, NULL, 'U'},
227 {T("allow-other"), no_argument, NULL, 'A'},
231 static const struct option optimize_options[] = {
232 {T("check"), no_argument, NULL, 'c'},
233 {T("recompress"), no_argument, NULL, 'r'},
237 static const struct option split_options[] = {
238 {T("check"), no_argument, NULL, 'c'},
242 static const struct option unmount_options[] = {
243 {T("commit"), no_argument, NULL, 'c'},
244 {T("check"), no_argument, NULL, 'C'},
245 {T("rebuild"), no_argument, NULL, 'R'},
251 /* Print formatted error message to stderr. */
253 imagex_error(const tchar *format, ...)
256 va_start(va, format);
257 tfputs(T("ERROR: "), stderr);
258 tvfprintf(stderr, format, va);
259 tputc(T('\n'), stderr);
263 /* Print formatted error message to stderr. */
265 imagex_error_with_errno(const tchar *format, ...)
267 int errno_save = errno;
269 va_start(va, format);
270 tfputs(T("ERROR: "), stderr);
271 tvfprintf(stderr, format, va);
272 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
277 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
279 if (image == WIMLIB_NO_IMAGE) {
280 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
281 " Please specify a 1-based image index or "
283 " You may use `"IMAGEX_PROGNAME" info' to list the images "
284 "contained in a WIM."),
285 image_name, wim_name);
292 verify_image_is_single(int image)
294 if (image == WIMLIB_ALL_IMAGES) {
295 imagex_error(T("Cannot specify all images for this action!"));
302 verify_image_exists_and_is_single(int image, const tchar *image_name,
303 const tchar *wim_name)
306 ret = verify_image_exists(image, image_name, wim_name);
308 ret = verify_image_is_single(image);
312 /* Parse the argument to --compress */
314 get_compression_type(const tchar *optarg)
316 if (tstrcasecmp(optarg, T("maximum")) == 0 || tstrcasecmp(optarg, T("lzx")) == 0)
317 return WIMLIB_COMPRESSION_TYPE_LZX;
318 else if (tstrcasecmp(optarg, T("fast")) == 0 || tstrcasecmp(optarg, T("xpress")) == 0)
319 return WIMLIB_COMPRESSION_TYPE_XPRESS;
320 else if (tstrcasecmp(optarg, T("none")) == 0)
321 return WIMLIB_COMPRESSION_TYPE_NONE;
323 imagex_error(T("Invalid compression type \"%"TS"\"! Must be "
324 "\"maximum\", \"fast\", or \"none\"."), optarg);
325 return WIMLIB_COMPRESSION_TYPE_INVALID;
329 /* Returns the size of a file given its name, or -1 if the file does not exist
330 * or its size cannot be determined. */
332 file_get_size(const tchar *filename)
335 if (tstat(filename, &st) == 0)
341 static const tchar *default_capture_config =
347 "\\System Volume Information\n"
351 "[CompressionExclusionList]\n"
355 "\\WINDOWS\\inf\\*.pnf\n"
359 PARSE_FILENAME_SUCCESS = 0,
360 PARSE_FILENAME_FAILURE = 1,
361 PARSE_FILENAME_NONE = 2,
365 * Parses a filename in the source list file format. (See the man page for
366 * 'wimlib-imagex capture' for details on this format and the meaning.)
367 * Accepted formats for filenames are an unquoted string (whitespace-delimited),
368 * or a double or single-quoted string.
370 * @line_p: Pointer to the pointer to the line of data. Will be updated
371 * to point past the filename iff the return value is
372 * PARSE_FILENAME_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
375 * @len_p: @len_p initially stores the length of the line of data, which may
376 * be 0, and it will be updated to the number of bytes remaining in
377 * the line iff the return value is PARSE_FILENAME_SUCCESS.
379 * @fn_ret: Iff the return value is PARSE_FILENAME_SUCCESS, a pointer to the
380 * parsed filename will be returned here.
382 * Returns: PARSE_FILENAME_SUCCESS if a filename was successfully parsed; or
383 * PARSE_FILENAME_FAILURE if the data was invalid due to a missing
384 * closing quote; or PARSE_FILENAME_NONE if the line ended before the
385 * beginning of a filename was found.
388 parse_filename(tchar **line_p, size_t *len_p, tchar **fn_ret)
391 tchar *line = *line_p;
395 /* Skip leading whitespace */
398 return PARSE_FILENAME_NONE;
399 if (!istspace(*line) && *line != T('\0'))
405 if (quote_char == T('"') || quote_char == T('\'')) {
406 /* Quoted filename */
410 line = tmemchr(line, quote_char, len);
412 imagex_error(T("Missing closing quote: %"TS), fn - 1);
413 return PARSE_FILENAME_FAILURE;
416 /* Unquoted filename. Go until whitespace. Line is terminated
417 * by '\0', so no need to check 'len'. */
421 } while (!istspace(*line) && *line != T('\0'));
428 return PARSE_FILENAME_SUCCESS;
431 /* Parses a line of data (not an empty line or comment) in the source list file
432 * format. (See the man page for 'wimlib-imagex capture' for details on this
433 * format and the meaning.)
435 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
436 * len == 0. The data in @line will be modified by this function call.
438 * @len: Length of the line of data.
440 * @source: On success, the capture source and target described by the line is
441 * written into this destination. Note that it will contain pointers
442 * to data in the @line array.
444 * Returns true if the line was valid; false otherwise. */
446 parse_source_list_line(tchar *line, size_t len,
447 struct wimlib_capture_source *source)
451 ret = parse_filename(&line, &len, &source->fs_source_path);
452 if (ret != PARSE_FILENAME_SUCCESS)
454 ret = parse_filename(&line, &len, &source->wim_target_path);
455 if (ret == PARSE_FILENAME_NONE)
456 source->wim_target_path = source->fs_source_path;
457 return ret != PARSE_FILENAME_FAILURE;
460 /* Returns %true if the given line of length @len > 0 is a comment or empty line
461 * in the source list file format. */
463 is_comment_line(const tchar *line, size_t len)
468 if (!istspace(*line) && *line != T('\0'))
477 /* Parses a file in the source list format. (See the man page for
478 * 'wimlib-imagex capture' for details on this format and the meaning.)
480 * @source_list_contents: Contents of the source list file. Note that this
481 * buffer will be modified to save memory allocations,
482 * and cannot be freed until the returned array of
483 * wimlib_capture_source's has also been freed.
485 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
488 * @nsources_ret: On success, the length of the returned array is
491 * Returns: An array of `struct wimlib_capture_source's that can be passed to
492 * the wimlib_add_image_multisource() function to specify how a WIM image is to
494 static struct wimlib_capture_source *
495 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
496 size_t *nsources_ret)
500 struct wimlib_capture_source *sources;
502 tchar *source_list_contents = *source_list_contents_p;
505 for (i = 0; i < source_list_nchars; i++)
506 if (source_list_contents[i] == T('\n'))
509 /* Handle last line not terminated by a newline */
510 if (source_list_nchars != 0 &&
511 source_list_contents[source_list_nchars - 1] != T('\n'))
513 source_list_contents = realloc(source_list_contents,
514 (source_list_nchars + 1) * sizeof(tchar));
515 if (!source_list_contents)
517 source_list_contents[source_list_nchars] = T('\n');
518 *source_list_contents_p = source_list_contents;
519 source_list_nchars++;
523 sources = calloc(nlines, sizeof(*sources));
526 p = source_list_contents;
528 for (i = 0; i < nlines; i++) {
529 /* XXX: Could use rawmemchr() here instead, but it may not be
530 * available on all platforms. */
531 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
532 size_t len = endp - p + 1;
534 if (!is_comment_line(p, len)) {
535 if (!parse_source_list_line(p, len, &sources[j++])) {
546 imagex_error(T("out of memory"));
550 /* Reads the contents of a file into memory. */
552 file_get_contents(const tchar *filename, size_t *len_ret)
559 if (tstat(filename, &stbuf) != 0) {
560 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
565 fp = tfopen(filename, T("rb"));
567 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
573 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
574 "contents of file \"%"TS"\""), len, filename);
577 if (fread(buf, 1, len, fp) != len) {
578 imagex_error_with_errno(T("Failed to read %zu bytes from the "
579 "file \"%"TS"\""), len, filename);
593 /* Read standard input until EOF and return the full contents in a malloc()ed
594 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
597 stdin_get_contents(size_t *len_ret)
599 /* stdin can, of course, be a pipe or other non-seekable file, so the
600 * total length of the data cannot be pre-determined */
602 size_t newlen = 1024;
606 char *p = realloc(buf, newlen);
607 size_t bytes_read, bytes_to_read;
609 imagex_error(T("out of memory while reading stdin"));
613 bytes_to_read = newlen - pos;
614 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
616 if (bytes_read != bytes_to_read) {
621 imagex_error_with_errno(T("error reading stdin"));
635 translate_text_to_tstr(char **text_p, size_t num_bytes,
636 size_t *num_tchars_ret)
639 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
641 *num_tchars_ret = num_bytes;
643 #else /* !__WIN32__ */
644 /* On Windows, translate the text to UTF-16LE */
645 const char *text_bytestr = *text_p;
649 if (num_bytes >= 2 &&
650 ((text_bytestr[0] == 0xff && text_bytestr[1] == 0xfe) ||
651 (text_bytestr[0] <= 0x7f && text_bytestr[1] == 0x00)))
653 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
654 * with something that looks like an ASCII character encoded as
655 * a UTF-16LE code unit. Assume the file is encoded as
656 * UTF-16LE. This is not a 100% reliable check. */
657 num_wchars = num_bytes / 2;
658 text_wstr = (wchar_t*)text_bytestr;
660 /* File does not look like UTF-16LE. Assume it is encoded in
661 * the current Windows code page. I think these are always
662 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
663 * should work as expected. */
664 text_wstr = win32_mbs_to_wcs(text_bytestr,
668 *num_tchars_ret = num_wchars;
670 #endif /* __WIN32__ */
674 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
679 contents = file_get_contents(filename, &num_bytes);
682 return translate_text_to_tstr(&contents, num_bytes, num_tchars_ret);
686 stdin_get_text_contents(size_t *num_tchars_ret)
691 contents = stdin_get_contents(&num_bytes);
694 return translate_text_to_tstr(&contents, num_bytes, num_tchars_ret);
697 /* Return 0 if a path names a file to which the current user has write access;
698 * -1 otherwise (and print an error message). */
700 file_writable(const tchar *path)
703 ret = taccess(path, W_OK);
705 imagex_error_with_errno(T("Can't modify \"%"TS"\""), path);
709 #define TO_PERCENT(numerator, denominator) \
710 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
712 /* Given an enumerated value for WIM compression type, return a descriptive
715 get_data_type(int ctype)
718 case WIMLIB_COMPRESSION_TYPE_NONE:
719 return T("uncompressed");
720 case WIMLIB_COMPRESSION_TYPE_LZX:
721 return T("LZX-compressed");
722 case WIMLIB_COMPRESSION_TYPE_XPRESS:
723 return T("XPRESS-compressed");
728 /* Progress callback function passed to various wimlib functions. */
730 imagex_progress_func(enum wimlib_progress_msg msg,
731 const union wimlib_progress_info *info)
733 unsigned percent_done;
735 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
736 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
737 info->write_streams.total_bytes);
738 if (info->write_streams.completed_streams == 0) {
739 const tchar *data_type;
741 data_type = get_data_type(info->write_streams.compression_type);
742 tprintf(T("Writing %"TS" data using %u thread%"TS"\n"),
743 data_type, info->write_streams.num_threads,
744 (info->write_streams.num_threads == 1) ? T("") : T("s"));
746 tprintf(T("\r%"PRIu64" MiB of %"PRIu64" MiB (uncompressed) "
747 "written (%u%% done)"),
748 info->write_streams.completed_bytes >> 20,
749 info->write_streams.total_bytes >> 20,
751 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
754 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
755 tprintf(T("Scanning \"%"TS"\""), info->scan.source);
756 if (*info->scan.wim_target_path) {
757 tprintf(T(" (loading as WIM path: \"/%"TS"\")...\n"),
758 info->scan.wim_target_path);
760 tprintf(T(" (loading as root of WIM image)...\n"));
763 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
764 if (info->scan.excluded)
765 tprintf(T("Excluding \"%"TS"\" from capture\n"), info->scan.cur_path);
767 tprintf(T("Scanning \"%"TS"\"\n"), info->scan.cur_path);
769 /*case WIMLIB_PROGRESS_MSG_SCAN_END:*/
771 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
772 percent_done = TO_PERCENT(info->integrity.completed_bytes,
773 info->integrity.total_bytes);
774 tprintf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" MiB "
775 "of %"PRIu64" MiB (%u%%) done"),
776 info->integrity.filename,
777 info->integrity.completed_bytes >> 20,
778 info->integrity.total_bytes >> 20,
780 if (info->integrity.completed_bytes == info->integrity.total_bytes)
783 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
784 percent_done = TO_PERCENT(info->integrity.completed_bytes,
785 info->integrity.total_bytes);
786 tprintf(T("\rCalculating integrity table for WIM: %"PRIu64" MiB "
787 "of %"PRIu64" MiB (%u%%) done"),
788 info->integrity.completed_bytes >> 20,
789 info->integrity.total_bytes >> 20,
791 if (info->integrity.completed_bytes == info->integrity.total_bytes)
794 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
795 tprintf(T("Applying image %d (%"TS") from \"%"TS"\" "
796 "to %"TS" \"%"TS"\"\n"),
798 info->extract.image_name,
799 info->extract.wimfile_name,
800 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
801 T("NTFS volume") : T("directory")),
802 info->extract.target);
804 /*case WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN:*/
805 /*printf("Applying directory structure to %s\n",*/
806 /*info->extract.target);*/
808 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
809 percent_done = TO_PERCENT(info->extract.completed_bytes,
810 info->extract.total_bytes);
811 tprintf(T("\rExtracting files: "
812 "%"PRIu64" MiB of %"PRIu64" MiB (%u%%) done"),
813 info->extract.completed_bytes >> 20,
814 info->extract.total_bytes >> 20,
816 if (info->extract.completed_bytes >= info->extract.total_bytes)
819 case WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY:
820 tprintf(T("%"TS"\n"), info->extract.cur_path);
822 case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS:
823 tprintf(T("Setting timestamps on all extracted files...\n"));
825 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END:
826 if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
827 tprintf(T("Unmounting NTFS volume \"%"TS"\"...\n"),
828 info->extract.target);
831 case WIMLIB_PROGRESS_MSG_JOIN_STREAMS:
832 percent_done = TO_PERCENT(info->join.completed_bytes,
833 info->join.total_bytes);
834 printf("Writing resources from part %u of %u: "
835 "%"PRIu64 " MiB of %"PRIu64" MiB (%u%%) written\n",
836 (info->join.completed_parts == info->join.total_parts) ?
837 info->join.completed_parts : info->join.completed_parts + 1,
838 info->join.total_parts,
839 info->join.completed_bytes >> 20,
840 info->join.total_bytes >> 20,
843 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
844 percent_done = TO_PERCENT(info->split.completed_bytes,
845 info->split.total_bytes);
846 tprintf(T("Writing \"%"TS"\": %"PRIu64" MiB of "
847 "%"PRIu64" MiB (%u%%) written\n"),
848 info->split.part_name,
849 info->split.completed_bytes >> 20,
850 info->split.total_bytes >> 20,
853 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
854 if (info->split.completed_bytes == info->split.total_bytes) {
855 tprintf(T("Finished writing %u split WIM parts\n"),
856 info->split.cur_part_number);
866 /* Open all the split WIM parts that correspond to a file glob.
868 * @first_part specifies the first part of the split WIM and it may be either
869 * included or omitted from the glob. */
871 open_swms_from_glob(const tchar *swm_glob,
872 const tchar *first_part,
874 WIMStruct ***additional_swms_ret,
875 unsigned *num_additional_swms_ret)
877 unsigned num_additional_swms = 0;
878 WIMStruct **additional_swms = NULL;
882 /* Warning: glob() is replaced in Windows native builds */
883 ret = tglob(swm_glob, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf);
885 if (ret == GLOB_NOMATCH) {
886 imagex_error(T("Found no files for glob \"%"TS"\""),
889 imagex_error_with_errno(T("Failed to process glob \"%"TS"\""),
895 num_additional_swms = globbuf.gl_pathc;
896 additional_swms = calloc(num_additional_swms, sizeof(additional_swms[0]));
897 if (!additional_swms) {
898 imagex_error(T("Out of memory"));
903 for (unsigned i = 0; i < num_additional_swms; i++) {
904 if (tstrcmp(globbuf.gl_pathv[i], first_part) == 0) {
908 ret = wimlib_open_wim(globbuf.gl_pathv[i],
909 open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK,
910 &additional_swms[i - offset],
911 imagex_progress_func);
915 *additional_swms_ret = additional_swms;
916 *num_additional_swms_ret = num_additional_swms - offset;
920 for (unsigned i = 0; i < num_additional_swms; i++)
921 wimlib_free(additional_swms[i]);
922 free(additional_swms);
931 parse_num_threads(const tchar *optarg)
934 unsigned nthreads = tstrtoul(optarg, &tmp, 10);
935 if (nthreads == UINT_MAX || *tmp || tmp == optarg) {
936 imagex_error(T("Number of threads must be a non-negative integer!"));
944 /* Apply one image, or all images, from a WIM file into a directory, OR apply
945 * one image from a WIM file to a NTFS volume. */
947 imagex_apply(int argc, tchar **argv)
950 int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
955 const tchar *wimfile;
957 const tchar *image_num_or_name;
958 int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
960 const tchar *swm_glob = NULL;
961 WIMStruct **additional_swms = NULL;
962 unsigned num_additional_swms = 0;
964 for_opt(c, apply_options) {
967 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
970 extract_flags |= WIMLIB_EXTRACT_FLAG_HARDLINK;
973 extract_flags |= WIMLIB_EXTRACT_FLAG_SYMLINK;
976 extract_flags |= WIMLIB_EXTRACT_FLAG_VERBOSE;
982 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
985 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
988 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
997 if (argc != 2 && argc != 3) {
1004 image_num_or_name = T("1");
1007 image_num_or_name = argv[1];
1011 ret = wimlib_open_wim(wimfile, open_flags, &w, imagex_progress_func);
1015 image = wimlib_resolve_image(w, image_num_or_name);
1016 ret = verify_image_exists(image, image_num_or_name, wimfile);
1020 num_images = wimlib_get_num_images(w);
1021 if (argc == 2 && num_images != 1) {
1022 imagex_error(T("\"%"TS"\" contains %d images; Please select one "
1023 "(or all)"), wimfile, num_images);
1030 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
1032 &num_additional_swms);
1039 ret = tstat(target, &stbuf);
1041 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1042 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1044 if (errno != ENOENT) {
1045 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1053 win32_acquire_restore_privileges();
1055 ret = wimlib_extract_image(w, image, target, extract_flags,
1056 additional_swms, num_additional_swms,
1057 imagex_progress_func);
1059 tprintf(T("Done applying WIM image.\n"));
1061 win32_release_restore_privileges();
1065 if (additional_swms) {
1066 for (unsigned i = 0; i < num_additional_swms; i++)
1067 wimlib_free(additional_swms[i]);
1068 free(additional_swms);
1073 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1074 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1075 * the desired image. 'wimlib-imagex append': add a new image to an existing
1078 imagex_capture_or_append(int argc, tchar **argv)
1082 int add_image_flags = 0;
1083 int write_flags = 0;
1084 int compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1085 const tchar *wimfile;
1088 const tchar *flags_element = NULL;
1089 WIMStruct *w = NULL;
1092 int cmd = tstrcmp(argv[0], T("append")) ? CAPTURE : APPEND;
1093 unsigned num_threads = 0;
1096 size_t source_name_len;
1099 const tchar *config_file = NULL;
1100 tchar *config_str = NULL;
1103 bool source_list = false;
1104 size_t source_list_nchars;
1105 tchar *source_list_contents = NULL;
1106 bool capture_sources_malloced = false;
1107 struct wimlib_capture_source *capture_sources;
1110 for_opt(c, capture_or_append_options) {
1113 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
1116 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1117 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1120 config_file = optarg;
1123 compression_type = get_compression_type(optarg);
1124 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1128 flags_element = optarg;
1131 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1134 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1137 num_threads = parse_num_threads(optarg);
1138 if (num_threads == UINT_MAX)
1142 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1145 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1151 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1154 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1164 if (argc < 2 || argc > 4) {
1175 /* Set default name to SOURCE argument, omitting any directory
1176 * prefixes and trailing slashes. This requires making a copy
1178 source_name_len = tstrlen(source);
1179 source_copy = alloca((source_name_len + 1) * sizeof(tchar));
1180 name = tbasename(tstrcpy(source_copy, source));
1182 /* Image description defaults to NULL if not given. */
1183 desc = (argc >= 4) ? argv[3] : NULL;
1186 /* Set up capture sources in source list mode */
1187 if (source[0] == T('-') && source[1] == T('\0')) {
1188 source_list_contents = stdin_get_text_contents(&source_list_nchars);
1190 source_list_contents = file_get_text_contents(source,
1191 &source_list_nchars);
1193 if (!source_list_contents)
1196 capture_sources = parse_source_list(&source_list_contents,
1199 if (!capture_sources) {
1203 capture_sources_malloced = true;
1205 /* Set up capture source in non-source-list mode (could be
1206 * either "normal" mode or "NTFS mode"--- see the man page). */
1207 capture_sources = alloca(sizeof(struct wimlib_capture_source));
1208 capture_sources[0].fs_source_path = source;
1209 capture_sources[0].wim_target_path = NULL;
1210 capture_sources[0].reserved = 0;
1215 config_str = file_get_text_contents(config_file, &config_len);
1223 ret = wimlib_open_wim(wimfile, open_flags, &w,
1224 imagex_progress_func);
1226 ret = wimlib_create_new_wim(compression_type, &w);
1232 ret = tstat(source, &stbuf);
1234 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
1235 tprintf(T("Capturing WIM image from NTFS "
1236 "filesystem on \"%"TS"\"\n"), source);
1237 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
1240 if (errno != ENOENT) {
1241 imagex_error_with_errno(T("Failed to stat "
1242 "\"%"TS"\""), source);
1249 win32_acquire_capture_privileges();
1252 ret = wimlib_add_image_multisource(w, capture_sources,
1254 (config_str ? config_str :
1255 default_capture_config),
1256 (config_str ? config_len :
1257 tstrlen(default_capture_config)),
1259 imagex_progress_func);
1261 goto out_release_privs;
1262 cur_image = wimlib_get_num_images(w);
1264 ret = wimlib_set_image_descripton(w, cur_image, desc);
1266 goto out_release_privs;
1268 if (flags_element) {
1269 ret = wimlib_set_image_flags(w, cur_image, flags_element);
1271 goto out_release_privs;
1273 if (cmd == APPEND) {
1274 ret = wimlib_overwrite(w, write_flags, num_threads,
1275 imagex_progress_func);
1277 ret = wimlib_write(w, wimfile, WIMLIB_ALL_IMAGES, write_flags,
1278 num_threads, imagex_progress_func);
1280 if (ret == WIMLIB_ERR_REOPEN)
1283 imagex_error(T("Failed to write the WIM file \"%"TS"\""),
1287 win32_release_capture_privileges();
1292 free(source_list_contents);
1293 if (capture_sources_malloced)
1294 free(capture_sources);
1298 /* Remove image(s) from a WIM. */
1300 imagex_delete(int argc, tchar **argv)
1304 int write_flags = 0;
1305 const tchar *wimfile;
1306 const tchar *image_num_or_name;
1311 for_opt(c, delete_options) {
1314 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1315 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1318 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
1330 imagex_error(T("Must specify a WIM file"));
1332 imagex_error(T("Must specify an image"));
1337 image_num_or_name = argv[1];
1339 ret = file_writable(wimfile);
1343 ret = wimlib_open_wim(wimfile, open_flags, &w,
1344 imagex_progress_func);
1348 image = wimlib_resolve_image(w, image_num_or_name);
1350 ret = verify_image_exists(image, image_num_or_name, wimfile);
1354 ret = wimlib_delete_image(w, image);
1356 imagex_error(T("Failed to delete image from \"%"TS"\""), wimfile);
1360 ret = wimlib_overwrite(w, write_flags, 0, imagex_progress_func);
1361 if (ret == WIMLIB_ERR_REOPEN)
1364 imagex_error(T("Failed to write the file \"%"TS"\" with image "
1365 "deleted"), wimfile);
1372 /* Print the files contained in an image(s) in a WIM file. */
1374 imagex_dir(int argc, tchar **argv)
1376 const tchar *wimfile;
1383 imagex_error(T("Must specify a WIM file"));
1388 imagex_error(T("Too many arguments"));
1394 ret = wimlib_open_wim(wimfile, WIMLIB_OPEN_FLAG_SPLIT_OK, &w,
1395 imagex_progress_func);
1400 image = wimlib_resolve_image(w, argv[2]);
1401 ret = verify_image_exists(image, argv[2], wimfile);
1405 /* Image was not specified. If the WIM only contains one image,
1406 * choose that one; otherwise, print an error. */
1407 num_images = wimlib_get_num_images(w);
1408 if (num_images != 1) {
1409 imagex_error(T("The file \"%"TS"\" contains %d images; Please "
1410 "select one."), wimfile, num_images);
1418 ret = wimlib_print_files(w, image);
1424 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
1427 imagex_export(int argc, tchar **argv)
1431 int export_flags = 0;
1432 int write_flags = 0;
1433 int compression_type = WIMLIB_COMPRESSION_TYPE_NONE;
1434 bool compression_type_specified = false;
1435 const tchar *src_wimfile;
1436 const tchar *src_image_num_or_name;
1437 const tchar *dest_wimfile;
1438 const tchar *dest_name;
1439 const tchar *dest_desc;
1440 WIMStruct *src_w = NULL;
1441 WIMStruct *dest_w = NULL;
1446 const tchar *swm_glob = NULL;
1447 WIMStruct **additional_swms = NULL;
1448 unsigned num_additional_swms = 0;
1449 unsigned num_threads = 0;
1451 for_opt(c, export_options) {
1454 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
1457 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1458 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1461 compression_type = get_compression_type(optarg);
1462 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1464 compression_type_specified = true;
1470 num_threads = parse_num_threads(optarg);
1471 if (num_threads == UINT_MAX)
1475 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1484 if (argc < 3 || argc > 5) {
1488 src_wimfile = argv[0];
1489 src_image_num_or_name = argv[1];
1490 dest_wimfile = argv[2];
1491 dest_name = (argc >= 4) ? argv[3] : NULL;
1492 dest_desc = (argc >= 5) ? argv[4] : NULL;
1493 ret = wimlib_open_wim(src_wimfile,
1494 open_flags | WIMLIB_OPEN_FLAG_SPLIT_OK, &src_w,
1495 imagex_progress_func);
1499 /* Determine if the destination is an existing file or not.
1500 * If so, we try to append the exported image(s) to it; otherwise, we
1501 * create a new WIM containing the exported image(s). */
1502 if (tstat(dest_wimfile, &stbuf) == 0) {
1506 /* Destination file exists. */
1508 if (!S_ISREG(stbuf.st_mode)) {
1509 imagex_error(T("\"%"TS"\" is not a regular file"),
1514 ret = wimlib_open_wim(dest_wimfile, open_flags, &dest_w,
1515 imagex_progress_func);
1519 ret = file_writable(dest_wimfile);
1523 dest_ctype = wimlib_get_compression_type(dest_w);
1524 if (compression_type_specified
1525 && compression_type != dest_ctype)
1527 imagex_error(T("Cannot specify a compression type that is "
1528 "not the same as that used in the "
1529 "destination WIM"));
1535 /* dest_wimfile is not an existing file, so create a new WIM. */
1536 if (!compression_type_specified)
1537 compression_type = wimlib_get_compression_type(src_w);
1538 if (errno == ENOENT) {
1539 ret = wimlib_create_new_wim(compression_type, &dest_w);
1543 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
1550 image = wimlib_resolve_image(src_w, src_image_num_or_name);
1551 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
1556 ret = open_swms_from_glob(swm_glob, src_wimfile, open_flags,
1558 &num_additional_swms);
1563 ret = wimlib_export_image(src_w, image, dest_w, dest_name, dest_desc,
1564 export_flags, additional_swms,
1565 num_additional_swms, imagex_progress_func);
1571 ret = wimlib_write(dest_w, dest_wimfile, WIMLIB_ALL_IMAGES,
1572 write_flags, num_threads,
1573 imagex_progress_func);
1575 ret = wimlib_overwrite(dest_w, write_flags, num_threads,
1576 imagex_progress_func);
1578 if (ret == WIMLIB_ERR_REOPEN)
1581 wimlib_free(dest_w);
1582 if (additional_swms) {
1583 for (unsigned i = 0; i < num_additional_swms; i++)
1584 wimlib_free(additional_swms[i]);
1585 free(additional_swms);
1590 /* Prints information about a WIM file; also can mark an image as bootable,
1591 * change the name of an image, or change the description of an image. */
1593 imagex_info(int argc, tchar **argv)
1598 bool header = false;
1599 bool lookup_table = false;
1601 bool metadata = false;
1602 bool short_header = true;
1603 const tchar *xml_out_file = NULL;
1604 const tchar *wimfile;
1605 const tchar *image_num_or_name = T("all");
1606 const tchar *new_name = NULL;
1607 const tchar *new_desc = NULL;
1612 int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1617 for_opt(c, info_options) {
1627 short_header = false;
1630 lookup_table = true;
1631 short_header = false;
1635 short_header = false;
1638 xml_out_file = optarg;
1639 short_header = false;
1643 short_header = false;
1653 if (argc == 0 || argc > 4) {
1659 image_num_or_name = argv[1];
1669 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1671 ret = wimlib_open_wim(wimfile, open_flags, &w,
1672 imagex_progress_func);
1676 part_number = wimlib_get_part_number(w, &total_parts);
1678 image = wimlib_resolve_image(w, image_num_or_name);
1679 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
1680 imagex_error(T("The image \"%"TS"\" does not exist"),
1683 imagex_error(T("If you would like to set the boot "
1684 "index to 0, specify image \"0\" with "
1685 "the --boot flag."));
1687 ret = WIMLIB_ERR_INVALID_IMAGE;
1691 num_images = wimlib_get_num_images(w);
1693 if (num_images == 0) {
1695 imagex_error(T("--boot is meaningless on a WIM with no "
1697 ret = WIMLIB_ERR_INVALID_IMAGE;
1702 if (image == WIMLIB_ALL_IMAGES && num_images > 1) {
1704 imagex_error(T("Cannot specify the --boot flag "
1705 "without specifying a specific "
1706 "image in a multi-image WIM"));
1707 ret = WIMLIB_ERR_INVALID_IMAGE;
1711 imagex_error(T("Cannot specify the NEW_NAME "
1712 "without specifying a specific "
1713 "image in a multi-image WIM"));
1714 ret = WIMLIB_ERR_INVALID_IMAGE;
1719 /* Operations that print information are separated from operations that
1720 * recreate the WIM file. */
1721 if (!new_name && !boot) {
1723 /* Read-only operations */
1725 if (image == WIMLIB_NO_IMAGE) {
1726 imagex_error(T("\"%"TS"\" is not a valid image"),
1728 ret = WIMLIB_ERR_INVALID_IMAGE;
1732 if (image == WIMLIB_ALL_IMAGES && short_header)
1733 wimlib_print_wim_information(w);
1736 wimlib_print_header(w);
1739 if (total_parts != 1) {
1740 tprintf(T("Warning: Only showing the lookup table "
1741 "for part %d of a %d-part WIM.\n"),
1742 part_number, total_parts);
1744 wimlib_print_lookup_table(w);
1748 ret = wimlib_extract_xml_data(w, stdout);
1754 fp = tfopen(xml_out_file, T("wb"));
1756 imagex_error_with_errno(T("Failed to open the "
1757 "file \"%"TS"\" for "
1763 ret = wimlib_extract_xml_data(w, fp);
1764 if (fclose(fp) != 0) {
1765 imagex_error(T("Failed to close the file "
1776 wimlib_print_available_images(w, image);
1779 ret = wimlib_print_metadata(w, image);
1785 /* Modification operations */
1786 if (total_parts != 1) {
1787 imagex_error(T("Modifying a split WIM is not supported."));
1791 if (image == WIMLIB_ALL_IMAGES)
1794 if (image == WIMLIB_NO_IMAGE && new_name) {
1795 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
1796 "when using image 0"), new_name);
1802 if (image == wimlib_get_boot_idx(w)) {
1803 tprintf(T("Image %d is already marked as "
1804 "bootable.\n"), image);
1807 tprintf(T("Marking image %d as bootable.\n"),
1809 wimlib_set_boot_idx(w, image);
1813 if (!tstrcmp(wimlib_get_image_name(w, image), new_name))
1815 tprintf(T("Image %d is already named \"%"TS"\".\n"),
1819 tprintf(T("Changing the name of image %d to "
1820 "\"%"TS"\".\n"), image, new_name);
1821 ret = wimlib_set_image_name(w, image, new_name);
1827 const tchar *old_desc;
1828 old_desc = wimlib_get_image_description(w, image);
1829 if (old_desc && !tstrcmp(old_desc, new_desc)) {
1830 tprintf(T("The description of image %d is already "
1831 "\"%"TS"\".\n"), image, new_desc);
1834 tprintf(T("Changing the description of image %d "
1835 "to \"%"TS"\".\n"), image, new_desc);
1836 ret = wimlib_set_image_descripton(w, image,
1843 /* Only call wimlib_overwrite() if something actually needs to
1845 if (boot || new_name || new_desc ||
1846 (check && !wimlib_has_integrity_table(w)))
1850 ret = file_writable(wimfile);
1855 write_flags = WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1859 ret = wimlib_overwrite(w, write_flags, 1,
1860 imagex_progress_func);
1861 if (ret == WIMLIB_ERR_REOPEN)
1864 tprintf(T("The file \"%"TS"\" was not modified because nothing "
1865 "needed to be done.\n"), wimfile);
1874 /* Join split WIMs into one part WIM */
1876 imagex_join(int argc, tchar **argv)
1879 int swm_open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1880 int wim_write_flags = 0;
1881 const tchar *output_path;
1883 for_opt(c, join_options) {
1886 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1887 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1897 imagex_error(T("Must specify one or more split WIM (.swm) "
1901 output_path = argv[0];
1902 return wimlib_join((const tchar * const *)++argv,
1907 imagex_progress_func);
1913 /* Mounts an image using a FUSE mount. */
1915 imagex_mount_rw_or_ro(int argc, tchar **argv)
1918 int mount_flags = 0;
1919 int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
1920 const tchar *wimfile;
1926 const tchar *swm_glob = NULL;
1927 WIMStruct **additional_swms = NULL;
1928 unsigned num_additional_swms = 0;
1929 const tchar *staging_dir = NULL;
1931 if (tstrcmp(argv[0], T("mountrw")) == 0)
1932 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
1934 for_opt(c, mount_options) {
1937 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
1940 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1943 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
1946 if (tstrcasecmp(optarg, T("none")) == 0)
1947 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
1948 else if (tstrcasecmp(optarg, T("xattr")) == 0)
1949 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
1950 else if (tstrcasecmp(optarg, T("windows")) == 0)
1951 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
1953 imagex_error(T("Unknown stream interface \"%"TS"\""),
1962 staging_dir = optarg;
1965 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
1973 if (argc != 2 && argc != 3)
1978 ret = wimlib_open_wim(wimfile, open_flags, &w,
1979 imagex_progress_func);
1984 ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
1986 &num_additional_swms);
1993 num_images = wimlib_get_num_images(w);
1994 if (num_images != 1) {
1995 imagex_error(T("The file \"%"TS"\" contains %d images; Please "
1996 "select one."), wimfile, num_images);
1997 usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
2004 image = wimlib_resolve_image(w, argv[1]);
2006 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
2011 if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
2012 ret = file_writable(wimfile);
2017 ret = wimlib_mount_image(w, image, dir, mount_flags, additional_swms,
2018 num_additional_swms, staging_dir);
2020 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
2022 image, wimfile, dir);
2027 if (additional_swms) {
2028 for (unsigned i = 0; i < num_additional_swms; i++)
2029 wimlib_free(additional_swms[i]);
2030 free(additional_swms);
2034 usage((mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
2039 /* Rebuild a WIM file */
2041 imagex_optimize(int argc, tchar **argv)
2045 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
2048 const tchar *wimfile;
2052 for_opt(c, optimize_options) {
2055 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2056 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2059 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2076 ret = wimlib_open_wim(wimfile, open_flags, &w,
2077 imagex_progress_func);
2081 old_size = file_get_size(argv[0]);
2082 tprintf(T("\"%"TS"\" original size: "), wimfile);
2084 tfputs(T("Unknown\n"), stdout);
2086 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
2088 ret = wimlib_overwrite(w, write_flags, 0, imagex_progress_func);
2091 new_size = file_get_size(argv[0]);
2092 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
2094 tfputs(T("Unknown\n"), stdout);
2096 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
2098 tfputs(T("Space saved: "), stdout);
2099 if (new_size != -1 && old_size != -1) {
2100 tprintf(T("%lld KiB\n"),
2101 ((long long)old_size - (long long)new_size) >> 10);
2103 tfputs(T("Unknown\n"), stdout);
2111 /* Split a WIM into a spanned set */
2113 imagex_split(int argc, tchar **argv)
2116 int open_flags = WIMLIB_OPEN_FLAG_SPLIT_OK;
2117 int write_flags = 0;
2118 unsigned long part_size;
2123 for_opt(c, split_options) {
2126 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2127 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2141 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
2142 if (tmp == argv[2] || *tmp) {
2143 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
2144 imagex_error(T("The part size must be an integer or "
2145 "floating-point number of megabytes."));
2148 ret = wimlib_open_wim(argv[0], open_flags, &w, imagex_progress_func);
2151 ret = wimlib_split(w, argv[1], part_size, write_flags, imagex_progress_func);
2156 /* Unmounts a mounted WIM image. */
2158 imagex_unmount(int argc, tchar **argv)
2161 int unmount_flags = 0;
2164 for_opt(c, unmount_options) {
2167 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
2170 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
2173 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
2187 ret = wimlib_unmount_image(argv[0], unmount_flags,
2188 imagex_progress_func);
2190 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
2194 struct imagex_command {
2196 int (*func)(int , tchar **);
2201 #define for_imagex_command(p) for (p = &imagex_commands[0]; \
2202 p != &imagex_commands[ARRAY_LEN(imagex_commands)]; p++)
2204 static const struct imagex_command imagex_commands[] = {
2205 {T("append"), imagex_capture_or_append, APPEND},
2206 {T("apply"), imagex_apply, APPLY},
2207 {T("capture"), imagex_capture_or_append, CAPTURE},
2208 {T("delete"), imagex_delete, DELETE},
2209 {T("dir"), imagex_dir, DIR},
2210 {T("export"), imagex_export, EXPORT},
2211 {T("info"), imagex_info, INFO},
2212 {T("join"), imagex_join, JOIN},
2213 {T("mount"), imagex_mount_rw_or_ro, MOUNT},
2214 {T("mountrw"), imagex_mount_rw_or_ro, MOUNTRW},
2215 {T("optimize"),imagex_optimize, OPTIMIZE},
2216 {T("split"), imagex_split, SPLIT},
2217 {T("unmount"), imagex_unmount, UNMOUNT},
2223 static const tchar *s =
2225 IMAGEX_PROGNAME " (" PACKAGE ") " PACKAGE_VERSION "\n"
2226 "Copyright (C) 2012, 2013 Eric Biggers\n"
2227 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
2228 "This is free software: you are free to change and redistribute it.\n"
2229 "There is NO WARRANTY, to the extent permitted by law.\n"
2231 "Report bugs to "PACKAGE_BUGREPORT".\n"
2238 help_or_version(int argc, tchar **argv)
2242 const struct imagex_command *cmd;
2244 for (i = 1; i < argc; i++) {
2252 if (!tstrcmp(p, T("help"))) {
2253 for_imagex_command(cmd) {
2254 if (!tstrcmp(cmd->name, argv[1])) {
2262 if (!tstrcmp(p, T("version"))) {
2273 const struct imagex_command *cmd;
2274 tprintf(T("Usage:\n%"TS), usage_strings[cmd_type]);
2275 for_imagex_command(cmd) {
2276 if (cmd->cmd == cmd_type) {
2277 tprintf(T("\nTry `man "IMAGEX_PROGNAME"-%"TS"' "
2278 "for more details.\n"), cmd->name);
2286 tfputs(T("Usage:\n"), stdout);
2287 for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
2288 tprintf(T(" %"TS), usage_strings[i]);
2289 static const tchar *extra =
2291 " "IMAGEX_PROGNAME" --help\n"
2292 " "IMAGEX_PROGNAME" --version\n"
2294 " The compression TYPE may be \"maximum\", \"fast\", or \"none\".\n"
2296 " Try `man "IMAGEX_PROGNAME"' for more information.\n"
2298 tfputs(extra, stdout);
2301 /* Entry point for wimlib's ImageX implementation. On UNIX the command
2302 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
2303 * something else), while an Windows the command arguments will be UTF-16LE
2304 * encoded 'wchar_t' strings. */
2307 wmain(int argc, wchar_t **argv, wchar_t **envp)
2309 main(int argc, char **argv)
2312 const struct imagex_command *cmd;
2316 setlocale(LC_ALL, "");
2318 char *codeset = nl_langinfo(CODESET);
2319 if (!strstr(codeset, "UTF-8") &&
2320 !strstr(codeset, "UTF8") &&
2321 !strstr(codeset, "utf-8") &&
2322 !strstr(codeset, "utf8"))
2325 "WARNING: Running "IMAGEX_PROGNAME" in a UTF-8 locale is recommended!\n"
2326 " (Maybe try: `export LANG=en_US.UTF-8'?\n", stderr);
2330 #endif /* !__WIN32__ */
2333 imagex_error(T("No command specified"));
2338 /* Handle --help and --version for all commands. Note that this will
2339 * not return if either of these arguments are present. */
2340 help_or_version(argc, argv);
2344 /* The user may like to see more informative error messages. */
2345 wimlib_set_print_errors(true);
2347 /* Do any initializations that the library needs */
2348 ret = wimlib_global_init();
2352 /* Search for the function to handle the ImageX subcommand. */
2353 for_imagex_command(cmd) {
2354 if (!tstrcmp(cmd->name, *argv)) {
2355 ret = cmd->func(argc, argv);
2356 goto out_check_write_error;
2360 imagex_error(T("Unrecognized command: `%"TS"'"), argv[0]);
2363 out_check_write_error:
2364 /* For 'wimlib-imagex info' and 'wimlib-imagex dir', data printed to
2365 * standard output is part of the program's actual behavior and not just
2366 * for informational purposes, so we should set a failure exit status if
2367 * there was a write error. */
2368 if (cmd == &imagex_commands[INFO] || cmd == &imagex_commands[DIR]) {
2369 if (ferror(stdout) || fclose(stdout)) {
2370 imagex_error_with_errno(T("output error"));
2376 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
2377 * outside of the wimlib library code. 0 indicates success. > 0
2378 * indicates a wimlib error code from which an error message can be
2381 imagex_error(T("Exiting with error code %d:\n"
2383 wimlib_get_error_string(ret));
2384 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
2385 imagex_error_with_errno(T("errno"));
2388 /* Make the library free any resources it's holding (not strictly
2389 * necessary because the process is ending anyway). */
2390 wimlib_global_cleanup();