4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014, 2015 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 "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
147 IMAGEX_CHUNK_SIZE_OPTION,
148 IMAGEX_COMMAND_OPTION,
149 IMAGEX_COMMIT_OPTION,
150 IMAGEX_COMPACT_OPTION,
151 IMAGEX_COMPRESS_OPTION,
152 IMAGEX_COMPRESS_SLOW_OPTION,
153 IMAGEX_CONFIG_OPTION,
155 IMAGEX_DELTA_FROM_OPTION,
156 IMAGEX_DEREFERENCE_OPTION,
157 IMAGEX_DEST_DIR_OPTION,
158 IMAGEX_DETAILED_OPTION,
159 IMAGEX_EXTRACT_XML_OPTION,
162 IMAGEX_HEADER_OPTION,
163 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
165 IMAGEX_METADATA_OPTION,
166 IMAGEX_NEW_IMAGE_OPTION,
167 IMAGEX_NOCHECK_OPTION,
168 IMAGEX_NORPFIX_OPTION,
169 IMAGEX_NOT_PIPABLE_OPTION,
170 IMAGEX_NO_ACLS_OPTION,
171 IMAGEX_NO_ATTRIBUTES_OPTION,
172 IMAGEX_NO_GLOBS_OPTION,
173 IMAGEX_NO_REPLACE_OPTION,
174 IMAGEX_NO_SOLID_SORT_OPTION,
175 IMAGEX_NULLGLOB_OPTION,
176 IMAGEX_ONE_FILE_ONLY_OPTION,
178 IMAGEX_PIPABLE_OPTION,
179 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
180 IMAGEX_REBUILD_OPTION,
181 IMAGEX_RECOMPRESS_OPTION,
182 IMAGEX_RECURSIVE_OPTION,
184 IMAGEX_RESUME_OPTION,
187 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
188 IMAGEX_SOLID_COMPRESS_OPTION,
190 IMAGEX_SOURCE_LIST_OPTION,
191 IMAGEX_STAGING_DIR_OPTION,
192 IMAGEX_STREAMS_INTERFACE_OPTION,
193 IMAGEX_STRICT_ACLS_OPTION,
194 IMAGEX_THREADS_OPTION,
195 IMAGEX_TO_STDOUT_OPTION,
196 IMAGEX_UNIX_DATA_OPTION,
197 IMAGEX_UPDATE_OF_OPTION,
198 IMAGEX_VERBOSE_OPTION,
199 IMAGEX_WIMBOOT_CONFIG_OPTION,
200 IMAGEX_WIMBOOT_OPTION,
204 static const struct option apply_options[] = {
205 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
206 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
207 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
208 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
209 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
210 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
211 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
212 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
213 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
214 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
215 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
217 /* --resume is undocumented for now as it needs improvement. */
218 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
219 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
220 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
224 static const struct option capture_or_append_options[] = {
225 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
226 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
227 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
228 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
229 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
230 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
231 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
232 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
233 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
234 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
235 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
236 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
237 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
238 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
239 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
240 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
241 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
242 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
243 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
244 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
245 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
246 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
247 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
248 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
249 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
250 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
251 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
252 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
253 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
254 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
255 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
256 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
260 static const struct option delete_options[] = {
261 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
262 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
266 static const struct option dir_options[] = {
267 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
268 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
269 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
270 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
274 static const struct option export_options[] = {
275 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
276 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
277 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
278 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
279 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
280 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
281 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
282 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
283 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
284 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
285 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
286 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
287 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
288 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
289 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
290 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
291 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
292 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
293 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
294 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
295 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
299 static const struct option extract_options[] = {
300 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
301 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
302 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
303 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
304 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
305 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
306 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
307 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
308 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
309 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
310 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
311 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
312 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
313 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
314 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
315 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
316 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
320 static const struct option info_options[] = {
321 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
322 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
323 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
324 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
325 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
326 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
327 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
328 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
329 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
330 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
334 static const struct option join_options[] = {
335 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
339 static const struct option mount_options[] = {
340 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
341 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
342 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
343 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
344 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
345 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
346 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
350 static const struct option optimize_options[] = {
351 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
352 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
353 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
354 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
355 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
356 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
357 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
358 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
359 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
360 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
361 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
362 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
363 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
364 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
365 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
366 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
367 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
368 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
372 static const struct option split_options[] = {
373 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
377 static const struct option unmount_options[] = {
378 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
379 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
380 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
381 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
382 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
383 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
387 static const struct option update_options[] = {
388 /* Careful: some of the options here set the defaults for update
389 * commands, but the flags given to an actual update command (and not to
390 * `imagex update' itself are also handled in
391 * update_command_add_option(). */
392 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
393 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
394 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
395 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
396 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
398 /* Default delete options */
399 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
400 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
402 /* Global add option */
403 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
405 /* Default add options */
406 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
407 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
408 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
409 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
410 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
411 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
412 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
417 static const struct option verify_options[] = {
418 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
419 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
425 # define _format_attribute(type, format_str, args_start) \
426 __attribute__((format(type, format_str, args_start)))
428 # define _format_attribute(type, format_str, args_start)
431 /* Print formatted error message to stderr. */
432 static void _format_attribute(printf, 1, 2)
433 imagex_error(const tchar *format, ...)
436 va_start(va, format);
437 tfputs(T("ERROR: "), stderr);
438 tvfprintf(stderr, format, va);
439 tputc(T('\n'), stderr);
443 /* Print formatted error message to stderr. */
444 static void _format_attribute(printf, 1, 2)
445 imagex_error_with_errno(const tchar *format, ...)
447 int errno_save = errno;
449 va_start(va, format);
450 tfputs(T("ERROR: "), stderr);
451 tvfprintf(stderr, format, va);
452 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
457 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
459 if (image == WIMLIB_NO_IMAGE) {
460 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
461 " Please specify a 1-based image index or "
462 "image name. To list the images\n"
463 " contained in the WIM archive, run\n"
465 " %"TS" \"%"TS"\"\n"),
466 image_name, wim_name,
467 get_cmd_string(CMD_INFO, false), wim_name);
468 return WIMLIB_ERR_INVALID_IMAGE;
474 verify_image_is_single(int image)
476 if (image == WIMLIB_ALL_IMAGES) {
477 imagex_error(T("Cannot specify all images for this action!"));
478 return WIMLIB_ERR_INVALID_IMAGE;
484 verify_image_exists_and_is_single(int image, const tchar *image_name,
485 const tchar *wim_name)
488 ret = verify_image_exists(image, image_name, wim_name);
490 ret = verify_image_is_single(image);
495 print_available_compression_types(FILE *fp)
497 static const tchar *s =
499 "Available compression types:\n"
502 " xpress (alias: \"fast\")\n"
503 " lzx (alias: \"maximum\") (default for capture)\n"
504 " lzms (alias: \"recovery\")\n"
510 /* Parse the argument to --compress */
512 get_compression_type(tchar *optarg)
515 unsigned int compression_level = 0;
518 plevel = tstrchr(optarg, T(':'));
524 ultmp = tstrtoul(plevel, &ptmp, 10);
525 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
526 imagex_error(T("Compression level must be a positive integer! "
527 "e.g. --compress=lzx:80"));
528 return WIMLIB_COMPRESSION_TYPE_INVALID;
530 compression_level = ultmp;
533 if (!tstrcasecmp(optarg, T("maximum")) ||
534 !tstrcasecmp(optarg, T("lzx")) ||
535 !tstrcasecmp(optarg, T("max")))
536 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
537 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
538 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
539 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
540 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
541 else if (!tstrcasecmp(optarg, T("none")))
542 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
544 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
545 print_available_compression_types(stderr);
546 return WIMLIB_COMPRESSION_TYPE_INVALID;
549 if (compression_level != 0)
550 wimlib_set_default_compression_level(ctype, compression_level);
554 /* Parse the argument to --compact */
556 set_compact_mode(const tchar *arg, int *extract_flags)
559 if (!tstrcasecmp(arg, T("xpress4k")))
560 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
561 else if (!tstrcasecmp(arg, T("xpress8k")))
562 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
563 else if (!tstrcasecmp(arg, T("xpress16k")))
564 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
565 else if (!tstrcasecmp(arg, T("lzx")))
566 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
569 *extract_flags |= flag;
574 "\"%"TS"\" is not a recognized System Compression format. The options are:"
576 " --compact=xpress4k\n"
577 " --compact=xpress8k\n"
578 " --compact=xpress16k\n"
586 set_compress_slow(void)
589 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
590 " Use the '--compress=TYPE:LEVEL' option instead.\n");
592 wimlib_set_default_compression_level(-1, 100);
596 const tchar **strings;
597 unsigned num_strings;
598 unsigned num_alloc_strings;
601 #define STRING_SET_INITIALIZER \
602 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
604 #define STRING_SET(_strings) \
605 struct string_set _strings = STRING_SET_INITIALIZER
608 string_set_append(struct string_set *set, const tchar *glob)
610 unsigned num_alloc_strings = set->num_alloc_strings;
612 if (set->num_strings == num_alloc_strings) {
613 const tchar **new_strings;
615 num_alloc_strings += 4;
616 new_strings = realloc(set->strings,
617 sizeof(set->strings[0]) * num_alloc_strings);
619 imagex_error(T("Out of memory!"));
622 set->strings = new_strings;
623 set->num_alloc_strings = num_alloc_strings;
625 set->strings[set->num_strings++] = glob;
630 string_set_destroy(struct string_set *set)
636 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
638 return wimlib_reference_resource_files(wim, set->strings,
640 WIMLIB_REF_FLAG_GLOB_ENABLE,
645 do_resource_not_found_warning(const tchar *wimfile,
646 const struct wimlib_wim_info *info,
647 const struct string_set *refglobs)
649 if (info->total_parts > 1) {
650 if (refglobs->num_strings == 0) {
651 imagex_error(T("\"%"TS"\" is part of a split WIM. "
652 "Use --ref to specify the other parts."),
655 imagex_error(T("Perhaps the '--ref' argument did not "
656 "specify all other parts of the split "
660 imagex_error(T("If this is a delta WIM, use the --ref argument "
661 "to specify the WIM(s) on which it is based."));
666 do_metadata_not_found_warning(const tchar *wimfile,
667 const struct wimlib_wim_info *info)
669 if (info->part_number != 1) {
670 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
671 " You must specify the first part."),
676 /* Returns the size of a file given its name, or -1 if the file does not exist
677 * or its size cannot be determined. */
679 file_get_size(const tchar *filename)
682 if (tstat(filename, &st) == 0)
689 PARSE_STRING_SUCCESS = 0,
690 PARSE_STRING_FAILURE = 1,
691 PARSE_STRING_NONE = 2,
695 * Parses a string token from an array of characters.
697 * Tokens are either whitespace-delimited, or double or single-quoted.
699 * @line_p: Pointer to the pointer to the line of data. Will be updated
700 * to point past the string token iff the return value is
701 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
704 * @len_p: @len_p initially stores the length of the line of data, which may
705 * be 0, and it will be updated to the number of bytes remaining in
706 * the line iff the return value is PARSE_STRING_SUCCESS.
708 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
709 * parsed string token will be returned here.
711 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
712 * PARSE_STRING_FAILURE if the data was invalid due to a missing
713 * closing quote; or PARSE_STRING_NONE if the line ended before the
714 * beginning of a string token was found.
717 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
720 tchar *line = *line_p;
724 /* Skip leading whitespace */
727 return PARSE_STRING_NONE;
728 if (!istspace(*line) && *line != T('\0'))
734 if (quote_char == T('"') || quote_char == T('\'')) {
739 line = tmemchr(line, quote_char, len);
741 imagex_error(T("Missing closing quote: %"TS), fn - 1);
742 return PARSE_STRING_FAILURE;
745 /* Unquoted string. Go until whitespace. Line is terminated
746 * by '\0', so no need to check 'len'. */
750 } while (!istspace(*line) && *line != T('\0'));
757 return PARSE_STRING_SUCCESS;
760 /* Parses a line of data (not an empty line or comment) in the source list file
761 * format. (See the man page for 'wimlib-imagex capture' for details on this
762 * format and the meaning.)
764 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
765 * len == 0. The data in @line will be modified by this function call.
767 * @len: Length of the line of data.
769 * @source: On success, the capture source and target described by the line is
770 * written into this destination. Note that it will contain pointers
771 * to data in the @line array.
773 * Returns true if the line was valid; false otherwise. */
775 parse_source_list_line(tchar *line, size_t len,
776 struct wimlib_capture_source *source)
780 ret = parse_string(&line, &len, &source->fs_source_path);
781 if (ret != PARSE_STRING_SUCCESS)
783 ret = parse_string(&line, &len, &source->wim_target_path);
784 if (ret == PARSE_STRING_NONE)
785 source->wim_target_path = source->fs_source_path;
786 return ret != PARSE_STRING_FAILURE;
789 /* Returns %true if the given line of length @len > 0 is a comment or empty line
790 * in the source list file format. */
792 is_comment_line(const tchar *line, size_t len)
795 if (*line == T('#') || *line == T(';'))
797 if (!istspace(*line) && *line != T('\0'))
807 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
810 tchar *contents = *contents_p;
811 size_t nchars = *nchars_p;
814 for (i = 0; i < nchars; i++)
815 if (contents[i] == T('\n'))
818 /* Handle last line not terminated by a newline */
819 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
820 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
822 imagex_error(T("Out of memory!"));
825 contents[nchars] = T('\n');
826 *contents_p = contents;
834 /* Parses a file in the source list format. (See the man page for
835 * 'wimlib-imagex capture' for details on this format and the meaning.)
837 * @source_list_contents: Contents of the source list file. Note that this
838 * buffer will be modified to save memory allocations,
839 * and cannot be freed until the returned array of
840 * wimlib_capture_source's has also been freed.
842 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
845 * @nsources_ret: On success, the length of the returned array is
848 * Returns: An array of `struct wimlib_capture_source's that can be passed to
849 * the wimlib_add_image_multisource() function to specify how a WIM image is to
851 static struct wimlib_capture_source *
852 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
853 size_t *nsources_ret)
857 struct wimlib_capture_source *sources;
860 nlines = text_file_count_lines(source_list_contents_p,
861 &source_list_nchars);
865 /* Always allocate at least 1 slot, just in case the implementation of
866 * calloc() returns NULL if 0 bytes are requested. */
867 sources = calloc(nlines ?: 1, sizeof(*sources));
869 imagex_error(T("out of memory"));
872 p = *source_list_contents_p;
874 for (i = 0; i < nlines; i++) {
875 /* XXX: Could use rawmemchr() here instead, but it may not be
876 * available on all platforms. */
877 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
878 size_t len = endp - p + 1;
880 if (!is_comment_line(p, len)) {
881 if (!parse_source_list_line(p, len, &sources[j++])) {
893 /* Reads the contents of a file into memory. */
895 file_get_contents(const tchar *filename, size_t *len_ret)
902 if (tstat(filename, &stbuf) != 0) {
903 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
908 fp = tfopen(filename, T("rb"));
910 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
914 buf = malloc(len ? len : 1);
916 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
917 "contents of file \"%"TS"\""), len, filename);
920 if (fread(buf, 1, len, fp) != len) {
921 imagex_error_with_errno(T("Failed to read %zu bytes from the "
922 "file \"%"TS"\""), len, filename);
936 /* Read standard input until EOF and return the full contents in a malloc()ed
937 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
940 stdin_get_contents(size_t *len_ret)
942 /* stdin can, of course, be a pipe or other non-seekable file, so the
943 * total length of the data cannot be pre-determined */
945 size_t newlen = 1024;
949 char *p = realloc(buf, newlen);
950 size_t bytes_read, bytes_to_read;
952 imagex_error(T("out of memory while reading stdin"));
956 bytes_to_read = newlen - pos;
957 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
959 if (bytes_read != bytes_to_read) {
964 imagex_error_with_errno(T("error reading stdin"));
978 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
981 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
983 *num_tchars_ret = num_bytes;
985 #else /* !__WIN32__ */
986 /* On Windows, translate the text to UTF-16LE */
990 if (num_bytes >= 2 &&
991 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
992 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
994 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
995 * with something that looks like an ASCII character encoded as
996 * a UTF-16LE code unit. Assume the file is encoded as
997 * UTF-16LE. This is not a 100% reliable check. */
998 num_wchars = num_bytes / 2;
999 text_wstr = (wchar_t*)text;
1001 /* File does not look like UTF-16LE. Assume it is encoded in
1002 * the current Windows code page. I think these are always
1003 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1004 * should work as expected. */
1005 text_wstr = win32_mbs_to_wcs(text,
1010 *num_tchars_ret = num_wchars;
1012 #endif /* __WIN32__ */
1016 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1021 contents = file_get_contents(filename, &num_bytes);
1024 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1028 stdin_get_text_contents(size_t *num_tchars_ret)
1033 contents = stdin_get_contents(&num_bytes);
1036 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1039 #define TO_PERCENT(numerator, denominator) \
1040 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1042 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1043 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1044 #define KIBIBYTE_MIN_NBYTES 10000ULL
1047 get_unit(uint64_t total_bytes, const tchar **name_ret)
1049 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1050 *name_ret = T("GiB");
1052 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1053 *name_ret = T("MiB");
1055 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1056 *name_ret = T("KiB");
1059 *name_ret = T("bytes");
1064 static struct wimlib_progress_info_scan last_scan_progress;
1067 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1069 uint64_t prev_count, cur_count;
1071 prev_count = last_scan_progress.num_nondirs_scanned +
1072 last_scan_progress.num_dirs_scanned;
1073 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1075 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1076 cur_count % 128 == 0)
1078 unsigned unit_shift;
1079 const tchar *unit_name;
1081 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1082 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1083 "%"PRIu64" directories) "),
1084 scan->num_bytes_scanned >> unit_shift,
1086 scan->num_nondirs_scanned,
1087 scan->num_dirs_scanned);
1088 last_scan_progress = *scan;
1091 /* Progress callback function passed to various wimlib functions. */
1092 static enum wimlib_progress_status
1093 imagex_progress_func(enum wimlib_progress_msg msg,
1094 union wimlib_progress_info *info,
1095 void *_ignored_context)
1097 unsigned percent_done;
1098 unsigned unit_shift;
1099 const tchar *unit_name;
1101 if (imagex_be_quiet)
1102 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1104 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1106 static bool first = true;
1108 imagex_printf(T("Writing %"TS"-compressed data "
1109 "using %u thread%"TS"\n"),
1110 wimlib_get_compression_type_string(
1111 info->write_streams.compression_type),
1112 info->write_streams.num_threads,
1113 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1117 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1118 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1119 info->write_streams.total_bytes);
1121 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1122 "written (%u%% done)"),
1123 info->write_streams.completed_bytes >> unit_shift,
1125 info->write_streams.total_bytes >> unit_shift,
1128 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1129 imagex_printf(T("\n"));
1131 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1132 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1133 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1134 imagex_printf(T("\n"));
1136 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1137 info->scan.wim_target_path);
1139 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1141 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1142 switch (info->scan.status) {
1143 case WIMLIB_SCAN_DENTRY_OK:
1144 report_scan_progress(&info->scan, false);
1146 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1147 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1149 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1150 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1151 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1153 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1154 /* Symlink fixups are enabled by default. This is
1155 * mainly intended for Windows, which for some reason
1156 * uses absolute junctions (with drive letters!) in the
1157 * default installation. On UNIX-like systems, warn the
1158 * user when fixing the target of an absolute symbolic
1159 * link, so they know to disable this if they want. */
1161 imagex_printf(T("\nWARNING: Adjusted target of "
1162 "absolute symbolic link \"%"TS"\"\n"
1163 " (Use --norpfix to capture "
1164 "absolute symbolic links as-is)\n"),
1165 info->scan.cur_path);
1172 case WIMLIB_PROGRESS_MSG_SCAN_END:
1173 report_scan_progress(&info->scan, true);
1174 imagex_printf(T("\n"));
1176 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1177 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1178 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1179 info->integrity.total_bytes);
1180 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1181 "of %"PRIu64" %"TS" (%u%%) done"),
1182 info->integrity.filename,
1183 info->integrity.completed_bytes >> unit_shift,
1185 info->integrity.total_bytes >> unit_shift,
1188 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1189 imagex_printf(T("\n"));
1191 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1192 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1193 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1194 info->integrity.total_bytes);
1195 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1196 "of %"PRIu64" %"TS" (%u%%) done"),
1197 info->integrity.completed_bytes >> unit_shift,
1199 info->integrity.total_bytes >> unit_shift,
1202 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1203 imagex_printf(T("\n"));
1205 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1206 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1207 "to %"TS" \"%"TS"\"\n"),
1208 info->extract.image,
1209 info->extract.image_name,
1210 info->extract.wimfile_name,
1211 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1212 T("NTFS volume") : T("directory")),
1213 info->extract.target);
1215 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1216 if (info->extract.end_file_count >= 2000) {
1217 percent_done = TO_PERCENT(info->extract.current_file_count,
1218 info->extract.end_file_count);
1219 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1220 info->extract.current_file_count,
1221 info->extract.end_file_count, percent_done);
1222 if (info->extract.current_file_count == info->extract.end_file_count)
1223 imagex_printf(T("\n"));
1226 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1227 percent_done = TO_PERCENT(info->extract.completed_bytes,
1228 info->extract.total_bytes);
1229 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1230 imagex_printf(T("\rExtracting file data: "
1231 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1232 info->extract.completed_bytes >> unit_shift,
1234 info->extract.total_bytes >> unit_shift,
1237 if (info->extract.completed_bytes >= info->extract.total_bytes)
1238 imagex_printf(T("\n"));
1240 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1241 if (info->extract.end_file_count >= 2000) {
1242 percent_done = TO_PERCENT(info->extract.current_file_count,
1243 info->extract.end_file_count);
1244 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1245 info->extract.current_file_count,
1246 info->extract.end_file_count, percent_done);
1247 if (info->extract.current_file_count == info->extract.end_file_count)
1248 imagex_printf(T("\n"));
1251 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1252 if (info->extract.total_parts != 1) {
1253 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1254 info->extract.part_number,
1255 info->extract.total_parts);
1258 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1259 percent_done = TO_PERCENT(info->split.completed_bytes,
1260 info->split.total_bytes);
1261 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1262 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1263 "%"PRIu64" %"TS" (%u%%) written\n"),
1264 info->split.part_name,
1265 info->split.cur_part_number,
1266 info->split.total_parts,
1267 info->split.completed_bytes >> unit_shift,
1269 info->split.total_bytes >> unit_shift,
1273 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1274 if (info->split.completed_bytes == info->split.total_bytes) {
1275 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1276 info->split.cur_part_number,
1277 info->split.total_parts);
1280 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1281 switch (info->update.command->op) {
1282 case WIMLIB_UPDATE_OP_DELETE:
1283 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1284 info->update.command->delete_.wim_path);
1286 case WIMLIB_UPDATE_OP_RENAME:
1287 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1288 info->update.command->rename.wim_source_path,
1289 info->update.command->rename.wim_target_path);
1291 case WIMLIB_UPDATE_OP_ADD:
1296 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1297 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1298 info->replace.path_in_wim);
1300 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1301 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1302 info->wimboot_exclude.path_in_wim);
1304 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1305 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1306 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1307 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1308 info->unmount.mounted_wim,
1309 info->unmount.mounted_image);
1311 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1312 info->unmount.mounted_wim,
1313 info->unmount.mounted_image);
1314 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1318 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1319 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1320 info->verify_image.current_image,
1321 info->verify_image.total_images);
1323 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1324 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1325 info->verify_streams.total_bytes);
1326 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1327 imagex_printf(T("\rVerifying file data: "
1328 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1329 info->verify_streams.completed_bytes >> unit_shift,
1331 info->verify_streams.total_bytes >> unit_shift,
1334 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1335 imagex_printf(T("\n"));
1340 fflush(imagex_info_file);
1341 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1345 parse_num_threads(const tchar *optarg)
1348 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1349 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1350 imagex_error(T("Number of threads must be a non-negative integer!"));
1358 parse_chunk_size(const tchar *optarg)
1361 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1362 if (chunk_size == 0) {
1363 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1364 " with optional K, M, or G suffix"));
1368 if (*tmp == T('k') || *tmp == T('K')) {
1371 } else if (*tmp == T('m') || *tmp == T('M')) {
1374 } else if (*tmp == T('g') || *tmp == T('G')) {
1378 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1379 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1383 if (chunk_size >= UINT32_MAX) {
1384 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1392 * Parse an option passed to an update command.
1394 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1397 * @option: Text string for the option (beginning with --)
1399 * @cmd: `struct wimlib_update_command' that is being constructed for
1402 * Returns true if the option was recognized; false if not.
1405 update_command_add_option(int op, const tchar *option,
1406 struct wimlib_update_command *cmd)
1408 bool recognized = true;
1410 case WIMLIB_UPDATE_OP_ADD:
1411 if (!tstrcmp(option, T("--verbose")))
1412 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1413 else if (!tstrcmp(option, T("--unix-data")))
1414 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1415 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1416 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1417 else if (!tstrcmp(option, T("--strict-acls")))
1418 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1419 else if (!tstrcmp(option, T("--dereference")))
1420 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1421 else if (!tstrcmp(option, T("--no-replace")))
1422 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1426 case WIMLIB_UPDATE_OP_DELETE:
1427 if (!tstrcmp(option, T("--force")))
1428 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1429 else if (!tstrcmp(option, T("--recursive")))
1430 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1441 /* How many nonoption arguments each `imagex update' command expects */
1442 static const unsigned update_command_num_nonoptions[] = {
1443 [WIMLIB_UPDATE_OP_ADD] = 2,
1444 [WIMLIB_UPDATE_OP_DELETE] = 1,
1445 [WIMLIB_UPDATE_OP_RENAME] = 2,
1449 update_command_add_nonoption(int op, const tchar *nonoption,
1450 struct wimlib_update_command *cmd,
1451 unsigned num_nonoptions)
1454 case WIMLIB_UPDATE_OP_ADD:
1455 if (num_nonoptions == 0)
1456 cmd->add.fs_source_path = (tchar*)nonoption;
1458 cmd->add.wim_target_path = (tchar*)nonoption;
1460 case WIMLIB_UPDATE_OP_DELETE:
1461 cmd->delete_.wim_path = (tchar*)nonoption;
1463 case WIMLIB_UPDATE_OP_RENAME:
1464 if (num_nonoptions == 0)
1465 cmd->rename.wim_source_path = (tchar*)nonoption;
1467 cmd->rename.wim_target_path = (tchar*)nonoption;
1473 * Parse a command passed on stdin to `imagex update'.
1475 * @line: Text of the command.
1476 * @len: Length of the line, including a null terminator
1479 * @command: A `struct wimlib_update_command' to fill in from the parsed
1482 * @line_number: Line number of the command, for diagnostics.
1484 * Returns true on success; returns false on parse error.
1487 parse_update_command(tchar *line, size_t len,
1488 struct wimlib_update_command *command,
1492 tchar *command_name;
1494 size_t num_nonoptions;
1496 /* Get the command name ("add", "delete", "rename") */
1497 ret = parse_string(&line, &len, &command_name);
1498 if (ret != PARSE_STRING_SUCCESS)
1501 if (!tstrcasecmp(command_name, T("add"))) {
1502 op = WIMLIB_UPDATE_OP_ADD;
1503 } else if (!tstrcasecmp(command_name, T("delete"))) {
1504 op = WIMLIB_UPDATE_OP_DELETE;
1505 } else if (!tstrcasecmp(command_name, T("rename"))) {
1506 op = WIMLIB_UPDATE_OP_RENAME;
1508 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1509 command_name, line_number);
1514 /* Parse additional options and non-options as needed */
1519 ret = parse_string(&line, &len, &next_string);
1520 if (ret == PARSE_STRING_NONE) /* End of line */
1522 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1524 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1526 if (!update_command_add_option(op, next_string, command))
1528 imagex_error(T("Unrecognized option \"%"TS"\" to "
1529 "update command \"%"TS"\" on line %zu"),
1530 next_string, command_name, line_number);
1536 if (num_nonoptions == update_command_num_nonoptions[op])
1538 imagex_error(T("Unexpected argument \"%"TS"\" in "
1539 "update command on line %zu\n"
1540 " (The \"%"TS"\" command only "
1541 "takes %zu nonoption arguments!)\n"),
1542 next_string, line_number,
1543 command_name, num_nonoptions);
1546 update_command_add_nonoption(op, next_string,
1547 command, num_nonoptions);
1552 if (num_nonoptions != update_command_num_nonoptions[op]) {
1553 imagex_error(T("Not enough arguments to update command "
1554 "\"%"TS"\" on line %zu"), command_name, line_number);
1560 static struct wimlib_update_command *
1561 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1562 size_t *num_cmds_ret)
1566 struct wimlib_update_command *cmds;
1569 nlines = text_file_count_lines(cmd_file_contents_p,
1574 /* Always allocate at least 1 slot, just in case the implementation of
1575 * calloc() returns NULL if 0 bytes are requested. */
1576 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1578 imagex_error(T("out of memory"));
1581 p = *cmd_file_contents_p;
1583 for (i = 0; i < nlines; i++) {
1584 /* XXX: Could use rawmemchr() here instead, but it may not be
1585 * available on all platforms. */
1586 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1587 size_t len = endp - p + 1;
1589 if (!is_comment_line(p, len)) {
1590 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1601 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1602 * one image from a WIM file to an NTFS volume. */
1604 imagex_apply(int argc, tchar **argv, int cmd)
1608 int image = WIMLIB_NO_IMAGE;
1610 struct wimlib_wim_info info;
1612 const tchar *wimfile;
1613 const tchar *target;
1614 const tchar *image_num_or_name = NULL;
1615 int extract_flags = 0;
1617 STRING_SET(refglobs);
1619 for_opt(c, apply_options) {
1621 case IMAGEX_CHECK_OPTION:
1622 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1624 case IMAGEX_VERBOSE_OPTION:
1625 /* No longer does anything. */
1627 case IMAGEX_REF_OPTION:
1628 ret = string_set_append(&refglobs, optarg);
1630 goto out_free_refglobs;
1632 case IMAGEX_UNIX_DATA_OPTION:
1633 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1635 case IMAGEX_NO_ACLS_OPTION:
1636 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1638 case IMAGEX_STRICT_ACLS_OPTION:
1639 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1641 case IMAGEX_NO_ATTRIBUTES_OPTION:
1642 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1644 case IMAGEX_NORPFIX_OPTION:
1645 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1647 case IMAGEX_RPFIX_OPTION:
1648 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1650 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1651 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1652 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1654 case IMAGEX_RESUME_OPTION:
1655 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1657 case IMAGEX_WIMBOOT_OPTION:
1658 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1660 case IMAGEX_COMPACT_OPTION:
1661 ret = set_compact_mode(optarg, &extract_flags);
1663 goto out_free_refglobs;
1671 if (argc != 2 && argc != 3)
1676 if (!tstrcmp(wimfile, T("-"))) {
1677 /* Attempt to apply pipable WIM from standard input. */
1679 image_num_or_name = NULL;
1682 image_num_or_name = argv[1];
1687 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1688 imagex_progress_func, NULL);
1690 goto out_free_refglobs;
1692 wimlib_get_wim_info(wim, &info);
1695 /* Image explicitly specified. */
1696 image_num_or_name = argv[1];
1697 image = wimlib_resolve_image(wim, image_num_or_name);
1698 ret = verify_image_exists(image, image_num_or_name, wimfile);
1700 goto out_wimlib_free;
1703 /* No image specified; default to image 1, but only if the WIM
1704 * contains exactly one image. */
1706 if (info.image_count != 1) {
1707 imagex_error(T("\"%"TS"\" contains %d images; "
1708 "Please select one (or all)."),
1709 wimfile, info.image_count);
1718 if (refglobs.num_strings) {
1720 imagex_error(T("Can't specify --ref when applying from stdin!"));
1722 goto out_wimlib_free;
1724 ret = wim_reference_globs(wim, &refglobs, open_flags);
1726 goto out_wimlib_free;
1731 /* Interpret a regular file or block device target as an NTFS
1735 if (tstat(target, &stbuf)) {
1736 if (errno != ENOENT) {
1737 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1740 goto out_wimlib_free;
1743 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1744 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1750 ret = wimlib_extract_image(wim, image, target, extract_flags);
1752 set_fd_to_binary_mode(STDIN_FILENO);
1753 ret = wimlib_extract_image_from_pipe_with_progress(
1758 imagex_progress_func,
1762 imagex_printf(T("Done applying WIM image.\n"));
1763 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1765 do_resource_not_found_warning(wimfile, &info, &refglobs);
1767 imagex_error(T( "If you are applying an image "
1768 "from a split pipable WIM,\n"
1769 " make sure you have "
1770 "concatenated together all parts."));
1772 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
1773 do_metadata_not_found_warning(wimfile, &info);
1778 string_set_destroy(&refglobs);
1782 usage(CMD_APPLY, stderr);
1784 goto out_free_refglobs;
1787 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1788 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1789 * the desired image. 'wimlib-imagex append': add a new image to an existing
1792 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1796 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1797 WIMLIB_ADD_FLAG_WINCONFIG |
1798 WIMLIB_ADD_FLAG_VERBOSE;
1799 int write_flags = 0;
1800 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1801 uint32_t chunk_size = UINT32_MAX;
1802 uint32_t solid_chunk_size = UINT32_MAX;
1803 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1804 const tchar *wimfile;
1808 const tchar *flags_element = NULL;
1811 STRING_SET(base_wimfiles);
1812 WIMStruct **base_wims;
1814 WIMStruct *template_wim;
1815 const tchar *template_wimfile = NULL;
1816 const tchar *template_image_name_or_num = NULL;
1817 int template_image = WIMLIB_NO_IMAGE;
1820 unsigned num_threads = 0;
1825 tchar *config_file = NULL;
1827 bool source_list = false;
1828 size_t source_list_nchars = 0;
1829 tchar *source_list_contents;
1830 bool capture_sources_malloced;
1831 struct wimlib_capture_source *capture_sources;
1833 bool name_defaulted;
1835 for_opt(c, capture_or_append_options) {
1837 case IMAGEX_BOOT_OPTION:
1838 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1840 case IMAGEX_CHECK_OPTION:
1841 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1842 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1844 case IMAGEX_NOCHECK_OPTION:
1845 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1847 case IMAGEX_CONFIG_OPTION:
1848 config_file = optarg;
1849 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1851 case IMAGEX_COMPRESS_OPTION:
1852 compression_type = get_compression_type(optarg);
1853 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1856 case IMAGEX_COMPRESS_SLOW_OPTION:
1857 set_compress_slow();
1859 case IMAGEX_CHUNK_SIZE_OPTION:
1860 chunk_size = parse_chunk_size(optarg);
1861 if (chunk_size == UINT32_MAX)
1864 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1865 solid_chunk_size = parse_chunk_size(optarg);
1866 if (solid_chunk_size == UINT32_MAX)
1869 case IMAGEX_SOLID_COMPRESS_OPTION:
1870 solid_ctype = get_compression_type(optarg);
1871 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1874 case IMAGEX_SOLID_OPTION:
1875 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1877 case IMAGEX_NO_SOLID_SORT_OPTION:
1878 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1880 case IMAGEX_FLAGS_OPTION:
1881 flags_element = optarg;
1883 case IMAGEX_DEREFERENCE_OPTION:
1884 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1886 case IMAGEX_VERBOSE_OPTION:
1887 /* No longer does anything. */
1889 case IMAGEX_THREADS_OPTION:
1890 num_threads = parse_num_threads(optarg);
1891 if (num_threads == UINT_MAX)
1894 case IMAGEX_REBUILD_OPTION:
1895 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1897 case IMAGEX_UNIX_DATA_OPTION:
1898 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1900 case IMAGEX_SOURCE_LIST_OPTION:
1903 case IMAGEX_NO_ACLS_OPTION:
1904 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1906 case IMAGEX_STRICT_ACLS_OPTION:
1907 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1909 case IMAGEX_RPFIX_OPTION:
1910 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1912 case IMAGEX_NORPFIX_OPTION:
1913 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1915 case IMAGEX_PIPABLE_OPTION:
1916 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1918 case IMAGEX_NOT_PIPABLE_OPTION:
1919 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1921 case IMAGEX_UPDATE_OF_OPTION:
1922 if (template_image_name_or_num) {
1923 imagex_error(T("'--update-of' can only be "
1924 "specified one time!"));
1928 colon = tstrrchr(optarg, T(':'));
1931 template_wimfile = optarg;
1933 template_image_name_or_num = colon + 1;
1935 template_wimfile = NULL;
1936 template_image_name_or_num = optarg;
1940 case IMAGEX_DELTA_FROM_OPTION:
1941 if (cmd != CMD_CAPTURE) {
1942 imagex_error(T("'--delta-from' is only "
1943 "valid for capture!"));
1946 ret = string_set_append(&base_wimfiles, optarg);
1948 goto out_free_base_wimfiles;
1949 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1951 case IMAGEX_WIMBOOT_OPTION:
1952 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1961 if (argc < 2 || argc > 4)
1967 /* Set default compression type and parameters. */
1970 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1971 /* No compression type specified. Use the default. */
1973 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1974 /* With --wimboot, default to XPRESS compression. */
1975 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1976 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1977 /* With --solid, default to LZMS compression. (However,
1978 * this will not affect solid resources!) */
1979 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1981 /* Otherwise, default to LZX compression. */
1982 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1986 if (!tstrcmp(wimfile, T("-"))) {
1987 /* Writing captured WIM to standard output. */
1989 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1990 imagex_error("Can't write a non-pipable WIM to "
1991 "standard output! Specify --pipable\n"
1992 " if you want to create a pipable WIM "
1993 "(but read the docs first).");
1997 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1999 if (cmd == CMD_APPEND) {
2000 imagex_error(T("Using standard output for append does "
2001 "not make sense."));
2004 wim_fd = STDOUT_FILENO;
2006 imagex_info_file = stderr;
2007 set_fd_to_binary_mode(wim_fd);
2010 /* If template image was specified using --update-of=IMAGE rather
2011 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2012 if (template_image_name_or_num && !template_wimfile) {
2013 if (base_wimfiles.num_strings == 1) {
2014 /* Capturing delta WIM based on single WIM: default to
2016 template_wimfile = base_wimfiles.strings[0];
2017 } else if (cmd == CMD_APPEND) {
2018 /* Appending to WIM: default to WIM being appended to.
2020 template_wimfile = wimfile;
2022 /* Capturing a normal (non-delta) WIM, so the WIM file
2023 * *must* be explicitly specified. */
2024 if (base_wimfiles.num_strings > 1) {
2025 imagex_error(T("For capture of delta WIM "
2026 "based on multiple existing "
2028 " '--update-of' must "
2029 "specify WIMFILE:IMAGE!"));
2031 imagex_error(T("For capture of non-delta WIM, "
2032 "'--update-of' must specify "
2041 name_defaulted = false;
2043 /* Set default name to SOURCE argument, omitting any directory
2044 * prefixes and trailing slashes. This requires making a copy
2045 * of @source. Leave some free characters at the end in case we
2046 * append a number to keep the name unique. */
2047 size_t source_name_len;
2049 source_name_len = tstrlen(source);
2050 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2051 name = tbasename(tstrcpy(source_copy, source));
2052 name_defaulted = true;
2054 /* Image description defaults to NULL if not given. */
2061 /* Set up capture sources in source list mode */
2062 if (source[0] == T('-') && source[1] == T('\0')) {
2063 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2065 source_list_contents = file_get_text_contents(source,
2066 &source_list_nchars);
2068 if (!source_list_contents)
2071 capture_sources = parse_source_list(&source_list_contents,
2074 if (!capture_sources) {
2076 goto out_free_source_list_contents;
2078 capture_sources_malloced = true;
2080 /* Set up capture source in non-source-list mode. */
2081 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2082 capture_sources[0].fs_source_path = source;
2083 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2084 capture_sources[0].reserved = 0;
2086 capture_sources_malloced = false;
2087 source_list_contents = NULL;
2090 /* Open the existing WIM, or create a new one. */
2091 if (cmd == CMD_APPEND) {
2092 ret = wimlib_open_wim_with_progress(wimfile,
2093 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2095 imagex_progress_func,
2098 goto out_free_capture_sources;
2100 ret = wimlib_create_new_wim(compression_type, &wim);
2102 goto out_free_capture_sources;
2103 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2106 /* Set chunk size if non-default. */
2107 if (chunk_size != UINT32_MAX) {
2108 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2111 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2112 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2113 ret = wimlib_set_output_chunk_size(wim, 4096);
2117 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2118 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2122 if (solid_chunk_size != UINT32_MAX) {
2123 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2129 /* Detect if source is regular file or block device and set NTFS volume
2134 if (tstat(source, &stbuf) == 0) {
2135 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2136 imagex_printf(T("Capturing WIM image from NTFS "
2137 "filesystem on \"%"TS"\"\n"), source);
2138 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2141 if (errno != ENOENT) {
2142 imagex_error_with_errno(T("Failed to stat "
2143 "\"%"TS"\""), source);
2151 /* If the user did not specify an image name, and the basename of the
2152 * source already exists as an image name in the WIM file, append a
2153 * suffix to make it unique. */
2154 if (cmd == CMD_APPEND && name_defaulted) {
2155 unsigned long conflict_idx;
2156 tchar *name_end = tstrchr(name, T('\0'));
2157 for (conflict_idx = 1;
2158 wimlib_image_name_in_use(wim, name);
2161 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2165 /* If capturing a delta WIM, reference resources from the base WIMs
2166 * before adding the new image. */
2167 if (base_wimfiles.num_strings) {
2168 base_wims = calloc(base_wimfiles.num_strings,
2169 sizeof(base_wims[0]));
2170 if (base_wims == NULL) {
2171 imagex_error(T("Out of memory!"));
2176 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2177 ret = wimlib_open_wim_with_progress(
2178 base_wimfiles.strings[i], open_flags,
2179 &base_wims[i], imagex_progress_func, NULL);
2181 goto out_free_base_wims;
2185 ret = wimlib_reference_resources(wim, base_wims,
2186 base_wimfiles.num_strings, 0);
2188 goto out_free_base_wims;
2190 if (base_wimfiles.num_strings == 1) {
2191 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2192 base_wimfiles.strings[0]);
2194 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2195 base_wimfiles.num_strings);
2202 /* If capturing or appending as an update of an existing (template) image,
2203 * open the WIM if needed and parse the image index. */
2204 if (template_image_name_or_num) {
2207 if (base_wimfiles.num_strings == 1 &&
2208 template_wimfile == base_wimfiles.strings[0]) {
2209 template_wim = base_wims[0];
2210 } else if (template_wimfile == wimfile) {
2213 ret = wimlib_open_wim_with_progress(template_wimfile,
2216 imagex_progress_func,
2219 goto out_free_base_wims;
2222 template_image = wimlib_resolve_image(template_wim,
2223 template_image_name_or_num);
2225 if (template_image_name_or_num[0] == T('-')) {
2228 struct wimlib_wim_info info;
2230 wimlib_get_wim_info(template_wim, &info);
2231 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2232 if (n >= 1 && n <= info.image_count &&
2234 tmp != template_image_name_or_num + 1)
2236 template_image = info.image_count - (n - 1);
2239 ret = verify_image_exists_and_is_single(template_image,
2240 template_image_name_or_num,
2243 goto out_free_template_wim;
2245 template_wim = NULL;
2248 ret = wimlib_add_image_multisource(wim,
2255 goto out_free_template_wim;
2257 if (desc || flags_element || template_image_name_or_num) {
2258 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2259 * on which the added one is to be based has been specified with
2260 * --update-of. Get the index of the image we just
2261 * added, then use it to call the appropriate functions. */
2262 struct wimlib_wim_info info;
2264 wimlib_get_wim_info(wim, &info);
2267 ret = wimlib_set_image_descripton(wim,
2271 goto out_free_template_wim;
2274 if (flags_element) {
2275 ret = wimlib_set_image_flags(wim, info.image_count,
2278 goto out_free_template_wim;
2281 /* Reference template image if the user provided one. */
2282 if (template_image_name_or_num) {
2283 imagex_printf(T("Using image %d "
2284 "from \"%"TS"\" as template\n"),
2285 template_image, template_wimfile);
2286 ret = wimlib_reference_template_image(wim,
2292 goto out_free_template_wim;
2296 /* Write the new WIM or overwrite the existing WIM with the new image
2298 if (cmd == CMD_APPEND) {
2299 ret = wimlib_overwrite(wim, write_flags, num_threads);
2300 } else if (wimfile) {
2301 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2302 write_flags, num_threads);
2304 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2305 write_flags, num_threads);
2307 out_free_template_wim:
2308 /* template_wim may alias base_wims[0] or wim. */
2309 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2310 template_wim != wim)
2311 wimlib_free(template_wim);
2313 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2314 wimlib_free(base_wims[i]);
2318 out_free_capture_sources:
2319 if (capture_sources_malloced)
2320 free(capture_sources);
2321 out_free_source_list_contents:
2322 free(source_list_contents);
2323 out_free_base_wimfiles:
2324 string_set_destroy(&base_wimfiles);
2331 goto out_free_base_wimfiles;
2334 /* Remove image(s) from a WIM. */
2336 imagex_delete(int argc, tchar **argv, int cmd)
2339 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2340 int write_flags = 0;
2341 const tchar *wimfile;
2342 const tchar *image_num_or_name;
2347 for_opt(c, delete_options) {
2349 case IMAGEX_CHECK_OPTION:
2350 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2351 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2353 case IMAGEX_SOFT_OPTION:
2354 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2365 imagex_error(T("Must specify a WIM file"));
2367 imagex_error(T("Must specify an image"));
2371 image_num_or_name = argv[1];
2373 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2374 imagex_progress_func, NULL);
2378 image = wimlib_resolve_image(wim, image_num_or_name);
2380 ret = verify_image_exists(image, image_num_or_name, wimfile);
2382 goto out_wimlib_free;
2384 ret = wimlib_delete_image(wim, image);
2386 imagex_error(T("Failed to delete image from \"%"TS"\""),
2388 goto out_wimlib_free;
2391 ret = wimlib_overwrite(wim, write_flags, 0);
2393 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2394 "deleted"), wimfile);
2402 usage(CMD_DELETE, stderr);
2407 struct print_dentry_options {
2412 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2414 tprintf(T("%"TS"\n"), dentry->full_path);
2417 static const struct {
2420 } file_attr_flags[] = {
2421 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2422 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2423 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2424 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2425 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2426 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2427 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2428 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2429 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2430 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2431 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2432 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2433 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2434 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2435 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2438 #define TIMESTR_MAX 100
2441 timespec_to_string(const struct timespec *spec, tchar *buf)
2443 time_t t = spec->tv_sec;
2446 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2447 buf[TIMESTR_MAX - 1] = '\0';
2451 print_time(const tchar *type, const struct timespec *spec)
2453 tchar timestr[TIMESTR_MAX];
2455 timespec_to_string(spec, timestr);
2457 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2460 static void print_byte_field(const uint8_t field[], size_t len)
2463 tprintf(T("%02hhx"), *field++);
2467 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2469 tchar attr_string[256];
2472 tputs(T("WIM Information:"));
2473 tputs(T("----------------"));
2474 tprintf(T("Path: %"TS"\n"), wimfile);
2475 tprintf(T("GUID: 0x"));
2476 print_byte_field(info->guid, sizeof(info->guid));
2478 tprintf(T("Version: %u\n"), info->wim_version);
2479 tprintf(T("Image Count: %d\n"), info->image_count);
2480 tprintf(T("Compression: %"TS"\n"),
2481 wimlib_get_compression_type_string(info->compression_type));
2482 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2484 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2485 tprintf(T("Boot Index: %d\n"), info->boot_index);
2486 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2488 attr_string[0] = T('\0');
2491 tstrcat(attr_string, T("Pipable, "));
2493 if (info->has_integrity_table)
2494 tstrcat(attr_string, T("Integrity info, "));
2496 if (info->has_rpfix)
2497 tstrcat(attr_string, T("Relative path junction, "));
2499 if (info->resource_only)
2500 tstrcat(attr_string, T("Resource only, "));
2502 if (info->metadata_only)
2503 tstrcat(attr_string, T("Metadata only, "));
2505 if (info->is_marked_readonly)
2506 tstrcat(attr_string, T("Readonly, "));
2508 p = tstrchr(attr_string, T('\0'));
2509 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2512 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2516 print_resource(const struct wimlib_resource_entry *resource,
2519 tprintf(T("Hash = 0x"));
2520 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2523 if (!resource->is_missing) {
2524 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2525 resource->uncompressed_size);
2526 if (resource->packed) {
2527 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2528 "bytes @ offset %"PRIu64"\n"),
2529 resource->raw_resource_uncompressed_size,
2530 resource->raw_resource_compressed_size,
2531 resource->raw_resource_offset_in_wim);
2533 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2536 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2537 resource->compressed_size);
2539 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2543 tprintf(T("Part Number = %u\n"), resource->part_number);
2544 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2546 tprintf(T("Flags = "));
2547 if (resource->is_compressed)
2548 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2549 if (resource->is_metadata)
2550 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2551 if (resource->is_free)
2552 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2553 if (resource->is_spanned)
2554 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2555 if (resource->packed)
2556 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2564 print_blobs(WIMStruct *wim)
2566 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2570 default_print_security_descriptor(const uint8_t *sd, size_t size)
2572 tprintf(T("Security Descriptor = "));
2573 print_byte_field(sd, size);
2578 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2582 "----------------------------------------------------------------------------\n"));
2583 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2584 if (dentry->dos_name)
2585 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2586 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2587 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2588 if (file_attr_flags[i].flag & dentry->attributes)
2589 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2590 file_attr_flags[i].name);
2592 if (dentry->security_descriptor) {
2593 print_security_descriptor(dentry->security_descriptor,
2594 dentry->security_descriptor_size);
2597 print_time(T("Creation Time"), &dentry->creation_time);
2598 print_time(T("Last Write Time"), &dentry->last_write_time);
2599 print_time(T("Last Access Time"), &dentry->last_access_time);
2602 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2603 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2605 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2606 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2608 if (dentry->unix_mode != 0) {
2609 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2610 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2611 dentry->unix_uid, dentry->unix_gid,
2612 dentry->unix_mode, dentry->unix_rdev);
2615 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2616 if (dentry->streams[i].stream_name) {
2617 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2618 dentry->streams[i].stream_name);
2619 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2620 tprintf(T("\tRaw encrypted data stream:\n"));
2621 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2622 tprintf(T("\tReparse point stream:\n"));
2624 tprintf(T("\tUnnamed data stream:\n"));
2626 print_resource(&dentry->streams[i].resource, NULL);
2631 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2633 const struct print_dentry_options *options = _options;
2634 if (!options->detailed)
2635 print_dentry_full_path(dentry);
2637 print_dentry_detailed(dentry);
2641 /* Print the files contained in an image(s) in a WIM file. */
2643 imagex_dir(int argc, tchar **argv, int cmd)
2645 const tchar *wimfile;
2646 WIMStruct *wim = NULL;
2649 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2651 struct print_dentry_options options = {
2654 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2656 STRING_SET(refglobs);
2658 for_opt(c, dir_options) {
2660 case IMAGEX_PATH_OPTION:
2663 case IMAGEX_DETAILED_OPTION:
2664 options.detailed = true;
2666 case IMAGEX_ONE_FILE_ONLY_OPTION:
2667 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2669 case IMAGEX_REF_OPTION:
2670 ret = string_set_append(&refglobs, optarg);
2672 goto out_free_refglobs;
2682 imagex_error(T("Must specify a WIM file"));
2686 imagex_error(T("Too many arguments"));
2691 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2692 imagex_progress_func, NULL);
2694 goto out_free_refglobs;
2697 image = wimlib_resolve_image(wim, argv[1]);
2698 ret = verify_image_exists(image, argv[1], wimfile);
2700 goto out_wimlib_free;
2702 /* No image specified; default to image 1, but only if the WIM
2703 * contains exactly one image. */
2705 struct wimlib_wim_info info;
2707 wimlib_get_wim_info(wim, &info);
2708 if (info.image_count != 1) {
2709 imagex_error(T("\"%"TS"\" contains %d images; Please "
2710 "select one (or all)."),
2711 wimfile, info.image_count);
2718 if (refglobs.num_strings) {
2719 ret = wim_reference_globs(wim, &refglobs, 0);
2721 goto out_wimlib_free;
2724 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2725 print_dentry, &options);
2726 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2727 struct wimlib_wim_info info;
2729 wimlib_get_wim_info(wim, &info);
2730 do_metadata_not_found_warning(wimfile, &info);
2735 string_set_destroy(&refglobs);
2739 usage(CMD_DIR, stderr);
2741 goto out_free_refglobs;
2744 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2747 imagex_export(int argc, tchar **argv, int cmd)
2751 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2752 int write_flags = 0;
2753 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2754 const tchar *src_wimfile;
2755 const tchar *src_image_num_or_name;
2756 const tchar *dest_wimfile;
2758 const tchar *dest_name;
2759 const tchar *dest_desc;
2761 struct wimlib_wim_info src_info;
2762 WIMStruct *dest_wim;
2767 STRING_SET(refglobs);
2768 unsigned num_threads = 0;
2769 uint32_t chunk_size = UINT32_MAX;
2770 uint32_t solid_chunk_size = UINT32_MAX;
2771 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2773 for_opt(c, export_options) {
2775 case IMAGEX_BOOT_OPTION:
2776 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2778 case IMAGEX_CHECK_OPTION:
2779 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2780 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2782 case IMAGEX_NOCHECK_OPTION:
2783 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2785 case IMAGEX_COMPRESS_OPTION:
2786 compression_type = get_compression_type(optarg);
2787 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2790 case IMAGEX_COMPRESS_SLOW_OPTION:
2791 set_compress_slow();
2792 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2794 case IMAGEX_RECOMPRESS_OPTION:
2795 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2797 case IMAGEX_SOLID_OPTION:
2798 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2800 case IMAGEX_NO_SOLID_SORT_OPTION:
2801 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2803 case IMAGEX_CHUNK_SIZE_OPTION:
2804 chunk_size = parse_chunk_size(optarg);
2805 if (chunk_size == UINT32_MAX)
2808 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2809 solid_chunk_size = parse_chunk_size(optarg);
2810 if (solid_chunk_size == UINT32_MAX)
2813 case IMAGEX_SOLID_COMPRESS_OPTION:
2814 solid_ctype = get_compression_type(optarg);
2815 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2818 case IMAGEX_REF_OPTION:
2819 ret = string_set_append(&refglobs, optarg);
2821 goto out_free_refglobs;
2823 case IMAGEX_THREADS_OPTION:
2824 num_threads = parse_num_threads(optarg);
2825 if (num_threads == UINT_MAX)
2828 case IMAGEX_REBUILD_OPTION:
2829 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2831 case IMAGEX_PIPABLE_OPTION:
2832 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2834 case IMAGEX_NOT_PIPABLE_OPTION:
2835 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2837 case IMAGEX_WIMBOOT_OPTION:
2838 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2846 if (argc < 3 || argc > 5)
2848 src_wimfile = argv[0];
2849 src_image_num_or_name = argv[1];
2850 dest_wimfile = argv[2];
2851 dest_name = (argc >= 4) ? argv[3] : NULL;
2852 dest_desc = (argc >= 5) ? argv[4] : NULL;
2853 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2854 imagex_progress_func, NULL);
2856 goto out_free_refglobs;
2858 wimlib_get_wim_info(src_wim, &src_info);
2860 /* Determine if the destination is an existing file or not. If so, we
2861 * try to append the exported image(s) to it; otherwise, we create a new
2862 * WIM containing the exported image(s). Furthermore, determine if we
2863 * need to write a pipable WIM directly to standard output. */
2865 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2867 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2868 imagex_error("Can't write a non-pipable WIM to "
2869 "standard output! Specify --pipable\n"
2870 " if you want to create a pipable WIM "
2871 "(but read the docs first).");
2873 goto out_free_src_wim;
2876 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2878 dest_wimfile = NULL;
2879 dest_wim_fd = STDOUT_FILENO;
2880 imagex_info_file = stderr;
2881 set_fd_to_binary_mode(dest_wim_fd);
2884 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2886 /* Destination file exists. */
2888 if (!S_ISREG(stbuf.st_mode)) {
2889 imagex_error(T("\"%"TS"\" is not a regular file"),
2892 goto out_free_src_wim;
2894 ret = wimlib_open_wim_with_progress(dest_wimfile,
2896 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2898 imagex_progress_func,
2901 goto out_free_src_wim;
2903 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2904 /* The user specified a compression type, but we're
2905 * exporting to an existing WIM. Make sure the
2906 * specified compression type is the same as the
2907 * compression type of the existing destination WIM. */
2908 struct wimlib_wim_info dest_info;
2910 wimlib_get_wim_info(dest_wim, &dest_info);
2911 if (compression_type != dest_info.compression_type) {
2912 imagex_error(T("Cannot specify a compression type that is "
2913 "not the same as that used in the "
2914 "destination WIM"));
2916 goto out_free_dest_wim;
2922 if (errno != ENOENT) {
2923 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2926 goto out_free_src_wim;
2929 /* dest_wimfile is not an existing file, so create a new WIM. */
2931 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2932 /* The user did not specify a compression type; default
2933 * to that of the source WIM, unless --solid or
2934 * --wimboot was specified. */
2936 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2937 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2938 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2939 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2941 compression_type = src_info.compression_type;
2943 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2945 goto out_free_src_wim;
2947 wimlib_register_progress_function(dest_wim,
2948 imagex_progress_func, NULL);
2950 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2951 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2953 /* For --wimboot export, use small XPRESS chunks. */
2954 wimlib_set_output_chunk_size(dest_wim, 4096);
2955 } else if (compression_type == src_info.compression_type &&
2956 chunk_size == UINT32_MAX)
2958 /* Use same chunk size if compression type is the same. */
2959 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2963 if (chunk_size != UINT32_MAX) {
2964 /* Set destination chunk size. */
2965 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2967 goto out_free_dest_wim;
2969 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2970 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2972 goto out_free_dest_wim;
2974 if (solid_chunk_size != UINT32_MAX) {
2975 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2977 goto out_free_dest_wim;
2980 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2981 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2983 goto out_free_dest_wim;
2985 if (refglobs.num_strings) {
2986 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2988 goto out_free_dest_wim;
2991 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2992 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2994 imagex_error(T("--boot specified for all-images export, but source WIM "
2995 "has no bootable image."));
2997 goto out_free_dest_wim;
3000 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3001 dest_desc, export_flags);
3003 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3004 do_resource_not_found_warning(src_wimfile,
3005 &src_info, &refglobs);
3006 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3007 do_metadata_not_found_warning(src_wimfile, &src_info);
3009 goto out_free_dest_wim;
3013 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3014 else if (dest_wimfile)
3015 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3016 write_flags, num_threads);
3018 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3019 WIMLIB_ALL_IMAGES, write_flags,
3022 wimlib_free(dest_wim);
3024 wimlib_free(src_wim);
3026 string_set_destroy(&refglobs);
3030 usage(CMD_EXPORT, stderr);
3033 goto out_free_refglobs;
3036 /* Extract files or directories from a WIM image */
3038 imagex_extract(int argc, tchar **argv, int cmd)
3045 const tchar *wimfile;
3046 const tchar *image_num_or_name;
3047 tchar *dest_dir = T(".");
3048 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3049 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3050 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3051 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3053 STRING_SET(refglobs);
3055 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3057 for_opt(c, extract_options) {
3059 case IMAGEX_CHECK_OPTION:
3060 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3062 case IMAGEX_VERBOSE_OPTION:
3063 /* No longer does anything. */
3065 case IMAGEX_REF_OPTION:
3066 ret = string_set_append(&refglobs, optarg);
3068 goto out_free_refglobs;
3070 case IMAGEX_UNIX_DATA_OPTION:
3071 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3073 case IMAGEX_NO_ACLS_OPTION:
3074 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3076 case IMAGEX_STRICT_ACLS_OPTION:
3077 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3079 case IMAGEX_NO_ATTRIBUTES_OPTION:
3080 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3082 case IMAGEX_DEST_DIR_OPTION:
3085 case IMAGEX_TO_STDOUT_OPTION:
3086 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3087 imagex_info_file = stderr;
3088 imagex_be_quiet = true;
3089 set_fd_to_binary_mode(STDOUT_FILENO);
3091 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3092 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3093 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3095 case IMAGEX_NO_GLOBS_OPTION:
3096 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3098 case IMAGEX_NULLGLOB_OPTION:
3099 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3101 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3102 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3104 case IMAGEX_WIMBOOT_OPTION:
3105 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3107 case IMAGEX_COMPACT_OPTION:
3108 ret = set_compact_mode(optarg, &extract_flags);
3110 goto out_free_refglobs;
3122 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3123 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3125 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3130 image_num_or_name = argv[1];
3135 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3136 imagex_progress_func, NULL);
3138 goto out_free_refglobs;
3140 image = wimlib_resolve_image(wim, image_num_or_name);
3141 ret = verify_image_exists_and_is_single(image,
3145 goto out_wimlib_free;
3147 if (refglobs.num_strings) {
3148 ret = wim_reference_globs(wim, &refglobs, open_flags);
3150 goto out_wimlib_free;
3156 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3159 while (argc != 0 && ret == 0) {
3163 num_paths < argc && argv[num_paths][0] != T('@');
3168 ret = wimlib_extract_paths(wim, image, dest_dir,
3169 (const tchar **)argv,
3171 extract_flags | notlist_extract_flags);
3175 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3184 if (!imagex_be_quiet)
3185 imagex_printf(T("Done extracting files.\n"));
3186 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3187 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3188 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3189 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3190 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3193 T("Note: You can use the '--nullglob' "
3194 "option to ignore missing files.\n"));
3196 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3197 "files and directories\n"
3198 " are in the WIM image.\n"),
3199 get_cmd_string(CMD_DIR, false));
3200 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3201 struct wimlib_wim_info info;
3203 wimlib_get_wim_info(wim, &info);
3204 do_resource_not_found_warning(wimfile, &info, &refglobs);
3205 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3206 struct wimlib_wim_info info;
3208 wimlib_get_wim_info(wim, &info);
3209 do_metadata_not_found_warning(wimfile, &info);
3214 string_set_destroy(&refglobs);
3218 usage(CMD_EXTRACT, stderr);
3221 goto out_free_refglobs;
3224 /* Prints information about a WIM file; also can mark an image as bootable,
3225 * change the name of an image, or change the description of an image. */
3227 imagex_info(int argc, tchar **argv, int cmd)
3232 bool nocheck = false;
3233 bool header = false;
3236 bool short_header = true;
3237 const tchar *xml_out_file = NULL;
3238 const tchar *wimfile;
3239 const tchar *image_num_or_name;
3240 const tchar *new_name;
3241 const tchar *new_desc;
3246 struct wimlib_wim_info info;
3248 for_opt(c, info_options) {
3250 case IMAGEX_BOOT_OPTION:
3253 case IMAGEX_CHECK_OPTION:
3256 case IMAGEX_NOCHECK_OPTION:
3259 case IMAGEX_HEADER_OPTION:
3261 short_header = false;
3263 case IMAGEX_BLOBS_OPTION:
3265 short_header = false;
3267 case IMAGEX_XML_OPTION:
3269 short_header = false;
3271 case IMAGEX_EXTRACT_XML_OPTION:
3272 xml_out_file = optarg;
3273 short_header = false;
3275 case IMAGEX_METADATA_OPTION:
3276 imagex_error(T("The --metadata option has been removed. "
3277 "Use 'wimdir --detail' instead."));
3286 if (argc < 1 || argc > 4)
3290 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3291 new_name = (argc >= 3) ? argv[2] : NULL;
3292 new_desc = (argc >= 4) ? argv[3] : NULL;
3294 if (check && nocheck) {
3295 imagex_error(T("Can't specify both --check and --nocheck"));
3300 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3302 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3303 imagex_progress_func, NULL);
3307 wimlib_get_wim_info(wim, &info);
3309 image = wimlib_resolve_image(wim, image_num_or_name);
3310 ret = WIMLIB_ERR_INVALID_IMAGE;
3311 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3312 verify_image_exists(image, image_num_or_name, wimfile);
3314 imagex_error(T("If you would like to set the boot "
3315 "index to 0, specify image \"0\" with "
3316 "the --boot flag."));
3318 goto out_wimlib_free;
3321 if (boot && info.image_count == 0) {
3322 imagex_error(T("--boot is meaningless on a WIM with no images"));
3323 goto out_wimlib_free;
3326 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3328 imagex_error(T("Cannot specify the --boot flag "
3329 "without specifying a specific "
3330 "image in a multi-image WIM"));
3331 goto out_wimlib_free;
3334 imagex_error(T("Cannot specify the NEW_NAME "
3335 "without specifying a specific "
3336 "image in a multi-image WIM"));
3337 goto out_wimlib_free;
3341 /* Operations that print information are separated from operations that
3342 * recreate the WIM file. */
3343 if (!new_name && !boot) {
3345 /* Read-only operations */
3347 if (image == WIMLIB_NO_IMAGE) {
3348 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3349 image_num_or_name, wimfile);
3350 goto out_wimlib_free;
3353 if (image == WIMLIB_ALL_IMAGES && short_header)
3354 print_wim_information(wimfile, &info);
3357 wimlib_print_header(wim);
3360 if (info.total_parts != 1) {
3361 tfprintf(stderr, T("Warning: Only showing the blobs "
3362 "for part %d of a %d-part WIM.\n"),
3363 info.part_number, info.total_parts);
3369 ret = wimlib_extract_xml_data(wim, stdout);
3371 goto out_wimlib_free;
3377 fp = tfopen(xml_out_file, T("wb"));
3379 imagex_error_with_errno(T("Failed to open the "
3380 "file \"%"TS"\" for "
3384 goto out_wimlib_free;
3386 ret = wimlib_extract_xml_data(wim, fp);
3388 imagex_error(T("Failed to close the file "
3394 goto out_wimlib_free;
3398 wimlib_print_available_images(wim, image);
3403 /* Modification operations */
3405 if (image == WIMLIB_ALL_IMAGES)
3408 if (image == WIMLIB_NO_IMAGE && new_name) {
3409 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3410 "when using image 0"), new_name);
3412 goto out_wimlib_free;
3416 if (image == info.boot_index) {
3417 imagex_printf(T("Image %d is already marked as "
3418 "bootable.\n"), image);
3421 imagex_printf(T("Marking image %d as bootable.\n"),
3423 info.boot_index = image;
3424 ret = wimlib_set_wim_info(wim, &info,
3425 WIMLIB_CHANGE_BOOT_INDEX);
3427 goto out_wimlib_free;
3431 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3433 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3437 imagex_printf(T("Changing the name of image %d to "
3438 "\"%"TS"\".\n"), image, new_name);
3439 ret = wimlib_set_image_name(wim, image, new_name);
3441 goto out_wimlib_free;
3445 const tchar *old_desc;
3446 old_desc = wimlib_get_image_description(wim, image);
3447 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3448 imagex_printf(T("The description of image %d is already "
3449 "\"%"TS"\".\n"), image, new_desc);
3452 imagex_printf(T("Changing the description of image %d "
3453 "to \"%"TS"\".\n"), image, new_desc);
3454 ret = wimlib_set_image_descripton(wim, image,
3457 goto out_wimlib_free;
3461 /* Only call wimlib_overwrite() if something actually needs to
3463 if (boot || new_name || new_desc ||
3464 (check && !info.has_integrity_table) ||
3465 (nocheck && info.has_integrity_table))
3467 int write_flags = 0;
3470 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3472 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3473 ret = wimlib_overwrite(wim, write_flags, 1);
3475 imagex_printf(T("The file \"%"TS"\" was not modified "
3476 "because nothing needed to be done.\n"),
3487 usage(CMD_INFO, stderr);
3493 /* Join split WIMs into one part WIM */
3495 imagex_join(int argc, tchar **argv, int cmd)
3498 int swm_open_flags = 0;
3499 int wim_write_flags = 0;
3500 const tchar *output_path;
3503 for_opt(c, join_options) {
3505 case IMAGEX_CHECK_OPTION:
3506 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3507 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3517 imagex_error(T("Must specify one or more split WIM (.swm) "
3521 output_path = argv[0];
3522 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3527 imagex_progress_func,
3533 usage(CMD_JOIN, stderr);
3538 #if WIM_MOUNTING_SUPPORTED
3540 /* Mounts a WIM image. */
3542 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3545 int mount_flags = 0;
3547 const tchar *staging_dir = NULL;
3548 const tchar *wimfile;
3551 struct wimlib_wim_info info;
3555 STRING_SET(refglobs);
3557 if (cmd == CMD_MOUNTRW) {
3558 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3559 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3562 for_opt(c, mount_options) {
3564 case IMAGEX_ALLOW_OTHER_OPTION:
3565 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3567 case IMAGEX_CHECK_OPTION:
3568 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3570 case IMAGEX_DEBUG_OPTION:
3571 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3573 case IMAGEX_STREAMS_INTERFACE_OPTION:
3574 if (!tstrcasecmp(optarg, T("none")))
3575 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3576 else if (!tstrcasecmp(optarg, T("xattr")))
3577 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3578 else if (!tstrcasecmp(optarg, T("windows")))
3579 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3581 imagex_error(T("Unknown stream interface \"%"TS"\""),
3586 case IMAGEX_REF_OPTION:
3587 ret = string_set_append(&refglobs, optarg);
3589 goto out_free_refglobs;
3591 case IMAGEX_STAGING_DIR_OPTION:
3592 staging_dir = optarg;
3594 case IMAGEX_UNIX_DATA_OPTION:
3595 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3603 if (argc != 2 && argc != 3)
3608 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3609 imagex_progress_func, NULL);
3611 goto out_free_refglobs;
3613 wimlib_get_wim_info(wim, &info);
3616 /* Image explicitly specified. */
3617 image = wimlib_resolve_image(wim, argv[1]);
3619 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3623 /* No image specified; default to image 1, but only if the WIM
3624 * contains exactly one image. */
3626 if (info.image_count != 1) {
3627 imagex_error(T("\"%"TS"\" contains %d images; Please "
3628 "select one."), wimfile, info.image_count);
3636 if (refglobs.num_strings) {
3637 ret = wim_reference_globs(wim, &refglobs, open_flags);
3642 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3644 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3645 do_metadata_not_found_warning(wimfile, &info);
3647 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3649 image, wimfile, dir);
3655 string_set_destroy(&refglobs);
3661 goto out_free_refglobs;
3663 #endif /* WIM_MOUNTING_SUPPORTED */
3665 /* Rebuild a WIM file */
3667 imagex_optimize(int argc, tchar **argv, int cmd)
3670 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3671 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3672 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3673 uint32_t chunk_size = UINT32_MAX;
3674 uint32_t solid_chunk_size = UINT32_MAX;
3675 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3678 const tchar *wimfile;
3681 unsigned num_threads = 0;
3683 for_opt(c, optimize_options) {
3685 case IMAGEX_CHECK_OPTION:
3686 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3687 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3689 case IMAGEX_NOCHECK_OPTION:
3690 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3692 case IMAGEX_COMPRESS_OPTION:
3693 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3694 compression_type = get_compression_type(optarg);
3695 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3698 case IMAGEX_COMPRESS_SLOW_OPTION:
3699 set_compress_slow();
3700 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3702 case IMAGEX_RECOMPRESS_OPTION:
3703 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3705 case IMAGEX_CHUNK_SIZE_OPTION:
3706 chunk_size = parse_chunk_size(optarg);
3707 if (chunk_size == UINT32_MAX)
3710 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3711 solid_chunk_size = parse_chunk_size(optarg);
3712 if (solid_chunk_size == UINT32_MAX)
3715 case IMAGEX_SOLID_COMPRESS_OPTION:
3716 solid_ctype = get_compression_type(optarg);
3717 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3720 case IMAGEX_SOLID_OPTION:
3721 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3722 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3724 case IMAGEX_NO_SOLID_SORT_OPTION:
3725 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3727 case IMAGEX_THREADS_OPTION:
3728 num_threads = parse_num_threads(optarg);
3729 if (num_threads == UINT_MAX)
3732 case IMAGEX_PIPABLE_OPTION:
3733 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3735 case IMAGEX_NOT_PIPABLE_OPTION:
3736 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3750 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3751 imagex_progress_func, NULL);
3755 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3756 /* Change compression type. */
3757 ret = wimlib_set_output_compression_type(wim, compression_type);
3759 goto out_wimlib_free;
3762 if (chunk_size != UINT32_MAX) {
3763 /* Change chunk size. */
3764 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3766 goto out_wimlib_free;
3768 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3769 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3771 goto out_wimlib_free;
3773 if (solid_chunk_size != UINT32_MAX) {
3774 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3776 goto out_wimlib_free;
3779 old_size = file_get_size(wimfile);
3780 tprintf(T("\"%"TS"\" original size: "), wimfile);
3782 tputs(T("Unknown"));
3784 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3786 ret = wimlib_overwrite(wim, write_flags, num_threads);
3788 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3789 goto out_wimlib_free;
3792 new_size = file_get_size(wimfile);
3793 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3795 tputs(T("Unknown"));
3797 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3799 tfputs(T("Space saved: "), stdout);
3800 if (new_size != -1 && old_size != -1) {
3801 tprintf(T("%lld KiB\n"),
3802 ((long long)old_size - (long long)new_size) >> 10);
3804 tputs(T("Unknown"));
3813 usage(CMD_OPTIMIZE, stderr);
3819 /* Split a WIM into a spanned set */
3821 imagex_split(int argc, tchar **argv, int cmd)
3825 int write_flags = 0;
3826 unsigned long part_size;
3831 for_opt(c, split_options) {
3833 case IMAGEX_CHECK_OPTION:
3834 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3835 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3847 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3848 if (tmp == argv[2] || *tmp) {
3849 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3850 imagex_error(T("The part size must be an integer or "
3851 "floating-point number of megabytes."));
3854 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3855 imagex_progress_func, NULL);
3859 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3865 usage(CMD_SPLIT, stderr);
3871 #if WIM_MOUNTING_SUPPORTED
3872 /* Unmounts a mounted WIM image. */
3874 imagex_unmount(int argc, tchar **argv, int cmd)
3877 int unmount_flags = 0;
3880 for_opt(c, unmount_options) {
3882 case IMAGEX_COMMIT_OPTION:
3883 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3885 case IMAGEX_CHECK_OPTION:
3886 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3888 case IMAGEX_REBUILD_OPTION:
3889 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3891 case IMAGEX_LAZY_OPTION:
3892 case IMAGEX_FORCE_OPTION:
3893 /* Now, unmount is lazy by default. However, committing
3894 * the image will fail with
3895 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3896 * file descriptors on the WIM image. The
3897 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3898 * descriptors to be closed. */
3899 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3901 case IMAGEX_NEW_IMAGE_OPTION:
3902 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3913 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3914 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3915 imagex_error(T("--new-image is meaningless "
3916 "without --commit also specified!"));
3921 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3922 imagex_progress_func, NULL);
3924 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3925 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3927 "\tNote: Use --commit --force to force changes "
3928 "to be committed, regardless\n"
3929 "\t of open files.\n"));
3936 usage(CMD_UNMOUNT, stderr);
3941 #endif /* WIM_MOUNTING_SUPPORTED */
3944 * Add, delete, or rename files in a WIM image.
3947 imagex_update(int argc, tchar **argv, int cmd)
3949 const tchar *wimfile;
3953 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3954 int write_flags = 0;
3955 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3956 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3957 WIMLIB_ADD_FLAG_VERBOSE |
3958 WIMLIB_ADD_FLAG_WINCONFIG;
3959 int default_delete_flags = 0;
3960 unsigned num_threads = 0;
3962 tchar *cmd_file_contents;
3963 size_t cmd_file_nchars;
3964 struct wimlib_update_command *cmds;
3966 tchar *command_str = NULL;
3967 tchar *config_file = NULL;
3968 tchar *wimboot_config = NULL;
3970 for_opt(c, update_options) {
3972 /* Generic or write options */
3973 case IMAGEX_THREADS_OPTION:
3974 num_threads = parse_num_threads(optarg);
3975 if (num_threads == UINT_MAX)
3978 case IMAGEX_CHECK_OPTION:
3979 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3980 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3982 case IMAGEX_REBUILD_OPTION:
3983 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3985 case IMAGEX_COMMAND_OPTION:
3987 imagex_error(T("--command may only be specified "
3988 "one time. Please provide\n"
3989 " the update commands "
3990 "on standard input instead."));
3993 command_str = tstrdup(optarg);
3995 imagex_error(T("Out of memory!"));
3999 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4000 wimboot_config = optarg;
4002 /* Default delete options */
4003 case IMAGEX_FORCE_OPTION:
4004 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4006 case IMAGEX_RECURSIVE_OPTION:
4007 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4010 /* Global add option */
4011 case IMAGEX_CONFIG_OPTION:
4012 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4013 config_file = optarg;
4016 /* Default add options */
4017 case IMAGEX_VERBOSE_OPTION:
4018 /* No longer does anything. */
4020 case IMAGEX_DEREFERENCE_OPTION:
4021 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4023 case IMAGEX_UNIX_DATA_OPTION:
4024 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4026 case IMAGEX_NO_ACLS_OPTION:
4027 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4029 case IMAGEX_STRICT_ACLS_OPTION:
4030 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4032 case IMAGEX_NO_REPLACE_OPTION:
4033 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4042 if (argc != 1 && argc != 2)
4046 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4047 imagex_progress_func, NULL);
4049 goto out_free_command_str;
4052 /* Image explicitly specified. */
4053 image = wimlib_resolve_image(wim, argv[1]);
4054 ret = verify_image_exists_and_is_single(image, argv[1],
4057 goto out_wimlib_free;
4059 /* No image specified; default to image 1, but only if the WIM
4060 * contains exactly one image. */
4061 struct wimlib_wim_info info;
4063 wimlib_get_wim_info(wim, &info);
4064 if (info.image_count != 1) {
4065 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4066 wimfile, info.image_count);
4073 /* Read update commands from standard input, or the command string if
4076 cmd_file_contents = NULL;
4077 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4081 goto out_free_cmd_file_contents;
4083 } else if (!wimboot_config) {
4084 if (isatty(STDIN_FILENO)) {
4085 tputs(T("Reading update commands from standard input..."));
4086 recommend_man_page(CMD_UPDATE, stdout);
4088 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4089 if (!cmd_file_contents) {
4091 goto out_wimlib_free;
4094 /* Parse the update commands */
4095 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4099 goto out_free_cmd_file_contents;
4102 cmd_file_contents = NULL;
4107 /* Set default flags and capture config on the update commands */
4108 for (size_t i = 0; i < num_cmds; i++) {
4109 switch (cmds[i].op) {
4110 case WIMLIB_UPDATE_OP_ADD:
4111 cmds[i].add.add_flags |= default_add_flags;
4112 cmds[i].add.config_file = config_file;
4114 case WIMLIB_UPDATE_OP_DELETE:
4115 cmds[i].delete_.delete_flags |= default_delete_flags;
4122 /* Execute the update commands */
4123 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4127 if (wimboot_config) {
4128 /* --wimboot-config=FILE is short for an
4129 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4131 struct wimlib_update_command cmd;
4133 cmd.op = WIMLIB_UPDATE_OP_ADD;
4134 cmd.add.fs_source_path = wimboot_config;
4135 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4136 cmd.add.config_file = NULL;
4137 cmd.add.add_flags = 0;
4139 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4144 /* Overwrite the updated WIM */
4145 ret = wimlib_overwrite(wim, write_flags, num_threads);
4148 out_free_cmd_file_contents:
4149 free(cmd_file_contents);
4152 out_free_command_str:
4157 usage(CMD_UPDATE, stderr);
4160 goto out_free_command_str;
4163 /* Verify a WIM file. */
4165 imagex_verify(int argc, tchar **argv, int cmd)
4168 const tchar *wimfile;
4170 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4171 int verify_flags = 0;
4172 STRING_SET(refglobs);
4175 for_opt(c, verify_options) {
4177 case IMAGEX_REF_OPTION:
4178 ret = string_set_append(&refglobs, optarg);
4180 goto out_free_refglobs;
4182 case IMAGEX_NOCHECK_OPTION:
4183 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4195 imagex_error(T("Must specify a WIM file!"));
4197 imagex_error(T("At most one WIM file can be specified!"));
4203 ret = wimlib_open_wim_with_progress(wimfile,
4206 imagex_progress_func,
4209 goto out_free_refglobs;
4211 ret = wim_reference_globs(wim, &refglobs, open_flags);
4213 goto out_wimlib_free;
4215 ret = wimlib_verify_wim(wim, verify_flags);
4217 tputc(T('\n'), stderr);
4218 imagex_error(T("\"%"TS"\" failed verification!"),
4220 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4221 refglobs.num_strings == 0)
4223 imagex_printf(T("Note: if this WIM file is not standalone, "
4224 "use the --ref option to specify the other parts.\n"));
4227 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4234 string_set_destroy(&refglobs);
4238 usage(CMD_VERIFY, stderr);
4240 goto out_free_refglobs;
4243 struct imagex_command {
4245 int (*func)(int argc, tchar **argv, int cmd);
4248 static const struct imagex_command imagex_commands[] = {
4249 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4250 [CMD_APPLY] = {T("apply"), imagex_apply},
4251 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4252 [CMD_DELETE] = {T("delete"), imagex_delete},
4253 [CMD_DIR ] = {T("dir"), imagex_dir},
4254 [CMD_EXPORT] = {T("export"), imagex_export},
4255 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4256 [CMD_INFO] = {T("info"), imagex_info},
4257 [CMD_JOIN] = {T("join"), imagex_join},
4258 #if WIM_MOUNTING_SUPPORTED
4259 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4260 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4262 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4263 [CMD_SPLIT] = {T("split"), imagex_split},
4264 #if WIM_MOUNTING_SUPPORTED
4265 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4267 [CMD_UPDATE] = {T("update"), imagex_update},
4268 [CMD_VERIFY] = {T("verify"), imagex_verify},
4273 /* Can be a directory or source list file. But source list file is probably
4274 * a rare use case, so just say directory. */
4275 # define SOURCE_STR T("DIRECTORY")
4277 /* Can only be a directory */
4278 # define TARGET_STR T("DIRECTORY")
4281 /* Can be a directory, NTFS volume, or source list file. */
4282 # define SOURCE_STR T("SOURCE")
4284 /* Can be a directory or NTFS volume. */
4285 # define TARGET_STR T("TARGET")
4289 static const tchar *usage_strings[] = {
4292 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4293 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4294 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4295 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4296 " [--wimboot] [--unix-data] [--dereference]\n"
4300 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4301 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4302 " [--no-attributes] [--rpfix] [--norpfix]\n"
4303 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4304 " [--compact=FORMAT]\n"
4308 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4309 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4310 " [--config=FILE] [--threads=NUM_THREADS]\n"
4311 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4312 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4313 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4317 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4321 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4325 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4326 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4327 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4328 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4329 " [--wimboot] [--solid]\n"
4333 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4334 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4335 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4336 " [--no-attributes] [--include-invalid-names]\n"
4337 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4341 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4342 " [--boot] [--check] [--nocheck] [--xml]\n"
4343 " [--extract-xml FILE] [--header] [--blobs]\n"
4347 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4349 #if WIM_MOUNTING_SUPPORTED
4352 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4353 " [--check] [--streams-interface=INTERFACE]\n"
4354 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4358 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4359 " [--check] [--streams-interface=INTERFACE]\n"
4360 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4366 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4367 " [--check] [--nocheck] [--solid]\n"
4372 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4374 #if WIM_MOUNTING_SUPPORTED
4377 " %"TS" DIRECTORY\n"
4378 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4383 " %"TS" WIMFILE [IMAGE]\n"
4384 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4385 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4386 " [--command=STRING] [--wimboot-config=FILE]\n"
4391 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4395 static const tchar *invocation_name;
4396 static int invocation_cmd = CMD_NONE;
4398 static const tchar *get_cmd_string(int cmd, bool nospace)
4400 static tchar buf[50];
4401 if (cmd == CMD_NONE) {
4402 return T("wimlib-imagex");
4403 } else if (invocation_cmd != CMD_NONE) {
4404 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4406 const tchar *format;
4409 format = T("%"TS"-%"TS"");
4411 format = T("%"TS" %"TS"");
4412 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4420 static const tchar *s =
4422 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4423 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4424 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4425 "This is free software: you are free to change and redistribute it.\n"
4426 "There is NO WARRANTY, to the extent permitted by law.\n"
4428 "Report bugs to "PACKAGE_BUGREPORT".\n"
4435 help_or_version(int argc, tchar **argv, int cmd)
4440 for (i = 1; i < argc; i++) {
4442 if (p[0] == T('-') && p[1] == T('-')) {
4444 if (!tstrcmp(p, T("help"))) {
4445 if (cmd == CMD_NONE)
4450 } else if (!tstrcmp(p, T("version"))) {
4459 print_usage_string(int cmd, FILE *fp)
4461 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4465 recommend_man_page(int cmd, FILE *fp)
4467 const tchar *format_str;
4469 format_str = T("Some uncommon options are not listed;\n"
4470 "See %"TS".pdf in the doc directory for more details.\n");
4472 format_str = T("Some uncommon options are not listed;\n"
4473 "Try `man %"TS"' for more details.\n");
4475 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4479 usage(int cmd, FILE *fp)
4481 tfprintf(fp, T("Usage:\n"));
4482 print_usage_string(cmd, fp);
4483 tfprintf(fp, T("\n"));
4484 recommend_man_page(cmd, fp);
4490 tfprintf(fp, T("Usage:\n"));
4491 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4492 print_usage_string(cmd, fp);
4493 tfprintf(fp, T("\n"));
4495 static const tchar *extra =
4498 " %"TS" --version\n"
4501 tfprintf(fp, extra, invocation_name, invocation_name);
4503 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4504 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4505 "For some commands IMAGE may be \"all\".\n"
4507 recommend_man_page(CMD_NONE, fp);
4510 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4511 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4512 * something else), while on Windows the command arguments will be UTF-16LE
4513 * encoded 'wchar_t' strings. */
4516 wmain(int argc, wchar_t **argv, wchar_t **envp)
4518 main(int argc, char **argv)
4525 imagex_info_file = stdout;
4526 invocation_name = tbasename(argv[0]);
4529 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4530 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4534 setlocale(LC_ALL, "");
4535 codeset = nl_langinfo(CODESET);
4536 if (!strstr(codeset, "UTF-8") &&
4537 !strstr(codeset, "UTF8") &&
4538 !strstr(codeset, "utf-8") &&
4539 !strstr(codeset, "utf8"))
4542 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4543 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4544 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4545 " to any value to force wimlib to use UTF-8.\n",
4551 #endif /* !__WIN32__ */
4554 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4555 if (igcase != NULL) {
4556 if (!tstrcmp(igcase, T("no")) ||
4557 !tstrcmp(igcase, T("0")))
4558 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4559 else if (!tstrcmp(igcase, T("yes")) ||
4560 !tstrcmp(igcase, T("1")))
4561 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4564 "WARNING: Ignoring unknown setting of "
4565 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4570 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4572 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4573 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4574 for (int i = 0; i < CMD_MAX; i++) {
4575 if (!tstrcmp(invocation_name + 3,
4576 imagex_commands[i].name))
4585 /* Unless already known from the invocation name, determine which
4586 * command was specified. */
4587 if (cmd == CMD_NONE) {
4589 imagex_error(T("No command specified!\n"));
4593 for (int i = 0; i < CMD_MAX; i++) {
4594 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4599 if (cmd != CMD_NONE) {
4605 /* Handle --help and --version. --help can be either for the program as
4606 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4607 * CMD_NONE). Note: help_or_version() will not return if a --help or
4608 * --version argument was found. */
4609 help_or_version(argc, argv, cmd);
4611 /* Bail if a valid command was not specified. */
4612 if (cmd == CMD_NONE) {
4613 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4618 /* Enable warning and error messages in wimlib to be more user-friendly.
4620 wimlib_set_print_errors(true);
4622 /* Initialize wimlib. */
4623 ret = wimlib_global_init(init_flags);
4625 goto out_check_status;
4627 /* Call the command handler function. */
4628 ret = imagex_commands[cmd].func(argc, argv, cmd);
4630 /* Check for error writing to standard output, especially since for some
4631 * commands, writing to standard output is part of the program's actual
4632 * behavior and not just for informational purposes. */
4633 if (ferror(stdout) || fclose(stdout)) {
4634 imagex_error_with_errno(T("error writing to standard output"));
4639 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4640 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4641 * error code from which an error message can be printed. */
4643 imagex_error(T("Exiting with error code %d:\n"
4645 wimlib_get_error_string(ret));
4646 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4647 imagex_error_with_errno(T("errno"));
4649 /* Make wimlib free any resources it's holding (although this is not
4650 * strictly necessary because the process is ending anyway). */
4651 wimlib_global_cleanup();