4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012-2017 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 bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
147 IMAGEX_CHUNK_SIZE_OPTION,
148 IMAGEX_COMMAND_OPTION,
149 IMAGEX_COMMIT_OPTION,
150 IMAGEX_COMPACT_OPTION,
151 IMAGEX_COMPRESS_OPTION,
152 IMAGEX_CONFIG_OPTION,
154 IMAGEX_DELTA_FROM_OPTION,
155 IMAGEX_DEREFERENCE_OPTION,
156 IMAGEX_DEST_DIR_OPTION,
157 IMAGEX_DETAILED_OPTION,
158 IMAGEX_EXTRACT_XML_OPTION,
161 IMAGEX_HEADER_OPTION,
162 IMAGEX_IMAGE_PROPERTY_OPTION,
163 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
165 IMAGEX_METADATA_OPTION,
166 IMAGEX_NEW_IMAGE_OPTION,
167 IMAGEX_NOCHECK_OPTION,
168 IMAGEX_NORPFIX_OPTION,
169 IMAGEX_NOT_PIPABLE_OPTION,
170 IMAGEX_NO_ACLS_OPTION,
171 IMAGEX_NO_ATTRIBUTES_OPTION,
172 IMAGEX_NO_GLOBS_OPTION,
173 IMAGEX_NO_REPLACE_OPTION,
174 IMAGEX_NO_SOLID_SORT_OPTION,
175 IMAGEX_NULLGLOB_OPTION,
176 IMAGEX_ONE_FILE_ONLY_OPTION,
178 IMAGEX_PIPABLE_OPTION,
179 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
180 IMAGEX_REBUILD_OPTION,
181 IMAGEX_RECOMPRESS_OPTION,
182 IMAGEX_RECURSIVE_OPTION,
185 IMAGEX_SNAPSHOT_OPTION,
187 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
188 IMAGEX_SOLID_COMPRESS_OPTION,
190 IMAGEX_SOURCE_LIST_OPTION,
191 IMAGEX_STAGING_DIR_OPTION,
192 IMAGEX_STREAMS_INTERFACE_OPTION,
193 IMAGEX_STRICT_ACLS_OPTION,
194 IMAGEX_THREADS_OPTION,
195 IMAGEX_TO_STDOUT_OPTION,
196 IMAGEX_UNIX_DATA_OPTION,
197 IMAGEX_UNSAFE_COMPACT_OPTION,
198 IMAGEX_UPDATE_OF_OPTION,
199 IMAGEX_VERBOSE_OPTION,
200 IMAGEX_WIMBOOT_CONFIG_OPTION,
201 IMAGEX_WIMBOOT_OPTION,
205 static const struct option apply_options[] = {
206 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
207 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
208 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
209 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
210 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
211 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
212 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
213 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
214 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
215 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
216 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
217 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
218 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
222 static const struct option capture_or_append_options[] = {
223 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
224 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
225 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
226 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
227 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
228 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
229 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
230 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
231 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
232 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
233 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
234 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
235 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
236 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
237 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
238 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
239 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
240 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
241 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
242 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
243 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
244 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
245 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
246 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
247 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
248 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
249 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
250 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
251 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
252 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
253 {T("snapshot"), no_argument, NULL, IMAGEX_SNAPSHOT_OPTION},
257 static const struct option delete_options[] = {
258 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
259 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
260 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
264 static const struct option dir_options[] = {
265 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
266 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
267 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
268 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
272 static const struct option export_options[] = {
273 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
274 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
275 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
276 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
277 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
278 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
279 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
280 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
281 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
282 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
283 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
284 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
285 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
286 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
287 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
288 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
289 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
290 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
294 static const struct option extract_options[] = {
295 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
296 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
297 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
298 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
299 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
300 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
301 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
302 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
303 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
304 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
305 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
306 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
307 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
308 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
309 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
310 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
311 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
315 static const struct option info_options[] = {
316 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
317 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
318 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
319 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
320 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
321 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
322 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
323 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
324 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
325 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
329 static const struct option join_options[] = {
330 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
334 #if WIM_MOUNTING_SUPPORTED
335 static const struct option mount_options[] = {
336 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
337 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
338 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
339 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
340 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
341 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
342 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
347 static const struct option optimize_options[] = {
348 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
349 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
350 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
351 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
352 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
353 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
354 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
355 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
356 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
357 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
358 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
359 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
360 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
361 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
365 static const struct option split_options[] = {
366 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
370 #if WIM_MOUNTING_SUPPORTED
371 static const struct option unmount_options[] = {
372 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
373 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
374 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
375 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
376 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
377 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
382 static const struct option update_options[] = {
383 /* Careful: some of the options here set the defaults for update
384 * commands, but the flags given to an actual update command (and not to
385 * `imagex update' itself are also handled in
386 * update_command_add_option(). */
387 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
388 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
389 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
390 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
391 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
393 /* Default delete options */
394 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
395 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
397 /* Global add option */
398 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
400 /* Default add options */
401 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
402 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
403 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
404 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
405 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
406 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
407 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
408 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
413 static const struct option verify_options[] = {
414 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
415 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
421 # define _format_attribute(type, format_str, args_start) \
422 __attribute__((format(type, format_str, args_start)))
424 # define _format_attribute(type, format_str, args_start)
427 /* Print formatted error message to stderr. */
428 static void _format_attribute(printf, 1, 2)
429 imagex_error(const tchar *format, ...)
432 va_start(va, format);
433 tfputs(T("ERROR: "), stderr);
434 tvfprintf(stderr, format, va);
435 tputc(T('\n'), stderr);
439 /* Print formatted error message to stderr. */
440 static void _format_attribute(printf, 1, 2)
441 imagex_error_with_errno(const tchar *format, ...)
443 int errno_save = errno;
445 va_start(va, format);
446 tfputs(T("ERROR: "), stderr);
447 tvfprintf(stderr, format, va);
448 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
453 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
455 if (image == WIMLIB_NO_IMAGE) {
456 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
457 " Please specify a 1-based image index or "
458 "image name. To list the images\n"
459 " contained in the WIM archive, run\n"
461 " %"TS" \"%"TS"\"\n"),
462 image_name, wim_name,
463 get_cmd_string(CMD_INFO, false), wim_name);
464 return WIMLIB_ERR_INVALID_IMAGE;
470 verify_image_is_single(int image)
472 if (image == WIMLIB_ALL_IMAGES) {
473 imagex_error(T("Cannot specify all images for this action!"));
474 return WIMLIB_ERR_INVALID_IMAGE;
480 verify_image_exists_and_is_single(int image, const tchar *image_name,
481 const tchar *wim_name)
484 ret = verify_image_exists(image, image_name, wim_name);
486 ret = verify_image_is_single(image);
491 print_available_compression_types(FILE *fp)
493 static const tchar * const s =
495 "Available compression types:\n"
498 " xpress (alias: \"fast\")\n"
499 " lzx (alias: \"maximum\") (default for capture)\n"
500 " lzms (alias: \"recovery\")\n"
506 /* Parse the argument to --compress or --solid-compress */
508 get_compression_type(tchar *optarg, bool solid)
511 unsigned int compression_level = 0;
514 plevel = tstrchr(optarg, T(':'));
520 ultmp = tstrtoul(plevel, &ptmp, 10);
521 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
522 imagex_error(T("Compression level must be a positive integer! "
523 "e.g. --compress=lzx:80"));
524 return WIMLIB_COMPRESSION_TYPE_INVALID;
526 compression_level = ultmp;
529 if (!tstrcasecmp(optarg, T("maximum")) ||
530 !tstrcasecmp(optarg, T("lzx")) ||
531 !tstrcasecmp(optarg, T("max"))) {
532 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
533 } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
534 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
535 } else if (!tstrcasecmp(optarg, T("recovery"))) {
539 "Warning: use of '--compress=recovery' is discouraged because it behaves\n"
540 " differently from DISM. Instead, you typically want to use '--solid' to\n"
541 " create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
542 " /compress:recovery. But if you really want *non-solid* LZMS compression,\n"
543 " then you may suppress this warning by specifying '--compress=lzms' instead\n"
544 " of '--compress=recovery'.\n"));
546 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
547 } else if (!tstrcasecmp(optarg, T("lzms"))) {
548 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
549 } else if (!tstrcasecmp(optarg, T("none"))) {
550 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
552 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
553 print_available_compression_types(stderr);
554 return WIMLIB_COMPRESSION_TYPE_INVALID;
557 if (compression_level != 0)
558 wimlib_set_default_compression_level(ctype, compression_level);
562 /* Parse the argument to --compact */
564 set_compact_mode(const tchar *arg, int *extract_flags)
567 if (!tstrcasecmp(arg, T("xpress4k")))
568 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
569 else if (!tstrcasecmp(arg, T("xpress8k")))
570 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
571 else if (!tstrcasecmp(arg, T("xpress16k")))
572 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
573 else if (!tstrcasecmp(arg, T("lzx")))
574 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
577 *extract_flags |= flag;
582 "\"%"TS"\" is not a recognized System Compression format. The options are:"
584 " --compact=xpress4k\n"
585 " --compact=xpress8k\n"
586 " --compact=xpress16k\n"
595 unsigned num_strings;
596 unsigned num_alloc_strings;
599 #define STRING_LIST_INITIALIZER \
600 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
602 #define STRING_LIST(_strings) \
603 struct string_list _strings = STRING_LIST_INITIALIZER
606 string_list_append(struct string_list *list, tchar *glob)
608 unsigned num_alloc_strings = list->num_alloc_strings;
610 if (list->num_strings == num_alloc_strings) {
613 num_alloc_strings += 4;
614 new_strings = realloc(list->strings,
615 sizeof(list->strings[0]) * num_alloc_strings);
617 imagex_error(T("Out of memory!"));
620 list->strings = new_strings;
621 list->num_alloc_strings = num_alloc_strings;
623 list->strings[list->num_strings++] = glob;
628 string_list_destroy(struct string_list *list)
634 wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
636 return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
638 WIMLIB_REF_FLAG_GLOB_ENABLE,
643 append_image_property_argument(struct string_list *image_properties)
645 if (!tstrchr(optarg, '=')) {
646 imagex_error(T("'--image-property' argument "
647 "must be in the form NAME=VALUE"));
650 return string_list_append(image_properties, optarg);
654 apply_image_properties(struct string_list *image_properties,
655 WIMStruct *wim, int image, bool *any_changes_ret)
657 bool any_changes = false;
658 for (unsigned i = 0; i < image_properties->num_strings; i++) {
660 const tchar *current_value;
663 name = image_properties->strings[i];
664 value = tstrchr(name, '=');
667 current_value = wimlib_get_image_property(wim, image, name);
668 if (current_value && !tstrcmp(current_value, value)) {
669 imagex_printf(T("The %"TS" property of image %d "
670 "already has value \"%"TS"\".\n"),
673 imagex_printf(T("Setting the %"TS" property of image "
674 "%d to \"%"TS"\".\n"),
676 ret = wimlib_set_image_property(wim, image, name, value);
683 *any_changes_ret = any_changes;
688 do_resource_not_found_warning(const tchar *wimfile,
689 const struct wimlib_wim_info *info,
690 const struct string_list *refglobs)
692 if (info->total_parts > 1) {
693 if (refglobs->num_strings == 0) {
694 imagex_error(T("\"%"TS"\" is part of a split WIM. "
695 "Use --ref to specify the other parts."),
698 imagex_error(T("Perhaps the '--ref' argument did not "
699 "specify all other parts of the split "
703 imagex_error(T("If this is a delta WIM, use the --ref argument "
704 "to specify the WIM(s) on which it is based."));
709 do_metadata_not_found_warning(const tchar *wimfile,
710 const struct wimlib_wim_info *info)
712 if (info->part_number != 1) {
713 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
714 " You must specify the first part."),
719 /* Returns the size of a file given its name, or -1 if the file does not exist
720 * or its size cannot be determined. */
722 file_get_size(const tchar *filename)
725 if (tstat(filename, &st) == 0)
732 PARSE_STRING_SUCCESS = 0,
733 PARSE_STRING_FAILURE = 1,
734 PARSE_STRING_NONE = 2,
738 * Parses a string token from an array of characters.
740 * Tokens are either whitespace-delimited, or double or single-quoted.
742 * @line_p: Pointer to the pointer to the line of data. Will be updated
743 * to point past the string token iff the return value is
744 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
747 * @len_p: @len_p initially stores the length of the line of data, which may
748 * be 0, and it will be updated to the number of bytes remaining in
749 * the line iff the return value is PARSE_STRING_SUCCESS.
751 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
752 * parsed string token will be returned here.
754 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
755 * PARSE_STRING_FAILURE if the data was invalid due to a missing
756 * closing quote; or PARSE_STRING_NONE if the line ended before the
757 * beginning of a string token was found.
760 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
763 tchar *line = *line_p;
767 /* Skip leading whitespace */
770 return PARSE_STRING_NONE;
771 if (!istspace(*line) && *line != T('\0'))
777 if (quote_char == T('"') || quote_char == T('\'')) {
782 line = tmemchr(line, quote_char, len);
784 imagex_error(T("Missing closing quote: %"TS), fn - 1);
785 return PARSE_STRING_FAILURE;
788 /* Unquoted string. Go until whitespace. Line is terminated
789 * by '\0', so no need to check 'len'. */
793 } while (!istspace(*line) && *line != T('\0'));
800 return PARSE_STRING_SUCCESS;
803 /* Parses a line of data (not an empty line or comment) in the source list file
804 * format. (See the man page for 'wimlib-imagex capture' for details on this
805 * format and the meaning.)
807 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
808 * len == 0. The data in @line will be modified by this function call.
810 * @len: Length of the line of data.
812 * @source: On success, the capture source and target described by the line is
813 * written into this destination. Note that it will contain pointers
814 * to data in the @line array.
816 * Returns true if the line was valid; false otherwise. */
818 parse_source_list_line(tchar *line, size_t len,
819 struct wimlib_capture_source *source)
823 ret = parse_string(&line, &len, &source->fs_source_path);
824 if (ret != PARSE_STRING_SUCCESS)
826 ret = parse_string(&line, &len, &source->wim_target_path);
827 if (ret == PARSE_STRING_NONE)
828 source->wim_target_path = source->fs_source_path;
829 return ret != PARSE_STRING_FAILURE;
832 /* Returns %true if the given line of length @len > 0 is a comment or empty line
833 * in the source list file format. */
835 is_comment_line(const tchar *line, size_t len)
838 if (*line == T('#') || *line == T(';'))
840 if (!istspace(*line) && *line != T('\0'))
850 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
853 tchar *contents = *contents_p;
854 size_t nchars = *nchars_p;
857 for (i = 0; i < nchars; i++)
858 if (contents[i] == T('\n'))
861 /* Handle last line not terminated by a newline */
862 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
863 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
865 imagex_error(T("Out of memory!"));
868 contents[nchars] = T('\n');
869 *contents_p = contents;
877 /* Parses a file in the source list format. (See the man page for
878 * 'wimlib-imagex capture' for details on this format and the meaning.)
880 * @source_list_contents: Contents of the source list file. Note that this
881 * buffer will be modified to save memory allocations,
882 * and cannot be freed until the returned array of
883 * wimlib_capture_source's has also been freed.
885 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
888 * @nsources_ret: On success, the length of the returned array is
891 * Returns: An array of `struct wimlib_capture_source's that can be passed to
892 * the wimlib_add_image_multisource() function to specify how a WIM image is to
894 static struct wimlib_capture_source *
895 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
896 size_t *nsources_ret)
900 struct wimlib_capture_source *sources;
903 nlines = text_file_count_lines(source_list_contents_p,
904 &source_list_nchars);
908 /* Always allocate at least 1 slot, just in case the implementation of
909 * calloc() returns NULL if 0 bytes are requested. */
910 sources = calloc(nlines ?: 1, sizeof(*sources));
912 imagex_error(T("out of memory"));
915 p = *source_list_contents_p;
917 for (i = 0; i < nlines; i++) {
918 /* XXX: Could use rawmemchr() here instead, but it may not be
919 * available on all platforms. */
920 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
921 size_t len = endp - p + 1;
923 if (!is_comment_line(p, len)) {
924 if (!parse_source_list_line(p, len, &sources[j++])) {
936 /* Reads the contents of a file into memory. */
938 file_get_contents(const tchar *filename, size_t *len_ret)
945 if (tstat(filename, &stbuf) != 0) {
946 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
951 fp = tfopen(filename, T("rb"));
953 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
957 buf = malloc(len ? len : 1);
959 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
960 "contents of file \"%"TS"\""), len, filename);
963 if (fread(buf, 1, len, fp) != len) {
964 imagex_error_with_errno(T("Failed to read %zu bytes from the "
965 "file \"%"TS"\""), len, filename);
979 /* Read standard input until EOF and return the full contents in a malloc()ed
980 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
983 stdin_get_contents(size_t *len_ret)
985 /* stdin can, of course, be a pipe or other non-seekable file, so the
986 * total length of the data cannot be pre-determined */
988 size_t newlen = 1024;
992 char *p = realloc(buf, newlen);
993 size_t bytes_read, bytes_to_read;
995 imagex_error(T("out of memory while reading stdin"));
999 bytes_to_read = newlen - pos;
1000 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
1002 if (bytes_read != bytes_to_read) {
1007 imagex_error_with_errno(T("error reading stdin"));
1021 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1024 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1026 *num_tchars_ret = num_bytes;
1028 #else /* !__WIN32__ */
1029 /* On Windows, translate the text to UTF-16LE */
1033 if (num_bytes >= 2 &&
1034 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1035 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1037 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1038 * with something that looks like an ASCII character encoded as
1039 * a UTF-16LE code unit. Assume the file is encoded as
1040 * UTF-16LE. This is not a 100% reliable check. */
1041 num_wchars = num_bytes / 2;
1042 text_wstr = (wchar_t*)text;
1044 /* File does not look like UTF-16LE. Assume it is encoded in
1045 * the current Windows code page. I think these are always
1046 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1047 * should work as expected. */
1048 text_wstr = win32_mbs_to_wcs(text,
1053 *num_tchars_ret = num_wchars;
1055 #endif /* __WIN32__ */
1059 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1064 contents = file_get_contents(filename, &num_bytes);
1067 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1071 stdin_get_text_contents(size_t *num_tchars_ret)
1076 contents = stdin_get_contents(&num_bytes);
1079 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1082 #define TO_PERCENT(numerator, denominator) \
1083 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1085 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1086 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1087 #define KIBIBYTE_MIN_NBYTES 10000ULL
1090 get_unit(uint64_t total_bytes, const tchar **name_ret)
1092 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1093 *name_ret = T("GiB");
1095 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1096 *name_ret = T("MiB");
1098 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1099 *name_ret = T("KiB");
1102 *name_ret = T("bytes");
1107 static struct wimlib_progress_info_scan last_scan_progress;
1110 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1112 uint64_t prev_count, cur_count;
1114 prev_count = last_scan_progress.num_nondirs_scanned +
1115 last_scan_progress.num_dirs_scanned;
1116 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1118 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1119 cur_count % 128 == 0)
1121 unsigned unit_shift;
1122 const tchar *unit_name;
1124 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1125 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1126 "%"PRIu64" directories) "),
1127 scan->num_bytes_scanned >> unit_shift,
1129 scan->num_nondirs_scanned,
1130 scan->num_dirs_scanned);
1131 last_scan_progress = *scan;
1134 /* Progress callback function passed to various wimlib functions. */
1135 static enum wimlib_progress_status
1136 imagex_progress_func(enum wimlib_progress_msg msg,
1137 union wimlib_progress_info *info,
1138 void *_ignored_context)
1140 unsigned percent_done;
1141 unsigned unit_shift;
1142 const tchar *unit_name;
1144 if (imagex_be_quiet)
1145 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1147 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1149 static bool started;
1151 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1152 imagex_printf(T("Using %"TS" compression "
1153 "with %u thread%"TS"\n"),
1154 wimlib_get_compression_type_string(
1155 info->write_streams.compression_type),
1156 info->write_streams.num_threads,
1157 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1162 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1163 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1164 info->write_streams.total_bytes);
1166 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1167 info->write_streams.completed_bytes >> unit_shift,
1169 info->write_streams.total_bytes >> unit_shift,
1172 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1173 imagex_printf(T("\n"));
1175 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1176 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1177 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1178 imagex_printf(T("\n"));
1180 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1181 info->scan.wim_target_path);
1183 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1185 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1186 switch (info->scan.status) {
1187 case WIMLIB_SCAN_DENTRY_OK:
1188 report_scan_progress(&info->scan, false);
1190 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1191 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1193 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1194 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1195 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1197 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1198 /* Symlink fixups are enabled by default. This is
1199 * mainly intended for Windows, which for some reason
1200 * uses absolute junctions (with drive letters!) in the
1201 * default installation. On UNIX-like systems, warn the
1202 * user when fixing the target of an absolute symbolic
1203 * link, so they know to disable this if they want. */
1205 imagex_printf(T("\nWARNING: Adjusted target of "
1206 "absolute symbolic link \"%"TS"\"\n"
1207 " (Use --norpfix to capture "
1208 "absolute symbolic links as-is)\n"),
1209 info->scan.cur_path);
1216 case WIMLIB_PROGRESS_MSG_SCAN_END:
1217 report_scan_progress(&info->scan, true);
1218 imagex_printf(T("\n"));
1220 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1221 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1222 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1223 info->integrity.total_bytes);
1224 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1225 "of %"PRIu64" %"TS" (%u%%) done"),
1226 info->integrity.filename,
1227 info->integrity.completed_bytes >> unit_shift,
1229 info->integrity.total_bytes >> unit_shift,
1232 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1233 imagex_printf(T("\n"));
1235 case WIMLIB_PROGRESS_MSG_CALC_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("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1240 "of %"PRIu64" %"TS" (%u%%) done"),
1241 info->integrity.completed_bytes >> unit_shift,
1243 info->integrity.total_bytes >> unit_shift,
1246 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1247 imagex_printf(T("\n"));
1249 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1250 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1251 "to %"TS" \"%"TS"\"\n"),
1252 info->extract.image,
1253 info->extract.image_name,
1254 info->extract.wimfile_name,
1255 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1256 T("NTFS volume") : T("directory")),
1257 info->extract.target);
1259 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1260 if (info->extract.end_file_count >= 2000) {
1261 percent_done = TO_PERCENT(info->extract.current_file_count,
1262 info->extract.end_file_count);
1263 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1264 info->extract.current_file_count,
1265 info->extract.end_file_count, percent_done);
1266 if (info->extract.current_file_count == info->extract.end_file_count)
1267 imagex_printf(T("\n"));
1270 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1271 percent_done = TO_PERCENT(info->extract.completed_bytes,
1272 info->extract.total_bytes);
1273 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1274 imagex_printf(T("\rExtracting file data: "
1275 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1276 info->extract.completed_bytes >> unit_shift,
1278 info->extract.total_bytes >> unit_shift,
1281 if (info->extract.completed_bytes >= info->extract.total_bytes)
1282 imagex_printf(T("\n"));
1284 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1285 if (info->extract.end_file_count >= 2000) {
1286 percent_done = TO_PERCENT(info->extract.current_file_count,
1287 info->extract.end_file_count);
1288 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1289 info->extract.current_file_count,
1290 info->extract.end_file_count, percent_done);
1291 if (info->extract.current_file_count == info->extract.end_file_count)
1292 imagex_printf(T("\n"));
1295 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1296 if (info->extract.total_parts != 1) {
1297 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1298 info->extract.part_number,
1299 info->extract.total_parts);
1302 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1303 percent_done = TO_PERCENT(info->split.completed_bytes,
1304 info->split.total_bytes);
1305 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1306 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1307 "%"PRIu64" %"TS" (%u%%) written\n"),
1308 info->split.part_name,
1309 info->split.cur_part_number,
1310 info->split.total_parts,
1311 info->split.completed_bytes >> unit_shift,
1313 info->split.total_bytes >> unit_shift,
1317 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1318 if (info->split.completed_bytes == info->split.total_bytes) {
1319 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1320 info->split.cur_part_number,
1321 info->split.total_parts);
1324 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1325 switch (info->update.command->op) {
1326 case WIMLIB_UPDATE_OP_DELETE:
1327 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1328 info->update.command->delete_.wim_path);
1330 case WIMLIB_UPDATE_OP_RENAME:
1331 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1332 info->update.command->rename.wim_source_path,
1333 info->update.command->rename.wim_target_path);
1335 case WIMLIB_UPDATE_OP_ADD:
1340 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1341 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1342 info->replace.path_in_wim);
1344 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1345 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1346 info->wimboot_exclude.path_in_wim);
1348 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1349 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1350 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1351 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1352 info->unmount.mounted_wim,
1353 info->unmount.mounted_image);
1355 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1356 info->unmount.mounted_wim,
1357 info->unmount.mounted_image);
1358 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1362 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1363 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1364 info->verify_image.current_image,
1365 info->verify_image.total_images);
1367 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1368 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1369 info->verify_streams.total_bytes);
1370 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1371 imagex_printf(T("\rVerifying file data: "
1372 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1373 info->verify_streams.completed_bytes >> unit_shift,
1375 info->verify_streams.total_bytes >> unit_shift,
1378 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1379 imagex_printf(T("\n"));
1384 fflush(imagex_info_file);
1385 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1389 parse_num_threads(const tchar *optarg)
1392 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1393 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1394 imagex_error(T("Number of threads must be a non-negative integer!"));
1402 parse_chunk_size(const tchar *optarg)
1405 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1406 if (chunk_size == 0) {
1407 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1408 " with optional K, M, or G suffix"));
1412 if (*tmp == T('k') || *tmp == T('K')) {
1415 } else if (*tmp == T('m') || *tmp == T('M')) {
1418 } else if (*tmp == T('g') || *tmp == T('G')) {
1422 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1423 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1427 if (chunk_size >= UINT32_MAX) {
1428 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1436 * Parse an option passed to an update command.
1438 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1441 * @option: Text string for the option (beginning with --)
1443 * @cmd: `struct wimlib_update_command' that is being constructed for
1446 * Returns true if the option was recognized; false if not.
1449 update_command_add_option(int op, const tchar *option,
1450 struct wimlib_update_command *cmd)
1452 bool recognized = true;
1454 case WIMLIB_UPDATE_OP_ADD:
1455 if (!tstrcmp(option, T("--verbose")))
1456 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1457 else if (!tstrcmp(option, T("--unix-data")))
1458 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1459 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1460 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1461 else if (!tstrcmp(option, T("--strict-acls")))
1462 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1463 else if (!tstrcmp(option, T("--dereference")))
1464 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1465 else if (!tstrcmp(option, T("--no-replace")))
1466 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1470 case WIMLIB_UPDATE_OP_DELETE:
1471 if (!tstrcmp(option, T("--force")))
1472 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1473 else if (!tstrcmp(option, T("--recursive")))
1474 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1485 /* How many nonoption arguments each `imagex update' command expects */
1486 static const unsigned update_command_num_nonoptions[] = {
1487 [WIMLIB_UPDATE_OP_ADD] = 2,
1488 [WIMLIB_UPDATE_OP_DELETE] = 1,
1489 [WIMLIB_UPDATE_OP_RENAME] = 2,
1493 update_command_add_nonoption(int op, const tchar *nonoption,
1494 struct wimlib_update_command *cmd,
1495 unsigned num_nonoptions)
1498 case WIMLIB_UPDATE_OP_ADD:
1499 if (num_nonoptions == 0)
1500 cmd->add.fs_source_path = (tchar*)nonoption;
1502 cmd->add.wim_target_path = (tchar*)nonoption;
1504 case WIMLIB_UPDATE_OP_DELETE:
1505 cmd->delete_.wim_path = (tchar*)nonoption;
1507 case WIMLIB_UPDATE_OP_RENAME:
1508 if (num_nonoptions == 0)
1509 cmd->rename.wim_source_path = (tchar*)nonoption;
1511 cmd->rename.wim_target_path = (tchar*)nonoption;
1517 * Parse a command passed on stdin to `imagex update'.
1519 * @line: Text of the command.
1520 * @len: Length of the line, including a null terminator
1523 * @command: A `struct wimlib_update_command' to fill in from the parsed
1526 * @line_number: Line number of the command, for diagnostics.
1528 * Returns true on success; returns false on parse error.
1531 parse_update_command(tchar *line, size_t len,
1532 struct wimlib_update_command *command,
1536 tchar *command_name;
1538 size_t num_nonoptions;
1540 /* Get the command name ("add", "delete", "rename") */
1541 ret = parse_string(&line, &len, &command_name);
1542 if (ret != PARSE_STRING_SUCCESS)
1545 if (!tstrcasecmp(command_name, T("add"))) {
1546 op = WIMLIB_UPDATE_OP_ADD;
1547 } else if (!tstrcasecmp(command_name, T("delete"))) {
1548 op = WIMLIB_UPDATE_OP_DELETE;
1549 } else if (!tstrcasecmp(command_name, T("rename"))) {
1550 op = WIMLIB_UPDATE_OP_RENAME;
1552 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1553 command_name, line_number);
1558 /* Parse additional options and non-options as needed */
1563 ret = parse_string(&line, &len, &next_string);
1564 if (ret == PARSE_STRING_NONE) /* End of line */
1566 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1568 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1570 if (!update_command_add_option(op, next_string, command))
1572 imagex_error(T("Unrecognized option \"%"TS"\" to "
1573 "update command \"%"TS"\" on line %zu"),
1574 next_string, command_name, line_number);
1580 if (num_nonoptions == update_command_num_nonoptions[op])
1582 imagex_error(T("Unexpected argument \"%"TS"\" in "
1583 "update command on line %zu\n"
1584 " (The \"%"TS"\" command only "
1585 "takes %zu nonoption arguments!)\n"),
1586 next_string, line_number,
1587 command_name, num_nonoptions);
1590 update_command_add_nonoption(op, next_string,
1591 command, num_nonoptions);
1596 if (num_nonoptions != update_command_num_nonoptions[op]) {
1597 imagex_error(T("Not enough arguments to update command "
1598 "\"%"TS"\" on line %zu"), command_name, line_number);
1604 static struct wimlib_update_command *
1605 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1606 size_t *num_cmds_ret)
1610 struct wimlib_update_command *cmds;
1613 nlines = text_file_count_lines(cmd_file_contents_p,
1618 /* Always allocate at least 1 slot, just in case the implementation of
1619 * calloc() returns NULL if 0 bytes are requested. */
1620 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1622 imagex_error(T("out of memory"));
1625 p = *cmd_file_contents_p;
1627 for (i = 0; i < nlines; i++) {
1628 /* XXX: Could use rawmemchr() here instead, but it may not be
1629 * available on all platforms. */
1630 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1631 size_t len = endp - p + 1;
1633 if (!is_comment_line(p, len)) {
1634 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1645 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1646 * one image from a WIM file to an NTFS volume. */
1648 imagex_apply(int argc, tchar **argv, int cmd)
1652 int image = WIMLIB_NO_IMAGE;
1654 struct wimlib_wim_info info;
1656 const tchar *wimfile;
1657 const tchar *target;
1658 const tchar *image_num_or_name = NULL;
1659 int extract_flags = 0;
1661 STRING_LIST(refglobs);
1663 for_opt(c, apply_options) {
1665 case IMAGEX_CHECK_OPTION:
1666 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1668 case IMAGEX_VERBOSE_OPTION:
1669 /* No longer does anything. */
1671 case IMAGEX_REF_OPTION:
1672 ret = string_list_append(&refglobs, optarg);
1674 goto out_free_refglobs;
1676 case IMAGEX_UNIX_DATA_OPTION:
1677 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1679 case IMAGEX_NO_ACLS_OPTION:
1680 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1682 case IMAGEX_STRICT_ACLS_OPTION:
1683 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1685 case IMAGEX_NO_ATTRIBUTES_OPTION:
1686 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1688 case IMAGEX_NORPFIX_OPTION:
1689 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1691 case IMAGEX_RPFIX_OPTION:
1692 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1694 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1695 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1696 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1698 case IMAGEX_WIMBOOT_OPTION:
1699 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1701 case IMAGEX_COMPACT_OPTION:
1702 ret = set_compact_mode(optarg, &extract_flags);
1704 goto out_free_refglobs;
1712 if (argc != 2 && argc != 3)
1717 if (!tstrcmp(wimfile, T("-"))) {
1718 /* Attempt to apply pipable WIM from standard input. */
1720 image_num_or_name = NULL;
1723 image_num_or_name = argv[1];
1728 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1729 imagex_progress_func, NULL);
1731 goto out_free_refglobs;
1733 wimlib_get_wim_info(wim, &info);
1736 /* Image explicitly specified. */
1737 image_num_or_name = argv[1];
1738 image = wimlib_resolve_image(wim, image_num_or_name);
1739 ret = verify_image_exists(image, image_num_or_name, wimfile);
1741 goto out_wimlib_free;
1744 /* No image specified; default to image 1, but only if the WIM
1745 * contains exactly one image. */
1747 if (info.image_count != 1) {
1748 imagex_error(T("\"%"TS"\" contains %d images; "
1749 "Please select one (or all)."),
1750 wimfile, info.image_count);
1759 if (refglobs.num_strings) {
1761 imagex_error(T("Can't specify --ref when applying from stdin!"));
1763 goto out_wimlib_free;
1765 ret = wim_reference_globs(wim, &refglobs, open_flags);
1767 goto out_wimlib_free;
1772 /* Interpret a regular file or block device target as an NTFS
1776 if (tstat(target, &stbuf)) {
1777 if (errno != ENOENT) {
1778 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1781 goto out_wimlib_free;
1784 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1785 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1791 ret = wimlib_extract_image(wim, image, target, extract_flags);
1793 set_fd_to_binary_mode(STDIN_FILENO);
1794 ret = wimlib_extract_image_from_pipe_with_progress(
1799 imagex_progress_func,
1803 imagex_printf(T("Done applying WIM image.\n"));
1804 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1806 do_resource_not_found_warning(wimfile, &info, &refglobs);
1808 imagex_error(T( "If you are applying an image "
1809 "from a split pipable WIM,\n"
1810 " make sure you have "
1811 "concatenated together all parts."));
1813 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1814 do_metadata_not_found_warning(wimfile, &info);
1819 string_list_destroy(&refglobs);
1823 usage(CMD_APPLY, stderr);
1825 goto out_free_refglobs;
1828 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1829 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1830 * the desired image. 'wimlib-imagex append': add a new image to an existing
1833 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1837 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1838 WIMLIB_ADD_FLAG_WINCONFIG |
1839 WIMLIB_ADD_FLAG_VERBOSE |
1840 WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1841 int write_flags = 0;
1842 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1843 uint32_t chunk_size = UINT32_MAX;
1844 uint32_t solid_chunk_size = UINT32_MAX;
1845 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1846 const tchar *wimfile;
1849 STRING_LIST(image_properties);
1852 STRING_LIST(base_wimfiles);
1853 WIMStruct **base_wims;
1855 WIMStruct *template_wim;
1856 const tchar *template_wimfile = NULL;
1857 const tchar *template_image_name_or_num = NULL;
1858 int template_image = WIMLIB_NO_IMAGE;
1861 unsigned num_threads = 0;
1866 tchar *config_file = NULL;
1868 bool source_list = false;
1869 size_t source_list_nchars = 0;
1870 tchar *source_list_contents;
1871 bool capture_sources_malloced;
1872 struct wimlib_capture_source *capture_sources;
1874 bool name_defaulted;
1876 for_opt(c, capture_or_append_options) {
1878 case IMAGEX_BOOT_OPTION:
1879 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1881 case IMAGEX_CHECK_OPTION:
1882 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1883 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1885 case IMAGEX_NOCHECK_OPTION:
1886 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1888 case IMAGEX_CONFIG_OPTION:
1889 config_file = optarg;
1890 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1892 case IMAGEX_COMPRESS_OPTION:
1893 compression_type = get_compression_type(optarg, false);
1894 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1897 case IMAGEX_CHUNK_SIZE_OPTION:
1898 chunk_size = parse_chunk_size(optarg);
1899 if (chunk_size == UINT32_MAX)
1902 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1903 solid_chunk_size = parse_chunk_size(optarg);
1904 if (solid_chunk_size == UINT32_MAX)
1907 case IMAGEX_SOLID_COMPRESS_OPTION:
1908 solid_ctype = get_compression_type(optarg, true);
1909 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1912 case IMAGEX_SOLID_OPTION:
1913 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1915 case IMAGEX_NO_SOLID_SORT_OPTION:
1916 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1918 case IMAGEX_FLAGS_OPTION: {
1919 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1920 tsprintf(p, T("FLAGS=%"TS), optarg);
1921 ret = string_list_append(&image_properties, p);
1926 case IMAGEX_IMAGE_PROPERTY_OPTION:
1927 ret = append_image_property_argument(&image_properties);
1931 case IMAGEX_DEREFERENCE_OPTION:
1932 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1934 case IMAGEX_VERBOSE_OPTION:
1935 /* No longer does anything. */
1937 case IMAGEX_THREADS_OPTION:
1938 num_threads = parse_num_threads(optarg);
1939 if (num_threads == UINT_MAX)
1942 case IMAGEX_REBUILD_OPTION:
1943 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1945 case IMAGEX_UNIX_DATA_OPTION:
1946 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1948 case IMAGEX_SOURCE_LIST_OPTION:
1951 case IMAGEX_NO_ACLS_OPTION:
1952 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1954 case IMAGEX_STRICT_ACLS_OPTION:
1955 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1957 case IMAGEX_RPFIX_OPTION:
1958 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1960 case IMAGEX_NORPFIX_OPTION:
1961 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1963 case IMAGEX_PIPABLE_OPTION:
1964 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1966 case IMAGEX_NOT_PIPABLE_OPTION:
1967 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1969 case IMAGEX_UPDATE_OF_OPTION:
1970 if (template_image_name_or_num) {
1971 imagex_error(T("'--update-of' can only be "
1972 "specified one time!"));
1976 colon = tstrrchr(optarg, T(':'));
1979 template_wimfile = optarg;
1981 template_image_name_or_num = colon + 1;
1983 template_wimfile = NULL;
1984 template_image_name_or_num = optarg;
1988 case IMAGEX_DELTA_FROM_OPTION:
1989 ret = string_list_append(&base_wimfiles, optarg);
1992 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1994 case IMAGEX_WIMBOOT_OPTION:
1995 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1997 case IMAGEX_UNSAFE_COMPACT_OPTION:
1998 if (cmd != CMD_APPEND) {
1999 imagex_error(T("'--unsafe-compact' is only "
2000 "valid for append!"));
2003 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2005 case IMAGEX_SNAPSHOT_OPTION:
2006 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2015 if (argc < 2 || argc > 4)
2021 /* Set default compression type and parameters. */
2024 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2025 /* No compression type specified. Use the default. */
2027 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2028 /* With --wimboot, default to XPRESS compression. */
2029 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2030 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2031 /* With --solid, default to LZMS compression. (However,
2032 * this will not affect solid resources!) */
2033 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2035 /* Otherwise, default to LZX compression. */
2036 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2040 if (!tstrcmp(wimfile, T("-"))) {
2041 /* Writing captured WIM to standard output. */
2043 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2044 imagex_error("Can't write a non-pipable WIM to "
2045 "standard output! Specify --pipable\n"
2046 " if you want to create a pipable WIM "
2047 "(but read the docs first).");
2051 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2053 if (cmd == CMD_APPEND) {
2054 imagex_error(T("Using standard output for append does "
2055 "not make sense."));
2058 wim_fd = STDOUT_FILENO;
2060 imagex_info_file = stderr;
2061 set_fd_to_binary_mode(wim_fd);
2064 /* If template image was specified using --update-of=IMAGE rather
2065 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2066 if (template_image_name_or_num && !template_wimfile) {
2067 if (base_wimfiles.num_strings == 1) {
2068 /* Capturing delta WIM based on single WIM: default to
2070 template_wimfile = base_wimfiles.strings[0];
2071 } else if (cmd == CMD_APPEND) {
2072 /* Appending to WIM: default to WIM being appended to.
2074 template_wimfile = wimfile;
2076 /* Capturing a normal (non-delta) WIM, so the WIM file
2077 * *must* be explicitly specified. */
2078 if (base_wimfiles.num_strings > 1) {
2079 imagex_error(T("For capture of delta WIM "
2080 "based on multiple existing "
2082 " '--update-of' must "
2083 "specify WIMFILE:IMAGE!"));
2085 imagex_error(T("For capture of non-delta WIM, "
2086 "'--update-of' must specify "
2095 name_defaulted = false;
2097 /* Set default name to SOURCE argument, omitting any directory
2098 * prefixes and trailing slashes. This requires making a copy
2099 * of @source. Leave some free characters at the end in case we
2100 * append a number to keep the name unique. */
2101 size_t source_name_len;
2103 source_name_len = tstrlen(source);
2104 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2105 name = tbasename(tstrcpy(source_copy, source));
2106 name_defaulted = true;
2109 /* Image description (if given). */
2111 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2112 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2113 ret = string_list_append(&image_properties, p);
2119 /* Set up capture sources in source list mode */
2120 if (source[0] == T('-') && source[1] == T('\0')) {
2121 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2123 source_list_contents = file_get_text_contents(source,
2124 &source_list_nchars);
2126 if (!source_list_contents)
2129 capture_sources = parse_source_list(&source_list_contents,
2132 if (!capture_sources) {
2134 goto out_free_source_list_contents;
2136 capture_sources_malloced = true;
2138 /* Set up capture source in non-source-list mode. */
2139 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2140 capture_sources[0].fs_source_path = source;
2141 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2142 capture_sources[0].reserved = 0;
2144 capture_sources_malloced = false;
2145 source_list_contents = NULL;
2148 /* Open the existing WIM, or create a new one. */
2149 if (cmd == CMD_APPEND) {
2150 ret = wimlib_open_wim_with_progress(wimfile,
2151 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2153 imagex_progress_func,
2156 goto out_free_capture_sources;
2158 ret = wimlib_create_new_wim(compression_type, &wim);
2160 goto out_free_capture_sources;
2161 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2164 /* Set chunk size if non-default. */
2165 if (chunk_size != UINT32_MAX) {
2166 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2169 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2171 int ctype = compression_type;
2173 if (cmd == CMD_APPEND) {
2174 struct wimlib_wim_info info;
2175 wimlib_get_wim_info(wim, &info);
2176 ctype = info.compression_type;
2179 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2180 ret = wimlib_set_output_chunk_size(wim, 4096);
2185 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2186 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2190 if (solid_chunk_size != UINT32_MAX) {
2191 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2197 /* Detect if source is regular file or block device and set NTFS volume
2202 if (tstat(source, &stbuf) == 0) {
2203 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2204 imagex_printf(T("Capturing WIM image from NTFS "
2205 "filesystem on \"%"TS"\"\n"), source);
2206 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2209 if (errno != ENOENT) {
2210 imagex_error_with_errno(T("Failed to stat "
2211 "\"%"TS"\""), source);
2219 /* If the user did not specify an image name, and the basename of the
2220 * source already exists as an image name in the WIM file, append a
2221 * suffix to make it unique. */
2222 if (cmd == CMD_APPEND && name_defaulted) {
2223 unsigned long conflict_idx;
2224 tchar *name_end = tstrchr(name, T('\0'));
2225 for (conflict_idx = 1;
2226 wimlib_image_name_in_use(wim, name);
2229 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2233 /* If capturing a delta WIM, reference resources from the base WIMs
2234 * before adding the new image. */
2235 if (base_wimfiles.num_strings) {
2236 base_wims = calloc(base_wimfiles.num_strings,
2237 sizeof(base_wims[0]));
2238 if (base_wims == NULL) {
2239 imagex_error(T("Out of memory!"));
2244 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2245 ret = wimlib_open_wim_with_progress(
2246 base_wimfiles.strings[i], open_flags,
2247 &base_wims[i], imagex_progress_func, NULL);
2249 goto out_free_base_wims;
2253 ret = wimlib_reference_resources(wim, base_wims,
2254 base_wimfiles.num_strings, 0);
2256 goto out_free_base_wims;
2258 if (base_wimfiles.num_strings == 1) {
2259 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2260 base_wimfiles.strings[0]);
2262 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2263 base_wimfiles.num_strings);
2270 /* If capturing or appending as an update of an existing (template) image,
2271 * open the WIM if needed and parse the image index. */
2272 if (template_image_name_or_num) {
2275 if (base_wimfiles.num_strings == 1 &&
2276 template_wimfile == base_wimfiles.strings[0]) {
2277 template_wim = base_wims[0];
2278 } else if (template_wimfile == wimfile) {
2281 ret = wimlib_open_wim_with_progress(template_wimfile,
2284 imagex_progress_func,
2287 goto out_free_base_wims;
2290 template_image = wimlib_resolve_image(template_wim,
2291 template_image_name_or_num);
2293 if (template_image_name_or_num[0] == T('-')) {
2296 struct wimlib_wim_info info;
2298 wimlib_get_wim_info(template_wim, &info);
2299 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2300 if (n >= 1 && n <= info.image_count &&
2302 tmp != template_image_name_or_num + 1)
2304 template_image = info.image_count - (n - 1);
2307 ret = verify_image_exists_and_is_single(template_image,
2308 template_image_name_or_num,
2311 goto out_free_template_wim;
2313 template_wim = NULL;
2316 ret = wimlib_add_image_multisource(wim,
2323 goto out_free_template_wim;
2325 if (image_properties.num_strings || template_image_name_or_num) {
2326 /* User asked to set additional image properties, or an image on
2327 * which the added one is to be based has been specified with
2329 struct wimlib_wim_info info;
2331 wimlib_get_wim_info(wim, &info);
2333 ret = apply_image_properties(&image_properties, wim,
2334 info.image_count, NULL);
2336 goto out_free_template_wim;
2338 /* Reference template image if the user provided one. */
2339 if (template_image_name_or_num) {
2340 imagex_printf(T("Using image %d "
2341 "from \"%"TS"\" as template\n"),
2342 template_image, template_wimfile);
2343 ret = wimlib_reference_template_image(wim,
2349 goto out_free_template_wim;
2353 /* Write the new WIM or overwrite the existing WIM with the new image
2355 if (cmd == CMD_APPEND) {
2356 ret = wimlib_overwrite(wim, write_flags, num_threads);
2357 } else if (wimfile) {
2358 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2359 write_flags, num_threads);
2361 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2362 write_flags, num_threads);
2364 out_free_template_wim:
2365 /* template_wim may alias base_wims[0] or wim. */
2366 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2367 template_wim != wim)
2368 wimlib_free(template_wim);
2370 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2371 wimlib_free(base_wims[i]);
2375 out_free_capture_sources:
2376 if (capture_sources_malloced)
2377 free(capture_sources);
2378 out_free_source_list_contents:
2379 free(source_list_contents);
2381 string_list_destroy(&image_properties);
2382 string_list_destroy(&base_wimfiles);
2392 /* Remove image(s) from a WIM. */
2394 imagex_delete(int argc, tchar **argv, int cmd)
2397 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2398 int write_flags = 0;
2399 const tchar *wimfile;
2400 const tchar *image_num_or_name;
2405 for_opt(c, delete_options) {
2407 case IMAGEX_CHECK_OPTION:
2408 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2409 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2411 case IMAGEX_SOFT_OPTION:
2412 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2414 case IMAGEX_UNSAFE_COMPACT_OPTION:
2415 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2426 imagex_error(T("Must specify a WIM file"));
2428 imagex_error(T("Must specify an image"));
2432 image_num_or_name = argv[1];
2434 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2435 imagex_progress_func, NULL);
2439 image = wimlib_resolve_image(wim, image_num_or_name);
2441 ret = verify_image_exists(image, image_num_or_name, wimfile);
2443 goto out_wimlib_free;
2445 ret = wimlib_delete_image(wim, image);
2447 imagex_error(T("Failed to delete image from \"%"TS"\""),
2449 goto out_wimlib_free;
2452 ret = wimlib_overwrite(wim, write_flags, 0);
2454 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2455 "deleted"), wimfile);
2463 usage(CMD_DELETE, stderr);
2468 struct print_dentry_options {
2473 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2475 tprintf(T("%"TS"\n"), dentry->full_path);
2478 static const struct {
2481 } file_attr_flags[] = {
2482 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2483 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2484 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2485 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2486 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2487 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2488 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2489 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2490 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2491 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2492 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2493 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2494 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2495 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2496 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2499 #define TIMESTR_MAX 100
2502 print_time(const tchar *type, const struct wimlib_timespec *wts,
2505 tchar timestr[TIMESTR_MAX];
2509 if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2510 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2515 tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2516 timestr[TIMESTR_MAX - 1] = '\0';
2518 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2521 static void print_byte_field(const uint8_t field[], size_t len)
2524 tprintf(T("%02hhx"), *field++);
2528 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2530 tchar attr_string[256];
2533 tputs(T("WIM Information:"));
2534 tputs(T("----------------"));
2535 tprintf(T("Path: %"TS"\n"), wimfile);
2536 tprintf(T("GUID: 0x"));
2537 print_byte_field(info->guid, sizeof(info->guid));
2539 tprintf(T("Version: %u\n"), info->wim_version);
2540 tprintf(T("Image Count: %d\n"), info->image_count);
2541 tprintf(T("Compression: %"TS"\n"),
2542 wimlib_get_compression_type_string(info->compression_type));
2543 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2545 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2546 tprintf(T("Boot Index: %d\n"), info->boot_index);
2547 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2549 attr_string[0] = T('\0');
2552 tstrcat(attr_string, T("Pipable, "));
2554 if (info->has_integrity_table)
2555 tstrcat(attr_string, T("Integrity info, "));
2557 if (info->has_rpfix)
2558 tstrcat(attr_string, T("Relative path junction, "));
2560 if (info->resource_only)
2561 tstrcat(attr_string, T("Resource only, "));
2563 if (info->metadata_only)
2564 tstrcat(attr_string, T("Metadata only, "));
2566 if (info->is_marked_readonly)
2567 tstrcat(attr_string, T("Readonly, "));
2569 p = tstrchr(attr_string, T('\0'));
2570 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2573 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2577 print_resource(const struct wimlib_resource_entry *resource,
2580 tprintf(T("Hash = 0x"));
2581 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2584 if (!resource->is_missing) {
2585 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2586 resource->uncompressed_size);
2587 if (resource->packed) {
2588 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2589 "bytes @ offset %"PRIu64"\n"),
2590 resource->raw_resource_uncompressed_size,
2591 resource->raw_resource_compressed_size,
2592 resource->raw_resource_offset_in_wim);
2594 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2597 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2598 resource->compressed_size);
2600 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2604 tprintf(T("Part Number = %u\n"), resource->part_number);
2605 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2607 tprintf(T("Flags = "));
2608 if (resource->is_compressed)
2609 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2610 if (resource->is_metadata)
2611 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2612 if (resource->is_free)
2613 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2614 if (resource->is_spanned)
2615 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2616 if (resource->packed)
2617 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2625 print_blobs(WIMStruct *wim)
2627 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2632 default_print_security_descriptor(const uint8_t *sd, size_t size)
2634 tprintf(T("Security Descriptor = "));
2635 print_byte_field(sd, size);
2641 is_null_guid(const uint8_t *guid)
2643 static const uint8_t null_guid[WIMLIB_GUID_LEN];
2645 return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2649 print_guid(const tchar *label, const uint8_t *guid)
2651 if (is_null_guid(guid))
2653 tprintf(T("%-20"TS"= 0x"), label);
2654 print_byte_field(guid, WIMLIB_GUID_LEN);
2659 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2662 "----------------------------------------------------------------------------\n"));
2663 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2664 if (dentry->dos_name)
2665 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2666 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2667 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2668 if (file_attr_flags[i].flag & dentry->attributes)
2669 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2670 file_attr_flags[i].name);
2672 if (dentry->security_descriptor) {
2673 print_security_descriptor(dentry->security_descriptor,
2674 dentry->security_descriptor_size);
2677 print_time(T("Creation Time"),
2678 &dentry->creation_time, dentry->creation_time_high);
2679 print_time(T("Last Write Time"),
2680 &dentry->last_write_time, dentry->last_write_time_high);
2681 print_time(T("Last Access Time"),
2682 &dentry->last_access_time, dentry->last_access_time_high);
2685 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2686 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2688 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2689 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2691 if (dentry->unix_mode != 0) {
2692 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2693 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2694 dentry->unix_uid, dentry->unix_gid,
2695 dentry->unix_mode, dentry->unix_rdev);
2698 if (!is_null_guid(dentry->object_id.object_id)) {
2699 print_guid(T("Object ID"), dentry->object_id.object_id);
2700 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2701 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2702 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2705 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2706 if (dentry->streams[i].stream_name) {
2707 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2708 dentry->streams[i].stream_name);
2709 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2710 tprintf(T("\tRaw encrypted data stream:\n"));
2711 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2712 tprintf(T("\tReparse point stream:\n"));
2714 tprintf(T("\tUnnamed data stream:\n"));
2716 print_resource(&dentry->streams[i].resource, NULL);
2721 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2723 const struct print_dentry_options *options = _options;
2724 if (!options->detailed)
2725 print_dentry_full_path(dentry);
2727 print_dentry_detailed(dentry);
2731 /* Print the files contained in an image(s) in a WIM file. */
2733 imagex_dir(int argc, tchar **argv, int cmd)
2735 const tchar *wimfile;
2736 WIMStruct *wim = NULL;
2739 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2741 struct print_dentry_options options = {
2744 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2746 STRING_LIST(refglobs);
2748 for_opt(c, dir_options) {
2750 case IMAGEX_PATH_OPTION:
2753 case IMAGEX_DETAILED_OPTION:
2754 options.detailed = true;
2756 case IMAGEX_ONE_FILE_ONLY_OPTION:
2757 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2759 case IMAGEX_REF_OPTION:
2760 ret = string_list_append(&refglobs, optarg);
2762 goto out_free_refglobs;
2772 imagex_error(T("Must specify a WIM file"));
2776 imagex_error(T("Too many arguments"));
2781 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2782 imagex_progress_func, NULL);
2784 goto out_free_refglobs;
2787 image = wimlib_resolve_image(wim, argv[1]);
2788 ret = verify_image_exists(image, argv[1], wimfile);
2790 goto out_wimlib_free;
2792 /* No image specified; default to image 1, but only if the WIM
2793 * contains exactly one image. */
2795 struct wimlib_wim_info info;
2797 wimlib_get_wim_info(wim, &info);
2798 if (info.image_count != 1) {
2799 imagex_error(T("\"%"TS"\" contains %d images; Please "
2800 "select one (or all)."),
2801 wimfile, info.image_count);
2808 if (refglobs.num_strings) {
2809 ret = wim_reference_globs(wim, &refglobs, 0);
2811 goto out_wimlib_free;
2814 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2815 print_dentry, &options);
2816 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2817 struct wimlib_wim_info info;
2819 wimlib_get_wim_info(wim, &info);
2820 do_metadata_not_found_warning(wimfile, &info);
2825 string_list_destroy(&refglobs);
2829 usage(CMD_DIR, stderr);
2831 goto out_free_refglobs;
2834 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2837 imagex_export(int argc, tchar **argv, int cmd)
2841 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2842 int write_flags = 0;
2843 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2844 const tchar *src_wimfile;
2845 const tchar *src_image_num_or_name;
2846 const tchar *dest_wimfile;
2848 const tchar *dest_name;
2849 const tchar *dest_desc;
2851 struct wimlib_wim_info src_info;
2852 WIMStruct *dest_wim;
2857 STRING_LIST(refglobs);
2858 unsigned num_threads = 0;
2859 uint32_t chunk_size = UINT32_MAX;
2860 uint32_t solid_chunk_size = UINT32_MAX;
2861 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2863 for_opt(c, export_options) {
2865 case IMAGEX_BOOT_OPTION:
2866 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2868 case IMAGEX_CHECK_OPTION:
2869 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2870 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2872 case IMAGEX_NOCHECK_OPTION:
2873 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2875 case IMAGEX_COMPRESS_OPTION:
2876 compression_type = get_compression_type(optarg, false);
2877 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2880 case IMAGEX_RECOMPRESS_OPTION:
2881 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2883 case IMAGEX_SOLID_OPTION:
2884 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2886 case IMAGEX_NO_SOLID_SORT_OPTION:
2887 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2889 case IMAGEX_CHUNK_SIZE_OPTION:
2890 chunk_size = parse_chunk_size(optarg);
2891 if (chunk_size == UINT32_MAX)
2894 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2895 solid_chunk_size = parse_chunk_size(optarg);
2896 if (solid_chunk_size == UINT32_MAX)
2899 case IMAGEX_SOLID_COMPRESS_OPTION:
2900 solid_ctype = get_compression_type(optarg, true);
2901 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2904 case IMAGEX_REF_OPTION:
2905 ret = string_list_append(&refglobs, optarg);
2907 goto out_free_refglobs;
2909 case IMAGEX_THREADS_OPTION:
2910 num_threads = parse_num_threads(optarg);
2911 if (num_threads == UINT_MAX)
2914 case IMAGEX_REBUILD_OPTION:
2915 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2917 case IMAGEX_PIPABLE_OPTION:
2918 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2920 case IMAGEX_NOT_PIPABLE_OPTION:
2921 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2923 case IMAGEX_WIMBOOT_OPTION:
2924 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2926 case IMAGEX_UNSAFE_COMPACT_OPTION:
2927 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2935 if (argc < 3 || argc > 5)
2937 src_wimfile = argv[0];
2938 src_image_num_or_name = argv[1];
2939 dest_wimfile = argv[2];
2940 dest_name = (argc >= 4) ? argv[3] : NULL;
2941 dest_desc = (argc >= 5) ? argv[4] : NULL;
2942 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2943 imagex_progress_func, NULL);
2945 goto out_free_refglobs;
2947 wimlib_get_wim_info(src_wim, &src_info);
2949 /* Determine if the destination is an existing file or not. If so, we
2950 * try to append the exported image(s) to it; otherwise, we create a new
2951 * WIM containing the exported image(s). Furthermore, determine if we
2952 * need to write a pipable WIM directly to standard output. */
2954 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2956 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2957 imagex_error("Can't write a non-pipable WIM to "
2958 "standard output! Specify --pipable\n"
2959 " if you want to create a pipable WIM "
2960 "(but read the docs first).");
2962 goto out_free_src_wim;
2965 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2967 dest_wimfile = NULL;
2968 dest_wim_fd = STDOUT_FILENO;
2969 imagex_info_file = stderr;
2970 set_fd_to_binary_mode(dest_wim_fd);
2973 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2975 /* Destination file exists. */
2977 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
2978 imagex_error(T("\"%"TS"\" is not a regular file "
2979 "or block device"), dest_wimfile);
2981 goto out_free_src_wim;
2983 ret = wimlib_open_wim_with_progress(dest_wimfile,
2985 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2987 imagex_progress_func,
2990 goto out_free_src_wim;
2992 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2993 /* The user specified a compression type, but we're
2994 * exporting to an existing WIM. Make sure the
2995 * specified compression type is the same as the
2996 * compression type of the existing destination WIM. */
2997 struct wimlib_wim_info dest_info;
2999 wimlib_get_wim_info(dest_wim, &dest_info);
3000 if (compression_type != dest_info.compression_type) {
3001 imagex_error(T("Cannot specify a compression type that is "
3002 "not the same as that used in the "
3003 "destination WIM"));
3005 goto out_free_dest_wim;
3011 if (errno != ENOENT) {
3012 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3015 goto out_free_src_wim;
3018 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3019 imagex_error(T("'--unsafe-compact' is only valid when "
3020 "exporting to an existing WIM file!"));
3022 goto out_free_src_wim;
3025 /* dest_wimfile is not an existing file, so create a new WIM. */
3027 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3028 /* The user did not specify a compression type; default
3029 * to that of the source WIM, unless --solid or
3030 * --wimboot was specified. */
3032 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3033 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3034 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3035 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3037 compression_type = src_info.compression_type;
3039 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3041 goto out_free_src_wim;
3043 wimlib_register_progress_function(dest_wim,
3044 imagex_progress_func, NULL);
3046 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3047 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3049 /* For --wimboot export, use small XPRESS chunks. */
3050 wimlib_set_output_chunk_size(dest_wim, 4096);
3051 } else if (compression_type == src_info.compression_type &&
3052 chunk_size == UINT32_MAX)
3054 /* Use same chunk size if compression type is the same. */
3055 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3059 if (chunk_size != UINT32_MAX) {
3060 /* Set destination chunk size. */
3061 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3063 goto out_free_dest_wim;
3065 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3066 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3068 goto out_free_dest_wim;
3070 if (solid_chunk_size != UINT32_MAX) {
3071 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3073 goto out_free_dest_wim;
3076 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3077 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3079 goto out_free_dest_wim;
3081 if (refglobs.num_strings) {
3082 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3084 goto out_free_dest_wim;
3087 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3088 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3090 imagex_error(T("--boot specified for all-images export, but source WIM "
3091 "has no bootable image."));
3093 goto out_free_dest_wim;
3096 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3097 dest_desc, export_flags);
3099 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3100 do_resource_not_found_warning(src_wimfile,
3101 &src_info, &refglobs);
3102 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3103 do_metadata_not_found_warning(src_wimfile, &src_info);
3105 goto out_free_dest_wim;
3109 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3110 else if (dest_wimfile)
3111 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3112 write_flags, num_threads);
3114 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3115 WIMLIB_ALL_IMAGES, write_flags,
3118 wimlib_free(dest_wim);
3120 wimlib_free(src_wim);
3122 string_list_destroy(&refglobs);
3126 usage(CMD_EXPORT, stderr);
3129 goto out_free_refglobs;
3132 /* Extract files or directories from a WIM image */
3134 imagex_extract(int argc, tchar **argv, int cmd)
3141 const tchar *wimfile;
3142 const tchar *image_num_or_name;
3143 tchar *dest_dir = T(".");
3144 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3145 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3146 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3147 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3149 STRING_LIST(refglobs);
3151 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3153 for_opt(c, extract_options) {
3155 case IMAGEX_CHECK_OPTION:
3156 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3158 case IMAGEX_VERBOSE_OPTION:
3159 /* No longer does anything. */
3161 case IMAGEX_REF_OPTION:
3162 ret = string_list_append(&refglobs, optarg);
3164 goto out_free_refglobs;
3166 case IMAGEX_UNIX_DATA_OPTION:
3167 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3169 case IMAGEX_NO_ACLS_OPTION:
3170 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3172 case IMAGEX_STRICT_ACLS_OPTION:
3173 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3175 case IMAGEX_NO_ATTRIBUTES_OPTION:
3176 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3178 case IMAGEX_DEST_DIR_OPTION:
3181 case IMAGEX_TO_STDOUT_OPTION:
3182 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3183 imagex_info_file = stderr;
3184 imagex_be_quiet = true;
3185 set_fd_to_binary_mode(STDOUT_FILENO);
3187 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3188 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3189 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3191 case IMAGEX_NO_GLOBS_OPTION:
3192 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3194 case IMAGEX_NULLGLOB_OPTION:
3195 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3197 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3198 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3200 case IMAGEX_WIMBOOT_OPTION:
3201 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3203 case IMAGEX_COMPACT_OPTION:
3204 ret = set_compact_mode(optarg, &extract_flags);
3206 goto out_free_refglobs;
3218 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3219 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3221 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3226 image_num_or_name = argv[1];
3231 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3232 imagex_progress_func, NULL);
3234 goto out_free_refglobs;
3236 image = wimlib_resolve_image(wim, image_num_or_name);
3237 ret = verify_image_exists_and_is_single(image,
3241 goto out_wimlib_free;
3243 if (refglobs.num_strings) {
3244 ret = wim_reference_globs(wim, &refglobs, open_flags);
3246 goto out_wimlib_free;
3252 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3255 while (argc != 0 && ret == 0) {
3259 num_paths < argc && argv[num_paths][0] != T('@');
3264 ret = wimlib_extract_paths(wim, image, dest_dir,
3265 (const tchar **)argv,
3267 extract_flags | notlist_extract_flags);
3271 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3280 if (!imagex_be_quiet)
3281 imagex_printf(T("Done extracting files.\n"));
3282 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3283 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3284 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3285 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3286 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3289 T("Note: You can use the '--nullglob' "
3290 "option to ignore missing files.\n"));
3292 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3293 "files and directories\n"
3294 " are in the WIM image.\n"),
3295 get_cmd_string(CMD_DIR, false));
3296 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3297 struct wimlib_wim_info info;
3299 wimlib_get_wim_info(wim, &info);
3300 do_resource_not_found_warning(wimfile, &info, &refglobs);
3301 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3302 struct wimlib_wim_info info;
3304 wimlib_get_wim_info(wim, &info);
3305 do_metadata_not_found_warning(wimfile, &info);
3310 string_list_destroy(&refglobs);
3314 usage(CMD_EXTRACT, stderr);
3317 goto out_free_refglobs;
3320 /* Prints information about a WIM file; also can mark an image as bootable,
3321 * change the name of an image, or change the description of an image. */
3323 imagex_info(int argc, tchar **argv, int cmd)
3328 bool nocheck = false;
3329 bool header = false;
3332 bool short_header = true;
3333 const tchar *xml_out_file = NULL;
3334 const tchar *wimfile;
3335 const tchar *image_num_or_name;
3336 STRING_LIST(image_properties);
3341 struct wimlib_wim_info info;
3343 for_opt(c, info_options) {
3345 case IMAGEX_BOOT_OPTION:
3348 case IMAGEX_CHECK_OPTION:
3351 case IMAGEX_NOCHECK_OPTION:
3354 case IMAGEX_HEADER_OPTION:
3356 short_header = false;
3358 case IMAGEX_BLOBS_OPTION:
3360 short_header = false;
3362 case IMAGEX_XML_OPTION:
3364 short_header = false;
3366 case IMAGEX_EXTRACT_XML_OPTION:
3367 xml_out_file = optarg;
3368 short_header = false;
3370 case IMAGEX_IMAGE_PROPERTY_OPTION:
3371 ret = append_image_property_argument(&image_properties);
3382 if (argc < 1 || argc > 4)
3386 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3390 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3391 tsprintf(p, T("NAME=%"TS), argv[2]);
3392 ret = string_list_append(&image_properties, p);
3399 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3400 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3401 ret = string_list_append(&image_properties, p);
3406 if (check && nocheck) {
3407 imagex_error(T("Can't specify both --check and --nocheck"));
3412 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3414 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3415 imagex_progress_func, NULL);
3419 wimlib_get_wim_info(wim, &info);
3421 image = wimlib_resolve_image(wim, image_num_or_name);
3422 ret = WIMLIB_ERR_INVALID_IMAGE;
3423 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3424 verify_image_exists(image, image_num_or_name, wimfile);
3426 imagex_error(T("If you would like to set the boot "
3427 "index to 0, specify image \"0\" with "
3428 "the --boot flag."));
3430 goto out_wimlib_free;
3433 if (boot && info.image_count == 0) {
3434 imagex_error(T("--boot is meaningless on a WIM with no images"));
3435 goto out_wimlib_free;
3438 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3440 imagex_error(T("Cannot specify the --boot flag "
3441 "without specifying a specific "
3442 "image in a multi-image WIM"));
3443 goto out_wimlib_free;
3445 if (image_properties.num_strings) {
3446 imagex_error(T("Can't change image properties without "
3447 "specifying a specific image in a "
3448 "multi-image WIM"));
3449 goto out_wimlib_free;
3453 /* Operations that print information are separated from operations that
3454 * recreate the WIM file. */
3455 if (!image_properties.num_strings && !boot) {
3457 /* Read-only operations */
3459 if (image == WIMLIB_NO_IMAGE) {
3460 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3461 image_num_or_name, wimfile);
3462 goto out_wimlib_free;
3465 if (image == WIMLIB_ALL_IMAGES && short_header)
3466 print_wim_information(wimfile, &info);
3469 wimlib_print_header(wim);
3472 if (info.total_parts != 1) {
3473 tfprintf(stderr, T("Warning: Only showing the blobs "
3474 "for part %d of a %d-part WIM.\n"),
3475 info.part_number, info.total_parts);
3481 ret = wimlib_extract_xml_data(wim, stdout);
3483 goto out_wimlib_free;
3489 fp = tfopen(xml_out_file, T("wb"));
3491 imagex_error_with_errno(T("Failed to open the "
3492 "file \"%"TS"\" for "
3496 goto out_wimlib_free;
3498 ret = wimlib_extract_xml_data(wim, fp);
3500 imagex_error(T("Failed to close the file "
3506 goto out_wimlib_free;
3510 wimlib_print_available_images(wim, image);
3514 /* Modification operations */
3515 bool any_property_changes;
3517 if (image == WIMLIB_ALL_IMAGES)
3520 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3521 imagex_error(T("Cannot change image properties "
3522 "when using image 0"));
3524 goto out_wimlib_free;
3528 if (image == info.boot_index) {
3529 imagex_printf(T("Image %d is already marked as "
3530 "bootable.\n"), image);
3533 imagex_printf(T("Marking image %d as bootable.\n"),
3535 info.boot_index = image;
3536 ret = wimlib_set_wim_info(wim, &info,
3537 WIMLIB_CHANGE_BOOT_INDEX);
3539 goto out_wimlib_free;
3543 ret = apply_image_properties(&image_properties, wim, image,
3544 &any_property_changes);
3546 goto out_wimlib_free;
3548 /* Only call wimlib_overwrite() if something actually needs to
3550 if (boot || any_property_changes ||
3551 (check && !info.has_integrity_table) ||
3552 (nocheck && info.has_integrity_table))
3554 int write_flags = 0;
3557 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3559 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3560 ret = wimlib_overwrite(wim, write_flags, 1);
3562 imagex_printf(T("The file \"%"TS"\" was not modified "
3563 "because nothing needed to be done.\n"),
3571 string_list_destroy(&image_properties);
3575 usage(CMD_INFO, stderr);
3581 /* Join split WIMs into one part WIM */
3583 imagex_join(int argc, tchar **argv, int cmd)
3586 int swm_open_flags = 0;
3587 int wim_write_flags = 0;
3588 const tchar *output_path;
3591 for_opt(c, join_options) {
3593 case IMAGEX_CHECK_OPTION:
3594 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3595 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3605 imagex_error(T("Must specify one or more split WIM (.swm) "
3609 output_path = argv[0];
3610 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3615 imagex_progress_func,
3621 usage(CMD_JOIN, stderr);
3626 #if WIM_MOUNTING_SUPPORTED
3628 /* Mounts a WIM image. */
3630 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3633 int mount_flags = 0;
3635 const tchar *staging_dir = NULL;
3636 const tchar *wimfile;
3639 struct wimlib_wim_info info;
3643 STRING_LIST(refglobs);
3645 if (cmd == CMD_MOUNTRW) {
3646 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3647 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3650 for_opt(c, mount_options) {
3652 case IMAGEX_ALLOW_OTHER_OPTION:
3653 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3655 case IMAGEX_CHECK_OPTION:
3656 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3658 case IMAGEX_DEBUG_OPTION:
3659 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3661 case IMAGEX_STREAMS_INTERFACE_OPTION:
3662 if (!tstrcasecmp(optarg, T("none")))
3663 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3664 else if (!tstrcasecmp(optarg, T("xattr")))
3665 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3666 else if (!tstrcasecmp(optarg, T("windows")))
3667 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3669 imagex_error(T("Unknown stream interface \"%"TS"\""),
3674 case IMAGEX_REF_OPTION:
3675 ret = string_list_append(&refglobs, optarg);
3677 goto out_free_refglobs;
3679 case IMAGEX_STAGING_DIR_OPTION:
3680 staging_dir = optarg;
3682 case IMAGEX_UNIX_DATA_OPTION:
3683 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3691 if (argc != 2 && argc != 3)
3696 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3697 imagex_progress_func, NULL);
3699 goto out_free_refglobs;
3701 wimlib_get_wim_info(wim, &info);
3704 /* Image explicitly specified. */
3705 image = wimlib_resolve_image(wim, argv[1]);
3707 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3711 /* No image specified; default to image 1, but only if the WIM
3712 * contains exactly one image. */
3714 if (info.image_count != 1) {
3715 imagex_error(T("\"%"TS"\" contains %d images; Please "
3716 "select one."), wimfile, info.image_count);
3724 if (refglobs.num_strings) {
3725 ret = wim_reference_globs(wim, &refglobs, open_flags);
3730 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3732 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3733 do_metadata_not_found_warning(wimfile, &info);
3735 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3737 image, wimfile, dir);
3743 string_list_destroy(&refglobs);
3749 goto out_free_refglobs;
3751 #endif /* WIM_MOUNTING_SUPPORTED */
3753 /* Rebuild a WIM file */
3755 imagex_optimize(int argc, tchar **argv, int cmd)
3758 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3759 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3760 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3761 uint32_t chunk_size = UINT32_MAX;
3762 uint32_t solid_chunk_size = UINT32_MAX;
3763 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3766 const tchar *wimfile;
3769 unsigned num_threads = 0;
3771 for_opt(c, optimize_options) {
3773 case IMAGEX_CHECK_OPTION:
3774 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3775 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3777 case IMAGEX_NOCHECK_OPTION:
3778 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3780 case IMAGEX_COMPRESS_OPTION:
3781 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3782 compression_type = get_compression_type(optarg, false);
3783 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3786 case IMAGEX_RECOMPRESS_OPTION:
3787 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3789 case IMAGEX_CHUNK_SIZE_OPTION:
3790 chunk_size = parse_chunk_size(optarg);
3791 if (chunk_size == UINT32_MAX)
3794 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3795 solid_chunk_size = parse_chunk_size(optarg);
3796 if (solid_chunk_size == UINT32_MAX)
3799 case IMAGEX_SOLID_COMPRESS_OPTION:
3800 solid_ctype = get_compression_type(optarg, true);
3801 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3804 case IMAGEX_SOLID_OPTION:
3805 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3806 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3808 case IMAGEX_NO_SOLID_SORT_OPTION:
3809 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3811 case IMAGEX_THREADS_OPTION:
3812 num_threads = parse_num_threads(optarg);
3813 if (num_threads == UINT_MAX)
3816 case IMAGEX_PIPABLE_OPTION:
3817 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3819 case IMAGEX_NOT_PIPABLE_OPTION:
3820 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3822 case IMAGEX_UNSAFE_COMPACT_OPTION:
3823 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3837 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3838 imagex_progress_func, NULL);
3842 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3843 /* Change compression type. */
3844 ret = wimlib_set_output_compression_type(wim, compression_type);
3846 goto out_wimlib_free;
3849 if (chunk_size != UINT32_MAX) {
3850 /* Change chunk size. */
3851 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3853 goto out_wimlib_free;
3855 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3856 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3858 goto out_wimlib_free;
3860 if (solid_chunk_size != UINT32_MAX) {
3861 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3863 goto out_wimlib_free;
3866 old_size = file_get_size(wimfile);
3867 tprintf(T("\"%"TS"\" original size: "), wimfile);
3869 tputs(T("Unknown"));
3871 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3873 ret = wimlib_overwrite(wim, write_flags, num_threads);
3875 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3876 goto out_wimlib_free;
3879 new_size = file_get_size(wimfile);
3880 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3882 tputs(T("Unknown"));
3884 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3886 tfputs(T("Space saved: "), stdout);
3887 if (new_size != -1 && old_size != -1) {
3888 tprintf(T("%lld KiB\n"),
3889 ((long long)old_size - (long long)new_size) >> 10);
3891 tputs(T("Unknown"));
3900 usage(CMD_OPTIMIZE, stderr);
3906 /* Split a WIM into a spanned set */
3908 imagex_split(int argc, tchar **argv, int cmd)
3912 int write_flags = 0;
3913 unsigned long part_size;
3918 for_opt(c, split_options) {
3920 case IMAGEX_CHECK_OPTION:
3921 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3922 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3934 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3935 if (tmp == argv[2] || *tmp) {
3936 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3937 imagex_error(T("The part size must be an integer or "
3938 "floating-point number of megabytes."));
3941 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3942 imagex_progress_func, NULL);
3946 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3952 usage(CMD_SPLIT, stderr);
3958 #if WIM_MOUNTING_SUPPORTED
3959 /* Unmounts a mounted WIM image. */
3961 imagex_unmount(int argc, tchar **argv, int cmd)
3964 int unmount_flags = 0;
3967 for_opt(c, unmount_options) {
3969 case IMAGEX_COMMIT_OPTION:
3970 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3972 case IMAGEX_CHECK_OPTION:
3973 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3975 case IMAGEX_REBUILD_OPTION:
3976 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3978 case IMAGEX_LAZY_OPTION:
3979 case IMAGEX_FORCE_OPTION:
3980 /* Now, unmount is lazy by default. However, committing
3981 * the image will fail with
3982 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3983 * file descriptors on the WIM image. The
3984 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3985 * descriptors to be closed. */
3986 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3988 case IMAGEX_NEW_IMAGE_OPTION:
3989 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4000 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4001 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4002 imagex_error(T("--new-image is meaningless "
4003 "without --commit also specified!"));
4008 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4009 imagex_progress_func, NULL);
4011 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4012 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4014 "\tNote: Use --commit --force to force changes "
4015 "to be committed, regardless\n"
4016 "\t of open files.\n"));
4023 usage(CMD_UNMOUNT, stderr);
4028 #endif /* WIM_MOUNTING_SUPPORTED */
4031 * Add, delete, or rename files in a WIM image.
4034 imagex_update(int argc, tchar **argv, int cmd)
4036 const tchar *wimfile;
4040 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4041 int write_flags = 0;
4042 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4043 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4044 WIMLIB_ADD_FLAG_VERBOSE |
4045 WIMLIB_ADD_FLAG_WINCONFIG;
4046 int default_delete_flags = 0;
4047 unsigned num_threads = 0;
4049 tchar *cmd_file_contents;
4050 size_t cmd_file_nchars;
4051 struct wimlib_update_command *cmds;
4053 tchar *command_str = NULL;
4054 tchar *config_file = NULL;
4055 tchar *wimboot_config = NULL;
4057 for_opt(c, update_options) {
4059 /* Generic or write options */
4060 case IMAGEX_THREADS_OPTION:
4061 num_threads = parse_num_threads(optarg);
4062 if (num_threads == UINT_MAX)
4065 case IMAGEX_CHECK_OPTION:
4066 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4067 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4069 case IMAGEX_REBUILD_OPTION:
4070 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4072 case IMAGEX_COMMAND_OPTION:
4074 imagex_error(T("--command may only be specified "
4075 "one time. Please provide\n"
4076 " the update commands "
4077 "on standard input instead."));
4080 command_str = tstrdup(optarg);
4082 imagex_error(T("Out of memory!"));
4086 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4087 wimboot_config = optarg;
4089 /* Default delete options */
4090 case IMAGEX_FORCE_OPTION:
4091 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4093 case IMAGEX_RECURSIVE_OPTION:
4094 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4097 /* Global add option */
4098 case IMAGEX_CONFIG_OPTION:
4099 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4100 config_file = optarg;
4103 /* Default add options */
4104 case IMAGEX_VERBOSE_OPTION:
4105 /* No longer does anything. */
4107 case IMAGEX_DEREFERENCE_OPTION:
4108 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4110 case IMAGEX_UNIX_DATA_OPTION:
4111 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4113 case IMAGEX_NO_ACLS_OPTION:
4114 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4116 case IMAGEX_STRICT_ACLS_OPTION:
4117 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4119 case IMAGEX_NO_REPLACE_OPTION:
4120 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4122 case IMAGEX_UNSAFE_COMPACT_OPTION:
4123 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4132 if (argc != 1 && argc != 2)
4136 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4137 imagex_progress_func, NULL);
4139 goto out_free_command_str;
4142 /* Image explicitly specified. */
4143 image = wimlib_resolve_image(wim, argv[1]);
4144 ret = verify_image_exists_and_is_single(image, argv[1],
4147 goto out_wimlib_free;
4149 /* No image specified; default to image 1, but only if the WIM
4150 * contains exactly one image. */
4151 struct wimlib_wim_info info;
4153 wimlib_get_wim_info(wim, &info);
4154 if (info.image_count != 1) {
4155 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4156 wimfile, info.image_count);
4163 /* Read update commands from standard input, or the command string if
4166 cmd_file_contents = NULL;
4167 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4171 goto out_free_cmd_file_contents;
4173 } else if (!wimboot_config) {
4174 if (isatty(STDIN_FILENO)) {
4175 tputs(T("Reading update commands from standard input..."));
4176 recommend_man_page(CMD_UPDATE, stdout);
4178 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4179 if (!cmd_file_contents) {
4181 goto out_wimlib_free;
4184 /* Parse the update commands */
4185 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4189 goto out_free_cmd_file_contents;
4192 cmd_file_contents = NULL;
4197 /* Set default flags and capture config on the update commands */
4198 for (size_t i = 0; i < num_cmds; i++) {
4199 switch (cmds[i].op) {
4200 case WIMLIB_UPDATE_OP_ADD:
4201 cmds[i].add.add_flags |= default_add_flags;
4202 cmds[i].add.config_file = config_file;
4204 case WIMLIB_UPDATE_OP_DELETE:
4205 cmds[i].delete_.delete_flags |= default_delete_flags;
4212 /* Execute the update commands */
4213 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4217 if (wimboot_config) {
4218 /* --wimboot-config=FILE is short for an
4219 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4221 struct wimlib_update_command cmd;
4223 cmd.op = WIMLIB_UPDATE_OP_ADD;
4224 cmd.add.fs_source_path = wimboot_config;
4225 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4226 cmd.add.config_file = NULL;
4227 cmd.add.add_flags = 0;
4229 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4234 /* Overwrite the updated WIM */
4235 ret = wimlib_overwrite(wim, write_flags, num_threads);
4238 out_free_cmd_file_contents:
4239 free(cmd_file_contents);
4242 out_free_command_str:
4247 usage(CMD_UPDATE, stderr);
4250 goto out_free_command_str;
4253 /* Verify a WIM file. */
4255 imagex_verify(int argc, tchar **argv, int cmd)
4258 const tchar *wimfile;
4260 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4261 int verify_flags = 0;
4262 STRING_LIST(refglobs);
4265 for_opt(c, verify_options) {
4267 case IMAGEX_REF_OPTION:
4268 ret = string_list_append(&refglobs, optarg);
4270 goto out_free_refglobs;
4272 case IMAGEX_NOCHECK_OPTION:
4273 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4285 imagex_error(T("Must specify a WIM file!"));
4287 imagex_error(T("At most one WIM file can be specified!"));
4293 ret = wimlib_open_wim_with_progress(wimfile,
4296 imagex_progress_func,
4299 goto out_free_refglobs;
4301 ret = wim_reference_globs(wim, &refglobs, open_flags);
4303 goto out_wimlib_free;
4305 ret = wimlib_verify_wim(wim, verify_flags);
4307 tputc(T('\n'), stderr);
4308 imagex_error(T("\"%"TS"\" failed verification!"),
4310 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4311 refglobs.num_strings == 0)
4313 imagex_printf(T("Note: if this WIM file is not standalone, "
4314 "use the --ref option to specify the other parts.\n"));
4317 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4324 string_list_destroy(&refglobs);
4328 usage(CMD_VERIFY, stderr);
4330 goto out_free_refglobs;
4333 struct imagex_command {
4335 int (*func)(int argc, tchar **argv, int cmd);
4338 static const struct imagex_command imagex_commands[] = {
4339 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4340 [CMD_APPLY] = {T("apply"), imagex_apply},
4341 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4342 [CMD_DELETE] = {T("delete"), imagex_delete},
4343 [CMD_DIR ] = {T("dir"), imagex_dir},
4344 [CMD_EXPORT] = {T("export"), imagex_export},
4345 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4346 [CMD_INFO] = {T("info"), imagex_info},
4347 [CMD_JOIN] = {T("join"), imagex_join},
4348 #if WIM_MOUNTING_SUPPORTED
4349 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4350 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4352 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4353 [CMD_SPLIT] = {T("split"), imagex_split},
4354 #if WIM_MOUNTING_SUPPORTED
4355 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4357 [CMD_UPDATE] = {T("update"), imagex_update},
4358 [CMD_VERIFY] = {T("verify"), imagex_verify},
4363 /* Can be a directory or source list file. But source list file is probably
4364 * a rare use case, so just say directory. */
4365 # define SOURCE_STR T("DIRECTORY")
4367 /* Can only be a directory */
4368 # define TARGET_STR T("DIRECTORY")
4371 /* Can be a directory, NTFS volume, or source list file. */
4372 # define SOURCE_STR T("SOURCE")
4374 /* Can be a directory or NTFS volume. */
4375 # define TARGET_STR T("TARGET")
4379 static const tchar * const usage_strings[] = {
4382 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4383 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4384 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4385 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4386 " [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4387 " [--dereference] [--snapshot]\n"
4391 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4392 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4393 " [--no-attributes] [--rpfix] [--norpfix]\n"
4394 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4395 " [--compact=FORMAT]\n"
4399 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4400 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4401 " [--config=FILE] [--threads=NUM_THREADS]\n"
4402 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4403 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4404 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4409 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4413 " %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4417 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4418 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4419 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4420 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4421 " [--wimboot] [--solid]\n"
4425 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4426 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4427 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4428 " [--no-attributes] [--include-invalid-names]\n"
4429 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4433 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4434 " [--boot] [--check] [--nocheck] [--xml]\n"
4435 " [--extract-xml FILE] [--header] [--blobs]\n"
4436 " [--image-property NAME=VALUE]\n"
4440 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4442 #if WIM_MOUNTING_SUPPORTED
4445 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4446 " [--check] [--streams-interface=INTERFACE]\n"
4447 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4451 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4452 " [--check] [--streams-interface=INTERFACE]\n"
4453 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4459 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4460 " [--check] [--nocheck] [--solid]\n"
4465 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4467 #if WIM_MOUNTING_SUPPORTED
4470 " %"TS" DIRECTORY\n"
4471 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4476 " %"TS" WIMFILE [IMAGE]\n"
4477 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4478 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4479 " [--command=STRING] [--wimboot-config=FILE]\n"
4484 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4488 static const tchar *invocation_name;
4489 static int invocation_cmd = CMD_NONE;
4491 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4493 static tchar buf[50];
4495 if (cmd == CMD_NONE)
4496 return T("wimlib-imagex");
4498 if (only_short_form || invocation_cmd != CMD_NONE) {
4499 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4501 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4502 imagex_commands[cmd].name);
4510 static const tchar * const s =
4512 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4513 "Copyright (C) 2012-2017 Eric Biggers\n"
4514 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4515 "This is free software: you are free to change and redistribute it.\n"
4516 "There is NO WARRANTY, to the extent permitted by law.\n"
4518 "Report bugs to "PACKAGE_BUGREPORT".\n"
4525 help_or_version(int argc, tchar **argv, int cmd)
4530 for (i = 1; i < argc; i++) {
4532 if (p[0] == T('-') && p[1] == T('-')) {
4534 if (!tstrcmp(p, T("help"))) {
4535 if (cmd == CMD_NONE)
4540 } else if (!tstrcmp(p, T("version"))) {
4549 print_usage_string(int cmd, FILE *fp)
4551 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4555 recommend_man_page(int cmd, FILE *fp)
4557 const tchar *format_str;
4559 format_str = T("Some uncommon options are not listed;\n"
4560 "See %"TS".pdf in the doc directory for more details.\n");
4562 format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4564 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4568 usage(int cmd, FILE *fp)
4570 tfprintf(fp, T("Usage:\n"));
4571 print_usage_string(cmd, fp);
4572 tfprintf(fp, T("\n"));
4573 recommend_man_page(cmd, fp);
4579 tfprintf(fp, T("Usage:\n"));
4580 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4581 print_usage_string(cmd, fp);
4582 tfprintf(fp, T("\n"));
4584 static const tchar * const extra =
4587 " %"TS" --version\n"
4590 tfprintf(fp, extra, invocation_name, invocation_name);
4592 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4593 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4594 "For some commands IMAGE may be \"all\".\n"
4596 recommend_man_page(CMD_NONE, fp);
4600 extern int wmain(int argc, wchar_t **argv);
4604 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4605 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4606 * something else), while on Windows the command arguments will be UTF-16LE
4607 * encoded 'wchar_t' strings. */
4609 main(int argc, tchar **argv)
4615 imagex_info_file = stdout;
4616 invocation_name = tbasename(argv[0]);
4619 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4620 if (igcase != NULL) {
4621 if (!tstrcmp(igcase, T("no")) ||
4622 !tstrcmp(igcase, T("0")))
4623 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4624 else if (!tstrcmp(igcase, T("yes")) ||
4625 !tstrcmp(igcase, T("1")))
4626 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4629 "WARNING: Ignoring unknown setting of "
4630 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4635 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4637 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4638 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4639 for (int i = 0; i < CMD_MAX; i++) {
4640 if (!tstrcmp(invocation_name + 3,
4641 imagex_commands[i].name))
4650 /* Unless already known from the invocation name, determine which
4651 * command was specified. */
4652 if (cmd == CMD_NONE) {
4654 imagex_error(T("No command specified!\n"));
4658 for (int i = 0; i < CMD_MAX; i++) {
4659 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4664 if (cmd != CMD_NONE) {
4670 /* Handle --help and --version. --help can be either for the program as
4671 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4672 * CMD_NONE). Note: help_or_version() will not return if a --help or
4673 * --version argument was found. */
4674 help_or_version(argc, argv, cmd);
4676 /* Bail if a valid command was not specified. */
4677 if (cmd == CMD_NONE) {
4678 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4683 /* Enable warning and error messages in wimlib to be more user-friendly.
4685 wimlib_set_print_errors(true);
4687 /* Initialize wimlib. */
4688 ret = wimlib_global_init(init_flags);
4690 goto out_check_status;
4692 /* Call the command handler function. */
4693 ret = imagex_commands[cmd].func(argc, argv, cmd);
4695 /* Check for error writing to standard output, especially since for some
4696 * commands, writing to standard output is part of the program's actual
4697 * behavior and not just for informational purposes. */
4698 if (ferror(stdout) || fclose(stdout)) {
4699 imagex_error_with_errno(T("error writing to standard output"));
4704 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4705 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4706 * error code from which an error message can be printed. */
4708 imagex_error(T("Exiting with error code %d:\n"
4710 wimlib_get_error_string(ret));
4711 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4712 imagex_error_with_errno(T("errno"));
4714 /* Make wimlib free any resources it's holding (although this is not
4715 * strictly necessary because the process is ending anyway). */
4716 wimlib_global_cleanup();