4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012-2016 Eric Biggers
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # include "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
147 IMAGEX_CHUNK_SIZE_OPTION,
148 IMAGEX_COMMAND_OPTION,
149 IMAGEX_COMMIT_OPTION,
150 IMAGEX_COMPACT_OPTION,
151 IMAGEX_COMPRESS_OPTION,
152 IMAGEX_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("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
325 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
326 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
330 static const struct option join_options[] = {
331 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
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},
346 static const struct option optimize_options[] = {
347 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
348 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
349 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
350 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
351 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
352 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
353 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
354 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
355 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
356 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
357 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
358 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
359 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
360 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
364 static const struct option split_options[] = {
365 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
369 static const struct option unmount_options[] = {
370 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
371 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
372 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
373 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
374 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
375 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
379 static const struct option update_options[] = {
380 /* Careful: some of the options here set the defaults for update
381 * commands, but the flags given to an actual update command (and not to
382 * `imagex update' itself are also handled in
383 * update_command_add_option(). */
384 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
385 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
386 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
387 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
388 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
390 /* Default delete options */
391 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
392 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
394 /* Global add option */
395 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
397 /* Default add options */
398 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
399 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
400 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
401 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
402 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
403 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
404 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
405 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
410 static const struct option verify_options[] = {
411 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
412 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
418 # define _format_attribute(type, format_str, args_start) \
419 __attribute__((format(type, format_str, args_start)))
421 # define _format_attribute(type, format_str, args_start)
424 /* Print formatted error message to stderr. */
425 static void _format_attribute(printf, 1, 2)
426 imagex_error(const tchar *format, ...)
429 va_start(va, format);
430 tfputs(T("ERROR: "), stderr);
431 tvfprintf(stderr, format, va);
432 tputc(T('\n'), stderr);
436 /* Print formatted error message to stderr. */
437 static void _format_attribute(printf, 1, 2)
438 imagex_error_with_errno(const tchar *format, ...)
440 int errno_save = errno;
442 va_start(va, format);
443 tfputs(T("ERROR: "), stderr);
444 tvfprintf(stderr, format, va);
445 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
450 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
452 if (image == WIMLIB_NO_IMAGE) {
453 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
454 " Please specify a 1-based image index or "
455 "image name. To list the images\n"
456 " contained in the WIM archive, run\n"
458 " %"TS" \"%"TS"\"\n"),
459 image_name, wim_name,
460 get_cmd_string(CMD_INFO, false), wim_name);
461 return WIMLIB_ERR_INVALID_IMAGE;
467 verify_image_is_single(int image)
469 if (image == WIMLIB_ALL_IMAGES) {
470 imagex_error(T("Cannot specify all images for this action!"));
471 return WIMLIB_ERR_INVALID_IMAGE;
477 verify_image_exists_and_is_single(int image, const tchar *image_name,
478 const tchar *wim_name)
481 ret = verify_image_exists(image, image_name, wim_name);
483 ret = verify_image_is_single(image);
488 print_available_compression_types(FILE *fp)
490 static const tchar *s =
492 "Available compression types:\n"
495 " xpress (alias: \"fast\")\n"
496 " lzx (alias: \"maximum\") (default for capture)\n"
497 " lzms (alias: \"recovery\")\n"
503 /* Parse the argument to --compress or --solid-compress */
505 get_compression_type(tchar *optarg, bool solid)
508 unsigned int compression_level = 0;
511 plevel = tstrchr(optarg, T(':'));
517 ultmp = tstrtoul(plevel, &ptmp, 10);
518 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
519 imagex_error(T("Compression level must be a positive integer! "
520 "e.g. --compress=lzx:80"));
521 return WIMLIB_COMPRESSION_TYPE_INVALID;
523 compression_level = ultmp;
526 if (!tstrcasecmp(optarg, T("maximum")) ||
527 !tstrcasecmp(optarg, T("lzx")) ||
528 !tstrcasecmp(optarg, T("max"))) {
529 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
530 } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
531 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
532 } else if (!tstrcasecmp(optarg, T("recovery"))) {
536 "Warning: use of '--compress=recovery' is discouraged because it behaves\n"
537 " differently from DISM. Instead, you typically want to use '--solid' to\n"
538 " create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
539 " /compress:recovery. But if you really want *non-solid* LZMS compression,\n"
540 " then you may suppress this warning by specifying '--compress=lzms' instead\n"
541 " of '--compress=recovery'.\n"));
543 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
544 } else if (!tstrcasecmp(optarg, T("lzms"))) {
545 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
546 } else if (!tstrcasecmp(optarg, T("none"))) {
547 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
549 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
550 print_available_compression_types(stderr);
551 return WIMLIB_COMPRESSION_TYPE_INVALID;
554 if (compression_level != 0)
555 wimlib_set_default_compression_level(ctype, compression_level);
559 /* Parse the argument to --compact */
561 set_compact_mode(const tchar *arg, int *extract_flags)
564 if (!tstrcasecmp(arg, T("xpress4k")))
565 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
566 else if (!tstrcasecmp(arg, T("xpress8k")))
567 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
568 else if (!tstrcasecmp(arg, T("xpress16k")))
569 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
570 else if (!tstrcasecmp(arg, T("lzx")))
571 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
574 *extract_flags |= flag;
579 "\"%"TS"\" is not a recognized System Compression format. The options are:"
581 " --compact=xpress4k\n"
582 " --compact=xpress8k\n"
583 " --compact=xpress16k\n"
592 unsigned num_strings;
593 unsigned num_alloc_strings;
596 #define STRING_SET_INITIALIZER \
597 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
599 #define STRING_SET(_strings) \
600 struct string_set _strings = STRING_SET_INITIALIZER
603 string_set_append(struct string_set *set, tchar *glob)
605 unsigned num_alloc_strings = set->num_alloc_strings;
607 if (set->num_strings == num_alloc_strings) {
610 num_alloc_strings += 4;
611 new_strings = realloc(set->strings,
612 sizeof(set->strings[0]) * num_alloc_strings);
614 imagex_error(T("Out of memory!"));
617 set->strings = new_strings;
618 set->num_alloc_strings = num_alloc_strings;
620 set->strings[set->num_strings++] = glob;
625 string_set_destroy(struct string_set *set)
631 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
633 return wimlib_reference_resource_files(wim, (const tchar **)set->strings,
635 WIMLIB_REF_FLAG_GLOB_ENABLE,
640 append_image_property_argument(struct string_set *image_properties)
642 if (!tstrchr(optarg, '=')) {
643 imagex_error(T("'--image-property' argument "
644 "must be in the form NAME=VALUE"));
647 return string_set_append(image_properties, optarg);
651 apply_image_properties(struct string_set *image_properties,
652 WIMStruct *wim, int image, bool *any_changes_ret)
654 bool any_changes = false;
655 for (unsigned i = 0; i < image_properties->num_strings; i++) {
657 const tchar *current_value;
660 name = image_properties->strings[i];
661 value = tstrchr(name, '=');
664 current_value = wimlib_get_image_property(wim, image, name);
665 if (current_value && !tstrcmp(current_value, value)) {
666 imagex_printf(T("The %"TS" property of image %d "
667 "already has value \"%"TS"\".\n"),
670 imagex_printf(T("Setting the %"TS" property of image "
671 "%d to \"%"TS"\".\n"),
673 ret = wimlib_set_image_property(wim, image, name, value);
680 *any_changes_ret = any_changes;
685 do_resource_not_found_warning(const tchar *wimfile,
686 const struct wimlib_wim_info *info,
687 const struct string_set *refglobs)
689 if (info->total_parts > 1) {
690 if (refglobs->num_strings == 0) {
691 imagex_error(T("\"%"TS"\" is part of a split WIM. "
692 "Use --ref to specify the other parts."),
695 imagex_error(T("Perhaps the '--ref' argument did not "
696 "specify all other parts of the split "
700 imagex_error(T("If this is a delta WIM, use the --ref argument "
701 "to specify the WIM(s) on which it is based."));
706 do_metadata_not_found_warning(const tchar *wimfile,
707 const struct wimlib_wim_info *info)
709 if (info->part_number != 1) {
710 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
711 " You must specify the first part."),
716 /* Returns the size of a file given its name, or -1 if the file does not exist
717 * or its size cannot be determined. */
719 file_get_size(const tchar *filename)
722 if (tstat(filename, &st) == 0)
729 PARSE_STRING_SUCCESS = 0,
730 PARSE_STRING_FAILURE = 1,
731 PARSE_STRING_NONE = 2,
735 * Parses a string token from an array of characters.
737 * Tokens are either whitespace-delimited, or double or single-quoted.
739 * @line_p: Pointer to the pointer to the line of data. Will be updated
740 * to point past the string token iff the return value is
741 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
744 * @len_p: @len_p initially stores the length of the line of data, which may
745 * be 0, and it will be updated to the number of bytes remaining in
746 * the line iff the return value is PARSE_STRING_SUCCESS.
748 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
749 * parsed string token will be returned here.
751 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
752 * PARSE_STRING_FAILURE if the data was invalid due to a missing
753 * closing quote; or PARSE_STRING_NONE if the line ended before the
754 * beginning of a string token was found.
757 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
760 tchar *line = *line_p;
764 /* Skip leading whitespace */
767 return PARSE_STRING_NONE;
768 if (!istspace(*line) && *line != T('\0'))
774 if (quote_char == T('"') || quote_char == T('\'')) {
779 line = tmemchr(line, quote_char, len);
781 imagex_error(T("Missing closing quote: %"TS), fn - 1);
782 return PARSE_STRING_FAILURE;
785 /* Unquoted string. Go until whitespace. Line is terminated
786 * by '\0', so no need to check 'len'. */
790 } while (!istspace(*line) && *line != T('\0'));
797 return PARSE_STRING_SUCCESS;
800 /* Parses a line of data (not an empty line or comment) in the source list file
801 * format. (See the man page for 'wimlib-imagex capture' for details on this
802 * format and the meaning.)
804 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
805 * len == 0. The data in @line will be modified by this function call.
807 * @len: Length of the line of data.
809 * @source: On success, the capture source and target described by the line is
810 * written into this destination. Note that it will contain pointers
811 * to data in the @line array.
813 * Returns true if the line was valid; false otherwise. */
815 parse_source_list_line(tchar *line, size_t len,
816 struct wimlib_capture_source *source)
820 ret = parse_string(&line, &len, &source->fs_source_path);
821 if (ret != PARSE_STRING_SUCCESS)
823 ret = parse_string(&line, &len, &source->wim_target_path);
824 if (ret == PARSE_STRING_NONE)
825 source->wim_target_path = source->fs_source_path;
826 return ret != PARSE_STRING_FAILURE;
829 /* Returns %true if the given line of length @len > 0 is a comment or empty line
830 * in the source list file format. */
832 is_comment_line(const tchar *line, size_t len)
835 if (*line == T('#') || *line == T(';'))
837 if (!istspace(*line) && *line != T('\0'))
847 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
850 tchar *contents = *contents_p;
851 size_t nchars = *nchars_p;
854 for (i = 0; i < nchars; i++)
855 if (contents[i] == T('\n'))
858 /* Handle last line not terminated by a newline */
859 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
860 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
862 imagex_error(T("Out of memory!"));
865 contents[nchars] = T('\n');
866 *contents_p = contents;
874 /* Parses a file in the source list format. (See the man page for
875 * 'wimlib-imagex capture' for details on this format and the meaning.)
877 * @source_list_contents: Contents of the source list file. Note that this
878 * buffer will be modified to save memory allocations,
879 * and cannot be freed until the returned array of
880 * wimlib_capture_source's has also been freed.
882 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
885 * @nsources_ret: On success, the length of the returned array is
888 * Returns: An array of `struct wimlib_capture_source's that can be passed to
889 * the wimlib_add_image_multisource() function to specify how a WIM image is to
891 static struct wimlib_capture_source *
892 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
893 size_t *nsources_ret)
897 struct wimlib_capture_source *sources;
900 nlines = text_file_count_lines(source_list_contents_p,
901 &source_list_nchars);
905 /* Always allocate at least 1 slot, just in case the implementation of
906 * calloc() returns NULL if 0 bytes are requested. */
907 sources = calloc(nlines ?: 1, sizeof(*sources));
909 imagex_error(T("out of memory"));
912 p = *source_list_contents_p;
914 for (i = 0; i < nlines; i++) {
915 /* XXX: Could use rawmemchr() here instead, but it may not be
916 * available on all platforms. */
917 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
918 size_t len = endp - p + 1;
920 if (!is_comment_line(p, len)) {
921 if (!parse_source_list_line(p, len, &sources[j++])) {
933 /* Reads the contents of a file into memory. */
935 file_get_contents(const tchar *filename, size_t *len_ret)
942 if (tstat(filename, &stbuf) != 0) {
943 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
948 fp = tfopen(filename, T("rb"));
950 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
954 buf = malloc(len ? len : 1);
956 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
957 "contents of file \"%"TS"\""), len, filename);
960 if (fread(buf, 1, len, fp) != len) {
961 imagex_error_with_errno(T("Failed to read %zu bytes from the "
962 "file \"%"TS"\""), len, filename);
976 /* Read standard input until EOF and return the full contents in a malloc()ed
977 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
980 stdin_get_contents(size_t *len_ret)
982 /* stdin can, of course, be a pipe or other non-seekable file, so the
983 * total length of the data cannot be pre-determined */
985 size_t newlen = 1024;
989 char *p = realloc(buf, newlen);
990 size_t bytes_read, bytes_to_read;
992 imagex_error(T("out of memory while reading stdin"));
996 bytes_to_read = newlen - pos;
997 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
999 if (bytes_read != bytes_to_read) {
1004 imagex_error_with_errno(T("error reading stdin"));
1018 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1021 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1023 *num_tchars_ret = num_bytes;
1025 #else /* !__WIN32__ */
1026 /* On Windows, translate the text to UTF-16LE */
1030 if (num_bytes >= 2 &&
1031 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1032 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1034 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1035 * with something that looks like an ASCII character encoded as
1036 * a UTF-16LE code unit. Assume the file is encoded as
1037 * UTF-16LE. This is not a 100% reliable check. */
1038 num_wchars = num_bytes / 2;
1039 text_wstr = (wchar_t*)text;
1041 /* File does not look like UTF-16LE. Assume it is encoded in
1042 * the current Windows code page. I think these are always
1043 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1044 * should work as expected. */
1045 text_wstr = win32_mbs_to_wcs(text,
1050 *num_tchars_ret = num_wchars;
1052 #endif /* __WIN32__ */
1056 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1061 contents = file_get_contents(filename, &num_bytes);
1064 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1068 stdin_get_text_contents(size_t *num_tchars_ret)
1073 contents = stdin_get_contents(&num_bytes);
1076 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1079 #define TO_PERCENT(numerator, denominator) \
1080 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1082 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1083 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1084 #define KIBIBYTE_MIN_NBYTES 10000ULL
1087 get_unit(uint64_t total_bytes, const tchar **name_ret)
1089 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1090 *name_ret = T("GiB");
1092 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1093 *name_ret = T("MiB");
1095 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1096 *name_ret = T("KiB");
1099 *name_ret = T("bytes");
1104 static struct wimlib_progress_info_scan last_scan_progress;
1107 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1109 uint64_t prev_count, cur_count;
1111 prev_count = last_scan_progress.num_nondirs_scanned +
1112 last_scan_progress.num_dirs_scanned;
1113 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1115 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1116 cur_count % 128 == 0)
1118 unsigned unit_shift;
1119 const tchar *unit_name;
1121 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1122 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1123 "%"PRIu64" directories) "),
1124 scan->num_bytes_scanned >> unit_shift,
1126 scan->num_nondirs_scanned,
1127 scan->num_dirs_scanned);
1128 last_scan_progress = *scan;
1131 /* Progress callback function passed to various wimlib functions. */
1132 static enum wimlib_progress_status
1133 imagex_progress_func(enum wimlib_progress_msg msg,
1134 union wimlib_progress_info *info,
1135 void *_ignored_context)
1137 unsigned percent_done;
1138 unsigned unit_shift;
1139 const tchar *unit_name;
1141 if (imagex_be_quiet)
1142 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1144 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1146 static bool started;
1148 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1149 imagex_printf(T("Using %"TS" compression "
1150 "with %u thread%"TS"\n"),
1151 wimlib_get_compression_type_string(
1152 info->write_streams.compression_type),
1153 info->write_streams.num_threads,
1154 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1159 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1160 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1161 info->write_streams.total_bytes);
1163 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1164 info->write_streams.completed_bytes >> unit_shift,
1166 info->write_streams.total_bytes >> unit_shift,
1169 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1170 imagex_printf(T("\n"));
1172 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1173 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1174 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1175 imagex_printf(T("\n"));
1177 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1178 info->scan.wim_target_path);
1180 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1182 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1183 switch (info->scan.status) {
1184 case WIMLIB_SCAN_DENTRY_OK:
1185 report_scan_progress(&info->scan, false);
1187 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1188 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1190 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1191 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1192 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1194 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1195 /* Symlink fixups are enabled by default. This is
1196 * mainly intended for Windows, which for some reason
1197 * uses absolute junctions (with drive letters!) in the
1198 * default installation. On UNIX-like systems, warn the
1199 * user when fixing the target of an absolute symbolic
1200 * link, so they know to disable this if they want. */
1202 imagex_printf(T("\nWARNING: Adjusted target of "
1203 "absolute symbolic link \"%"TS"\"\n"
1204 " (Use --norpfix to capture "
1205 "absolute symbolic links as-is)\n"),
1206 info->scan.cur_path);
1213 case WIMLIB_PROGRESS_MSG_SCAN_END:
1214 report_scan_progress(&info->scan, true);
1215 imagex_printf(T("\n"));
1217 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1218 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1219 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1220 info->integrity.total_bytes);
1221 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1222 "of %"PRIu64" %"TS" (%u%%) done"),
1223 info->integrity.filename,
1224 info->integrity.completed_bytes >> unit_shift,
1226 info->integrity.total_bytes >> unit_shift,
1229 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1230 imagex_printf(T("\n"));
1232 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1233 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1234 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1235 info->integrity.total_bytes);
1236 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1237 "of %"PRIu64" %"TS" (%u%%) done"),
1238 info->integrity.completed_bytes >> unit_shift,
1240 info->integrity.total_bytes >> unit_shift,
1243 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1244 imagex_printf(T("\n"));
1246 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1247 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1248 "to %"TS" \"%"TS"\"\n"),
1249 info->extract.image,
1250 info->extract.image_name,
1251 info->extract.wimfile_name,
1252 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1253 T("NTFS volume") : T("directory")),
1254 info->extract.target);
1256 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1257 if (info->extract.end_file_count >= 2000) {
1258 percent_done = TO_PERCENT(info->extract.current_file_count,
1259 info->extract.end_file_count);
1260 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1261 info->extract.current_file_count,
1262 info->extract.end_file_count, percent_done);
1263 if (info->extract.current_file_count == info->extract.end_file_count)
1264 imagex_printf(T("\n"));
1267 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1268 percent_done = TO_PERCENT(info->extract.completed_bytes,
1269 info->extract.total_bytes);
1270 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1271 imagex_printf(T("\rExtracting file data: "
1272 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1273 info->extract.completed_bytes >> unit_shift,
1275 info->extract.total_bytes >> unit_shift,
1278 if (info->extract.completed_bytes >= info->extract.total_bytes)
1279 imagex_printf(T("\n"));
1281 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1282 if (info->extract.end_file_count >= 2000) {
1283 percent_done = TO_PERCENT(info->extract.current_file_count,
1284 info->extract.end_file_count);
1285 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1286 info->extract.current_file_count,
1287 info->extract.end_file_count, percent_done);
1288 if (info->extract.current_file_count == info->extract.end_file_count)
1289 imagex_printf(T("\n"));
1292 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1293 if (info->extract.total_parts != 1) {
1294 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1295 info->extract.part_number,
1296 info->extract.total_parts);
1299 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1300 percent_done = TO_PERCENT(info->split.completed_bytes,
1301 info->split.total_bytes);
1302 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1303 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1304 "%"PRIu64" %"TS" (%u%%) written\n"),
1305 info->split.part_name,
1306 info->split.cur_part_number,
1307 info->split.total_parts,
1308 info->split.completed_bytes >> unit_shift,
1310 info->split.total_bytes >> unit_shift,
1314 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1315 if (info->split.completed_bytes == info->split.total_bytes) {
1316 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1317 info->split.cur_part_number,
1318 info->split.total_parts);
1321 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1322 switch (info->update.command->op) {
1323 case WIMLIB_UPDATE_OP_DELETE:
1324 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1325 info->update.command->delete_.wim_path);
1327 case WIMLIB_UPDATE_OP_RENAME:
1328 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1329 info->update.command->rename.wim_source_path,
1330 info->update.command->rename.wim_target_path);
1332 case WIMLIB_UPDATE_OP_ADD:
1337 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1338 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1339 info->replace.path_in_wim);
1341 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1342 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1343 info->wimboot_exclude.path_in_wim);
1345 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1346 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1347 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1348 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1349 info->unmount.mounted_wim,
1350 info->unmount.mounted_image);
1352 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1353 info->unmount.mounted_wim,
1354 info->unmount.mounted_image);
1355 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1359 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1360 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1361 info->verify_image.current_image,
1362 info->verify_image.total_images);
1364 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1365 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1366 info->verify_streams.total_bytes);
1367 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1368 imagex_printf(T("\rVerifying file data: "
1369 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1370 info->verify_streams.completed_bytes >> unit_shift,
1372 info->verify_streams.total_bytes >> unit_shift,
1375 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1376 imagex_printf(T("\n"));
1381 fflush(imagex_info_file);
1382 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1386 parse_num_threads(const tchar *optarg)
1389 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1390 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1391 imagex_error(T("Number of threads must be a non-negative integer!"));
1399 parse_chunk_size(const tchar *optarg)
1402 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1403 if (chunk_size == 0) {
1404 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1405 " with optional K, M, or G suffix"));
1409 if (*tmp == T('k') || *tmp == T('K')) {
1412 } else if (*tmp == T('m') || *tmp == T('M')) {
1415 } else if (*tmp == T('g') || *tmp == T('G')) {
1419 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1420 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1424 if (chunk_size >= UINT32_MAX) {
1425 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1433 * Parse an option passed to an update command.
1435 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1438 * @option: Text string for the option (beginning with --)
1440 * @cmd: `struct wimlib_update_command' that is being constructed for
1443 * Returns true if the option was recognized; false if not.
1446 update_command_add_option(int op, const tchar *option,
1447 struct wimlib_update_command *cmd)
1449 bool recognized = true;
1451 case WIMLIB_UPDATE_OP_ADD:
1452 if (!tstrcmp(option, T("--verbose")))
1453 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1454 else if (!tstrcmp(option, T("--unix-data")))
1455 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1456 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1457 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1458 else if (!tstrcmp(option, T("--strict-acls")))
1459 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1460 else if (!tstrcmp(option, T("--dereference")))
1461 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1462 else if (!tstrcmp(option, T("--no-replace")))
1463 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1467 case WIMLIB_UPDATE_OP_DELETE:
1468 if (!tstrcmp(option, T("--force")))
1469 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1470 else if (!tstrcmp(option, T("--recursive")))
1471 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1482 /* How many nonoption arguments each `imagex update' command expects */
1483 static const unsigned update_command_num_nonoptions[] = {
1484 [WIMLIB_UPDATE_OP_ADD] = 2,
1485 [WIMLIB_UPDATE_OP_DELETE] = 1,
1486 [WIMLIB_UPDATE_OP_RENAME] = 2,
1490 update_command_add_nonoption(int op, const tchar *nonoption,
1491 struct wimlib_update_command *cmd,
1492 unsigned num_nonoptions)
1495 case WIMLIB_UPDATE_OP_ADD:
1496 if (num_nonoptions == 0)
1497 cmd->add.fs_source_path = (tchar*)nonoption;
1499 cmd->add.wim_target_path = (tchar*)nonoption;
1501 case WIMLIB_UPDATE_OP_DELETE:
1502 cmd->delete_.wim_path = (tchar*)nonoption;
1504 case WIMLIB_UPDATE_OP_RENAME:
1505 if (num_nonoptions == 0)
1506 cmd->rename.wim_source_path = (tchar*)nonoption;
1508 cmd->rename.wim_target_path = (tchar*)nonoption;
1514 * Parse a command passed on stdin to `imagex update'.
1516 * @line: Text of the command.
1517 * @len: Length of the line, including a null terminator
1520 * @command: A `struct wimlib_update_command' to fill in from the parsed
1523 * @line_number: Line number of the command, for diagnostics.
1525 * Returns true on success; returns false on parse error.
1528 parse_update_command(tchar *line, size_t len,
1529 struct wimlib_update_command *command,
1533 tchar *command_name;
1535 size_t num_nonoptions;
1537 /* Get the command name ("add", "delete", "rename") */
1538 ret = parse_string(&line, &len, &command_name);
1539 if (ret != PARSE_STRING_SUCCESS)
1542 if (!tstrcasecmp(command_name, T("add"))) {
1543 op = WIMLIB_UPDATE_OP_ADD;
1544 } else if (!tstrcasecmp(command_name, T("delete"))) {
1545 op = WIMLIB_UPDATE_OP_DELETE;
1546 } else if (!tstrcasecmp(command_name, T("rename"))) {
1547 op = WIMLIB_UPDATE_OP_RENAME;
1549 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1550 command_name, line_number);
1555 /* Parse additional options and non-options as needed */
1560 ret = parse_string(&line, &len, &next_string);
1561 if (ret == PARSE_STRING_NONE) /* End of line */
1563 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1565 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1567 if (!update_command_add_option(op, next_string, command))
1569 imagex_error(T("Unrecognized option \"%"TS"\" to "
1570 "update command \"%"TS"\" on line %zu"),
1571 next_string, command_name, line_number);
1577 if (num_nonoptions == update_command_num_nonoptions[op])
1579 imagex_error(T("Unexpected argument \"%"TS"\" in "
1580 "update command on line %zu\n"
1581 " (The \"%"TS"\" command only "
1582 "takes %zu nonoption arguments!)\n"),
1583 next_string, line_number,
1584 command_name, num_nonoptions);
1587 update_command_add_nonoption(op, next_string,
1588 command, num_nonoptions);
1593 if (num_nonoptions != update_command_num_nonoptions[op]) {
1594 imagex_error(T("Not enough arguments to update command "
1595 "\"%"TS"\" on line %zu"), command_name, line_number);
1601 static struct wimlib_update_command *
1602 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1603 size_t *num_cmds_ret)
1607 struct wimlib_update_command *cmds;
1610 nlines = text_file_count_lines(cmd_file_contents_p,
1615 /* Always allocate at least 1 slot, just in case the implementation of
1616 * calloc() returns NULL if 0 bytes are requested. */
1617 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1619 imagex_error(T("out of memory"));
1622 p = *cmd_file_contents_p;
1624 for (i = 0; i < nlines; i++) {
1625 /* XXX: Could use rawmemchr() here instead, but it may not be
1626 * available on all platforms. */
1627 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1628 size_t len = endp - p + 1;
1630 if (!is_comment_line(p, len)) {
1631 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1642 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1643 * one image from a WIM file to an NTFS volume. */
1645 imagex_apply(int argc, tchar **argv, int cmd)
1649 int image = WIMLIB_NO_IMAGE;
1651 struct wimlib_wim_info info;
1653 const tchar *wimfile;
1654 const tchar *target;
1655 const tchar *image_num_or_name = NULL;
1656 int extract_flags = 0;
1658 STRING_SET(refglobs);
1660 for_opt(c, apply_options) {
1662 case IMAGEX_CHECK_OPTION:
1663 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1665 case IMAGEX_VERBOSE_OPTION:
1666 /* No longer does anything. */
1668 case IMAGEX_REF_OPTION:
1669 ret = string_set_append(&refglobs, optarg);
1671 goto out_free_refglobs;
1673 case IMAGEX_UNIX_DATA_OPTION:
1674 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1676 case IMAGEX_NO_ACLS_OPTION:
1677 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1679 case IMAGEX_STRICT_ACLS_OPTION:
1680 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1682 case IMAGEX_NO_ATTRIBUTES_OPTION:
1683 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1685 case IMAGEX_NORPFIX_OPTION:
1686 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1688 case IMAGEX_RPFIX_OPTION:
1689 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1691 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1692 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1693 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1695 case IMAGEX_WIMBOOT_OPTION:
1696 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1698 case IMAGEX_COMPACT_OPTION:
1699 ret = set_compact_mode(optarg, &extract_flags);
1701 goto out_free_refglobs;
1709 if (argc != 2 && argc != 3)
1714 if (!tstrcmp(wimfile, T("-"))) {
1715 /* Attempt to apply pipable WIM from standard input. */
1717 image_num_or_name = NULL;
1720 image_num_or_name = argv[1];
1725 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1726 imagex_progress_func, NULL);
1728 goto out_free_refglobs;
1730 wimlib_get_wim_info(wim, &info);
1733 /* Image explicitly specified. */
1734 image_num_or_name = argv[1];
1735 image = wimlib_resolve_image(wim, image_num_or_name);
1736 ret = verify_image_exists(image, image_num_or_name, wimfile);
1738 goto out_wimlib_free;
1741 /* No image specified; default to image 1, but only if the WIM
1742 * contains exactly one image. */
1744 if (info.image_count != 1) {
1745 imagex_error(T("\"%"TS"\" contains %d images; "
1746 "Please select one (or all)."),
1747 wimfile, info.image_count);
1756 if (refglobs.num_strings) {
1758 imagex_error(T("Can't specify --ref when applying from stdin!"));
1760 goto out_wimlib_free;
1762 ret = wim_reference_globs(wim, &refglobs, open_flags);
1764 goto out_wimlib_free;
1769 /* Interpret a regular file or block device target as an NTFS
1773 if (tstat(target, &stbuf)) {
1774 if (errno != ENOENT) {
1775 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1778 goto out_wimlib_free;
1781 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1782 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1788 ret = wimlib_extract_image(wim, image, target, extract_flags);
1790 set_fd_to_binary_mode(STDIN_FILENO);
1791 ret = wimlib_extract_image_from_pipe_with_progress(
1796 imagex_progress_func,
1800 imagex_printf(T("Done applying WIM image.\n"));
1801 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1803 do_resource_not_found_warning(wimfile, &info, &refglobs);
1805 imagex_error(T( "If you are applying an image "
1806 "from a split pipable WIM,\n"
1807 " make sure you have "
1808 "concatenated together all parts."));
1810 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1811 do_metadata_not_found_warning(wimfile, &info);
1816 string_set_destroy(&refglobs);
1820 usage(CMD_APPLY, stderr);
1822 goto out_free_refglobs;
1825 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1826 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1827 * the desired image. 'wimlib-imagex append': add a new image to an existing
1830 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1834 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1835 WIMLIB_ADD_FLAG_WINCONFIG |
1836 WIMLIB_ADD_FLAG_VERBOSE;
1837 int write_flags = 0;
1838 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1839 uint32_t chunk_size = UINT32_MAX;
1840 uint32_t solid_chunk_size = UINT32_MAX;
1841 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1842 const tchar *wimfile;
1845 STRING_SET(image_properties);
1848 STRING_SET(base_wimfiles);
1849 WIMStruct **base_wims;
1851 WIMStruct *template_wim;
1852 const tchar *template_wimfile = NULL;
1853 const tchar *template_image_name_or_num = NULL;
1854 int template_image = WIMLIB_NO_IMAGE;
1857 unsigned num_threads = 0;
1862 tchar *config_file = NULL;
1864 bool source_list = false;
1865 size_t source_list_nchars = 0;
1866 tchar *source_list_contents;
1867 bool capture_sources_malloced;
1868 struct wimlib_capture_source *capture_sources;
1870 bool name_defaulted;
1872 for_opt(c, capture_or_append_options) {
1874 case IMAGEX_BOOT_OPTION:
1875 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1877 case IMAGEX_CHECK_OPTION:
1878 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1879 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1881 case IMAGEX_NOCHECK_OPTION:
1882 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1884 case IMAGEX_CONFIG_OPTION:
1885 config_file = optarg;
1886 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1888 case IMAGEX_COMPRESS_OPTION:
1889 compression_type = get_compression_type(optarg, false);
1890 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1893 case IMAGEX_CHUNK_SIZE_OPTION:
1894 chunk_size = parse_chunk_size(optarg);
1895 if (chunk_size == UINT32_MAX)
1898 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1899 solid_chunk_size = parse_chunk_size(optarg);
1900 if (solid_chunk_size == UINT32_MAX)
1903 case IMAGEX_SOLID_COMPRESS_OPTION:
1904 solid_ctype = get_compression_type(optarg, true);
1905 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1908 case IMAGEX_SOLID_OPTION:
1909 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1911 case IMAGEX_NO_SOLID_SORT_OPTION:
1912 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1914 case IMAGEX_FLAGS_OPTION: {
1915 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1916 tsprintf(p, T("FLAGS=%"TS), optarg);
1917 ret = string_set_append(&image_properties, p);
1922 case IMAGEX_IMAGE_PROPERTY_OPTION:
1923 ret = append_image_property_argument(&image_properties);
1927 case IMAGEX_DEREFERENCE_OPTION:
1928 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1930 case IMAGEX_VERBOSE_OPTION:
1931 /* No longer does anything. */
1933 case IMAGEX_THREADS_OPTION:
1934 num_threads = parse_num_threads(optarg);
1935 if (num_threads == UINT_MAX)
1938 case IMAGEX_REBUILD_OPTION:
1939 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1941 case IMAGEX_UNIX_DATA_OPTION:
1942 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1944 case IMAGEX_SOURCE_LIST_OPTION:
1947 case IMAGEX_NO_ACLS_OPTION:
1948 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1950 case IMAGEX_STRICT_ACLS_OPTION:
1951 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1953 case IMAGEX_RPFIX_OPTION:
1954 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1956 case IMAGEX_NORPFIX_OPTION:
1957 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1959 case IMAGEX_PIPABLE_OPTION:
1960 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1962 case IMAGEX_NOT_PIPABLE_OPTION:
1963 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1965 case IMAGEX_UPDATE_OF_OPTION:
1966 if (template_image_name_or_num) {
1967 imagex_error(T("'--update-of' can only be "
1968 "specified one time!"));
1972 colon = tstrrchr(optarg, T(':'));
1975 template_wimfile = optarg;
1977 template_image_name_or_num = colon + 1;
1979 template_wimfile = NULL;
1980 template_image_name_or_num = optarg;
1984 case IMAGEX_DELTA_FROM_OPTION:
1985 if (cmd != CMD_CAPTURE) {
1986 imagex_error(T("'--delta-from' is only "
1987 "valid for capture!"));
1990 ret = string_set_append(&base_wimfiles, optarg);
1993 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1995 case IMAGEX_WIMBOOT_OPTION:
1996 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1998 case IMAGEX_UNSAFE_COMPACT_OPTION:
1999 if (cmd != CMD_APPEND) {
2000 imagex_error(T("'--unsafe-compact' is only "
2001 "valid for append!"));
2004 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2006 case IMAGEX_SNAPSHOT_OPTION:
2007 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2016 if (argc < 2 || argc > 4)
2022 /* Set default compression type and parameters. */
2025 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2026 /* No compression type specified. Use the default. */
2028 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2029 /* With --wimboot, default to XPRESS compression. */
2030 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2031 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2032 /* With --solid, default to LZMS compression. (However,
2033 * this will not affect solid resources!) */
2034 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2036 /* Otherwise, default to LZX compression. */
2037 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2041 if (!tstrcmp(wimfile, T("-"))) {
2042 /* Writing captured WIM to standard output. */
2044 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2045 imagex_error("Can't write a non-pipable WIM to "
2046 "standard output! Specify --pipable\n"
2047 " if you want to create a pipable WIM "
2048 "(but read the docs first).");
2052 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2054 if (cmd == CMD_APPEND) {
2055 imagex_error(T("Using standard output for append does "
2056 "not make sense."));
2059 wim_fd = STDOUT_FILENO;
2061 imagex_info_file = stderr;
2062 set_fd_to_binary_mode(wim_fd);
2065 /* If template image was specified using --update-of=IMAGE rather
2066 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2067 if (template_image_name_or_num && !template_wimfile) {
2068 if (base_wimfiles.num_strings == 1) {
2069 /* Capturing delta WIM based on single WIM: default to
2071 template_wimfile = base_wimfiles.strings[0];
2072 } else if (cmd == CMD_APPEND) {
2073 /* Appending to WIM: default to WIM being appended to.
2075 template_wimfile = wimfile;
2077 /* Capturing a normal (non-delta) WIM, so the WIM file
2078 * *must* be explicitly specified. */
2079 if (base_wimfiles.num_strings > 1) {
2080 imagex_error(T("For capture of delta WIM "
2081 "based on multiple existing "
2083 " '--update-of' must "
2084 "specify WIMFILE:IMAGE!"));
2086 imagex_error(T("For capture of non-delta WIM, "
2087 "'--update-of' must specify "
2096 name_defaulted = false;
2098 /* Set default name to SOURCE argument, omitting any directory
2099 * prefixes and trailing slashes. This requires making a copy
2100 * of @source. Leave some free characters at the end in case we
2101 * append a number to keep the name unique. */
2102 size_t source_name_len;
2104 source_name_len = tstrlen(source);
2105 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2106 name = tbasename(tstrcpy(source_copy, source));
2107 name_defaulted = true;
2110 /* Image description (if given). */
2112 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2113 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2114 ret = string_set_append(&image_properties, p);
2120 /* Set up capture sources in source list mode */
2121 if (source[0] == T('-') && source[1] == T('\0')) {
2122 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2124 source_list_contents = file_get_text_contents(source,
2125 &source_list_nchars);
2127 if (!source_list_contents)
2130 capture_sources = parse_source_list(&source_list_contents,
2133 if (!capture_sources) {
2135 goto out_free_source_list_contents;
2137 capture_sources_malloced = true;
2139 /* Set up capture source in non-source-list mode. */
2140 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2141 capture_sources[0].fs_source_path = source;
2142 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2143 capture_sources[0].reserved = 0;
2145 capture_sources_malloced = false;
2146 source_list_contents = NULL;
2149 /* Open the existing WIM, or create a new one. */
2150 if (cmd == CMD_APPEND) {
2151 ret = wimlib_open_wim_with_progress(wimfile,
2152 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2154 imagex_progress_func,
2157 goto out_free_capture_sources;
2159 ret = wimlib_create_new_wim(compression_type, &wim);
2161 goto out_free_capture_sources;
2162 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2165 /* Set chunk size if non-default. */
2166 if (chunk_size != UINT32_MAX) {
2167 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2170 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2172 int ctype = compression_type;
2174 if (cmd == CMD_APPEND) {
2175 struct wimlib_wim_info info;
2176 wimlib_get_wim_info(wim, &info);
2177 ctype = info.compression_type;
2180 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2181 ret = wimlib_set_output_chunk_size(wim, 4096);
2186 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2187 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2191 if (solid_chunk_size != UINT32_MAX) {
2192 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2198 /* Detect if source is regular file or block device and set NTFS volume
2203 if (tstat(source, &stbuf) == 0) {
2204 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2205 imagex_printf(T("Capturing WIM image from NTFS "
2206 "filesystem on \"%"TS"\"\n"), source);
2207 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2210 if (errno != ENOENT) {
2211 imagex_error_with_errno(T("Failed to stat "
2212 "\"%"TS"\""), source);
2220 /* If the user did not specify an image name, and the basename of the
2221 * source already exists as an image name in the WIM file, append a
2222 * suffix to make it unique. */
2223 if (cmd == CMD_APPEND && name_defaulted) {
2224 unsigned long conflict_idx;
2225 tchar *name_end = tstrchr(name, T('\0'));
2226 for (conflict_idx = 1;
2227 wimlib_image_name_in_use(wim, name);
2230 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2234 /* If capturing a delta WIM, reference resources from the base WIMs
2235 * before adding the new image. */
2236 if (base_wimfiles.num_strings) {
2237 base_wims = calloc(base_wimfiles.num_strings,
2238 sizeof(base_wims[0]));
2239 if (base_wims == NULL) {
2240 imagex_error(T("Out of memory!"));
2245 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2246 ret = wimlib_open_wim_with_progress(
2247 base_wimfiles.strings[i], open_flags,
2248 &base_wims[i], imagex_progress_func, NULL);
2250 goto out_free_base_wims;
2254 ret = wimlib_reference_resources(wim, base_wims,
2255 base_wimfiles.num_strings, 0);
2257 goto out_free_base_wims;
2259 if (base_wimfiles.num_strings == 1) {
2260 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2261 base_wimfiles.strings[0]);
2263 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2264 base_wimfiles.num_strings);
2271 /* If capturing or appending as an update of an existing (template) image,
2272 * open the WIM if needed and parse the image index. */
2273 if (template_image_name_or_num) {
2276 if (base_wimfiles.num_strings == 1 &&
2277 template_wimfile == base_wimfiles.strings[0]) {
2278 template_wim = base_wims[0];
2279 } else if (template_wimfile == wimfile) {
2282 ret = wimlib_open_wim_with_progress(template_wimfile,
2285 imagex_progress_func,
2288 goto out_free_base_wims;
2291 template_image = wimlib_resolve_image(template_wim,
2292 template_image_name_or_num);
2294 if (template_image_name_or_num[0] == T('-')) {
2297 struct wimlib_wim_info info;
2299 wimlib_get_wim_info(template_wim, &info);
2300 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2301 if (n >= 1 && n <= info.image_count &&
2303 tmp != template_image_name_or_num + 1)
2305 template_image = info.image_count - (n - 1);
2308 ret = verify_image_exists_and_is_single(template_image,
2309 template_image_name_or_num,
2312 goto out_free_template_wim;
2314 template_wim = NULL;
2317 ret = wimlib_add_image_multisource(wim,
2324 goto out_free_template_wim;
2326 if (image_properties.num_strings || template_image_name_or_num) {
2327 /* User asked to set additional image properties, or an image on
2328 * which the added one is to be based has been specified with
2330 struct wimlib_wim_info info;
2332 wimlib_get_wim_info(wim, &info);
2334 ret = apply_image_properties(&image_properties, wim,
2335 info.image_count, NULL);
2337 goto out_free_template_wim;
2339 /* Reference template image if the user provided one. */
2340 if (template_image_name_or_num) {
2341 imagex_printf(T("Using image %d "
2342 "from \"%"TS"\" as template\n"),
2343 template_image, template_wimfile);
2344 ret = wimlib_reference_template_image(wim,
2350 goto out_free_template_wim;
2354 /* Write the new WIM or overwrite the existing WIM with the new image
2356 if (cmd == CMD_APPEND) {
2357 ret = wimlib_overwrite(wim, write_flags, num_threads);
2358 } else if (wimfile) {
2359 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2360 write_flags, num_threads);
2362 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2363 write_flags, num_threads);
2365 out_free_template_wim:
2366 /* template_wim may alias base_wims[0] or wim. */
2367 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2368 template_wim != wim)
2369 wimlib_free(template_wim);
2371 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2372 wimlib_free(base_wims[i]);
2376 out_free_capture_sources:
2377 if (capture_sources_malloced)
2378 free(capture_sources);
2379 out_free_source_list_contents:
2380 free(source_list_contents);
2382 string_set_destroy(&image_properties);
2383 string_set_destroy(&base_wimfiles);
2393 /* Remove image(s) from a WIM. */
2395 imagex_delete(int argc, tchar **argv, int cmd)
2398 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2399 int write_flags = 0;
2400 const tchar *wimfile;
2401 const tchar *image_num_or_name;
2406 for_opt(c, delete_options) {
2408 case IMAGEX_CHECK_OPTION:
2409 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2410 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2412 case IMAGEX_SOFT_OPTION:
2413 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2415 case IMAGEX_UNSAFE_COMPACT_OPTION:
2416 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2427 imagex_error(T("Must specify a WIM file"));
2429 imagex_error(T("Must specify an image"));
2433 image_num_or_name = argv[1];
2435 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2436 imagex_progress_func, NULL);
2440 image = wimlib_resolve_image(wim, image_num_or_name);
2442 ret = verify_image_exists(image, image_num_or_name, wimfile);
2444 goto out_wimlib_free;
2446 ret = wimlib_delete_image(wim, image);
2448 imagex_error(T("Failed to delete image from \"%"TS"\""),
2450 goto out_wimlib_free;
2453 ret = wimlib_overwrite(wim, write_flags, 0);
2455 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2456 "deleted"), wimfile);
2464 usage(CMD_DELETE, stderr);
2469 struct print_dentry_options {
2474 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2476 tprintf(T("%"TS"\n"), dentry->full_path);
2479 static const struct {
2482 } file_attr_flags[] = {
2483 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2484 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2485 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2486 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2487 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2488 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2489 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2490 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2491 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2492 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2493 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2494 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2495 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2496 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2497 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2500 #define TIMESTR_MAX 100
2503 timespec_to_string(const struct timespec *spec, tchar *buf)
2505 time_t t = spec->tv_sec;
2508 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2509 buf[TIMESTR_MAX - 1] = '\0';
2513 print_time(const tchar *type, const struct timespec *spec)
2515 tchar timestr[TIMESTR_MAX];
2517 timespec_to_string(spec, timestr);
2519 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2522 static void print_byte_field(const uint8_t field[], size_t len)
2525 tprintf(T("%02hhx"), *field++);
2529 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2531 tchar attr_string[256];
2534 tputs(T("WIM Information:"));
2535 tputs(T("----------------"));
2536 tprintf(T("Path: %"TS"\n"), wimfile);
2537 tprintf(T("GUID: 0x"));
2538 print_byte_field(info->guid, sizeof(info->guid));
2540 tprintf(T("Version: %u\n"), info->wim_version);
2541 tprintf(T("Image Count: %d\n"), info->image_count);
2542 tprintf(T("Compression: %"TS"\n"),
2543 wimlib_get_compression_type_string(info->compression_type));
2544 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2546 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2547 tprintf(T("Boot Index: %d\n"), info->boot_index);
2548 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2550 attr_string[0] = T('\0');
2553 tstrcat(attr_string, T("Pipable, "));
2555 if (info->has_integrity_table)
2556 tstrcat(attr_string, T("Integrity info, "));
2558 if (info->has_rpfix)
2559 tstrcat(attr_string, T("Relative path junction, "));
2561 if (info->resource_only)
2562 tstrcat(attr_string, T("Resource only, "));
2564 if (info->metadata_only)
2565 tstrcat(attr_string, T("Metadata only, "));
2567 if (info->is_marked_readonly)
2568 tstrcat(attr_string, T("Readonly, "));
2570 p = tstrchr(attr_string, T('\0'));
2571 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2574 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2578 print_resource(const struct wimlib_resource_entry *resource,
2581 tprintf(T("Hash = 0x"));
2582 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2585 if (!resource->is_missing) {
2586 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2587 resource->uncompressed_size);
2588 if (resource->packed) {
2589 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2590 "bytes @ offset %"PRIu64"\n"),
2591 resource->raw_resource_uncompressed_size,
2592 resource->raw_resource_compressed_size,
2593 resource->raw_resource_offset_in_wim);
2595 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2598 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2599 resource->compressed_size);
2601 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2605 tprintf(T("Part Number = %u\n"), resource->part_number);
2606 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2608 tprintf(T("Flags = "));
2609 if (resource->is_compressed)
2610 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2611 if (resource->is_metadata)
2612 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2613 if (resource->is_free)
2614 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2615 if (resource->is_spanned)
2616 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2617 if (resource->packed)
2618 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2626 print_blobs(WIMStruct *wim)
2628 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);
2640 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2644 "----------------------------------------------------------------------------\n"));
2645 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2646 if (dentry->dos_name)
2647 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2648 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2649 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2650 if (file_attr_flags[i].flag & dentry->attributes)
2651 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2652 file_attr_flags[i].name);
2654 if (dentry->security_descriptor) {
2655 print_security_descriptor(dentry->security_descriptor,
2656 dentry->security_descriptor_size);
2659 print_time(T("Creation Time"), &dentry->creation_time);
2660 print_time(T("Last Write Time"), &dentry->last_write_time);
2661 print_time(T("Last Access Time"), &dentry->last_access_time);
2664 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2665 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2667 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2668 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2670 if (dentry->unix_mode != 0) {
2671 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2672 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2673 dentry->unix_uid, dentry->unix_gid,
2674 dentry->unix_mode, dentry->unix_rdev);
2677 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2678 if (dentry->streams[i].stream_name) {
2679 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2680 dentry->streams[i].stream_name);
2681 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2682 tprintf(T("\tRaw encrypted data stream:\n"));
2683 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2684 tprintf(T("\tReparse point stream:\n"));
2686 tprintf(T("\tUnnamed data stream:\n"));
2688 print_resource(&dentry->streams[i].resource, NULL);
2693 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2695 const struct print_dentry_options *options = _options;
2696 if (!options->detailed)
2697 print_dentry_full_path(dentry);
2699 print_dentry_detailed(dentry);
2703 /* Print the files contained in an image(s) in a WIM file. */
2705 imagex_dir(int argc, tchar **argv, int cmd)
2707 const tchar *wimfile;
2708 WIMStruct *wim = NULL;
2711 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2713 struct print_dentry_options options = {
2716 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2718 STRING_SET(refglobs);
2720 for_opt(c, dir_options) {
2722 case IMAGEX_PATH_OPTION:
2725 case IMAGEX_DETAILED_OPTION:
2726 options.detailed = true;
2728 case IMAGEX_ONE_FILE_ONLY_OPTION:
2729 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2731 case IMAGEX_REF_OPTION:
2732 ret = string_set_append(&refglobs, optarg);
2734 goto out_free_refglobs;
2744 imagex_error(T("Must specify a WIM file"));
2748 imagex_error(T("Too many arguments"));
2753 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2754 imagex_progress_func, NULL);
2756 goto out_free_refglobs;
2759 image = wimlib_resolve_image(wim, argv[1]);
2760 ret = verify_image_exists(image, argv[1], wimfile);
2762 goto out_wimlib_free;
2764 /* No image specified; default to image 1, but only if the WIM
2765 * contains exactly one image. */
2767 struct wimlib_wim_info info;
2769 wimlib_get_wim_info(wim, &info);
2770 if (info.image_count != 1) {
2771 imagex_error(T("\"%"TS"\" contains %d images; Please "
2772 "select one (or all)."),
2773 wimfile, info.image_count);
2780 if (refglobs.num_strings) {
2781 ret = wim_reference_globs(wim, &refglobs, 0);
2783 goto out_wimlib_free;
2786 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2787 print_dentry, &options);
2788 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2789 struct wimlib_wim_info info;
2791 wimlib_get_wim_info(wim, &info);
2792 do_metadata_not_found_warning(wimfile, &info);
2797 string_set_destroy(&refglobs);
2801 usage(CMD_DIR, stderr);
2803 goto out_free_refglobs;
2806 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2809 imagex_export(int argc, tchar **argv, int cmd)
2813 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2814 int write_flags = 0;
2815 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2816 const tchar *src_wimfile;
2817 const tchar *src_image_num_or_name;
2818 const tchar *dest_wimfile;
2820 const tchar *dest_name;
2821 const tchar *dest_desc;
2823 struct wimlib_wim_info src_info;
2824 WIMStruct *dest_wim;
2829 STRING_SET(refglobs);
2830 unsigned num_threads = 0;
2831 uint32_t chunk_size = UINT32_MAX;
2832 uint32_t solid_chunk_size = UINT32_MAX;
2833 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2835 for_opt(c, export_options) {
2837 case IMAGEX_BOOT_OPTION:
2838 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2840 case IMAGEX_CHECK_OPTION:
2841 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2842 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2844 case IMAGEX_NOCHECK_OPTION:
2845 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2847 case IMAGEX_COMPRESS_OPTION:
2848 compression_type = get_compression_type(optarg, false);
2849 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2852 case IMAGEX_RECOMPRESS_OPTION:
2853 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2855 case IMAGEX_SOLID_OPTION:
2856 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2858 case IMAGEX_NO_SOLID_SORT_OPTION:
2859 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2861 case IMAGEX_CHUNK_SIZE_OPTION:
2862 chunk_size = parse_chunk_size(optarg);
2863 if (chunk_size == UINT32_MAX)
2866 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2867 solid_chunk_size = parse_chunk_size(optarg);
2868 if (solid_chunk_size == UINT32_MAX)
2871 case IMAGEX_SOLID_COMPRESS_OPTION:
2872 solid_ctype = get_compression_type(optarg, true);
2873 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2876 case IMAGEX_REF_OPTION:
2877 ret = string_set_append(&refglobs, optarg);
2879 goto out_free_refglobs;
2881 case IMAGEX_THREADS_OPTION:
2882 num_threads = parse_num_threads(optarg);
2883 if (num_threads == UINT_MAX)
2886 case IMAGEX_REBUILD_OPTION:
2887 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2889 case IMAGEX_PIPABLE_OPTION:
2890 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2892 case IMAGEX_NOT_PIPABLE_OPTION:
2893 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2895 case IMAGEX_WIMBOOT_OPTION:
2896 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2898 case IMAGEX_UNSAFE_COMPACT_OPTION:
2899 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2907 if (argc < 3 || argc > 5)
2909 src_wimfile = argv[0];
2910 src_image_num_or_name = argv[1];
2911 dest_wimfile = argv[2];
2912 dest_name = (argc >= 4) ? argv[3] : NULL;
2913 dest_desc = (argc >= 5) ? argv[4] : NULL;
2914 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2915 imagex_progress_func, NULL);
2917 goto out_free_refglobs;
2919 wimlib_get_wim_info(src_wim, &src_info);
2921 /* Determine if the destination is an existing file or not. If so, we
2922 * try to append the exported image(s) to it; otherwise, we create a new
2923 * WIM containing the exported image(s). Furthermore, determine if we
2924 * need to write a pipable WIM directly to standard output. */
2926 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2928 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2929 imagex_error("Can't write a non-pipable WIM to "
2930 "standard output! Specify --pipable\n"
2931 " if you want to create a pipable WIM "
2932 "(but read the docs first).");
2934 goto out_free_src_wim;
2937 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2939 dest_wimfile = NULL;
2940 dest_wim_fd = STDOUT_FILENO;
2941 imagex_info_file = stderr;
2942 set_fd_to_binary_mode(dest_wim_fd);
2945 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2947 /* Destination file exists. */
2949 if (!S_ISREG(stbuf.st_mode)) {
2950 imagex_error(T("\"%"TS"\" is not a regular file"),
2953 goto out_free_src_wim;
2955 ret = wimlib_open_wim_with_progress(dest_wimfile,
2957 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2959 imagex_progress_func,
2962 goto out_free_src_wim;
2964 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2965 /* The user specified a compression type, but we're
2966 * exporting to an existing WIM. Make sure the
2967 * specified compression type is the same as the
2968 * compression type of the existing destination WIM. */
2969 struct wimlib_wim_info dest_info;
2971 wimlib_get_wim_info(dest_wim, &dest_info);
2972 if (compression_type != dest_info.compression_type) {
2973 imagex_error(T("Cannot specify a compression type that is "
2974 "not the same as that used in the "
2975 "destination WIM"));
2977 goto out_free_dest_wim;
2983 if (errno != ENOENT) {
2984 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2987 goto out_free_src_wim;
2990 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
2991 imagex_error(T("'--unsafe-compact' is only valid when "
2992 "exporting to an existing WIM file!"));
2994 goto out_free_src_wim;
2997 /* dest_wimfile is not an existing file, so create a new WIM. */
2999 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3000 /* The user did not specify a compression type; default
3001 * to that of the source WIM, unless --solid or
3002 * --wimboot was specified. */
3004 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3005 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3006 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3007 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3009 compression_type = src_info.compression_type;
3011 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3013 goto out_free_src_wim;
3015 wimlib_register_progress_function(dest_wim,
3016 imagex_progress_func, NULL);
3018 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3019 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3021 /* For --wimboot export, use small XPRESS chunks. */
3022 wimlib_set_output_chunk_size(dest_wim, 4096);
3023 } else if (compression_type == src_info.compression_type &&
3024 chunk_size == UINT32_MAX)
3026 /* Use same chunk size if compression type is the same. */
3027 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3031 if (chunk_size != UINT32_MAX) {
3032 /* Set destination chunk size. */
3033 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3035 goto out_free_dest_wim;
3037 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3038 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3040 goto out_free_dest_wim;
3042 if (solid_chunk_size != UINT32_MAX) {
3043 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3045 goto out_free_dest_wim;
3048 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3049 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3051 goto out_free_dest_wim;
3053 if (refglobs.num_strings) {
3054 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3056 goto out_free_dest_wim;
3059 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3060 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3062 imagex_error(T("--boot specified for all-images export, but source WIM "
3063 "has no bootable image."));
3065 goto out_free_dest_wim;
3068 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3069 dest_desc, export_flags);
3071 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3072 do_resource_not_found_warning(src_wimfile,
3073 &src_info, &refglobs);
3074 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3075 do_metadata_not_found_warning(src_wimfile, &src_info);
3077 goto out_free_dest_wim;
3081 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3082 else if (dest_wimfile)
3083 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3084 write_flags, num_threads);
3086 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3087 WIMLIB_ALL_IMAGES, write_flags,
3090 wimlib_free(dest_wim);
3092 wimlib_free(src_wim);
3094 string_set_destroy(&refglobs);
3098 usage(CMD_EXPORT, stderr);
3101 goto out_free_refglobs;
3104 /* Extract files or directories from a WIM image */
3106 imagex_extract(int argc, tchar **argv, int cmd)
3113 const tchar *wimfile;
3114 const tchar *image_num_or_name;
3115 tchar *dest_dir = T(".");
3116 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3117 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3118 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3119 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3121 STRING_SET(refglobs);
3123 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3125 for_opt(c, extract_options) {
3127 case IMAGEX_CHECK_OPTION:
3128 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3130 case IMAGEX_VERBOSE_OPTION:
3131 /* No longer does anything. */
3133 case IMAGEX_REF_OPTION:
3134 ret = string_set_append(&refglobs, optarg);
3136 goto out_free_refglobs;
3138 case IMAGEX_UNIX_DATA_OPTION:
3139 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3141 case IMAGEX_NO_ACLS_OPTION:
3142 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3144 case IMAGEX_STRICT_ACLS_OPTION:
3145 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3147 case IMAGEX_NO_ATTRIBUTES_OPTION:
3148 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3150 case IMAGEX_DEST_DIR_OPTION:
3153 case IMAGEX_TO_STDOUT_OPTION:
3154 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3155 imagex_info_file = stderr;
3156 imagex_be_quiet = true;
3157 set_fd_to_binary_mode(STDOUT_FILENO);
3159 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3160 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3161 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3163 case IMAGEX_NO_GLOBS_OPTION:
3164 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3166 case IMAGEX_NULLGLOB_OPTION:
3167 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3169 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3170 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3172 case IMAGEX_WIMBOOT_OPTION:
3173 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3175 case IMAGEX_COMPACT_OPTION:
3176 ret = set_compact_mode(optarg, &extract_flags);
3178 goto out_free_refglobs;
3190 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3191 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3193 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3198 image_num_or_name = argv[1];
3203 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3204 imagex_progress_func, NULL);
3206 goto out_free_refglobs;
3208 image = wimlib_resolve_image(wim, image_num_or_name);
3209 ret = verify_image_exists_and_is_single(image,
3213 goto out_wimlib_free;
3215 if (refglobs.num_strings) {
3216 ret = wim_reference_globs(wim, &refglobs, open_flags);
3218 goto out_wimlib_free;
3224 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3227 while (argc != 0 && ret == 0) {
3231 num_paths < argc && argv[num_paths][0] != T('@');
3236 ret = wimlib_extract_paths(wim, image, dest_dir,
3237 (const tchar **)argv,
3239 extract_flags | notlist_extract_flags);
3243 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3252 if (!imagex_be_quiet)
3253 imagex_printf(T("Done extracting files.\n"));
3254 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3255 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3256 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3257 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3258 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3261 T("Note: You can use the '--nullglob' "
3262 "option to ignore missing files.\n"));
3264 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3265 "files and directories\n"
3266 " are in the WIM image.\n"),
3267 get_cmd_string(CMD_DIR, false));
3268 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3269 struct wimlib_wim_info info;
3271 wimlib_get_wim_info(wim, &info);
3272 do_resource_not_found_warning(wimfile, &info, &refglobs);
3273 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3274 struct wimlib_wim_info info;
3276 wimlib_get_wim_info(wim, &info);
3277 do_metadata_not_found_warning(wimfile, &info);
3282 string_set_destroy(&refglobs);
3286 usage(CMD_EXTRACT, stderr);
3289 goto out_free_refglobs;
3292 /* Prints information about a WIM file; also can mark an image as bootable,
3293 * change the name of an image, or change the description of an image. */
3295 imagex_info(int argc, tchar **argv, int cmd)
3300 bool nocheck = false;
3301 bool header = false;
3304 bool short_header = true;
3305 const tchar *xml_out_file = NULL;
3306 const tchar *wimfile;
3307 const tchar *image_num_or_name;
3308 STRING_SET(image_properties);
3313 struct wimlib_wim_info info;
3315 for_opt(c, info_options) {
3317 case IMAGEX_BOOT_OPTION:
3320 case IMAGEX_CHECK_OPTION:
3323 case IMAGEX_NOCHECK_OPTION:
3326 case IMAGEX_HEADER_OPTION:
3328 short_header = false;
3330 case IMAGEX_BLOBS_OPTION:
3332 short_header = false;
3334 case IMAGEX_XML_OPTION:
3336 short_header = false;
3338 case IMAGEX_EXTRACT_XML_OPTION:
3339 xml_out_file = optarg;
3340 short_header = false;
3342 case IMAGEX_METADATA_OPTION:
3343 imagex_error(T("The --metadata option has been removed. "
3344 "Use 'wimdir --detail' instead."));
3346 case IMAGEX_IMAGE_PROPERTY_OPTION:
3347 ret = append_image_property_argument(&image_properties);
3358 if (argc < 1 || argc > 4)
3362 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3366 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3367 tsprintf(p, T("NAME=%"TS), argv[2]);
3368 ret = string_set_append(&image_properties, p);
3375 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3376 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3377 ret = string_set_append(&image_properties, p);
3382 if (check && nocheck) {
3383 imagex_error(T("Can't specify both --check and --nocheck"));
3388 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3390 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3391 imagex_progress_func, NULL);
3395 wimlib_get_wim_info(wim, &info);
3397 image = wimlib_resolve_image(wim, image_num_or_name);
3398 ret = WIMLIB_ERR_INVALID_IMAGE;
3399 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3400 verify_image_exists(image, image_num_or_name, wimfile);
3402 imagex_error(T("If you would like to set the boot "
3403 "index to 0, specify image \"0\" with "
3404 "the --boot flag."));
3406 goto out_wimlib_free;
3409 if (boot && info.image_count == 0) {
3410 imagex_error(T("--boot is meaningless on a WIM with no images"));
3411 goto out_wimlib_free;
3414 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3416 imagex_error(T("Cannot specify the --boot flag "
3417 "without specifying a specific "
3418 "image in a multi-image WIM"));
3419 goto out_wimlib_free;
3421 if (image_properties.num_strings) {
3422 imagex_error(T("Can't change image properties without "
3423 "specifying a specific image in a "
3424 "multi-image WIM"));
3425 goto out_wimlib_free;
3429 /* Operations that print information are separated from operations that
3430 * recreate the WIM file. */
3431 if (!image_properties.num_strings && !boot) {
3433 /* Read-only operations */
3435 if (image == WIMLIB_NO_IMAGE) {
3436 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3437 image_num_or_name, wimfile);
3438 goto out_wimlib_free;
3441 if (image == WIMLIB_ALL_IMAGES && short_header)
3442 print_wim_information(wimfile, &info);
3445 wimlib_print_header(wim);
3448 if (info.total_parts != 1) {
3449 tfprintf(stderr, T("Warning: Only showing the blobs "
3450 "for part %d of a %d-part WIM.\n"),
3451 info.part_number, info.total_parts);
3457 ret = wimlib_extract_xml_data(wim, stdout);
3459 goto out_wimlib_free;
3465 fp = tfopen(xml_out_file, T("wb"));
3467 imagex_error_with_errno(T("Failed to open the "
3468 "file \"%"TS"\" for "
3472 goto out_wimlib_free;
3474 ret = wimlib_extract_xml_data(wim, fp);
3476 imagex_error(T("Failed to close the file "
3482 goto out_wimlib_free;
3486 wimlib_print_available_images(wim, image);
3490 /* Modification operations */
3491 bool any_property_changes;
3493 if (image == WIMLIB_ALL_IMAGES)
3496 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3497 imagex_error(T("Cannot change image properties "
3498 "when using image 0"));
3500 goto out_wimlib_free;
3504 if (image == info.boot_index) {
3505 imagex_printf(T("Image %d is already marked as "
3506 "bootable.\n"), image);
3509 imagex_printf(T("Marking image %d as bootable.\n"),
3511 info.boot_index = image;
3512 ret = wimlib_set_wim_info(wim, &info,
3513 WIMLIB_CHANGE_BOOT_INDEX);
3515 goto out_wimlib_free;
3519 ret = apply_image_properties(&image_properties, wim, image,
3520 &any_property_changes);
3522 goto out_wimlib_free;
3524 /* Only call wimlib_overwrite() if something actually needs to
3526 if (boot || any_property_changes ||
3527 (check && !info.has_integrity_table) ||
3528 (nocheck && info.has_integrity_table))
3530 int write_flags = 0;
3533 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3535 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3536 ret = wimlib_overwrite(wim, write_flags, 1);
3538 imagex_printf(T("The file \"%"TS"\" was not modified "
3539 "because nothing needed to be done.\n"),
3547 string_set_destroy(&image_properties);
3551 usage(CMD_INFO, stderr);
3557 /* Join split WIMs into one part WIM */
3559 imagex_join(int argc, tchar **argv, int cmd)
3562 int swm_open_flags = 0;
3563 int wim_write_flags = 0;
3564 const tchar *output_path;
3567 for_opt(c, join_options) {
3569 case IMAGEX_CHECK_OPTION:
3570 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3571 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3581 imagex_error(T("Must specify one or more split WIM (.swm) "
3585 output_path = argv[0];
3586 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3591 imagex_progress_func,
3597 usage(CMD_JOIN, stderr);
3602 #if WIM_MOUNTING_SUPPORTED
3604 /* Mounts a WIM image. */
3606 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3609 int mount_flags = 0;
3611 const tchar *staging_dir = NULL;
3612 const tchar *wimfile;
3615 struct wimlib_wim_info info;
3619 STRING_SET(refglobs);
3621 if (cmd == CMD_MOUNTRW) {
3622 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3623 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3626 for_opt(c, mount_options) {
3628 case IMAGEX_ALLOW_OTHER_OPTION:
3629 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3631 case IMAGEX_CHECK_OPTION:
3632 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3634 case IMAGEX_DEBUG_OPTION:
3635 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3637 case IMAGEX_STREAMS_INTERFACE_OPTION:
3638 if (!tstrcasecmp(optarg, T("none")))
3639 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3640 else if (!tstrcasecmp(optarg, T("xattr")))
3641 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3642 else if (!tstrcasecmp(optarg, T("windows")))
3643 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3645 imagex_error(T("Unknown stream interface \"%"TS"\""),
3650 case IMAGEX_REF_OPTION:
3651 ret = string_set_append(&refglobs, optarg);
3653 goto out_free_refglobs;
3655 case IMAGEX_STAGING_DIR_OPTION:
3656 staging_dir = optarg;
3658 case IMAGEX_UNIX_DATA_OPTION:
3659 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3667 if (argc != 2 && argc != 3)
3672 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3673 imagex_progress_func, NULL);
3675 goto out_free_refglobs;
3677 wimlib_get_wim_info(wim, &info);
3680 /* Image explicitly specified. */
3681 image = wimlib_resolve_image(wim, argv[1]);
3683 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3687 /* No image specified; default to image 1, but only if the WIM
3688 * contains exactly one image. */
3690 if (info.image_count != 1) {
3691 imagex_error(T("\"%"TS"\" contains %d images; Please "
3692 "select one."), wimfile, info.image_count);
3700 if (refglobs.num_strings) {
3701 ret = wim_reference_globs(wim, &refglobs, open_flags);
3706 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3708 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3709 do_metadata_not_found_warning(wimfile, &info);
3711 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3713 image, wimfile, dir);
3719 string_set_destroy(&refglobs);
3725 goto out_free_refglobs;
3727 #endif /* WIM_MOUNTING_SUPPORTED */
3729 /* Rebuild a WIM file */
3731 imagex_optimize(int argc, tchar **argv, int cmd)
3734 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3735 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3736 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3737 uint32_t chunk_size = UINT32_MAX;
3738 uint32_t solid_chunk_size = UINT32_MAX;
3739 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3742 const tchar *wimfile;
3745 unsigned num_threads = 0;
3747 for_opt(c, optimize_options) {
3749 case IMAGEX_CHECK_OPTION:
3750 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3751 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3753 case IMAGEX_NOCHECK_OPTION:
3754 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3756 case IMAGEX_COMPRESS_OPTION:
3757 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3758 compression_type = get_compression_type(optarg, false);
3759 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3762 case IMAGEX_RECOMPRESS_OPTION:
3763 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3765 case IMAGEX_CHUNK_SIZE_OPTION:
3766 chunk_size = parse_chunk_size(optarg);
3767 if (chunk_size == UINT32_MAX)
3770 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3771 solid_chunk_size = parse_chunk_size(optarg);
3772 if (solid_chunk_size == UINT32_MAX)
3775 case IMAGEX_SOLID_COMPRESS_OPTION:
3776 solid_ctype = get_compression_type(optarg, true);
3777 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3780 case IMAGEX_SOLID_OPTION:
3781 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3782 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3784 case IMAGEX_NO_SOLID_SORT_OPTION:
3785 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3787 case IMAGEX_THREADS_OPTION:
3788 num_threads = parse_num_threads(optarg);
3789 if (num_threads == UINT_MAX)
3792 case IMAGEX_PIPABLE_OPTION:
3793 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3795 case IMAGEX_NOT_PIPABLE_OPTION:
3796 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3798 case IMAGEX_UNSAFE_COMPACT_OPTION:
3799 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3813 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3814 imagex_progress_func, NULL);
3818 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3819 /* Change compression type. */
3820 ret = wimlib_set_output_compression_type(wim, compression_type);
3822 goto out_wimlib_free;
3825 if (chunk_size != UINT32_MAX) {
3826 /* Change chunk size. */
3827 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3829 goto out_wimlib_free;
3831 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3832 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3834 goto out_wimlib_free;
3836 if (solid_chunk_size != UINT32_MAX) {
3837 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3839 goto out_wimlib_free;
3842 old_size = file_get_size(wimfile);
3843 tprintf(T("\"%"TS"\" original size: "), wimfile);
3845 tputs(T("Unknown"));
3847 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3849 ret = wimlib_overwrite(wim, write_flags, num_threads);
3851 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3852 goto out_wimlib_free;
3855 new_size = file_get_size(wimfile);
3856 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3858 tputs(T("Unknown"));
3860 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3862 tfputs(T("Space saved: "), stdout);
3863 if (new_size != -1 && old_size != -1) {
3864 tprintf(T("%lld KiB\n"),
3865 ((long long)old_size - (long long)new_size) >> 10);
3867 tputs(T("Unknown"));
3876 usage(CMD_OPTIMIZE, stderr);
3882 /* Split a WIM into a spanned set */
3884 imagex_split(int argc, tchar **argv, int cmd)
3888 int write_flags = 0;
3889 unsigned long part_size;
3894 for_opt(c, split_options) {
3896 case IMAGEX_CHECK_OPTION:
3897 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3898 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3910 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3911 if (tmp == argv[2] || *tmp) {
3912 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3913 imagex_error(T("The part size must be an integer or "
3914 "floating-point number of megabytes."));
3917 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3918 imagex_progress_func, NULL);
3922 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3928 usage(CMD_SPLIT, stderr);
3934 #if WIM_MOUNTING_SUPPORTED
3935 /* Unmounts a mounted WIM image. */
3937 imagex_unmount(int argc, tchar **argv, int cmd)
3940 int unmount_flags = 0;
3943 for_opt(c, unmount_options) {
3945 case IMAGEX_COMMIT_OPTION:
3946 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3948 case IMAGEX_CHECK_OPTION:
3949 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3951 case IMAGEX_REBUILD_OPTION:
3952 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3954 case IMAGEX_LAZY_OPTION:
3955 case IMAGEX_FORCE_OPTION:
3956 /* Now, unmount is lazy by default. However, committing
3957 * the image will fail with
3958 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3959 * file descriptors on the WIM image. The
3960 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3961 * descriptors to be closed. */
3962 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3964 case IMAGEX_NEW_IMAGE_OPTION:
3965 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3976 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3977 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3978 imagex_error(T("--new-image is meaningless "
3979 "without --commit also specified!"));
3984 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3985 imagex_progress_func, NULL);
3987 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3988 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3990 "\tNote: Use --commit --force to force changes "
3991 "to be committed, regardless\n"
3992 "\t of open files.\n"));
3999 usage(CMD_UNMOUNT, stderr);
4004 #endif /* WIM_MOUNTING_SUPPORTED */
4007 * Add, delete, or rename files in a WIM image.
4010 imagex_update(int argc, tchar **argv, int cmd)
4012 const tchar *wimfile;
4016 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4017 int write_flags = 0;
4018 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4019 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4020 WIMLIB_ADD_FLAG_VERBOSE |
4021 WIMLIB_ADD_FLAG_WINCONFIG;
4022 int default_delete_flags = 0;
4023 unsigned num_threads = 0;
4025 tchar *cmd_file_contents;
4026 size_t cmd_file_nchars;
4027 struct wimlib_update_command *cmds;
4029 tchar *command_str = NULL;
4030 tchar *config_file = NULL;
4031 tchar *wimboot_config = NULL;
4033 for_opt(c, update_options) {
4035 /* Generic or write options */
4036 case IMAGEX_THREADS_OPTION:
4037 num_threads = parse_num_threads(optarg);
4038 if (num_threads == UINT_MAX)
4041 case IMAGEX_CHECK_OPTION:
4042 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4043 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4045 case IMAGEX_REBUILD_OPTION:
4046 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4048 case IMAGEX_COMMAND_OPTION:
4050 imagex_error(T("--command may only be specified "
4051 "one time. Please provide\n"
4052 " the update commands "
4053 "on standard input instead."));
4056 command_str = tstrdup(optarg);
4058 imagex_error(T("Out of memory!"));
4062 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4063 wimboot_config = optarg;
4065 /* Default delete options */
4066 case IMAGEX_FORCE_OPTION:
4067 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4069 case IMAGEX_RECURSIVE_OPTION:
4070 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4073 /* Global add option */
4074 case IMAGEX_CONFIG_OPTION:
4075 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4076 config_file = optarg;
4079 /* Default add options */
4080 case IMAGEX_VERBOSE_OPTION:
4081 /* No longer does anything. */
4083 case IMAGEX_DEREFERENCE_OPTION:
4084 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4086 case IMAGEX_UNIX_DATA_OPTION:
4087 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4089 case IMAGEX_NO_ACLS_OPTION:
4090 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4092 case IMAGEX_STRICT_ACLS_OPTION:
4093 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4095 case IMAGEX_NO_REPLACE_OPTION:
4096 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4098 case IMAGEX_UNSAFE_COMPACT_OPTION:
4099 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4108 if (argc != 1 && argc != 2)
4112 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4113 imagex_progress_func, NULL);
4115 goto out_free_command_str;
4118 /* Image explicitly specified. */
4119 image = wimlib_resolve_image(wim, argv[1]);
4120 ret = verify_image_exists_and_is_single(image, argv[1],
4123 goto out_wimlib_free;
4125 /* No image specified; default to image 1, but only if the WIM
4126 * contains exactly one image. */
4127 struct wimlib_wim_info info;
4129 wimlib_get_wim_info(wim, &info);
4130 if (info.image_count != 1) {
4131 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4132 wimfile, info.image_count);
4139 /* Read update commands from standard input, or the command string if
4142 cmd_file_contents = NULL;
4143 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4147 goto out_free_cmd_file_contents;
4149 } else if (!wimboot_config) {
4150 if (isatty(STDIN_FILENO)) {
4151 tputs(T("Reading update commands from standard input..."));
4152 recommend_man_page(CMD_UPDATE, stdout);
4154 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4155 if (!cmd_file_contents) {
4157 goto out_wimlib_free;
4160 /* Parse the update commands */
4161 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4165 goto out_free_cmd_file_contents;
4168 cmd_file_contents = NULL;
4173 /* Set default flags and capture config on the update commands */
4174 for (size_t i = 0; i < num_cmds; i++) {
4175 switch (cmds[i].op) {
4176 case WIMLIB_UPDATE_OP_ADD:
4177 cmds[i].add.add_flags |= default_add_flags;
4178 cmds[i].add.config_file = config_file;
4180 case WIMLIB_UPDATE_OP_DELETE:
4181 cmds[i].delete_.delete_flags |= default_delete_flags;
4188 /* Execute the update commands */
4189 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4193 if (wimboot_config) {
4194 /* --wimboot-config=FILE is short for an
4195 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4197 struct wimlib_update_command cmd;
4199 cmd.op = WIMLIB_UPDATE_OP_ADD;
4200 cmd.add.fs_source_path = wimboot_config;
4201 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4202 cmd.add.config_file = NULL;
4203 cmd.add.add_flags = 0;
4205 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4210 /* Overwrite the updated WIM */
4211 ret = wimlib_overwrite(wim, write_flags, num_threads);
4214 out_free_cmd_file_contents:
4215 free(cmd_file_contents);
4218 out_free_command_str:
4223 usage(CMD_UPDATE, stderr);
4226 goto out_free_command_str;
4229 /* Verify a WIM file. */
4231 imagex_verify(int argc, tchar **argv, int cmd)
4234 const tchar *wimfile;
4236 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4237 int verify_flags = 0;
4238 STRING_SET(refglobs);
4241 for_opt(c, verify_options) {
4243 case IMAGEX_REF_OPTION:
4244 ret = string_set_append(&refglobs, optarg);
4246 goto out_free_refglobs;
4248 case IMAGEX_NOCHECK_OPTION:
4249 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4261 imagex_error(T("Must specify a WIM file!"));
4263 imagex_error(T("At most one WIM file can be specified!"));
4269 ret = wimlib_open_wim_with_progress(wimfile,
4272 imagex_progress_func,
4275 goto out_free_refglobs;
4277 ret = wim_reference_globs(wim, &refglobs, open_flags);
4279 goto out_wimlib_free;
4281 ret = wimlib_verify_wim(wim, verify_flags);
4283 tputc(T('\n'), stderr);
4284 imagex_error(T("\"%"TS"\" failed verification!"),
4286 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4287 refglobs.num_strings == 0)
4289 imagex_printf(T("Note: if this WIM file is not standalone, "
4290 "use the --ref option to specify the other parts.\n"));
4293 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4300 string_set_destroy(&refglobs);
4304 usage(CMD_VERIFY, stderr);
4306 goto out_free_refglobs;
4309 struct imagex_command {
4311 int (*func)(int argc, tchar **argv, int cmd);
4314 static const struct imagex_command imagex_commands[] = {
4315 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4316 [CMD_APPLY] = {T("apply"), imagex_apply},
4317 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4318 [CMD_DELETE] = {T("delete"), imagex_delete},
4319 [CMD_DIR ] = {T("dir"), imagex_dir},
4320 [CMD_EXPORT] = {T("export"), imagex_export},
4321 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4322 [CMD_INFO] = {T("info"), imagex_info},
4323 [CMD_JOIN] = {T("join"), imagex_join},
4324 #if WIM_MOUNTING_SUPPORTED
4325 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4326 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4328 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4329 [CMD_SPLIT] = {T("split"), imagex_split},
4330 #if WIM_MOUNTING_SUPPORTED
4331 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4333 [CMD_UPDATE] = {T("update"), imagex_update},
4334 [CMD_VERIFY] = {T("verify"), imagex_verify},
4339 /* Can be a directory or source list file. But source list file is probably
4340 * a rare use case, so just say directory. */
4341 # define SOURCE_STR T("DIRECTORY")
4343 /* Can only be a directory */
4344 # define TARGET_STR T("DIRECTORY")
4347 /* Can be a directory, NTFS volume, or source list file. */
4348 # define SOURCE_STR T("SOURCE")
4350 /* Can be a directory or NTFS volume. */
4351 # define TARGET_STR T("TARGET")
4355 static const tchar *usage_strings[] = {
4358 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4359 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4360 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4361 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4362 " [--wimboot] [--unix-data] [--dereference] [--snapshot]\n"
4366 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4367 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4368 " [--no-attributes] [--rpfix] [--norpfix]\n"
4369 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4370 " [--compact=FORMAT]\n"
4374 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4375 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4376 " [--config=FILE] [--threads=NUM_THREADS]\n"
4377 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4378 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4379 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4384 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4388 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4392 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4393 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4394 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4395 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4396 " [--wimboot] [--solid]\n"
4400 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4401 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4402 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4403 " [--no-attributes] [--include-invalid-names]\n"
4404 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4408 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4409 " [--boot] [--check] [--nocheck] [--xml]\n"
4410 " [--extract-xml FILE] [--header] [--blobs]\n"
4411 " [--image-property NAME=VALUE]\n"
4415 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4417 #if WIM_MOUNTING_SUPPORTED
4420 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4421 " [--check] [--streams-interface=INTERFACE]\n"
4422 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4426 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4427 " [--check] [--streams-interface=INTERFACE]\n"
4428 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4434 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4435 " [--check] [--nocheck] [--solid]\n"
4440 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4442 #if WIM_MOUNTING_SUPPORTED
4445 " %"TS" DIRECTORY\n"
4446 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4451 " %"TS" WIMFILE [IMAGE]\n"
4452 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4453 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4454 " [--command=STRING] [--wimboot-config=FILE]\n"
4459 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4463 static const tchar *invocation_name;
4464 static int invocation_cmd = CMD_NONE;
4466 static const tchar *get_cmd_string(int cmd, bool nospace)
4468 static tchar buf[50];
4469 if (cmd == CMD_NONE) {
4470 return T("wimlib-imagex");
4471 } else if (invocation_cmd != CMD_NONE) {
4472 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4474 const tchar *format;
4477 format = T("%"TS"-%"TS"");
4479 format = T("%"TS" %"TS"");
4480 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4488 static const tchar *s =
4490 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4491 "Copyright (C) 2012-2016 Eric Biggers\n"
4492 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4493 "This is free software: you are free to change and redistribute it.\n"
4494 "There is NO WARRANTY, to the extent permitted by law.\n"
4496 "Report bugs to "PACKAGE_BUGREPORT".\n"
4503 help_or_version(int argc, tchar **argv, int cmd)
4508 for (i = 1; i < argc; i++) {
4510 if (p[0] == T('-') && p[1] == T('-')) {
4512 if (!tstrcmp(p, T("help"))) {
4513 if (cmd == CMD_NONE)
4518 } else if (!tstrcmp(p, T("version"))) {
4527 print_usage_string(int cmd, FILE *fp)
4529 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4533 recommend_man_page(int cmd, FILE *fp)
4535 const tchar *format_str;
4537 format_str = T("Some uncommon options are not listed;\n"
4538 "See %"TS".pdf in the doc directory for more details.\n");
4540 format_str = T("Some uncommon options are not listed;\n"
4541 "Try `man %"TS"' for more details.\n");
4543 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4547 usage(int cmd, FILE *fp)
4549 tfprintf(fp, T("Usage:\n"));
4550 print_usage_string(cmd, fp);
4551 tfprintf(fp, T("\n"));
4552 recommend_man_page(cmd, fp);
4558 tfprintf(fp, T("Usage:\n"));
4559 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4560 print_usage_string(cmd, fp);
4561 tfprintf(fp, T("\n"));
4563 static const tchar *extra =
4566 " %"TS" --version\n"
4569 tfprintf(fp, extra, invocation_name, invocation_name);
4571 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4572 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4573 "For some commands IMAGE may be \"all\".\n"
4575 recommend_man_page(CMD_NONE, fp);
4578 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4579 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4580 * something else), while on Windows the command arguments will be UTF-16LE
4581 * encoded 'wchar_t' strings. */
4584 wmain(int argc, wchar_t **argv, wchar_t **envp)
4586 main(int argc, char **argv)
4593 imagex_info_file = stdout;
4594 invocation_name = tbasename(argv[0]);
4597 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4598 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4602 setlocale(LC_ALL, "");
4603 codeset = nl_langinfo(CODESET);
4604 if (!strstr(codeset, "UTF-8") &&
4605 !strstr(codeset, "UTF8") &&
4606 !strstr(codeset, "utf-8") &&
4607 !strstr(codeset, "utf8"))
4610 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4611 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4612 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4613 " to any value to force wimlib to use UTF-8.\n",
4619 #endif /* !__WIN32__ */
4622 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4623 if (igcase != NULL) {
4624 if (!tstrcmp(igcase, T("no")) ||
4625 !tstrcmp(igcase, T("0")))
4626 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4627 else if (!tstrcmp(igcase, T("yes")) ||
4628 !tstrcmp(igcase, T("1")))
4629 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4632 "WARNING: Ignoring unknown setting of "
4633 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4638 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4640 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4641 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4642 for (int i = 0; i < CMD_MAX; i++) {
4643 if (!tstrcmp(invocation_name + 3,
4644 imagex_commands[i].name))
4653 /* Unless already known from the invocation name, determine which
4654 * command was specified. */
4655 if (cmd == CMD_NONE) {
4657 imagex_error(T("No command specified!\n"));
4661 for (int i = 0; i < CMD_MAX; i++) {
4662 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4667 if (cmd != CMD_NONE) {
4673 /* Handle --help and --version. --help can be either for the program as
4674 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4675 * CMD_NONE). Note: help_or_version() will not return if a --help or
4676 * --version argument was found. */
4677 help_or_version(argc, argv, cmd);
4679 /* Bail if a valid command was not specified. */
4680 if (cmd == CMD_NONE) {
4681 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4686 /* Enable warning and error messages in wimlib to be more user-friendly.
4688 wimlib_set_print_errors(true);
4690 /* Initialize wimlib. */
4691 ret = wimlib_global_init(init_flags);
4693 goto out_check_status;
4695 /* Call the command handler function. */
4696 ret = imagex_commands[cmd].func(argc, argv, cmd);
4698 /* Check for error writing to standard output, especially since for some
4699 * commands, writing to standard output is part of the program's actual
4700 * behavior and not just for informational purposes. */
4701 if (ferror(stdout) || fclose(stdout)) {
4702 imagex_error_with_errno(T("error writing to standard output"));
4707 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4708 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4709 * error code from which an error message can be printed. */
4711 imagex_error(T("Exiting with error code %d:\n"
4713 wimlib_get_error_string(ret));
4714 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4715 imagex_error_with_errno(T("errno"));
4717 /* Make wimlib free any resources it's holding (although this is not
4718 * strictly necessary because the process is ending anyway). */
4719 wimlib_global_cleanup();