4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012-2018 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 only_short_form);
136 static FILE *imagex_info_file;
138 #define imagex_printf(format, ...) \
139 if (imagex_info_file) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
142 static void imagex_suppress_output(void)
144 imagex_info_file = NULL;
147 static void imagex_output_to_stderr(void)
149 if (imagex_info_file)
150 imagex_info_file = stderr;
153 static void imagex_flush_output(void)
155 if (imagex_info_file)
156 fflush(imagex_info_file);
160 IMAGEX_ALLOW_OTHER_OPTION,
164 IMAGEX_CHUNK_SIZE_OPTION,
165 IMAGEX_COMMAND_OPTION,
166 IMAGEX_COMMIT_OPTION,
167 IMAGEX_COMPACT_OPTION,
168 IMAGEX_COMPRESS_OPTION,
169 IMAGEX_CONFIG_OPTION,
171 IMAGEX_DELTA_FROM_OPTION,
172 IMAGEX_DEREFERENCE_OPTION,
173 IMAGEX_DEST_DIR_OPTION,
174 IMAGEX_DETAILED_OPTION,
175 IMAGEX_EXTRACT_XML_OPTION,
178 IMAGEX_HEADER_OPTION,
179 IMAGEX_IMAGE_PROPERTY_OPTION,
180 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
182 IMAGEX_METADATA_OPTION,
183 IMAGEX_NEW_IMAGE_OPTION,
184 IMAGEX_NOCHECK_OPTION,
185 IMAGEX_NORPFIX_OPTION,
186 IMAGEX_NOT_PIPABLE_OPTION,
187 IMAGEX_NO_ACLS_OPTION,
188 IMAGEX_NO_ATTRIBUTES_OPTION,
189 IMAGEX_NO_GLOBS_OPTION,
190 IMAGEX_NO_REPLACE_OPTION,
191 IMAGEX_NO_SOLID_SORT_OPTION,
192 IMAGEX_NULLGLOB_OPTION,
193 IMAGEX_ONE_FILE_ONLY_OPTION,
195 IMAGEX_PIPABLE_OPTION,
196 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
197 IMAGEX_REBUILD_OPTION,
198 IMAGEX_RECOMPRESS_OPTION,
199 IMAGEX_RECURSIVE_OPTION,
202 IMAGEX_SNAPSHOT_OPTION,
204 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
205 IMAGEX_SOLID_COMPRESS_OPTION,
207 IMAGEX_SOURCE_LIST_OPTION,
208 IMAGEX_STAGING_DIR_OPTION,
209 IMAGEX_STREAMS_INTERFACE_OPTION,
210 IMAGEX_STRICT_ACLS_OPTION,
211 IMAGEX_THREADS_OPTION,
212 IMAGEX_TO_STDOUT_OPTION,
213 IMAGEX_UNIX_DATA_OPTION,
214 IMAGEX_UNSAFE_COMPACT_OPTION,
215 IMAGEX_UPDATE_OF_OPTION,
216 IMAGEX_VERBOSE_OPTION,
217 IMAGEX_WIMBOOT_CONFIG_OPTION,
218 IMAGEX_WIMBOOT_OPTION,
222 static const struct option apply_options[] = {
223 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
224 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
225 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
226 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
227 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
228 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
229 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
230 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
231 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
232 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
233 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
234 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
235 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
239 static const struct option capture_or_append_options[] = {
240 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
241 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
242 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
243 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
244 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
245 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
246 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
247 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
248 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
249 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
250 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
251 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
252 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
253 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
254 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
255 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
256 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
257 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
258 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
259 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
260 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
261 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
262 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
263 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
264 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
265 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
266 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
267 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
268 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
269 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
270 {T("snapshot"), no_argument, NULL, IMAGEX_SNAPSHOT_OPTION},
274 static const struct option delete_options[] = {
275 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
276 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
277 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
281 static const struct option dir_options[] = {
282 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
283 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
284 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
285 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
289 static const struct option export_options[] = {
290 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
291 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
292 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
293 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
294 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
295 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
296 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
297 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
298 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
299 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
300 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
301 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
302 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
303 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
304 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
305 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
306 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
307 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
311 static const struct option extract_options[] = {
312 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
313 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
314 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
315 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
316 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
317 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
318 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
319 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
320 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
321 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
322 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
323 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
324 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
325 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
326 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
327 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
328 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
332 static const struct option info_options[] = {
333 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
334 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
335 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
336 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
337 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
338 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
339 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
340 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
341 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
342 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
346 static const struct option join_options[] = {
347 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
351 #if WIM_MOUNTING_SUPPORTED
352 static const struct option mount_options[] = {
353 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
354 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
355 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
356 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
357 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
358 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
359 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
364 static const struct option optimize_options[] = {
365 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
366 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
367 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
368 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
369 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
370 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
371 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
372 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
373 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
374 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
375 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
376 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
377 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
378 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
382 static const struct option split_options[] = {
383 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
387 #if WIM_MOUNTING_SUPPORTED
388 static const struct option unmount_options[] = {
389 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
390 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
391 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
392 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
393 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
394 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
399 static const struct option update_options[] = {
400 /* Careful: some of the options here set the defaults for update
401 * commands, but the flags given to an actual update command (and not to
402 * `imagex update' itself are also handled in
403 * update_command_add_option(). */
404 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
405 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
406 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
407 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
408 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
410 /* Default delete options */
411 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
412 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
414 /* Global add option */
415 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
417 /* Default add options */
418 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
419 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
420 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
421 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
422 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
423 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
424 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
425 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
430 static const struct option verify_options[] = {
431 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
432 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
438 # define _format_attribute(type, format_str, args_start) \
439 __attribute__((format(type, format_str, args_start)))
441 # define _format_attribute(type, format_str, args_start)
444 /* Print formatted error message to stderr. */
445 static void _format_attribute(printf, 1, 2)
446 imagex_error(const tchar *format, ...)
449 va_start(va, format);
450 tfputs(T("ERROR: "), stderr);
451 tvfprintf(stderr, format, va);
452 tputc(T('\n'), stderr);
456 /* Print formatted error message to stderr. */
457 static void _format_attribute(printf, 1, 2)
458 imagex_error_with_errno(const tchar *format, ...)
460 int errno_save = errno;
462 va_start(va, format);
463 tfputs(T("ERROR: "), stderr);
464 tvfprintf(stderr, format, va);
465 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
470 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
472 if (image == WIMLIB_NO_IMAGE) {
473 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
474 " Please specify a 1-based image index or "
475 "image name. To list the images\n"
476 " contained in the WIM archive, run\n"
478 " %"TS" \"%"TS"\"\n"),
479 image_name, wim_name,
480 get_cmd_string(CMD_INFO, false), wim_name);
481 return WIMLIB_ERR_INVALID_IMAGE;
487 verify_image_is_single(int image)
489 if (image == WIMLIB_ALL_IMAGES) {
490 imagex_error(T("Cannot specify all images for this action!"));
491 return WIMLIB_ERR_INVALID_IMAGE;
497 verify_image_exists_and_is_single(int image, const tchar *image_name,
498 const tchar *wim_name)
501 ret = verify_image_exists(image, image_name, wim_name);
503 ret = verify_image_is_single(image);
508 print_available_compression_types(FILE *fp)
510 static const tchar * const s =
512 "Available compression types:\n"
515 " xpress (alias: \"fast\")\n"
516 " lzx (alias: \"maximum\") (default for capture)\n"
517 " lzms (alias: \"recovery\")\n"
523 /* Parse the argument to --compress or --solid-compress */
525 get_compression_type(tchar *optarg, bool solid)
528 unsigned int compression_level = 0;
531 plevel = tstrchr(optarg, T(':'));
537 ultmp = tstrtoul(plevel, &ptmp, 10);
538 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
539 imagex_error(T("Compression level must be a positive integer! "
540 "e.g. --compress=lzx:80"));
541 return WIMLIB_COMPRESSION_TYPE_INVALID;
543 compression_level = ultmp;
546 if (!tstrcasecmp(optarg, T("maximum")) ||
547 !tstrcasecmp(optarg, T("lzx")) ||
548 !tstrcasecmp(optarg, T("max"))) {
549 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
550 } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
551 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
552 } else if (!tstrcasecmp(optarg, T("recovery"))) {
556 "Warning: use of '--compress=recovery' is discouraged because it behaves\n"
557 " differently from DISM. Instead, you typically want to use '--solid' to\n"
558 " create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
559 " /compress:recovery. But if you really want *non-solid* LZMS compression,\n"
560 " then you may suppress this warning by specifying '--compress=lzms' instead\n"
561 " of '--compress=recovery'.\n"));
563 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
564 } else if (!tstrcasecmp(optarg, T("lzms"))) {
565 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
566 } else if (!tstrcasecmp(optarg, T("none"))) {
567 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
569 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
570 print_available_compression_types(stderr);
571 return WIMLIB_COMPRESSION_TYPE_INVALID;
574 if (compression_level != 0)
575 wimlib_set_default_compression_level(ctype, compression_level);
579 /* Parse the argument to --compact */
581 set_compact_mode(const tchar *arg, int *extract_flags)
584 if (!tstrcasecmp(arg, T("xpress4k")))
585 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
586 else if (!tstrcasecmp(arg, T("xpress8k")))
587 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
588 else if (!tstrcasecmp(arg, T("xpress16k")))
589 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
590 else if (!tstrcasecmp(arg, T("lzx")))
591 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
594 *extract_flags |= flag;
599 "\"%"TS"\" is not a recognized System Compression format. The options are:"
601 " --compact=xpress4k\n"
602 " --compact=xpress8k\n"
603 " --compact=xpress16k\n"
612 unsigned num_strings;
613 unsigned num_alloc_strings;
616 #define STRING_LIST_INITIALIZER \
617 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
619 #define STRING_LIST(_strings) \
620 struct string_list _strings = STRING_LIST_INITIALIZER
623 string_list_append(struct string_list *list, tchar *glob)
625 unsigned num_alloc_strings = list->num_alloc_strings;
627 if (list->num_strings == num_alloc_strings) {
630 num_alloc_strings += 4;
631 new_strings = realloc(list->strings,
632 sizeof(list->strings[0]) * num_alloc_strings);
634 imagex_error(T("Out of memory!"));
637 list->strings = new_strings;
638 list->num_alloc_strings = num_alloc_strings;
640 list->strings[list->num_strings++] = glob;
645 string_list_destroy(struct string_list *list)
651 wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
653 return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
655 WIMLIB_REF_FLAG_GLOB_ENABLE,
660 append_image_property_argument(struct string_list *image_properties)
662 if (!tstrchr(optarg, '=')) {
663 imagex_error(T("'--image-property' argument "
664 "must be in the form NAME=VALUE"));
667 return string_list_append(image_properties, optarg);
671 apply_image_properties(struct string_list *image_properties,
672 WIMStruct *wim, int image, bool *any_changes_ret)
674 bool any_changes = false;
675 for (unsigned i = 0; i < image_properties->num_strings; i++) {
677 const tchar *current_value;
680 name = image_properties->strings[i];
681 value = tstrchr(name, '=');
684 current_value = wimlib_get_image_property(wim, image, name);
685 if (current_value && !tstrcmp(current_value, value)) {
686 imagex_printf(T("The %"TS" property of image %d "
687 "already has value \"%"TS"\".\n"),
690 imagex_printf(T("Setting the %"TS" property of image "
691 "%d to \"%"TS"\".\n"),
693 ret = wimlib_set_image_property(wim, image, name, value);
700 *any_changes_ret = any_changes;
705 do_resource_not_found_warning(const tchar *wimfile,
706 const struct wimlib_wim_info *info,
707 const struct string_list *refglobs)
709 if (info->total_parts > 1) {
710 if (refglobs->num_strings == 0) {
711 imagex_error(T("\"%"TS"\" is part of a split WIM. "
712 "Use --ref to specify the other parts."),
715 imagex_error(T("Perhaps the '--ref' argument did not "
716 "specify all other parts of the split "
720 imagex_error(T("If this is a delta WIM, use the --ref argument "
721 "to specify the WIM(s) on which it is based."));
726 do_metadata_not_found_warning(const tchar *wimfile,
727 const struct wimlib_wim_info *info)
729 if (info->part_number != 1) {
730 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
731 " You must specify the first part."),
736 /* Returns the size of a file given its name, or -1 if the file does not exist
737 * or its size cannot be determined. */
739 file_get_size(const tchar *filename)
742 if (tstat(filename, &st) == 0)
749 PARSE_STRING_SUCCESS = 0,
750 PARSE_STRING_FAILURE = 1,
751 PARSE_STRING_NONE = 2,
755 * Parses a string token from an array of characters.
757 * Tokens are either whitespace-delimited, or double or single-quoted.
759 * @line_p: Pointer to the pointer to the line of data. Will be updated
760 * to point past the string token iff the return value is
761 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
764 * @len_p: @len_p initially stores the length of the line of data, which may
765 * be 0, and it will be updated to the number of bytes remaining in
766 * the line iff the return value is PARSE_STRING_SUCCESS.
768 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
769 * parsed string token will be returned here.
771 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
772 * PARSE_STRING_FAILURE if the data was invalid due to a missing
773 * closing quote; or PARSE_STRING_NONE if the line ended before the
774 * beginning of a string token was found.
777 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
780 tchar *line = *line_p;
784 /* Skip leading whitespace */
787 return PARSE_STRING_NONE;
788 if (!istspace(*line) && *line != T('\0'))
794 if (quote_char == T('"') || quote_char == T('\'')) {
799 line = tmemchr(line, quote_char, len);
801 imagex_error(T("Missing closing quote: %"TS), fn - 1);
802 return PARSE_STRING_FAILURE;
805 /* Unquoted string. Go until whitespace. Line is terminated
806 * by '\0', so no need to check 'len'. */
810 } while (!istspace(*line) && *line != T('\0'));
817 return PARSE_STRING_SUCCESS;
820 /* Parses a line of data (not an empty line or comment) in the source list file
821 * format. (See the man page for 'wimlib-imagex capture' for details on this
822 * format and the meaning.)
824 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
825 * len == 0. The data in @line will be modified by this function call.
827 * @len: Length of the line of data.
829 * @source: On success, the capture source and target described by the line is
830 * written into this destination. Note that it will contain pointers
831 * to data in the @line array.
833 * Returns true if the line was valid; false otherwise. */
835 parse_source_list_line(tchar *line, size_t len,
836 struct wimlib_capture_source *source)
840 ret = parse_string(&line, &len, &source->fs_source_path);
841 if (ret != PARSE_STRING_SUCCESS)
843 ret = parse_string(&line, &len, &source->wim_target_path);
844 if (ret == PARSE_STRING_NONE)
845 source->wim_target_path = source->fs_source_path;
846 return ret != PARSE_STRING_FAILURE;
849 /* Returns %true if the given line of length @len > 0 is a comment or empty line
850 * in the source list file format. */
852 is_comment_line(const tchar *line, size_t len)
855 if (*line == T('#') || *line == T(';'))
857 if (!istspace(*line) && *line != T('\0'))
867 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
870 tchar *contents = *contents_p;
871 size_t nchars = *nchars_p;
874 for (i = 0; i < nchars; i++)
875 if (contents[i] == T('\n'))
878 /* Handle last line not terminated by a newline */
879 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
880 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
882 imagex_error(T("Out of memory!"));
885 contents[nchars] = T('\n');
886 *contents_p = contents;
894 /* Parses a file in the source list format. (See the man page for
895 * 'wimlib-imagex capture' for details on this format and the meaning.)
897 * @source_list_contents: Contents of the source list file. Note that this
898 * buffer will be modified to save memory allocations,
899 * and cannot be freed until the returned array of
900 * wimlib_capture_source's has also been freed.
902 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
905 * @nsources_ret: On success, the length of the returned array is
908 * Returns: An array of `struct wimlib_capture_source's that can be passed to
909 * the wimlib_add_image_multisource() function to specify how a WIM image is to
911 static struct wimlib_capture_source *
912 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
913 size_t *nsources_ret)
917 struct wimlib_capture_source *sources;
920 nlines = text_file_count_lines(source_list_contents_p,
921 &source_list_nchars);
925 /* Always allocate at least 1 slot, just in case the implementation of
926 * calloc() returns NULL if 0 bytes are requested. */
927 sources = calloc(nlines ?: 1, sizeof(*sources));
929 imagex_error(T("out of memory"));
932 p = *source_list_contents_p;
934 for (i = 0; i < nlines; i++) {
935 /* XXX: Could use rawmemchr() here instead, but it may not be
936 * available on all platforms. */
937 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
938 size_t len = endp - p + 1;
940 if (!is_comment_line(p, len)) {
941 if (!parse_source_list_line(p, len, &sources[j++])) {
953 /* Reads the contents of a file into memory. */
955 file_get_contents(const tchar *filename, size_t *len_ret)
962 if (tstat(filename, &stbuf) != 0) {
963 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
968 fp = tfopen(filename, T("rb"));
970 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
974 buf = malloc(len ? len : 1);
976 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
977 "contents of file \"%"TS"\""), len, filename);
980 if (fread(buf, 1, len, fp) != len) {
981 imagex_error_with_errno(T("Failed to read %zu bytes from the "
982 "file \"%"TS"\""), len, filename);
996 /* Read standard input until EOF and return the full contents in a malloc()ed
997 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
1000 stdin_get_contents(size_t *len_ret)
1002 /* stdin can, of course, be a pipe or other non-seekable file, so the
1003 * total length of the data cannot be pre-determined */
1005 size_t newlen = 1024;
1009 char *p = realloc(buf, newlen);
1010 size_t bytes_read, bytes_to_read;
1012 imagex_error(T("out of memory while reading stdin"));
1016 bytes_to_read = newlen - pos;
1017 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
1019 if (bytes_read != bytes_to_read) {
1024 imagex_error_with_errno(T("error reading stdin"));
1038 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1041 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1043 *num_tchars_ret = num_bytes;
1045 #else /* !__WIN32__ */
1046 /* On Windows, translate the text to UTF-16LE */
1050 if (num_bytes >= 2 &&
1051 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1052 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1054 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1055 * with something that looks like an ASCII character encoded as
1056 * a UTF-16LE code unit. Assume the file is encoded as
1057 * UTF-16LE. This is not a 100% reliable check. */
1058 num_wchars = num_bytes / 2;
1059 text_wstr = (wchar_t*)text;
1061 /* File does not look like UTF-16LE. Assume it is encoded in
1062 * the current Windows code page. I think these are always
1063 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1064 * should work as expected. */
1065 text_wstr = win32_mbs_to_wcs(text,
1070 *num_tchars_ret = num_wchars;
1072 #endif /* __WIN32__ */
1076 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1081 contents = file_get_contents(filename, &num_bytes);
1084 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1088 stdin_get_text_contents(size_t *num_tchars_ret)
1093 contents = stdin_get_contents(&num_bytes);
1096 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1099 #define TO_PERCENT(numerator, denominator) \
1100 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1102 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1103 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1104 #define KIBIBYTE_MIN_NBYTES 10000ULL
1107 get_unit(uint64_t total_bytes, const tchar **name_ret)
1109 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1110 *name_ret = T("GiB");
1112 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1113 *name_ret = T("MiB");
1115 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1116 *name_ret = T("KiB");
1119 *name_ret = T("bytes");
1124 static struct wimlib_progress_info_scan last_scan_progress;
1127 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1129 uint64_t prev_count, cur_count;
1131 prev_count = last_scan_progress.num_nondirs_scanned +
1132 last_scan_progress.num_dirs_scanned;
1133 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1135 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1136 cur_count % 128 == 0)
1138 unsigned unit_shift;
1139 const tchar *unit_name;
1141 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1142 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1143 "%"PRIu64" directories) "),
1144 scan->num_bytes_scanned >> unit_shift,
1146 scan->num_nondirs_scanned,
1147 scan->num_dirs_scanned);
1148 last_scan_progress = *scan;
1151 /* Progress callback function passed to various wimlib functions. */
1152 static enum wimlib_progress_status
1153 imagex_progress_func(enum wimlib_progress_msg msg,
1154 union wimlib_progress_info *info,
1155 void *_ignored_context)
1157 unsigned percent_done;
1158 unsigned unit_shift;
1159 const tchar *unit_name;
1162 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1164 static bool started;
1166 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1167 imagex_printf(T("Using %"TS" compression "
1168 "with %u thread%"TS"\n"),
1169 wimlib_get_compression_type_string(
1170 info->write_streams.compression_type),
1171 info->write_streams.num_threads,
1172 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1177 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1178 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1179 info->write_streams.total_bytes);
1181 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1182 info->write_streams.completed_bytes >> unit_shift,
1184 info->write_streams.total_bytes >> unit_shift,
1187 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1188 imagex_printf(T("\n"));
1190 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1191 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1192 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1193 imagex_printf(T("\n"));
1195 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1196 info->scan.wim_target_path);
1198 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1200 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1201 switch (info->scan.status) {
1202 case WIMLIB_SCAN_DENTRY_OK:
1203 report_scan_progress(&info->scan, false);
1205 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1206 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1208 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1209 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1210 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1212 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1213 /* Symlink fixups are enabled by default. This is
1214 * mainly intended for Windows, which for some reason
1215 * uses absolute junctions (with drive letters!) in the
1216 * default installation. On UNIX-like systems, warn the
1217 * user when fixing the target of an absolute symbolic
1218 * link, so they know to disable this if they want. */
1220 imagex_printf(T("\nWARNING: Adjusted target of "
1221 "absolute symbolic link \"%"TS"\"\n"
1222 " (Use --norpfix to capture "
1223 "absolute symbolic links as-is)\n"),
1224 info->scan.cur_path);
1231 case WIMLIB_PROGRESS_MSG_SCAN_END:
1232 report_scan_progress(&info->scan, true);
1233 imagex_printf(T("\n"));
1235 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1236 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1237 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1238 info->integrity.total_bytes);
1239 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1240 "of %"PRIu64" %"TS" (%u%%) done"),
1241 info->integrity.filename,
1242 info->integrity.completed_bytes >> unit_shift,
1244 info->integrity.total_bytes >> unit_shift,
1247 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1248 imagex_printf(T("\n"));
1250 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1251 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1252 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1253 info->integrity.total_bytes);
1254 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1255 "of %"PRIu64" %"TS" (%u%%) done"),
1256 info->integrity.completed_bytes >> unit_shift,
1258 info->integrity.total_bytes >> unit_shift,
1261 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1262 imagex_printf(T("\n"));
1264 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1265 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1266 "to %"TS" \"%"TS"\"\n"),
1267 info->extract.image,
1268 info->extract.image_name,
1269 info->extract.wimfile_name,
1270 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1271 T("NTFS volume") : T("directory")),
1272 info->extract.target);
1274 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1275 if (info->extract.end_file_count >= 2000) {
1276 percent_done = TO_PERCENT(info->extract.current_file_count,
1277 info->extract.end_file_count);
1278 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1279 info->extract.current_file_count,
1280 info->extract.end_file_count, percent_done);
1281 if (info->extract.current_file_count == info->extract.end_file_count)
1282 imagex_printf(T("\n"));
1285 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1286 percent_done = TO_PERCENT(info->extract.completed_bytes,
1287 info->extract.total_bytes);
1288 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1289 imagex_printf(T("\rExtracting file data: "
1290 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1291 info->extract.completed_bytes >> unit_shift,
1293 info->extract.total_bytes >> unit_shift,
1296 if (info->extract.completed_bytes >= info->extract.total_bytes)
1297 imagex_printf(T("\n"));
1299 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1300 if (info->extract.end_file_count >= 2000) {
1301 percent_done = TO_PERCENT(info->extract.current_file_count,
1302 info->extract.end_file_count);
1303 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1304 info->extract.current_file_count,
1305 info->extract.end_file_count, percent_done);
1306 if (info->extract.current_file_count == info->extract.end_file_count)
1307 imagex_printf(T("\n"));
1310 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1311 if (info->extract.total_parts != 1) {
1312 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1313 info->extract.part_number,
1314 info->extract.total_parts);
1317 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1318 percent_done = TO_PERCENT(info->split.completed_bytes,
1319 info->split.total_bytes);
1320 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1321 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1322 "%"PRIu64" %"TS" (%u%%) written\n"),
1323 info->split.part_name,
1324 info->split.cur_part_number,
1325 info->split.total_parts,
1326 info->split.completed_bytes >> unit_shift,
1328 info->split.total_bytes >> unit_shift,
1332 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1333 if (info->split.completed_bytes == info->split.total_bytes) {
1334 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1335 info->split.cur_part_number,
1336 info->split.total_parts);
1339 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1340 switch (info->update.command->op) {
1341 case WIMLIB_UPDATE_OP_DELETE:
1342 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1343 info->update.command->delete_.wim_path);
1345 case WIMLIB_UPDATE_OP_RENAME:
1346 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1347 info->update.command->rename.wim_source_path,
1348 info->update.command->rename.wim_target_path);
1350 case WIMLIB_UPDATE_OP_ADD:
1355 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1356 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1357 info->replace.path_in_wim);
1359 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1360 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1361 info->wimboot_exclude.path_in_wim);
1363 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1364 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1365 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1366 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1367 info->unmount.mounted_wim,
1368 info->unmount.mounted_image);
1370 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1371 info->unmount.mounted_wim,
1372 info->unmount.mounted_image);
1373 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1377 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1378 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1379 info->verify_image.current_image,
1380 info->verify_image.total_images);
1382 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1383 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1384 info->verify_streams.total_bytes);
1385 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1386 imagex_printf(T("\rVerifying file data: "
1387 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1388 info->verify_streams.completed_bytes >> unit_shift,
1390 info->verify_streams.total_bytes >> unit_shift,
1393 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1394 imagex_printf(T("\n"));
1399 imagex_flush_output();
1400 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1404 parse_num_threads(const tchar *optarg)
1407 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1408 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1409 imagex_error(T("Number of threads must be a non-negative integer!"));
1417 parse_chunk_size(const tchar *optarg)
1420 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1421 if (chunk_size == 0) {
1422 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1423 " with optional K, M, or G suffix"));
1427 if (*tmp == T('k') || *tmp == T('K')) {
1430 } else if (*tmp == T('m') || *tmp == T('M')) {
1433 } else if (*tmp == T('g') || *tmp == T('G')) {
1437 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1438 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1442 if (chunk_size >= UINT32_MAX) {
1443 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1451 * Parse an option passed to an update command.
1453 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1456 * @option: Text string for the option (beginning with --)
1458 * @cmd: `struct wimlib_update_command' that is being constructed for
1461 * Returns true if the option was recognized; false if not.
1464 update_command_add_option(int op, const tchar *option,
1465 struct wimlib_update_command *cmd)
1467 bool recognized = true;
1469 case WIMLIB_UPDATE_OP_ADD:
1470 if (!tstrcmp(option, T("--verbose")))
1471 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1472 else if (!tstrcmp(option, T("--unix-data")))
1473 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1474 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1475 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1476 else if (!tstrcmp(option, T("--strict-acls")))
1477 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1478 else if (!tstrcmp(option, T("--dereference")))
1479 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1480 else if (!tstrcmp(option, T("--no-replace")))
1481 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1485 case WIMLIB_UPDATE_OP_DELETE:
1486 if (!tstrcmp(option, T("--force")))
1487 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1488 else if (!tstrcmp(option, T("--recursive")))
1489 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1500 /* How many nonoption arguments each `imagex update' command expects */
1501 static const unsigned update_command_num_nonoptions[] = {
1502 [WIMLIB_UPDATE_OP_ADD] = 2,
1503 [WIMLIB_UPDATE_OP_DELETE] = 1,
1504 [WIMLIB_UPDATE_OP_RENAME] = 2,
1508 update_command_add_nonoption(int op, const tchar *nonoption,
1509 struct wimlib_update_command *cmd,
1510 unsigned num_nonoptions)
1513 case WIMLIB_UPDATE_OP_ADD:
1514 if (num_nonoptions == 0)
1515 cmd->add.fs_source_path = (tchar*)nonoption;
1517 cmd->add.wim_target_path = (tchar*)nonoption;
1519 case WIMLIB_UPDATE_OP_DELETE:
1520 cmd->delete_.wim_path = (tchar*)nonoption;
1522 case WIMLIB_UPDATE_OP_RENAME:
1523 if (num_nonoptions == 0)
1524 cmd->rename.wim_source_path = (tchar*)nonoption;
1526 cmd->rename.wim_target_path = (tchar*)nonoption;
1532 * Parse a command passed on stdin to `imagex update'.
1534 * @line: Text of the command.
1535 * @len: Length of the line, including a null terminator
1538 * @command: A `struct wimlib_update_command' to fill in from the parsed
1541 * @line_number: Line number of the command, for diagnostics.
1543 * Returns true on success; returns false on parse error.
1546 parse_update_command(tchar *line, size_t len,
1547 struct wimlib_update_command *command,
1551 tchar *command_name;
1553 size_t num_nonoptions;
1555 /* Get the command name ("add", "delete", "rename") */
1556 ret = parse_string(&line, &len, &command_name);
1557 if (ret != PARSE_STRING_SUCCESS)
1560 if (!tstrcasecmp(command_name, T("add"))) {
1561 op = WIMLIB_UPDATE_OP_ADD;
1562 } else if (!tstrcasecmp(command_name, T("delete"))) {
1563 op = WIMLIB_UPDATE_OP_DELETE;
1564 } else if (!tstrcasecmp(command_name, T("rename"))) {
1565 op = WIMLIB_UPDATE_OP_RENAME;
1567 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1568 command_name, line_number);
1573 /* Parse additional options and non-options as needed */
1578 ret = parse_string(&line, &len, &next_string);
1579 if (ret == PARSE_STRING_NONE) /* End of line */
1581 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1583 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1585 if (!update_command_add_option(op, next_string, command))
1587 imagex_error(T("Unrecognized option \"%"TS"\" to "
1588 "update command \"%"TS"\" on line %zu"),
1589 next_string, command_name, line_number);
1595 if (num_nonoptions == update_command_num_nonoptions[op])
1597 imagex_error(T("Unexpected argument \"%"TS"\" in "
1598 "update command on line %zu\n"
1599 " (The \"%"TS"\" command only "
1600 "takes %zu nonoption arguments!)\n"),
1601 next_string, line_number,
1602 command_name, num_nonoptions);
1605 update_command_add_nonoption(op, next_string,
1606 command, num_nonoptions);
1611 if (num_nonoptions != update_command_num_nonoptions[op]) {
1612 imagex_error(T("Not enough arguments to update command "
1613 "\"%"TS"\" on line %zu"), command_name, line_number);
1619 static struct wimlib_update_command *
1620 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1621 size_t *num_cmds_ret)
1625 struct wimlib_update_command *cmds;
1628 nlines = text_file_count_lines(cmd_file_contents_p,
1633 /* Always allocate at least 1 slot, just in case the implementation of
1634 * calloc() returns NULL if 0 bytes are requested. */
1635 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1637 imagex_error(T("out of memory"));
1640 p = *cmd_file_contents_p;
1642 for (i = 0; i < nlines; i++) {
1643 /* XXX: Could use rawmemchr() here instead, but it may not be
1644 * available on all platforms. */
1645 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1646 size_t len = endp - p + 1;
1648 if (!is_comment_line(p, len)) {
1649 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1660 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1661 * one image from a WIM file to an NTFS volume. */
1663 imagex_apply(int argc, tchar **argv, int cmd)
1667 int image = WIMLIB_NO_IMAGE;
1669 struct wimlib_wim_info info;
1671 const tchar *wimfile;
1672 const tchar *target;
1673 const tchar *image_num_or_name = NULL;
1674 int extract_flags = 0;
1676 STRING_LIST(refglobs);
1678 for_opt(c, apply_options) {
1680 case IMAGEX_CHECK_OPTION:
1681 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1683 case IMAGEX_VERBOSE_OPTION:
1684 /* No longer does anything. */
1686 case IMAGEX_REF_OPTION:
1687 ret = string_list_append(&refglobs, optarg);
1689 goto out_free_refglobs;
1691 case IMAGEX_UNIX_DATA_OPTION:
1692 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1694 case IMAGEX_NO_ACLS_OPTION:
1695 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1697 case IMAGEX_STRICT_ACLS_OPTION:
1698 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1700 case IMAGEX_NO_ATTRIBUTES_OPTION:
1701 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1703 case IMAGEX_NORPFIX_OPTION:
1704 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1706 case IMAGEX_RPFIX_OPTION:
1707 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1709 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1710 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1711 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1713 case IMAGEX_WIMBOOT_OPTION:
1714 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1716 case IMAGEX_COMPACT_OPTION:
1717 ret = set_compact_mode(optarg, &extract_flags);
1719 goto out_free_refglobs;
1727 if (argc != 2 && argc != 3)
1732 if (!tstrcmp(wimfile, T("-"))) {
1733 /* Attempt to apply pipable WIM from standard input. */
1735 image_num_or_name = NULL;
1738 image_num_or_name = argv[1];
1743 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1744 imagex_progress_func, NULL);
1746 goto out_free_refglobs;
1748 wimlib_get_wim_info(wim, &info);
1751 /* Image explicitly specified. */
1752 image_num_or_name = argv[1];
1753 image = wimlib_resolve_image(wim, image_num_or_name);
1754 ret = verify_image_exists(image, image_num_or_name, wimfile);
1756 goto out_wimlib_free;
1759 /* No image specified; default to image 1, but only if the WIM
1760 * contains exactly one image. */
1762 if (info.image_count != 1) {
1763 imagex_error(T("\"%"TS"\" contains %d images; "
1764 "Please select one (or all)."),
1765 wimfile, info.image_count);
1774 if (refglobs.num_strings) {
1776 imagex_error(T("Can't specify --ref when applying from stdin!"));
1778 goto out_wimlib_free;
1780 ret = wim_reference_globs(wim, &refglobs, open_flags);
1782 goto out_wimlib_free;
1787 /* Interpret a regular file or block device target as an NTFS
1791 if (tstat(target, &stbuf)) {
1792 if (errno != ENOENT) {
1793 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1796 goto out_wimlib_free;
1799 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1800 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1806 ret = wimlib_extract_image(wim, image, target, extract_flags);
1808 set_fd_to_binary_mode(STDIN_FILENO);
1809 ret = wimlib_extract_image_from_pipe_with_progress(
1814 imagex_progress_func,
1818 imagex_printf(T("Done applying WIM image.\n"));
1819 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1821 do_resource_not_found_warning(wimfile, &info, &refglobs);
1823 imagex_error(T( "If you are applying an image "
1824 "from a split pipable WIM,\n"
1825 " make sure you have "
1826 "concatenated together all parts."));
1828 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1829 do_metadata_not_found_warning(wimfile, &info);
1834 string_list_destroy(&refglobs);
1838 usage(CMD_APPLY, stderr);
1840 goto out_free_refglobs;
1843 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1844 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1845 * the desired image. 'wimlib-imagex append': add a new image to an existing
1848 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1852 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1853 WIMLIB_ADD_FLAG_WINCONFIG |
1854 WIMLIB_ADD_FLAG_VERBOSE |
1855 WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1856 int write_flags = 0;
1857 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1858 uint32_t chunk_size = UINT32_MAX;
1859 uint32_t solid_chunk_size = UINT32_MAX;
1860 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1861 const tchar *wimfile;
1864 STRING_LIST(image_properties);
1867 STRING_LIST(base_wimfiles);
1868 WIMStruct **base_wims;
1870 WIMStruct *template_wim = NULL;
1871 const tchar *template_wimfile = NULL;
1872 const tchar *template_image_name_or_num = NULL;
1873 int template_image = WIMLIB_NO_IMAGE;
1876 unsigned num_threads = 0;
1881 tchar *config_file = NULL;
1883 bool source_list = false;
1884 size_t source_list_nchars = 0;
1885 tchar *source_list_contents;
1886 bool capture_sources_malloced;
1887 struct wimlib_capture_source *capture_sources;
1889 bool name_defaulted;
1891 for_opt(c, capture_or_append_options) {
1893 case IMAGEX_BOOT_OPTION:
1894 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1896 case IMAGEX_CHECK_OPTION:
1897 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1898 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1900 case IMAGEX_NOCHECK_OPTION:
1901 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1903 case IMAGEX_CONFIG_OPTION:
1904 config_file = optarg;
1905 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1907 case IMAGEX_COMPRESS_OPTION:
1908 compression_type = get_compression_type(optarg, false);
1909 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1912 case IMAGEX_CHUNK_SIZE_OPTION:
1913 chunk_size = parse_chunk_size(optarg);
1914 if (chunk_size == UINT32_MAX)
1917 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1918 solid_chunk_size = parse_chunk_size(optarg);
1919 if (solid_chunk_size == UINT32_MAX)
1922 case IMAGEX_SOLID_COMPRESS_OPTION:
1923 solid_ctype = get_compression_type(optarg, true);
1924 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1927 case IMAGEX_SOLID_OPTION:
1928 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1930 case IMAGEX_NO_SOLID_SORT_OPTION:
1931 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1933 case IMAGEX_FLAGS_OPTION: {
1934 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1935 tsprintf(p, T("FLAGS=%"TS), optarg);
1936 ret = string_list_append(&image_properties, p);
1941 case IMAGEX_IMAGE_PROPERTY_OPTION:
1942 ret = append_image_property_argument(&image_properties);
1946 case IMAGEX_DEREFERENCE_OPTION:
1947 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1949 case IMAGEX_VERBOSE_OPTION:
1950 /* No longer does anything. */
1952 case IMAGEX_THREADS_OPTION:
1953 num_threads = parse_num_threads(optarg);
1954 if (num_threads == UINT_MAX)
1957 case IMAGEX_REBUILD_OPTION:
1958 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1960 case IMAGEX_UNIX_DATA_OPTION:
1961 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1963 case IMAGEX_SOURCE_LIST_OPTION:
1966 case IMAGEX_NO_ACLS_OPTION:
1967 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1969 case IMAGEX_STRICT_ACLS_OPTION:
1970 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1972 case IMAGEX_RPFIX_OPTION:
1973 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1975 case IMAGEX_NORPFIX_OPTION:
1976 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1978 case IMAGEX_PIPABLE_OPTION:
1979 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1981 case IMAGEX_NOT_PIPABLE_OPTION:
1982 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1984 case IMAGEX_UPDATE_OF_OPTION:
1985 if (template_image_name_or_num) {
1986 imagex_error(T("'--update-of' can only be "
1987 "specified one time!"));
1991 colon = tstrrchr(optarg, T(':'));
1994 template_wimfile = optarg;
1996 template_image_name_or_num = colon + 1;
1998 template_wimfile = NULL;
1999 template_image_name_or_num = optarg;
2003 case IMAGEX_DELTA_FROM_OPTION:
2004 ret = string_list_append(&base_wimfiles, optarg);
2007 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2009 case IMAGEX_WIMBOOT_OPTION:
2010 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2012 case IMAGEX_UNSAFE_COMPACT_OPTION:
2013 if (cmd != CMD_APPEND) {
2014 imagex_error(T("'--unsafe-compact' is only "
2015 "valid for append!"));
2018 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2020 case IMAGEX_SNAPSHOT_OPTION:
2021 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2030 if (argc < 2 || argc > 4)
2036 /* Set default compression type and parameters. */
2039 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2040 /* No compression type specified. Use the default. */
2042 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2043 /* With --wimboot, default to XPRESS compression. */
2044 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2045 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2046 /* With --solid, default to LZMS compression. (However,
2047 * this will not affect solid resources!) */
2048 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2050 /* Otherwise, default to LZX compression. */
2051 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2055 if (!tstrcmp(wimfile, T("-"))) {
2056 /* Writing captured WIM to standard output. */
2058 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2059 imagex_error("Can't write a non-pipable WIM to "
2060 "standard output! Specify --pipable\n"
2061 " if you want to create a pipable WIM "
2062 "(but read the docs first).");
2066 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2068 if (cmd == CMD_APPEND) {
2069 imagex_error(T("Using standard output for append does "
2070 "not make sense."));
2073 wim_fd = STDOUT_FILENO;
2075 imagex_output_to_stderr();
2076 set_fd_to_binary_mode(wim_fd);
2079 /* If template image was specified using --update-of=IMAGE rather
2080 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2081 if (template_image_name_or_num && !template_wimfile) {
2082 if (base_wimfiles.num_strings == 1) {
2083 /* Capturing delta WIM based on single WIM: default to
2085 template_wimfile = base_wimfiles.strings[0];
2086 } else if (cmd == CMD_APPEND) {
2087 /* Appending to WIM: default to WIM being appended to.
2089 template_wimfile = wimfile;
2091 /* Capturing a normal (non-delta) WIM, so the WIM file
2092 * *must* be explicitly specified. */
2093 if (base_wimfiles.num_strings > 1) {
2094 imagex_error(T("For capture of delta WIM "
2095 "based on multiple existing "
2097 " '--update-of' must "
2098 "specify WIMFILE:IMAGE!"));
2100 imagex_error(T("For capture of non-delta WIM, "
2101 "'--update-of' must specify "
2110 name_defaulted = false;
2112 /* Set default name to SOURCE argument, omitting any directory
2113 * prefixes and trailing slashes. This requires making a copy
2114 * of @source. Leave some free characters at the end in case we
2115 * append a number to keep the name unique. */
2116 size_t source_name_len;
2118 source_name_len = tstrlen(source);
2119 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2120 name = tbasename(tstrcpy(source_copy, source));
2121 name_defaulted = true;
2124 /* Image description (if given). */
2126 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2127 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2128 ret = string_list_append(&image_properties, p);
2134 /* Set up capture sources in source list mode */
2135 if (source[0] == T('-') && source[1] == T('\0')) {
2136 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2138 source_list_contents = file_get_text_contents(source,
2139 &source_list_nchars);
2141 if (!source_list_contents)
2144 capture_sources = parse_source_list(&source_list_contents,
2147 if (!capture_sources) {
2149 goto out_free_source_list_contents;
2151 capture_sources_malloced = true;
2153 /* Set up capture source in non-source-list mode. */
2154 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2155 capture_sources[0].fs_source_path = source;
2156 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2157 capture_sources[0].reserved = 0;
2159 capture_sources_malloced = false;
2160 source_list_contents = NULL;
2163 /* Open the existing WIM, or create a new one. */
2164 if (cmd == CMD_APPEND) {
2165 ret = wimlib_open_wim_with_progress(wimfile,
2166 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2168 imagex_progress_func,
2171 goto out_free_capture_sources;
2173 ret = wimlib_create_new_wim(compression_type, &wim);
2175 goto out_free_capture_sources;
2176 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2179 /* Set chunk size if non-default. */
2180 if (chunk_size != UINT32_MAX) {
2181 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2184 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2186 int ctype = compression_type;
2188 if (cmd == CMD_APPEND) {
2189 struct wimlib_wim_info info;
2190 wimlib_get_wim_info(wim, &info);
2191 ctype = info.compression_type;
2194 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2195 ret = wimlib_set_output_chunk_size(wim, 4096);
2200 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2201 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2205 if (solid_chunk_size != UINT32_MAX) {
2206 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2212 /* Detect if source is regular file or block device and set NTFS volume
2217 if (tstat(source, &stbuf) == 0) {
2218 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2219 imagex_printf(T("Capturing WIM image from NTFS "
2220 "filesystem on \"%"TS"\"\n"), source);
2221 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2224 if (errno != ENOENT) {
2225 imagex_error_with_errno(T("Failed to stat "
2226 "\"%"TS"\""), source);
2234 /* If the user did not specify an image name, and the basename of the
2235 * source already exists as an image name in the WIM file, append a
2236 * suffix to make it unique. */
2237 if (cmd == CMD_APPEND && name_defaulted) {
2238 unsigned long conflict_idx;
2239 tchar *name_end = tstrchr(name, T('\0'));
2240 for (conflict_idx = 1;
2241 wimlib_image_name_in_use(wim, name);
2244 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2248 /* If capturing a delta WIM, reference resources from the base WIMs
2249 * before adding the new image. */
2250 if (base_wimfiles.num_strings) {
2251 base_wims = calloc(base_wimfiles.num_strings,
2252 sizeof(base_wims[0]));
2253 if (base_wims == NULL) {
2254 imagex_error(T("Out of memory!"));
2259 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2260 ret = wimlib_open_wim_with_progress(
2261 base_wimfiles.strings[i], open_flags,
2262 &base_wims[i], imagex_progress_func, NULL);
2264 goto out_free_base_wims;
2268 ret = wimlib_reference_resources(wim, base_wims,
2269 base_wimfiles.num_strings, 0);
2271 goto out_free_base_wims;
2273 if (base_wimfiles.num_strings == 1) {
2274 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2275 base_wimfiles.strings[0]);
2277 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2278 base_wimfiles.num_strings);
2285 /* If capturing or appending as an update of an existing (template) image,
2286 * open the WIM if needed and parse the image index. */
2287 if (template_image_name_or_num) {
2289 if (cmd == CMD_APPEND && !tstrcmp(template_wimfile, wimfile)) {
2292 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2293 if (!tstrcmp(template_wimfile,
2294 base_wimfiles.strings[i])) {
2295 template_wim = base_wims[i];
2301 if (!template_wim) {
2302 ret = wimlib_open_wim_with_progress(template_wimfile,
2305 imagex_progress_func,
2308 goto out_free_base_wims;
2311 template_image = wimlib_resolve_image(template_wim,
2312 template_image_name_or_num);
2314 if (template_image_name_or_num[0] == T('-')) {
2317 struct wimlib_wim_info info;
2319 wimlib_get_wim_info(template_wim, &info);
2320 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2321 if (n >= 1 && n <= info.image_count &&
2323 tmp != template_image_name_or_num + 1)
2325 template_image = info.image_count - (n - 1);
2328 ret = verify_image_exists_and_is_single(template_image,
2329 template_image_name_or_num,
2332 goto out_free_template_wim;
2335 ret = wimlib_add_image_multisource(wim,
2342 goto out_free_template_wim;
2344 if (image_properties.num_strings || template_image_name_or_num) {
2345 /* User asked to set additional image properties, or an image on
2346 * which the added one is to be based has been specified with
2348 struct wimlib_wim_info info;
2350 wimlib_get_wim_info(wim, &info);
2352 ret = apply_image_properties(&image_properties, wim,
2353 info.image_count, NULL);
2355 goto out_free_template_wim;
2357 /* Reference template image if the user provided one. */
2358 if (template_image_name_or_num) {
2359 imagex_printf(T("Using image %d "
2360 "from \"%"TS"\" as template\n"),
2361 template_image, template_wimfile);
2362 ret = wimlib_reference_template_image(wim,
2368 goto out_free_template_wim;
2372 /* Write the new WIM or overwrite the existing WIM with the new image
2374 if (cmd == CMD_APPEND) {
2375 ret = wimlib_overwrite(wim, write_flags, num_threads);
2376 } else if (wimfile) {
2377 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2378 write_flags, num_threads);
2380 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2381 write_flags, num_threads);
2383 out_free_template_wim:
2384 /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
2385 if (template_wim == wim)
2386 goto out_free_base_wims;
2387 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2388 if (template_wim == base_wims[i])
2389 goto out_free_base_wims;
2390 wimlib_free(template_wim);
2392 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2393 wimlib_free(base_wims[i]);
2397 out_free_capture_sources:
2398 if (capture_sources_malloced)
2399 free(capture_sources);
2400 out_free_source_list_contents:
2401 free(source_list_contents);
2403 string_list_destroy(&image_properties);
2404 string_list_destroy(&base_wimfiles);
2414 /* Remove image(s) from a WIM. */
2416 imagex_delete(int argc, tchar **argv, int cmd)
2419 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2420 int write_flags = 0;
2421 const tchar *wimfile;
2422 const tchar *image_num_or_name;
2427 for_opt(c, delete_options) {
2429 case IMAGEX_CHECK_OPTION:
2430 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2431 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2433 case IMAGEX_SOFT_OPTION:
2434 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2436 case IMAGEX_UNSAFE_COMPACT_OPTION:
2437 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2448 imagex_error(T("Must specify a WIM file"));
2450 imagex_error(T("Must specify an image"));
2454 image_num_or_name = argv[1];
2456 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2457 imagex_progress_func, NULL);
2461 image = wimlib_resolve_image(wim, image_num_or_name);
2463 ret = verify_image_exists(image, image_num_or_name, wimfile);
2465 goto out_wimlib_free;
2467 ret = wimlib_delete_image(wim, image);
2469 imagex_error(T("Failed to delete image from \"%"TS"\""),
2471 goto out_wimlib_free;
2474 ret = wimlib_overwrite(wim, write_flags, 0);
2476 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2477 "deleted"), wimfile);
2485 usage(CMD_DELETE, stderr);
2490 struct print_dentry_options {
2495 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2497 tprintf(T("%"TS"\n"), dentry->full_path);
2500 static const struct {
2503 } file_attr_flags[] = {
2504 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2505 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2506 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2507 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2508 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2509 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2510 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2511 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2512 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2513 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2514 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2515 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2516 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2517 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2518 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2521 #define TIMESTR_MAX 100
2524 print_time(const tchar *type, const struct wimlib_timespec *wts,
2527 tchar timestr[TIMESTR_MAX];
2531 if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2532 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2537 tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2538 timestr[TIMESTR_MAX - 1] = '\0';
2540 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2543 static void print_byte_field(const uint8_t field[], size_t len)
2546 tprintf(T("%02hhx"), *field++);
2550 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2552 tchar attr_string[256];
2555 tputs(T("WIM Information:"));
2556 tputs(T("----------------"));
2557 tprintf(T("Path: %"TS"\n"), wimfile);
2558 tprintf(T("GUID: 0x"));
2559 print_byte_field(info->guid, sizeof(info->guid));
2561 tprintf(T("Version: %u\n"), info->wim_version);
2562 tprintf(T("Image Count: %d\n"), info->image_count);
2563 tprintf(T("Compression: %"TS"\n"),
2564 wimlib_get_compression_type_string(info->compression_type));
2565 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2567 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2568 tprintf(T("Boot Index: %d\n"), info->boot_index);
2569 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2571 attr_string[0] = T('\0');
2574 tstrcat(attr_string, T("Pipable, "));
2576 if (info->has_integrity_table)
2577 tstrcat(attr_string, T("Integrity info, "));
2579 if (info->has_rpfix)
2580 tstrcat(attr_string, T("Relative path junction, "));
2582 if (info->resource_only)
2583 tstrcat(attr_string, T("Resource only, "));
2585 if (info->metadata_only)
2586 tstrcat(attr_string, T("Metadata only, "));
2588 if (info->is_marked_readonly)
2589 tstrcat(attr_string, T("Readonly, "));
2591 p = tstrchr(attr_string, T('\0'));
2592 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2595 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2599 print_resource(const struct wimlib_resource_entry *resource,
2602 tprintf(T("Hash = 0x"));
2603 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2606 if (!resource->is_missing) {
2607 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2608 resource->uncompressed_size);
2609 if (resource->packed) {
2610 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2611 "bytes @ offset %"PRIu64"\n"),
2612 resource->raw_resource_uncompressed_size,
2613 resource->raw_resource_compressed_size,
2614 resource->raw_resource_offset_in_wim);
2616 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2619 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2620 resource->compressed_size);
2622 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2626 tprintf(T("Part Number = %u\n"), resource->part_number);
2627 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2629 tprintf(T("Flags = "));
2630 if (resource->is_compressed)
2631 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2632 if (resource->is_metadata)
2633 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2634 if (resource->is_free)
2635 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2636 if (resource->is_spanned)
2637 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2638 if (resource->packed)
2639 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2647 print_blobs(WIMStruct *wim)
2649 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2654 default_print_security_descriptor(const uint8_t *sd, size_t size)
2656 tprintf(T("Security Descriptor = "));
2657 print_byte_field(sd, size);
2663 is_null_guid(const uint8_t *guid)
2665 static const uint8_t null_guid[WIMLIB_GUID_LEN];
2667 return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2671 print_guid(const tchar *label, const uint8_t *guid)
2673 if (is_null_guid(guid))
2675 tprintf(T("%-20"TS"= 0x"), label);
2676 print_byte_field(guid, WIMLIB_GUID_LEN);
2681 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2684 "----------------------------------------------------------------------------\n"));
2685 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2686 if (dentry->dos_name)
2687 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2688 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2689 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2690 if (file_attr_flags[i].flag & dentry->attributes)
2691 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2692 file_attr_flags[i].name);
2694 if (dentry->security_descriptor) {
2695 print_security_descriptor(dentry->security_descriptor,
2696 dentry->security_descriptor_size);
2699 print_time(T("Creation Time"),
2700 &dentry->creation_time, dentry->creation_time_high);
2701 print_time(T("Last Write Time"),
2702 &dentry->last_write_time, dentry->last_write_time_high);
2703 print_time(T("Last Access Time"),
2704 &dentry->last_access_time, dentry->last_access_time_high);
2707 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2708 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2710 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2711 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2713 if (dentry->unix_mode != 0) {
2714 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2715 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2716 dentry->unix_uid, dentry->unix_gid,
2717 dentry->unix_mode, dentry->unix_rdev);
2720 if (!is_null_guid(dentry->object_id.object_id)) {
2721 print_guid(T("Object ID"), dentry->object_id.object_id);
2722 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2723 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2724 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2727 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2728 if (dentry->streams[i].stream_name) {
2729 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2730 dentry->streams[i].stream_name);
2731 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2732 tprintf(T("\tRaw encrypted data stream:\n"));
2733 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2734 tprintf(T("\tReparse point stream:\n"));
2736 tprintf(T("\tUnnamed data stream:\n"));
2738 print_resource(&dentry->streams[i].resource, NULL);
2743 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2745 const struct print_dentry_options *options = _options;
2746 if (!options->detailed)
2747 print_dentry_full_path(dentry);
2749 print_dentry_detailed(dentry);
2753 /* Print the files contained in an image(s) in a WIM file. */
2755 imagex_dir(int argc, tchar **argv, int cmd)
2757 const tchar *wimfile;
2758 WIMStruct *wim = NULL;
2761 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2763 struct print_dentry_options options = {
2766 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2768 STRING_LIST(refglobs);
2770 for_opt(c, dir_options) {
2772 case IMAGEX_PATH_OPTION:
2775 case IMAGEX_DETAILED_OPTION:
2776 options.detailed = true;
2778 case IMAGEX_ONE_FILE_ONLY_OPTION:
2779 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2781 case IMAGEX_REF_OPTION:
2782 ret = string_list_append(&refglobs, optarg);
2784 goto out_free_refglobs;
2794 imagex_error(T("Must specify a WIM file"));
2798 imagex_error(T("Too many arguments"));
2803 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2804 imagex_progress_func, NULL);
2806 goto out_free_refglobs;
2809 image = wimlib_resolve_image(wim, argv[1]);
2810 ret = verify_image_exists(image, argv[1], wimfile);
2812 goto out_wimlib_free;
2814 /* No image specified; default to image 1, but only if the WIM
2815 * contains exactly one image. */
2817 struct wimlib_wim_info info;
2819 wimlib_get_wim_info(wim, &info);
2820 if (info.image_count != 1) {
2821 imagex_error(T("\"%"TS"\" contains %d images; Please "
2822 "select one (or all)."),
2823 wimfile, info.image_count);
2830 if (refglobs.num_strings) {
2831 ret = wim_reference_globs(wim, &refglobs, 0);
2833 goto out_wimlib_free;
2836 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2837 print_dentry, &options);
2838 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2839 struct wimlib_wim_info info;
2841 wimlib_get_wim_info(wim, &info);
2842 do_metadata_not_found_warning(wimfile, &info);
2847 string_list_destroy(&refglobs);
2851 usage(CMD_DIR, stderr);
2853 goto out_free_refglobs;
2856 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2859 imagex_export(int argc, tchar **argv, int cmd)
2863 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2864 int write_flags = 0;
2865 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2866 const tchar *src_wimfile;
2867 const tchar *src_image_num_or_name;
2868 const tchar *dest_wimfile;
2870 const tchar *dest_name;
2871 const tchar *dest_desc;
2873 struct wimlib_wim_info src_info;
2874 WIMStruct *dest_wim;
2879 STRING_LIST(refglobs);
2880 unsigned num_threads = 0;
2881 uint32_t chunk_size = UINT32_MAX;
2882 uint32_t solid_chunk_size = UINT32_MAX;
2883 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2885 for_opt(c, export_options) {
2887 case IMAGEX_BOOT_OPTION:
2888 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2890 case IMAGEX_CHECK_OPTION:
2891 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2892 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2894 case IMAGEX_NOCHECK_OPTION:
2895 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2897 case IMAGEX_COMPRESS_OPTION:
2898 compression_type = get_compression_type(optarg, false);
2899 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2902 case IMAGEX_RECOMPRESS_OPTION:
2903 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2905 case IMAGEX_SOLID_OPTION:
2906 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2908 case IMAGEX_NO_SOLID_SORT_OPTION:
2909 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2911 case IMAGEX_CHUNK_SIZE_OPTION:
2912 chunk_size = parse_chunk_size(optarg);
2913 if (chunk_size == UINT32_MAX)
2916 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2917 solid_chunk_size = parse_chunk_size(optarg);
2918 if (solid_chunk_size == UINT32_MAX)
2921 case IMAGEX_SOLID_COMPRESS_OPTION:
2922 solid_ctype = get_compression_type(optarg, true);
2923 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2926 case IMAGEX_REF_OPTION:
2927 ret = string_list_append(&refglobs, optarg);
2929 goto out_free_refglobs;
2931 case IMAGEX_THREADS_OPTION:
2932 num_threads = parse_num_threads(optarg);
2933 if (num_threads == UINT_MAX)
2936 case IMAGEX_REBUILD_OPTION:
2937 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2939 case IMAGEX_PIPABLE_OPTION:
2940 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2942 case IMAGEX_NOT_PIPABLE_OPTION:
2943 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2945 case IMAGEX_WIMBOOT_OPTION:
2946 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2948 case IMAGEX_UNSAFE_COMPACT_OPTION:
2949 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2957 if (argc < 3 || argc > 5)
2959 src_wimfile = argv[0];
2960 src_image_num_or_name = argv[1];
2961 dest_wimfile = argv[2];
2962 dest_name = (argc >= 4) ? argv[3] : NULL;
2963 dest_desc = (argc >= 5) ? argv[4] : NULL;
2964 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2965 imagex_progress_func, NULL);
2967 goto out_free_refglobs;
2969 wimlib_get_wim_info(src_wim, &src_info);
2971 /* Determine if the destination is an existing file or not. If so, we
2972 * try to append the exported image(s) to it; otherwise, we create a new
2973 * WIM containing the exported image(s). Furthermore, determine if we
2974 * need to write a pipable WIM directly to standard output. */
2976 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2978 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2979 imagex_error("Can't write a non-pipable WIM to "
2980 "standard output! Specify --pipable\n"
2981 " if you want to create a pipable WIM "
2982 "(but read the docs first).");
2984 goto out_free_src_wim;
2987 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2989 dest_wimfile = NULL;
2990 dest_wim_fd = STDOUT_FILENO;
2991 imagex_output_to_stderr();
2992 set_fd_to_binary_mode(dest_wim_fd);
2995 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2997 /* Destination file exists. */
2999 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
3000 imagex_error(T("\"%"TS"\" is not a regular file "
3001 "or block device"), dest_wimfile);
3003 goto out_free_src_wim;
3005 ret = wimlib_open_wim_with_progress(dest_wimfile,
3007 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
3009 imagex_progress_func,
3012 goto out_free_src_wim;
3014 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3015 /* The user specified a compression type, but we're
3016 * exporting to an existing WIM. Make sure the
3017 * specified compression type is the same as the
3018 * compression type of the existing destination WIM. */
3019 struct wimlib_wim_info dest_info;
3021 wimlib_get_wim_info(dest_wim, &dest_info);
3022 if (compression_type != dest_info.compression_type) {
3023 imagex_error(T("Cannot specify a compression type that is "
3024 "not the same as that used in the "
3025 "destination WIM"));
3027 goto out_free_dest_wim;
3033 if (errno != ENOENT) {
3034 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3037 goto out_free_src_wim;
3040 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3041 imagex_error(T("'--unsafe-compact' is only valid when "
3042 "exporting to an existing WIM file!"));
3044 goto out_free_src_wim;
3047 /* dest_wimfile is not an existing file, so create a new WIM. */
3049 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3050 /* The user did not specify a compression type; default
3051 * to that of the source WIM, unless --solid or
3052 * --wimboot was specified. */
3054 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3055 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3056 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3057 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3059 compression_type = src_info.compression_type;
3061 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3063 goto out_free_src_wim;
3065 wimlib_register_progress_function(dest_wim,
3066 imagex_progress_func, NULL);
3068 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3069 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3071 /* For --wimboot export, use small XPRESS chunks. */
3072 wimlib_set_output_chunk_size(dest_wim, 4096);
3073 } else if (compression_type == src_info.compression_type &&
3074 chunk_size == UINT32_MAX)
3076 /* Use same chunk size if compression type is the same. */
3077 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3081 if (chunk_size != UINT32_MAX) {
3082 /* Set destination chunk size. */
3083 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3085 goto out_free_dest_wim;
3087 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3088 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3090 goto out_free_dest_wim;
3092 if (solid_chunk_size != UINT32_MAX) {
3093 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3095 goto out_free_dest_wim;
3098 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3099 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3101 goto out_free_dest_wim;
3103 if (refglobs.num_strings) {
3104 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3106 goto out_free_dest_wim;
3109 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3110 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3112 imagex_error(T("--boot specified for all-images export, but source WIM "
3113 "has no bootable image."));
3115 goto out_free_dest_wim;
3118 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3119 dest_desc, export_flags);
3121 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3122 do_resource_not_found_warning(src_wimfile,
3123 &src_info, &refglobs);
3124 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3125 do_metadata_not_found_warning(src_wimfile, &src_info);
3127 goto out_free_dest_wim;
3131 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3132 else if (dest_wimfile)
3133 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3134 write_flags, num_threads);
3136 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3137 WIMLIB_ALL_IMAGES, write_flags,
3140 wimlib_free(dest_wim);
3142 wimlib_free(src_wim);
3144 string_list_destroy(&refglobs);
3148 usage(CMD_EXPORT, stderr);
3151 goto out_free_refglobs;
3154 /* Extract files or directories from a WIM image */
3156 imagex_extract(int argc, tchar **argv, int cmd)
3163 const tchar *wimfile;
3164 const tchar *image_num_or_name;
3165 tchar *dest_dir = T(".");
3166 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3167 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3168 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3169 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3171 STRING_LIST(refglobs);
3173 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3175 for_opt(c, extract_options) {
3177 case IMAGEX_CHECK_OPTION:
3178 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3180 case IMAGEX_VERBOSE_OPTION:
3181 /* No longer does anything. */
3183 case IMAGEX_REF_OPTION:
3184 ret = string_list_append(&refglobs, optarg);
3186 goto out_free_refglobs;
3188 case IMAGEX_UNIX_DATA_OPTION:
3189 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3191 case IMAGEX_NO_ACLS_OPTION:
3192 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3194 case IMAGEX_STRICT_ACLS_OPTION:
3195 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3197 case IMAGEX_NO_ATTRIBUTES_OPTION:
3198 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3200 case IMAGEX_DEST_DIR_OPTION:
3203 case IMAGEX_TO_STDOUT_OPTION:
3204 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3205 imagex_suppress_output();
3206 set_fd_to_binary_mode(STDOUT_FILENO);
3208 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3209 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3210 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3212 case IMAGEX_NO_GLOBS_OPTION:
3213 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3215 case IMAGEX_NULLGLOB_OPTION:
3216 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3218 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3219 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3221 case IMAGEX_WIMBOOT_OPTION:
3222 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3224 case IMAGEX_COMPACT_OPTION:
3225 ret = set_compact_mode(optarg, &extract_flags);
3227 goto out_free_refglobs;
3239 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3240 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3242 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3247 image_num_or_name = argv[1];
3252 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3253 imagex_progress_func, NULL);
3255 goto out_free_refglobs;
3257 image = wimlib_resolve_image(wim, image_num_or_name);
3258 ret = verify_image_exists_and_is_single(image,
3262 goto out_wimlib_free;
3264 if (refglobs.num_strings) {
3265 ret = wim_reference_globs(wim, &refglobs, open_flags);
3267 goto out_wimlib_free;
3273 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3276 while (argc != 0 && ret == 0) {
3280 num_paths < argc && argv[num_paths][0] != T('@');
3285 ret = wimlib_extract_paths(wim, image, dest_dir,
3286 (const tchar **)argv,
3288 extract_flags | notlist_extract_flags);
3292 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3301 imagex_printf(T("Done extracting files.\n"));
3302 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3303 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3304 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3305 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3306 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3309 T("Note: You can use the '--nullglob' "
3310 "option to ignore missing files.\n"));
3312 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3313 "files and directories\n"
3314 " are in the WIM image.\n"),
3315 get_cmd_string(CMD_DIR, false));
3316 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3317 struct wimlib_wim_info info;
3319 wimlib_get_wim_info(wim, &info);
3320 do_resource_not_found_warning(wimfile, &info, &refglobs);
3321 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3322 struct wimlib_wim_info info;
3324 wimlib_get_wim_info(wim, &info);
3325 do_metadata_not_found_warning(wimfile, &info);
3330 string_list_destroy(&refglobs);
3334 usage(CMD_EXTRACT, stderr);
3337 goto out_free_refglobs;
3340 /* Prints information about a WIM file; also can mark an image as bootable,
3341 * change the name of an image, or change the description of an image. */
3343 imagex_info(int argc, tchar **argv, int cmd)
3348 bool nocheck = false;
3349 bool header = false;
3352 bool short_header = true;
3353 const tchar *xml_out_file = NULL;
3354 const tchar *wimfile;
3355 const tchar *image_num_or_name;
3356 STRING_LIST(image_properties);
3361 struct wimlib_wim_info info;
3363 for_opt(c, info_options) {
3365 case IMAGEX_BOOT_OPTION:
3368 case IMAGEX_CHECK_OPTION:
3371 case IMAGEX_NOCHECK_OPTION:
3374 case IMAGEX_HEADER_OPTION:
3376 short_header = false;
3378 case IMAGEX_BLOBS_OPTION:
3380 short_header = false;
3382 case IMAGEX_XML_OPTION:
3384 short_header = false;
3386 case IMAGEX_EXTRACT_XML_OPTION:
3387 xml_out_file = optarg;
3388 short_header = false;
3390 case IMAGEX_IMAGE_PROPERTY_OPTION:
3391 ret = append_image_property_argument(&image_properties);
3402 if (argc < 1 || argc > 4)
3406 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3410 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3411 tsprintf(p, T("NAME=%"TS), argv[2]);
3412 ret = string_list_append(&image_properties, p);
3419 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3420 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3421 ret = string_list_append(&image_properties, p);
3426 if (check && nocheck) {
3427 imagex_error(T("Can't specify both --check and --nocheck"));
3432 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3434 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3435 imagex_progress_func, NULL);
3439 wimlib_get_wim_info(wim, &info);
3441 image = wimlib_resolve_image(wim, image_num_or_name);
3442 ret = WIMLIB_ERR_INVALID_IMAGE;
3443 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3444 verify_image_exists(image, image_num_or_name, wimfile);
3446 imagex_error(T("If you would like to set the boot "
3447 "index to 0, specify image \"0\" with "
3448 "the --boot flag."));
3450 goto out_wimlib_free;
3453 if (boot && info.image_count == 0) {
3454 imagex_error(T("--boot is meaningless on a WIM with no images"));
3455 goto out_wimlib_free;
3458 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3460 imagex_error(T("Cannot specify the --boot flag "
3461 "without specifying a specific "
3462 "image in a multi-image WIM"));
3463 goto out_wimlib_free;
3465 if (image_properties.num_strings) {
3466 imagex_error(T("Can't change image properties without "
3467 "specifying a specific image in a "
3468 "multi-image WIM"));
3469 goto out_wimlib_free;
3473 /* Operations that print information are separated from operations that
3474 * recreate the WIM file. */
3475 if (!image_properties.num_strings && !boot) {
3477 /* Read-only operations */
3479 if (image == WIMLIB_NO_IMAGE) {
3480 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3481 image_num_or_name, wimfile);
3482 goto out_wimlib_free;
3485 if (image == WIMLIB_ALL_IMAGES && short_header)
3486 print_wim_information(wimfile, &info);
3489 wimlib_print_header(wim);
3492 if (info.total_parts != 1) {
3493 tfprintf(stderr, T("Warning: Only showing the blobs "
3494 "for part %d of a %d-part WIM.\n"),
3495 info.part_number, info.total_parts);
3501 ret = wimlib_extract_xml_data(wim, stdout);
3503 goto out_wimlib_free;
3509 fp = tfopen(xml_out_file, T("wb"));
3511 imagex_error_with_errno(T("Failed to open the "
3512 "file \"%"TS"\" for "
3516 goto out_wimlib_free;
3518 ret = wimlib_extract_xml_data(wim, fp);
3520 imagex_error(T("Failed to close the file "
3526 goto out_wimlib_free;
3530 wimlib_print_available_images(wim, image);
3534 /* Modification operations */
3535 bool any_property_changes;
3537 if (image == WIMLIB_ALL_IMAGES)
3540 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3541 imagex_error(T("Cannot change image properties "
3542 "when using image 0"));
3544 goto out_wimlib_free;
3548 if (image == info.boot_index) {
3549 imagex_printf(T("Image %d is already marked as "
3550 "bootable.\n"), image);
3553 imagex_printf(T("Marking image %d as bootable.\n"),
3555 info.boot_index = image;
3556 ret = wimlib_set_wim_info(wim, &info,
3557 WIMLIB_CHANGE_BOOT_INDEX);
3559 goto out_wimlib_free;
3563 ret = apply_image_properties(&image_properties, wim, image,
3564 &any_property_changes);
3566 goto out_wimlib_free;
3568 /* Only call wimlib_overwrite() if something actually needs to
3570 if (boot || any_property_changes ||
3571 (check && !info.has_integrity_table) ||
3572 (nocheck && info.has_integrity_table))
3574 int write_flags = 0;
3577 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3579 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3580 ret = wimlib_overwrite(wim, write_flags, 1);
3582 imagex_printf(T("The file \"%"TS"\" was not modified "
3583 "because nothing needed to be done.\n"),
3591 string_list_destroy(&image_properties);
3595 usage(CMD_INFO, stderr);
3601 /* Join split WIMs into one part WIM */
3603 imagex_join(int argc, tchar **argv, int cmd)
3606 int swm_open_flags = 0;
3607 int wim_write_flags = 0;
3608 const tchar *output_path;
3611 for_opt(c, join_options) {
3613 case IMAGEX_CHECK_OPTION:
3614 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3615 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3625 imagex_error(T("Must specify one or more split WIM (.swm) "
3629 output_path = argv[0];
3630 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3635 imagex_progress_func,
3641 usage(CMD_JOIN, stderr);
3646 #if WIM_MOUNTING_SUPPORTED
3648 /* Mounts a WIM image. */
3650 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3653 int mount_flags = 0;
3655 const tchar *staging_dir = NULL;
3656 const tchar *wimfile;
3659 struct wimlib_wim_info info;
3663 STRING_LIST(refglobs);
3665 if (cmd == CMD_MOUNTRW) {
3666 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3667 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3670 for_opt(c, mount_options) {
3672 case IMAGEX_ALLOW_OTHER_OPTION:
3673 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3675 case IMAGEX_CHECK_OPTION:
3676 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3678 case IMAGEX_DEBUG_OPTION:
3679 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3681 case IMAGEX_STREAMS_INTERFACE_OPTION:
3682 if (!tstrcasecmp(optarg, T("none")))
3683 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3684 else if (!tstrcasecmp(optarg, T("xattr")))
3685 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3686 else if (!tstrcasecmp(optarg, T("windows")))
3687 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3689 imagex_error(T("Unknown stream interface \"%"TS"\""),
3694 case IMAGEX_REF_OPTION:
3695 ret = string_list_append(&refglobs, optarg);
3697 goto out_free_refglobs;
3699 case IMAGEX_STAGING_DIR_OPTION:
3700 staging_dir = optarg;
3702 case IMAGEX_UNIX_DATA_OPTION:
3703 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3711 if (argc != 2 && argc != 3)
3716 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3717 imagex_progress_func, NULL);
3719 goto out_free_refglobs;
3721 wimlib_get_wim_info(wim, &info);
3724 /* Image explicitly specified. */
3725 image = wimlib_resolve_image(wim, argv[1]);
3727 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3731 /* No image specified; default to image 1, but only if the WIM
3732 * contains exactly one image. */
3734 if (info.image_count != 1) {
3735 imagex_error(T("\"%"TS"\" contains %d images; Please "
3736 "select one."), wimfile, info.image_count);
3744 if (refglobs.num_strings) {
3745 ret = wim_reference_globs(wim, &refglobs, open_flags);
3750 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3752 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3753 do_metadata_not_found_warning(wimfile, &info);
3755 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3757 image, wimfile, dir);
3763 string_list_destroy(&refglobs);
3769 goto out_free_refglobs;
3771 #endif /* WIM_MOUNTING_SUPPORTED */
3773 /* Rebuild a WIM file */
3775 imagex_optimize(int argc, tchar **argv, int cmd)
3778 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3779 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3780 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3781 uint32_t chunk_size = UINT32_MAX;
3782 uint32_t solid_chunk_size = UINT32_MAX;
3783 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3786 const tchar *wimfile;
3789 unsigned num_threads = 0;
3791 for_opt(c, optimize_options) {
3793 case IMAGEX_CHECK_OPTION:
3794 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3795 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3797 case IMAGEX_NOCHECK_OPTION:
3798 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3800 case IMAGEX_COMPRESS_OPTION:
3801 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3802 compression_type = get_compression_type(optarg, false);
3803 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3806 case IMAGEX_RECOMPRESS_OPTION:
3807 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3809 case IMAGEX_CHUNK_SIZE_OPTION:
3810 chunk_size = parse_chunk_size(optarg);
3811 if (chunk_size == UINT32_MAX)
3814 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3815 solid_chunk_size = parse_chunk_size(optarg);
3816 if (solid_chunk_size == UINT32_MAX)
3819 case IMAGEX_SOLID_COMPRESS_OPTION:
3820 solid_ctype = get_compression_type(optarg, true);
3821 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3824 case IMAGEX_SOLID_OPTION:
3825 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3826 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3828 case IMAGEX_NO_SOLID_SORT_OPTION:
3829 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3831 case IMAGEX_THREADS_OPTION:
3832 num_threads = parse_num_threads(optarg);
3833 if (num_threads == UINT_MAX)
3836 case IMAGEX_PIPABLE_OPTION:
3837 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3839 case IMAGEX_NOT_PIPABLE_OPTION:
3840 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3842 case IMAGEX_UNSAFE_COMPACT_OPTION:
3843 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3857 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3858 imagex_progress_func, NULL);
3862 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3863 /* Change compression type. */
3864 ret = wimlib_set_output_compression_type(wim, compression_type);
3866 goto out_wimlib_free;
3869 if (chunk_size != UINT32_MAX) {
3870 /* Change chunk size. */
3871 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3873 goto out_wimlib_free;
3875 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3876 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3878 goto out_wimlib_free;
3880 if (solid_chunk_size != UINT32_MAX) {
3881 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3883 goto out_wimlib_free;
3886 old_size = file_get_size(wimfile);
3887 tprintf(T("\"%"TS"\" original size: "), wimfile);
3889 tputs(T("Unknown"));
3891 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3893 ret = wimlib_overwrite(wim, write_flags, num_threads);
3895 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3896 goto out_wimlib_free;
3899 new_size = file_get_size(wimfile);
3900 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3902 tputs(T("Unknown"));
3904 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3906 tfputs(T("Space saved: "), stdout);
3907 if (new_size != -1 && old_size != -1) {
3908 tprintf(T("%lld KiB\n"),
3909 ((long long)old_size - (long long)new_size) >> 10);
3911 tputs(T("Unknown"));
3920 usage(CMD_OPTIMIZE, stderr);
3926 /* Split a WIM into a spanned set */
3928 imagex_split(int argc, tchar **argv, int cmd)
3932 int write_flags = 0;
3933 unsigned long part_size;
3938 for_opt(c, split_options) {
3940 case IMAGEX_CHECK_OPTION:
3941 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3942 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3954 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3955 if (tmp == argv[2] || *tmp) {
3956 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3957 imagex_error(T("The part size must be an integer or "
3958 "floating-point number of megabytes."));
3961 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3962 imagex_progress_func, NULL);
3966 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3972 usage(CMD_SPLIT, stderr);
3978 #if WIM_MOUNTING_SUPPORTED
3979 /* Unmounts a mounted WIM image. */
3981 imagex_unmount(int argc, tchar **argv, int cmd)
3984 int unmount_flags = 0;
3987 for_opt(c, unmount_options) {
3989 case IMAGEX_COMMIT_OPTION:
3990 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3992 case IMAGEX_CHECK_OPTION:
3993 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3995 case IMAGEX_REBUILD_OPTION:
3996 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3998 case IMAGEX_LAZY_OPTION:
3999 case IMAGEX_FORCE_OPTION:
4000 /* Now, unmount is lazy by default. However, committing
4001 * the image will fail with
4002 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
4003 * file descriptors on the WIM image. The
4004 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
4005 * descriptors to be closed. */
4006 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4008 case IMAGEX_NEW_IMAGE_OPTION:
4009 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4020 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4021 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4022 imagex_error(T("--new-image is meaningless "
4023 "without --commit also specified!"));
4028 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4029 imagex_progress_func, NULL);
4031 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4032 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4034 "\tNote: Use --commit --force to force changes "
4035 "to be committed, regardless\n"
4036 "\t of open files.\n"));
4043 usage(CMD_UNMOUNT, stderr);
4048 #endif /* WIM_MOUNTING_SUPPORTED */
4051 * Add, delete, or rename files in a WIM image.
4054 imagex_update(int argc, tchar **argv, int cmd)
4056 const tchar *wimfile;
4060 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4061 int write_flags = 0;
4062 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4063 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4064 WIMLIB_ADD_FLAG_VERBOSE |
4065 WIMLIB_ADD_FLAG_WINCONFIG;
4066 int default_delete_flags = 0;
4067 unsigned num_threads = 0;
4069 tchar *cmd_file_contents;
4070 size_t cmd_file_nchars;
4071 struct wimlib_update_command *cmds;
4073 tchar *command_str = NULL;
4074 tchar *config_file = NULL;
4075 tchar *wimboot_config = NULL;
4077 for_opt(c, update_options) {
4079 /* Generic or write options */
4080 case IMAGEX_THREADS_OPTION:
4081 num_threads = parse_num_threads(optarg);
4082 if (num_threads == UINT_MAX)
4085 case IMAGEX_CHECK_OPTION:
4086 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4087 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4089 case IMAGEX_REBUILD_OPTION:
4090 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4092 case IMAGEX_COMMAND_OPTION:
4094 imagex_error(T("--command may only be specified "
4095 "one time. Please provide\n"
4096 " the update commands "
4097 "on standard input instead."));
4100 command_str = tstrdup(optarg);
4102 imagex_error(T("Out of memory!"));
4106 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4107 wimboot_config = optarg;
4109 /* Default delete options */
4110 case IMAGEX_FORCE_OPTION:
4111 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4113 case IMAGEX_RECURSIVE_OPTION:
4114 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4117 /* Global add option */
4118 case IMAGEX_CONFIG_OPTION:
4119 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4120 config_file = optarg;
4123 /* Default add options */
4124 case IMAGEX_VERBOSE_OPTION:
4125 /* No longer does anything. */
4127 case IMAGEX_DEREFERENCE_OPTION:
4128 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4130 case IMAGEX_UNIX_DATA_OPTION:
4131 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4133 case IMAGEX_NO_ACLS_OPTION:
4134 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4136 case IMAGEX_STRICT_ACLS_OPTION:
4137 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4139 case IMAGEX_NO_REPLACE_OPTION:
4140 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4142 case IMAGEX_UNSAFE_COMPACT_OPTION:
4143 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4152 if (argc != 1 && argc != 2)
4156 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4157 imagex_progress_func, NULL);
4159 goto out_free_command_str;
4162 /* Image explicitly specified. */
4163 image = wimlib_resolve_image(wim, argv[1]);
4164 ret = verify_image_exists_and_is_single(image, argv[1],
4167 goto out_wimlib_free;
4169 /* No image specified; default to image 1, but only if the WIM
4170 * contains exactly one image. */
4171 struct wimlib_wim_info info;
4173 wimlib_get_wim_info(wim, &info);
4174 if (info.image_count != 1) {
4175 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4176 wimfile, info.image_count);
4183 /* Read update commands from standard input, or the command string if
4186 cmd_file_contents = NULL;
4187 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4191 goto out_free_cmd_file_contents;
4193 } else if (!wimboot_config) {
4194 if (isatty(STDIN_FILENO)) {
4195 tputs(T("Reading update commands from standard input..."));
4196 recommend_man_page(CMD_UPDATE, stdout);
4198 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4199 if (!cmd_file_contents) {
4201 goto out_wimlib_free;
4204 /* Parse the update commands */
4205 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4209 goto out_free_cmd_file_contents;
4212 cmd_file_contents = NULL;
4217 /* Set default flags and capture config on the update commands */
4218 for (size_t i = 0; i < num_cmds; i++) {
4219 switch (cmds[i].op) {
4220 case WIMLIB_UPDATE_OP_ADD:
4221 cmds[i].add.add_flags |= default_add_flags;
4222 cmds[i].add.config_file = config_file;
4224 case WIMLIB_UPDATE_OP_DELETE:
4225 cmds[i].delete_.delete_flags |= default_delete_flags;
4232 /* Execute the update commands */
4233 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4237 if (wimboot_config) {
4238 /* --wimboot-config=FILE is short for an
4239 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4241 struct wimlib_update_command cmd;
4243 cmd.op = WIMLIB_UPDATE_OP_ADD;
4244 cmd.add.fs_source_path = wimboot_config;
4245 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4246 cmd.add.config_file = NULL;
4247 cmd.add.add_flags = 0;
4249 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4254 /* Overwrite the updated WIM */
4255 ret = wimlib_overwrite(wim, write_flags, num_threads);
4258 out_free_cmd_file_contents:
4259 free(cmd_file_contents);
4262 out_free_command_str:
4267 usage(CMD_UPDATE, stderr);
4270 goto out_free_command_str;
4273 /* Verify a WIM file. */
4275 imagex_verify(int argc, tchar **argv, int cmd)
4278 const tchar *wimfile;
4280 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4281 int verify_flags = 0;
4282 STRING_LIST(refglobs);
4285 for_opt(c, verify_options) {
4287 case IMAGEX_REF_OPTION:
4288 ret = string_list_append(&refglobs, optarg);
4290 goto out_free_refglobs;
4292 case IMAGEX_NOCHECK_OPTION:
4293 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4305 imagex_error(T("Must specify a WIM file!"));
4307 imagex_error(T("At most one WIM file can be specified!"));
4313 ret = wimlib_open_wim_with_progress(wimfile,
4316 imagex_progress_func,
4319 goto out_free_refglobs;
4321 ret = wim_reference_globs(wim, &refglobs, open_flags);
4323 goto out_wimlib_free;
4325 ret = wimlib_verify_wim(wim, verify_flags);
4327 tputc(T('\n'), stderr);
4328 imagex_error(T("\"%"TS"\" failed verification!"),
4330 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4331 refglobs.num_strings == 0)
4333 imagex_printf(T("Note: if this WIM file is not standalone, "
4334 "use the --ref option to specify the other parts.\n"));
4337 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4344 string_list_destroy(&refglobs);
4348 usage(CMD_VERIFY, stderr);
4350 goto out_free_refglobs;
4353 struct imagex_command {
4355 int (*func)(int argc, tchar **argv, int cmd);
4358 static const struct imagex_command imagex_commands[] = {
4359 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4360 [CMD_APPLY] = {T("apply"), imagex_apply},
4361 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4362 [CMD_DELETE] = {T("delete"), imagex_delete},
4363 [CMD_DIR ] = {T("dir"), imagex_dir},
4364 [CMD_EXPORT] = {T("export"), imagex_export},
4365 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4366 [CMD_INFO] = {T("info"), imagex_info},
4367 [CMD_JOIN] = {T("join"), imagex_join},
4368 #if WIM_MOUNTING_SUPPORTED
4369 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4370 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4372 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4373 [CMD_SPLIT] = {T("split"), imagex_split},
4374 #if WIM_MOUNTING_SUPPORTED
4375 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4377 [CMD_UPDATE] = {T("update"), imagex_update},
4378 [CMD_VERIFY] = {T("verify"), imagex_verify},
4383 /* Can be a directory or source list file. But source list file is probably
4384 * a rare use case, so just say directory. */
4385 # define SOURCE_STR T("DIRECTORY")
4387 /* Can only be a directory */
4388 # define TARGET_STR T("DIRECTORY")
4391 /* Can be a directory, NTFS volume, or source list file. */
4392 # define SOURCE_STR T("SOURCE")
4394 /* Can be a directory or NTFS volume. */
4395 # define TARGET_STR T("TARGET")
4399 static const tchar * const usage_strings[] = {
4402 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4403 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4404 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4405 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4406 " [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4407 " [--dereference] [--snapshot]\n"
4411 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4412 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4413 " [--no-attributes] [--rpfix] [--norpfix]\n"
4414 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4415 " [--compact=FORMAT]\n"
4419 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4420 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4421 " [--config=FILE] [--threads=NUM_THREADS]\n"
4422 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4423 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4424 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4429 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4433 " %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4437 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4438 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4439 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4440 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4441 " [--wimboot] [--solid]\n"
4445 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4446 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4447 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4448 " [--no-attributes] [--include-invalid-names]\n"
4449 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4453 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4454 " [--boot] [--check] [--nocheck] [--xml]\n"
4455 " [--extract-xml FILE] [--header] [--blobs]\n"
4456 " [--image-property NAME=VALUE]\n"
4460 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4462 #if WIM_MOUNTING_SUPPORTED
4465 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4466 " [--check] [--streams-interface=INTERFACE]\n"
4467 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4471 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4472 " [--check] [--streams-interface=INTERFACE]\n"
4473 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4479 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4480 " [--check] [--nocheck] [--solid]\n"
4485 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4487 #if WIM_MOUNTING_SUPPORTED
4490 " %"TS" DIRECTORY\n"
4491 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4496 " %"TS" WIMFILE [IMAGE]\n"
4497 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4498 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4499 " [--command=STRING] [--wimboot-config=FILE]\n"
4504 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4508 static const tchar *invocation_name;
4509 static int invocation_cmd = CMD_NONE;
4511 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4513 static tchar buf[50];
4515 if (cmd == CMD_NONE)
4516 return T("wimlib-imagex");
4518 if (only_short_form || invocation_cmd != CMD_NONE) {
4519 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4521 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4522 imagex_commands[cmd].name);
4530 static const tchar * const fmt =
4532 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4533 "Copyright (C) 2012-2018 Eric Biggers\n"
4534 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4535 "This is free software: you are free to change and redistribute it.\n"
4536 "There is NO WARRANTY, to the extent permitted by law.\n"
4538 "Report bugs to "PACKAGE_BUGREPORT".\n"
4540 tfprintf(stdout, fmt, wimlib_get_version_string());
4544 do_common_options(int *argc_p, tchar **argv, int cmd)
4550 for (i = 1; i < argc; i++) {
4552 if (p[0] == T('-') && p[1] == T('-')) {
4554 if (!tstrcmp(p, T("help"))) {
4555 if (cmd == CMD_NONE)
4560 } else if (!tstrcmp(p, T("version"))) {
4563 } else if (!tstrcmp(p, T("quiet"))) {
4564 imagex_suppress_output();
4565 memmove(&argv[i], &argv[i + 1],
4566 (argc - i) * sizeof(argv[i]));
4569 } else if (!*p) /* reached "--", no more options */
4578 print_usage_string(int cmd, FILE *fp)
4580 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4584 recommend_man_page(int cmd, FILE *fp)
4586 const tchar *format_str;
4588 format_str = T("Some uncommon options are not listed;\n"
4589 "See %"TS".pdf in the doc directory for more details.\n");
4591 format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4593 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4597 usage(int cmd, FILE *fp)
4599 tfprintf(fp, T("Usage:\n"));
4600 print_usage_string(cmd, fp);
4601 tfprintf(fp, T("\n"));
4602 recommend_man_page(cmd, fp);
4608 tfprintf(fp, T("Usage:\n"));
4609 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4610 print_usage_string(cmd, fp);
4611 tfprintf(fp, T("\n"));
4613 static const tchar * const extra =
4616 " %"TS" --version\n"
4619 tfprintf(fp, extra, invocation_name, invocation_name);
4621 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4622 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4623 "For some commands IMAGE may be \"all\".\n"
4625 recommend_man_page(CMD_NONE, fp);
4629 extern int wmain(int argc, wchar_t **argv);
4633 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4634 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4635 * something else), while on Windows the command arguments will be UTF-16LE
4636 * encoded 'wchar_t' strings. */
4638 main(int argc, tchar **argv)
4644 imagex_info_file = stdout;
4645 invocation_name = tbasename(argv[0]);
4648 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4649 if (igcase != NULL) {
4650 if (!tstrcmp(igcase, T("no")) ||
4651 !tstrcmp(igcase, T("0")))
4652 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4653 else if (!tstrcmp(igcase, T("yes")) ||
4654 !tstrcmp(igcase, T("1")))
4655 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4658 "WARNING: Ignoring unknown setting of "
4659 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4664 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4666 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4667 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4668 for (int i = 0; i < CMD_MAX; i++) {
4669 if (!tstrcmp(invocation_name + 3,
4670 imagex_commands[i].name))
4679 /* Unless already known from the invocation name, determine which
4680 * command was specified. */
4681 if (cmd == CMD_NONE) {
4683 imagex_error(T("No command specified!\n"));
4687 for (int i = 0; i < CMD_MAX; i++) {
4688 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4693 if (cmd != CMD_NONE) {
4699 /* Handle common options. May exit early (for --help or --version). */
4700 do_common_options(&argc, argv, cmd);
4702 /* Bail if a valid command was not specified. */
4703 if (cmd == CMD_NONE) {
4704 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4709 /* Enable warning and error messages in wimlib to be more user-friendly.
4711 wimlib_set_print_errors(true);
4713 /* Initialize wimlib. */
4714 ret = wimlib_global_init(init_flags);
4716 goto out_check_status;
4718 /* Call the command handler function. */
4719 ret = imagex_commands[cmd].func(argc, argv, cmd);
4721 /* Check for error writing to standard output, especially since for some
4722 * commands, writing to standard output is part of the program's actual
4723 * behavior and not just for informational purposes. */
4724 if (ferror(stdout) || fclose(stdout)) {
4725 imagex_error_with_errno(T("error writing to standard output"));
4730 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4731 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4732 * error code from which an error message can be printed. */
4734 imagex_error(T("Exiting with error code %d:\n"
4736 wimlib_get_error_string(ret));
4737 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4738 imagex_error_with_errno(T("errno"));
4740 /* Make wimlib free any resources it's holding (although this is not
4741 * strictly necessary because the process is ending anyway). */
4742 wimlib_global_cleanup();