4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012-2023 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)
61 /* NetBSD is missing getopt_long_only() but has getopt_long() */
62 #ifndef HAVE_GETOPT_LONG_ONLY
63 # define getopt_long_only getopt_long
67 /* Don't confuse the user by presenting the mounting commands on Windows when
68 * they will never work. However on UNIX-like systems we always present them,
69 * even if WITH_FUSE is not defined at this point, as to not tie the build of
70 * wimlib-imagex to a specific build of wimlib. */
72 # define WIM_MOUNTING_SUPPORTED 0
74 # define WIM_MOUNTING_SUPPORTED 1
77 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
80 is_any_path_separator(tchar c)
82 return c == T('/') || c == T('\\');
85 /* Like basename(), but handles both forward and backwards slashes. */
87 tbasename(tchar *path)
89 tchar *p = tstrchr(path, T('\0'));
94 if (!is_any_path_separator(*--p))
102 if (is_any_path_separator(*--p))
107 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
121 #if WIM_MOUNTING_SUPPORTED
127 #if WIM_MOUNTING_SUPPORTED
135 static void usage(int cmd, FILE *fp);
136 static void usage_all(FILE *fp);
137 static void recommend_man_page(int cmd, FILE *fp);
138 static const tchar *get_cmd_string(int cmd, bool only_short_form);
140 static FILE *imagex_info_file;
142 #define imagex_printf(format, ...) \
143 if (imagex_info_file) \
144 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
146 static void imagex_suppress_output(void)
148 imagex_info_file = NULL;
151 static void imagex_output_to_stderr(void)
153 if (imagex_info_file)
154 imagex_info_file = stderr;
157 static void imagex_flush_output(void)
159 if (imagex_info_file)
160 fflush(imagex_info_file);
164 IMAGEX_ALLOW_OTHER_OPTION,
168 IMAGEX_CHUNK_SIZE_OPTION,
169 IMAGEX_COMMAND_OPTION,
170 IMAGEX_COMMIT_OPTION,
171 IMAGEX_COMPACT_OPTION,
172 IMAGEX_COMPRESS_OPTION,
173 IMAGEX_CONFIG_OPTION,
174 IMAGEX_CREATE_OPTION,
176 IMAGEX_DELTA_FROM_OPTION,
177 IMAGEX_DEREFERENCE_OPTION,
178 IMAGEX_DEST_DIR_OPTION,
179 IMAGEX_DETAILED_OPTION,
180 IMAGEX_EXTRACT_XML_OPTION,
183 IMAGEX_HEADER_OPTION,
184 IMAGEX_IMAGE_PROPERTY_OPTION,
185 IMAGEX_INCLUDE_INTEGRITY_OPTION,
186 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
188 IMAGEX_METADATA_OPTION,
189 IMAGEX_NEW_IMAGE_OPTION,
190 IMAGEX_NOCHECK_OPTION,
191 IMAGEX_NORPFIX_OPTION,
192 IMAGEX_NOT_PIPABLE_OPTION,
193 IMAGEX_NO_ACLS_OPTION,
194 IMAGEX_NO_ATTRIBUTES_OPTION,
195 IMAGEX_NO_GLOBS_OPTION,
196 IMAGEX_NO_REPLACE_OPTION,
197 IMAGEX_NO_SOLID_SORT_OPTION,
198 IMAGEX_NULLGLOB_OPTION,
199 IMAGEX_ONE_FILE_ONLY_OPTION,
201 IMAGEX_PIPABLE_OPTION,
202 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
203 IMAGEX_REBUILD_OPTION,
204 IMAGEX_RECOMPRESS_OPTION,
205 IMAGEX_RECOVER_DATA_OPTION,
206 IMAGEX_RECURSIVE_OPTION,
209 IMAGEX_SNAPSHOT_OPTION,
211 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
212 IMAGEX_SOLID_COMPRESS_OPTION,
214 IMAGEX_SOURCE_LIST_OPTION,
215 IMAGEX_STAGING_DIR_OPTION,
216 IMAGEX_STREAMS_INTERFACE_OPTION,
217 IMAGEX_STRICT_ACLS_OPTION,
218 IMAGEX_THREADS_OPTION,
219 IMAGEX_TO_STDOUT_OPTION,
220 IMAGEX_UNIX_DATA_OPTION,
221 IMAGEX_UNSAFE_COMPACT_OPTION,
222 IMAGEX_UPDATE_OF_OPTION,
223 IMAGEX_VERBOSE_OPTION,
224 IMAGEX_WIMBOOT_CONFIG_OPTION,
225 IMAGEX_WIMBOOT_OPTION,
229 static const struct option apply_options[] = {
230 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
231 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
232 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
233 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
234 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
235 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
236 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
237 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
238 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
239 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
240 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
241 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
242 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
243 {T("recover-data"), no_argument, NULL, IMAGEX_RECOVER_DATA_OPTION},
247 static const struct option capture_or_append_options[] = {
248 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
249 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
250 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
251 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
252 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
253 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
254 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
255 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
256 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
257 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
258 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
259 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
260 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
261 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
262 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
263 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
264 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
265 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
266 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
267 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
268 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
269 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
270 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
271 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
272 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
273 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
274 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
275 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
276 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
277 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
278 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
279 {T("snapshot"), no_argument, NULL, IMAGEX_SNAPSHOT_OPTION},
280 {T("create"), no_argument, NULL, IMAGEX_CREATE_OPTION},
284 static const struct option delete_options[] = {
285 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
286 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
287 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
288 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
292 static const struct option dir_options[] = {
293 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
294 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
295 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
296 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
300 static const struct option export_options[] = {
301 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
302 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
303 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
304 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
305 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
306 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
307 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
308 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
309 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
310 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
311 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
312 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
313 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
314 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
315 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
316 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
317 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
318 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
319 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
323 static const struct option extract_options[] = {
324 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
325 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
326 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
327 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
328 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
329 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
330 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
331 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
332 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
333 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
334 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
335 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
336 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
337 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
338 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
339 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
340 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
341 {T("recover-data"), no_argument, NULL, IMAGEX_RECOVER_DATA_OPTION},
345 static const struct option info_options[] = {
346 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
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("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
351 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
352 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
353 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
354 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
355 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
356 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
360 static const struct option join_options[] = {
361 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
362 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
366 #if WIM_MOUNTING_SUPPORTED
367 static const struct option mount_options[] = {
368 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
369 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
370 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
371 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
372 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
373 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
374 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
379 static const struct option optimize_options[] = {
380 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
381 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
382 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
383 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
384 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
385 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
386 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
387 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
388 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
389 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
390 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
391 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
392 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
393 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
394 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
398 static const struct option split_options[] = {
399 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
400 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
404 #if WIM_MOUNTING_SUPPORTED
405 static const struct option unmount_options[] = {
406 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
407 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
408 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
409 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
410 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
411 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
416 static const struct option update_options[] = {
417 /* Careful: some of the options here set the defaults for update
418 * commands, but the flags given to an actual update command (and not to
419 * `imagex update' itself are also handled in
420 * update_command_add_option(). */
421 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
422 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
423 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
424 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
425 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
426 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
428 /* Default delete options */
429 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
430 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
432 /* Global add option */
433 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
435 /* Default add options */
436 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
437 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
438 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
439 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
440 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
441 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
442 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
443 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
448 static const struct option verify_options[] = {
449 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
450 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
456 # define _format_attribute(type, format_str, args_start) \
457 __attribute__((format(type, format_str, args_start)))
459 # define _format_attribute(type, format_str, args_start)
462 /* Print formatted error message to stderr. */
463 static void _format_attribute(printf, 1, 2)
464 imagex_error(const tchar *format, ...)
467 va_start(va, format);
468 tfputs(T("ERROR: "), stderr);
469 tvfprintf(stderr, format, va);
470 tputc(T('\n'), stderr);
474 /* Print formatted error message to stderr. */
475 static void _format_attribute(printf, 1, 2)
476 imagex_error_with_errno(const tchar *format, ...)
478 int errno_save = errno;
480 va_start(va, format);
481 tfputs(T("ERROR: "), stderr);
482 tvfprintf(stderr, format, va);
483 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
488 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
490 if (image == WIMLIB_NO_IMAGE) {
491 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
492 " Please specify a 1-based image index or "
493 "image name. To list the images\n"
494 " contained in the WIM archive, run\n"
496 " %"TS" \"%"TS"\"\n"),
497 image_name, wim_name,
498 get_cmd_string(CMD_INFO, false), wim_name);
499 return WIMLIB_ERR_INVALID_IMAGE;
505 verify_image_is_single(int image)
507 if (image == WIMLIB_ALL_IMAGES) {
508 imagex_error(T("Cannot specify all images for this action!"));
509 return WIMLIB_ERR_INVALID_IMAGE;
515 verify_image_exists_and_is_single(int image, const tchar *image_name,
516 const tchar *wim_name)
519 ret = verify_image_exists(image, image_name, wim_name);
521 ret = verify_image_is_single(image);
526 print_available_compression_types(FILE *fp)
528 static const tchar * const s =
530 "Available compression types:\n"
533 " xpress (alias: \"fast\")\n"
534 " lzx (alias: \"maximum\") (default for capture)\n"
535 " lzms (alias: \"recovery\")\n"
541 /* Parse the argument to --compress or --solid-compress */
543 get_compression_type(tchar *optarg, bool solid)
546 unsigned int compression_level = 0;
549 plevel = tstrchr(optarg, T(':'));
555 ultmp = tstrtoul(plevel, &ptmp, 10);
556 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
557 imagex_error(T("Compression level must be a positive integer! "
558 "e.g. --compress=lzx:80"));
559 return WIMLIB_COMPRESSION_TYPE_INVALID;
561 compression_level = ultmp;
564 if (!tstrcasecmp(optarg, T("maximum")) ||
565 !tstrcasecmp(optarg, T("lzx")) ||
566 !tstrcasecmp(optarg, T("max"))) {
567 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
568 } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
569 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
570 } else if (!tstrcasecmp(optarg, T("recovery"))) {
574 "Warning: use of '--compress=recovery' is discouraged because it behaves\n"
575 " differently from DISM. Instead, you typically want to use '--solid' to\n"
576 " create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
577 " /compress:recovery. But if you really want *non-solid* LZMS compression,\n"
578 " then you may suppress this warning by specifying '--compress=lzms' instead\n"
579 " of '--compress=recovery'.\n"));
581 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
582 } else if (!tstrcasecmp(optarg, T("lzms"))) {
583 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
584 } else if (!tstrcasecmp(optarg, T("none"))) {
585 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
587 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
588 print_available_compression_types(stderr);
589 return WIMLIB_COMPRESSION_TYPE_INVALID;
592 if (compression_level != 0)
593 wimlib_set_default_compression_level(ctype, compression_level);
597 /* Parse the argument to --compact */
599 set_compact_mode(const tchar *arg, int *extract_flags)
602 if (!tstrcasecmp(arg, T("xpress4k")))
603 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
604 else if (!tstrcasecmp(arg, T("xpress8k")))
605 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
606 else if (!tstrcasecmp(arg, T("xpress16k")))
607 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
608 else if (!tstrcasecmp(arg, T("lzx")))
609 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
612 *extract_flags |= flag;
617 "\"%"TS"\" is not a recognized System Compression format. The options are:"
619 " --compact=xpress4k\n"
620 " --compact=xpress8k\n"
621 " --compact=xpress16k\n"
630 unsigned num_strings;
631 unsigned num_alloc_strings;
634 #define STRING_LIST_INITIALIZER \
635 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
637 #define STRING_LIST(_strings) \
638 struct string_list _strings = STRING_LIST_INITIALIZER
641 string_list_append(struct string_list *list, tchar *glob)
643 unsigned num_alloc_strings = list->num_alloc_strings;
645 if (list->num_strings == num_alloc_strings) {
648 num_alloc_strings += 4;
649 new_strings = realloc(list->strings,
650 sizeof(list->strings[0]) * num_alloc_strings);
652 imagex_error(T("Out of memory!"));
655 list->strings = new_strings;
656 list->num_alloc_strings = num_alloc_strings;
658 list->strings[list->num_strings++] = glob;
663 string_list_destroy(struct string_list *list)
669 wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
671 return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
673 WIMLIB_REF_FLAG_GLOB_ENABLE,
678 append_image_property_argument(struct string_list *image_properties)
680 if (!tstrchr(optarg, '=')) {
681 imagex_error(T("'--image-property' argument "
682 "must be in the form NAME=VALUE"));
685 return string_list_append(image_properties, optarg);
689 apply_image_properties(struct string_list *image_properties,
690 WIMStruct *wim, int image, bool *any_changes_ret)
692 bool any_changes = false;
693 for (unsigned i = 0; i < image_properties->num_strings; i++) {
695 const tchar *current_value;
698 name = image_properties->strings[i];
699 value = tstrchr(name, '=');
702 current_value = wimlib_get_image_property(wim, image, name);
703 if (current_value && !tstrcmp(current_value, value)) {
704 imagex_printf(T("The %"TS" property of image %d "
705 "already has value \"%"TS"\".\n"),
708 imagex_printf(T("Setting the %"TS" property of image "
709 "%d to \"%"TS"\".\n"),
711 ret = wimlib_set_image_property(wim, image, name, value);
718 *any_changes_ret = any_changes;
723 do_resource_not_found_warning(const tchar *wimfile,
724 const struct wimlib_wim_info *info,
725 const struct string_list *refglobs)
727 if (info->total_parts > 1) {
728 if (refglobs->num_strings == 0) {
729 imagex_error(T("\"%"TS"\" is part of a split WIM. "
730 "Use --ref to specify the other parts."),
733 imagex_error(T("Perhaps the '--ref' argument did not "
734 "specify all other parts of the split "
738 imagex_error(T("If this is a delta WIM, use the --ref argument "
739 "to specify the WIM(s) on which it is based."));
744 do_metadata_not_found_warning(const tchar *wimfile,
745 const struct wimlib_wim_info *info)
747 if (info->part_number != 1) {
748 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
749 " You must specify the first part."),
754 /* Returns the size of a file given its name, or -1 if the file does not exist
755 * or its size cannot be determined. */
757 file_get_size(const tchar *filename)
760 if (tstat(filename, &st) == 0)
767 PARSE_STRING_SUCCESS = 0,
768 PARSE_STRING_FAILURE = 1,
769 PARSE_STRING_NONE = 2,
773 * Parses a string token from an array of characters.
775 * Tokens are either whitespace-delimited, or double or single-quoted.
777 * @line_p: Pointer to the pointer to the line of data. Will be updated
778 * to point past the string token iff the return value is
779 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
782 * @len_p: @len_p initially stores the length of the line of data, which may
783 * be 0, and it will be updated to the number of bytes remaining in
784 * the line iff the return value is PARSE_STRING_SUCCESS.
786 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
787 * parsed string token will be returned here.
789 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
790 * PARSE_STRING_FAILURE if the data was invalid due to a missing
791 * closing quote; or PARSE_STRING_NONE if the line ended before the
792 * beginning of a string token was found.
795 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
798 tchar *line = *line_p;
802 /* Skip leading whitespace */
805 return PARSE_STRING_NONE;
806 if (!istspace(*line) && *line != T('\0'))
812 if (quote_char == T('"') || quote_char == T('\'')) {
817 line = tmemchr(line, quote_char, len);
819 imagex_error(T("Missing closing quote: %"TS), fn - 1);
820 return PARSE_STRING_FAILURE;
823 /* Unquoted string. Go until whitespace. Line is terminated
824 * by '\0', so no need to check 'len'. */
828 } while (!istspace(*line) && *line != T('\0'));
835 return PARSE_STRING_SUCCESS;
838 /* Parses a line of data (not an empty line or comment) in the source list file
839 * format. (See the man page for 'wimlib-imagex capture' for details on this
840 * format and the meaning.)
842 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
843 * len == 0. The data in @line will be modified by this function call.
845 * @len: Length of the line of data.
847 * @source: On success, the capture source and target described by the line is
848 * written into this destination. Note that it will contain pointers
849 * to data in the @line array.
851 * Returns true if the line was valid; false otherwise. */
853 parse_source_list_line(tchar *line, size_t len,
854 struct wimlib_capture_source *source)
858 ret = parse_string(&line, &len, &source->fs_source_path);
859 if (ret != PARSE_STRING_SUCCESS)
861 ret = parse_string(&line, &len, &source->wim_target_path);
862 if (ret == PARSE_STRING_NONE)
863 source->wim_target_path = source->fs_source_path;
864 return ret != PARSE_STRING_FAILURE;
867 /* Returns %true if the given line of length @len > 0 is a comment or empty line
868 * in the source list file format. */
870 is_comment_line(const tchar *line, size_t len)
873 if (*line == T('#') || *line == T(';'))
875 if (!istspace(*line) && *line != T('\0'))
885 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
888 tchar *contents = *contents_p;
889 size_t nchars = *nchars_p;
892 for (i = 0; i < nchars; i++)
893 if (contents[i] == T('\n'))
896 /* Handle last line not terminated by a newline */
897 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
898 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
900 imagex_error(T("Out of memory!"));
903 contents[nchars] = T('\n');
904 *contents_p = contents;
912 /* Parses a file in the source list format. (See the man page for
913 * 'wimlib-imagex capture' for details on this format and the meaning.)
915 * @source_list_contents: Contents of the source list file. Note that this
916 * buffer will be modified to save memory allocations,
917 * and cannot be freed until the returned array of
918 * wimlib_capture_source's has also been freed.
920 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
923 * @nsources_ret: On success, the length of the returned array is
926 * Returns: An array of `struct wimlib_capture_source's that can be passed to
927 * the wimlib_add_image_multisource() function to specify how a WIM image is to
929 static struct wimlib_capture_source *
930 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
931 size_t *nsources_ret)
935 struct wimlib_capture_source *sources;
938 nlines = text_file_count_lines(source_list_contents_p,
939 &source_list_nchars);
943 /* Always allocate at least 1 slot, just in case the implementation of
944 * calloc() returns NULL if 0 bytes are requested. */
945 sources = calloc(nlines ?: 1, sizeof(*sources));
947 imagex_error(T("out of memory"));
950 p = *source_list_contents_p;
952 for (i = 0; i < nlines; i++) {
953 /* XXX: Could use rawmemchr() here instead, but it may not be
954 * available on all platforms. */
955 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
956 size_t len = endp - p + 1;
958 if (!is_comment_line(p, len)) {
959 if (!parse_source_list_line(p, len, &sources[j++])) {
971 /* Reads the contents of a file into memory. */
973 file_get_contents(const tchar *filename, size_t *len_ret)
980 if (tstat(filename, &stbuf) != 0) {
981 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
986 fp = tfopen(filename, T("rb"));
988 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
992 buf = malloc(len ? len : 1);
994 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
995 "contents of file \"%"TS"\""), len, filename);
998 if (fread(buf, 1, len, fp) != len) {
999 imagex_error_with_errno(T("Failed to read %zu bytes from the "
1000 "file \"%"TS"\""), len, filename);
1014 /* Read standard input until EOF and return the full contents in a malloc()ed
1015 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
1018 stdin_get_contents(size_t *len_ret)
1020 /* stdin can, of course, be a pipe or other non-seekable file, so the
1021 * total length of the data cannot be pre-determined */
1023 size_t newlen = 1024;
1027 char *p = realloc(buf, newlen);
1028 size_t bytes_read, bytes_to_read;
1030 imagex_error(T("out of memory while reading stdin"));
1034 bytes_to_read = newlen - pos;
1035 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
1037 if (bytes_read != bytes_to_read) {
1042 imagex_error_with_errno(T("error reading stdin"));
1056 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1059 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1061 *num_tchars_ret = num_bytes;
1064 /* On Windows, translate the text to UTF-16LE */
1068 if (num_bytes >= 2 &&
1069 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1070 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1072 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1073 * with something that looks like an ASCII character encoded as
1074 * a UTF-16LE code unit. Assume the file is encoded as
1075 * UTF-16LE. This is not a 100% reliable check. */
1076 num_wchars = num_bytes / 2;
1077 text_wstr = (wchar_t*)text;
1079 /* File does not look like UTF-16LE. Assume it is encoded in
1080 * the current Windows code page. I think these are always
1081 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1082 * should work as expected. */
1083 text_wstr = win32_mbs_to_wcs(text,
1088 *num_tchars_ret = num_wchars;
1094 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1099 contents = file_get_contents(filename, &num_bytes);
1102 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1106 stdin_get_text_contents(size_t *num_tchars_ret)
1111 contents = stdin_get_contents(&num_bytes);
1114 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1117 #define TO_PERCENT(numerator, denominator) \
1118 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1120 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1121 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1122 #define KIBIBYTE_MIN_NBYTES 10000ULL
1125 get_unit(uint64_t total_bytes, const tchar **name_ret)
1127 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1128 *name_ret = T("GiB");
1130 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1131 *name_ret = T("MiB");
1133 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1134 *name_ret = T("KiB");
1137 *name_ret = T("bytes");
1142 static struct wimlib_progress_info_scan last_scan_progress;
1145 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1147 uint64_t prev_count, cur_count;
1149 prev_count = last_scan_progress.num_nondirs_scanned +
1150 last_scan_progress.num_dirs_scanned;
1151 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1153 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1154 cur_count % 128 == 0)
1156 unsigned unit_shift;
1157 const tchar *unit_name;
1159 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1160 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1161 "%"PRIu64" directories) "),
1162 scan->num_bytes_scanned >> unit_shift,
1164 scan->num_nondirs_scanned,
1165 scan->num_dirs_scanned);
1166 last_scan_progress = *scan;
1170 static struct wimlib_progress_info_split last_split_progress;
1173 report_split_progress(uint64_t bytes_completed_in_part)
1175 uint64_t completed_bytes = last_split_progress.completed_bytes +
1176 bytes_completed_in_part;
1177 unsigned percent_done = TO_PERCENT(completed_bytes,
1178 last_split_progress.total_bytes);
1179 unsigned unit_shift;
1180 const tchar *unit_name;
1182 unit_shift = get_unit(last_split_progress.total_bytes, &unit_name);
1183 imagex_printf(T("\rSplitting WIM: %"PRIu64" %"TS" of "
1184 "%"PRIu64" %"TS" (%u%%) written, part %u of %u"),
1185 completed_bytes >> unit_shift,
1187 last_split_progress.total_bytes >> unit_shift,
1190 last_split_progress.cur_part_number,
1191 last_split_progress.total_parts);
1194 /* Progress callback function passed to various wimlib functions. */
1195 static enum wimlib_progress_status
1196 imagex_progress_func(enum wimlib_progress_msg msg,
1197 union wimlib_progress_info *info,
1198 void *_ignored_context)
1200 unsigned percent_done;
1201 unsigned unit_shift;
1202 const tchar *unit_name;
1205 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1206 if (last_split_progress.total_bytes != 0) {
1207 /* wimlib_split() in progress; use the split-specific
1208 * progress message. */
1209 report_split_progress(info->write_streams.completed_compressed_bytes);
1213 static bool started;
1215 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1216 imagex_printf(T("Using %"TS" compression "
1217 "with %u thread%"TS"\n"),
1218 wimlib_get_compression_type_string(
1219 info->write_streams.compression_type),
1220 info->write_streams.num_threads,
1221 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1226 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1227 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1228 info->write_streams.total_bytes);
1230 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1231 info->write_streams.completed_bytes >> unit_shift,
1233 info->write_streams.total_bytes >> unit_shift,
1236 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1237 imagex_printf(T("\n"));
1239 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1240 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1241 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1242 imagex_printf(T("\n"));
1244 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1245 info->scan.wim_target_path);
1247 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1249 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1250 switch (info->scan.status) {
1251 case WIMLIB_SCAN_DENTRY_OK:
1252 report_scan_progress(&info->scan, false);
1254 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1255 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1257 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1258 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1259 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1261 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1262 /* Symlink fixups are enabled by default. This is
1263 * mainly intended for Windows, which for some reason
1264 * uses absolute junctions (with drive letters!) in the
1265 * default installation. On UNIX-like systems, warn the
1266 * user when fixing the target of an absolute symbolic
1267 * link, so they know to disable this if they want. */
1269 imagex_printf(T("\nWARNING: Adjusted target of "
1270 "absolute symbolic link \"%"TS"\"\n"
1271 " (Use --norpfix to capture "
1272 "absolute symbolic links as-is)\n"),
1273 info->scan.cur_path);
1280 case WIMLIB_PROGRESS_MSG_SCAN_END:
1281 report_scan_progress(&info->scan, true);
1282 imagex_printf(T("\n"));
1284 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1285 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1286 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1287 info->integrity.total_bytes);
1288 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1289 "of %"PRIu64" %"TS" (%u%%) done"),
1290 info->integrity.filename,
1291 info->integrity.completed_bytes >> unit_shift,
1293 info->integrity.total_bytes >> unit_shift,
1296 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1297 imagex_printf(T("\n"));
1299 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1300 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1301 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1302 info->integrity.total_bytes);
1303 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1304 "of %"PRIu64" %"TS" (%u%%) done"),
1305 info->integrity.completed_bytes >> unit_shift,
1307 info->integrity.total_bytes >> unit_shift,
1310 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1311 imagex_printf(T("\n"));
1313 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1314 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1315 "to %"TS" \"%"TS"\"\n"),
1316 info->extract.image,
1317 info->extract.image_name,
1318 info->extract.wimfile_name,
1319 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1320 T("NTFS volume") : T("directory")),
1321 info->extract.target);
1323 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1324 if (info->extract.end_file_count >= 2000) {
1325 percent_done = TO_PERCENT(info->extract.current_file_count,
1326 info->extract.end_file_count);
1327 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1328 info->extract.current_file_count,
1329 info->extract.end_file_count, percent_done);
1330 if (info->extract.current_file_count == info->extract.end_file_count)
1331 imagex_printf(T("\n"));
1334 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1335 percent_done = TO_PERCENT(info->extract.completed_bytes,
1336 info->extract.total_bytes);
1337 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1338 imagex_printf(T("\rExtracting file data: "
1339 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1340 info->extract.completed_bytes >> unit_shift,
1342 info->extract.total_bytes >> unit_shift,
1345 if (info->extract.completed_bytes >= info->extract.total_bytes)
1346 imagex_printf(T("\n"));
1348 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1349 if (info->extract.end_file_count >= 2000) {
1350 percent_done = TO_PERCENT(info->extract.current_file_count,
1351 info->extract.end_file_count);
1352 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1353 info->extract.current_file_count,
1354 info->extract.end_file_count, percent_done);
1355 if (info->extract.current_file_count == info->extract.end_file_count)
1356 imagex_printf(T("\n"));
1359 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1360 if (info->extract.total_parts != 1) {
1361 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1362 info->extract.part_number,
1363 info->extract.total_parts);
1366 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1367 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1368 last_split_progress = info->split;
1369 report_split_progress(0);
1371 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1372 switch (info->update.command->op) {
1373 case WIMLIB_UPDATE_OP_DELETE:
1374 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1375 info->update.command->delete_.wim_path);
1377 case WIMLIB_UPDATE_OP_RENAME:
1378 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1379 info->update.command->rename.wim_source_path,
1380 info->update.command->rename.wim_target_path);
1382 case WIMLIB_UPDATE_OP_ADD:
1387 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1388 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1389 info->replace.path_in_wim);
1391 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1392 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1393 info->wimboot_exclude.path_in_wim);
1395 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1396 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1397 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1398 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1399 info->unmount.mounted_wim,
1400 info->unmount.mounted_image);
1402 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1403 info->unmount.mounted_wim,
1404 info->unmount.mounted_image);
1405 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1409 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1410 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1411 info->verify_image.current_image,
1412 info->verify_image.total_images);
1414 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1415 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1416 info->verify_streams.total_bytes);
1417 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1418 imagex_printf(T("\rVerifying file data: "
1419 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1420 info->verify_streams.completed_bytes >> unit_shift,
1422 info->verify_streams.total_bytes >> unit_shift,
1425 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1426 imagex_printf(T("\n"));
1431 imagex_flush_output();
1432 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1436 parse_num_threads(const tchar *optarg)
1439 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1440 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1441 imagex_error(T("Number of threads must be a non-negative integer!"));
1449 parse_chunk_size(const tchar *optarg)
1452 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1453 if (chunk_size == 0) {
1454 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1455 " with optional K, M, or G suffix"));
1459 if (*tmp == T('k') || *tmp == T('K')) {
1462 } else if (*tmp == T('m') || *tmp == T('M')) {
1465 } else if (*tmp == T('g') || *tmp == T('G')) {
1469 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1470 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1474 if (chunk_size >= UINT32_MAX) {
1475 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1483 * Parse an option passed to an update command.
1485 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1488 * @option: Text string for the option (beginning with --)
1490 * @cmd: `struct wimlib_update_command' that is being constructed for
1493 * Returns true if the option was recognized; false if not.
1496 update_command_add_option(int op, const tchar *option,
1497 struct wimlib_update_command *cmd)
1499 bool recognized = true;
1501 case WIMLIB_UPDATE_OP_ADD:
1502 if (!tstrcmp(option, T("--verbose")))
1503 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1504 else if (!tstrcmp(option, T("--unix-data")))
1505 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1506 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1507 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1508 else if (!tstrcmp(option, T("--strict-acls")))
1509 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1510 else if (!tstrcmp(option, T("--dereference")))
1511 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1512 else if (!tstrcmp(option, T("--no-replace")))
1513 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1517 case WIMLIB_UPDATE_OP_DELETE:
1518 if (!tstrcmp(option, T("--force")))
1519 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1520 else if (!tstrcmp(option, T("--recursive")))
1521 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1532 /* How many nonoption arguments each `imagex update' command expects */
1533 static const unsigned update_command_num_nonoptions[] = {
1534 [WIMLIB_UPDATE_OP_ADD] = 2,
1535 [WIMLIB_UPDATE_OP_DELETE] = 1,
1536 [WIMLIB_UPDATE_OP_RENAME] = 2,
1540 update_command_add_nonoption(int op, const tchar *nonoption,
1541 struct wimlib_update_command *cmd,
1542 unsigned num_nonoptions)
1545 case WIMLIB_UPDATE_OP_ADD:
1546 if (num_nonoptions == 0)
1547 cmd->add.fs_source_path = (tchar*)nonoption;
1549 cmd->add.wim_target_path = (tchar*)nonoption;
1551 case WIMLIB_UPDATE_OP_DELETE:
1552 cmd->delete_.wim_path = (tchar*)nonoption;
1554 case WIMLIB_UPDATE_OP_RENAME:
1555 if (num_nonoptions == 0)
1556 cmd->rename.wim_source_path = (tchar*)nonoption;
1558 cmd->rename.wim_target_path = (tchar*)nonoption;
1564 * Parse a command passed on stdin to `imagex update'.
1566 * @line: Text of the command.
1567 * @len: Length of the line, including a null terminator
1570 * @command: A `struct wimlib_update_command' to fill in from the parsed
1573 * @line_number: Line number of the command, for diagnostics.
1575 * Returns true on success; returns false on parse error.
1578 parse_update_command(tchar *line, size_t len,
1579 struct wimlib_update_command *command,
1583 tchar *command_name;
1585 size_t num_nonoptions;
1587 /* Get the command name ("add", "delete", "rename") */
1588 ret = parse_string(&line, &len, &command_name);
1589 if (ret != PARSE_STRING_SUCCESS)
1592 if (!tstrcasecmp(command_name, T("add"))) {
1593 op = WIMLIB_UPDATE_OP_ADD;
1594 } else if (!tstrcasecmp(command_name, T("delete"))) {
1595 op = WIMLIB_UPDATE_OP_DELETE;
1596 } else if (!tstrcasecmp(command_name, T("rename"))) {
1597 op = WIMLIB_UPDATE_OP_RENAME;
1599 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1600 command_name, line_number);
1605 /* Parse additional options and non-options as needed */
1610 ret = parse_string(&line, &len, &next_string);
1611 if (ret == PARSE_STRING_NONE) /* End of line */
1613 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1615 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1617 if (!update_command_add_option(op, next_string, command))
1619 imagex_error(T("Unrecognized option \"%"TS"\" to "
1620 "update command \"%"TS"\" on line %zu"),
1621 next_string, command_name, line_number);
1627 if (num_nonoptions == update_command_num_nonoptions[op])
1629 imagex_error(T("Unexpected argument \"%"TS"\" in "
1630 "update command on line %zu\n"
1631 " (The \"%"TS"\" command only "
1632 "takes %zu nonoption arguments!)\n"),
1633 next_string, line_number,
1634 command_name, num_nonoptions);
1637 update_command_add_nonoption(op, next_string,
1638 command, num_nonoptions);
1643 if (num_nonoptions != update_command_num_nonoptions[op]) {
1644 imagex_error(T("Not enough arguments to update command "
1645 "\"%"TS"\" on line %zu"), command_name, line_number);
1651 static struct wimlib_update_command *
1652 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1653 size_t *num_cmds_ret)
1657 struct wimlib_update_command *cmds;
1660 nlines = text_file_count_lines(cmd_file_contents_p,
1665 /* Always allocate at least 1 slot, just in case the implementation of
1666 * calloc() returns NULL if 0 bytes are requested. */
1667 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1669 imagex_error(T("out of memory"));
1672 p = *cmd_file_contents_p;
1674 for (i = 0; i < nlines; i++) {
1675 /* XXX: Could use rawmemchr() here instead, but it may not be
1676 * available on all platforms. */
1677 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1678 size_t len = endp - p + 1;
1680 if (!is_comment_line(p, len)) {
1681 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1692 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1693 * one image from a WIM file to an NTFS volume. */
1695 imagex_apply(int argc, tchar **argv, int cmd)
1699 int image = WIMLIB_NO_IMAGE;
1701 struct wimlib_wim_info info;
1703 const tchar *wimfile;
1704 const tchar *target;
1705 const tchar *image_num_or_name = NULL;
1706 int extract_flags = 0;
1708 STRING_LIST(refglobs);
1710 for_opt(c, apply_options) {
1712 case IMAGEX_CHECK_OPTION:
1713 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1715 case IMAGEX_VERBOSE_OPTION:
1716 /* No longer does anything. */
1718 case IMAGEX_REF_OPTION:
1719 ret = string_list_append(&refglobs, optarg);
1721 goto out_free_refglobs;
1723 case IMAGEX_UNIX_DATA_OPTION:
1724 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1726 case IMAGEX_NO_ACLS_OPTION:
1727 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1729 case IMAGEX_STRICT_ACLS_OPTION:
1730 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1732 case IMAGEX_NO_ATTRIBUTES_OPTION:
1733 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1735 case IMAGEX_NORPFIX_OPTION:
1736 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1738 case IMAGEX_RPFIX_OPTION:
1739 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1741 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1742 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1743 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1745 case IMAGEX_WIMBOOT_OPTION:
1746 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1748 case IMAGEX_COMPACT_OPTION:
1749 ret = set_compact_mode(optarg, &extract_flags);
1751 goto out_free_refglobs;
1753 case IMAGEX_RECOVER_DATA_OPTION:
1754 extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
1762 if (argc != 2 && argc != 3)
1767 if (!tstrcmp(wimfile, T("-"))) {
1768 /* Attempt to apply pipable WIM from standard input. */
1770 image_num_or_name = NULL;
1773 image_num_or_name = argv[1];
1778 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1779 imagex_progress_func, NULL);
1781 goto out_free_refglobs;
1783 wimlib_get_wim_info(wim, &info);
1786 /* Image explicitly specified. */
1787 image_num_or_name = argv[1];
1788 image = wimlib_resolve_image(wim, image_num_or_name);
1789 ret = verify_image_exists(image, image_num_or_name, wimfile);
1791 goto out_wimlib_free;
1794 /* No image specified; default to image 1, but only if the WIM
1795 * contains exactly one image. */
1797 if (info.image_count != 1) {
1798 imagex_error(T("\"%"TS"\" contains %d images; "
1799 "Please select one (or all)."),
1800 wimfile, info.image_count);
1809 if (refglobs.num_strings) {
1811 imagex_error(T("Can't specify --ref when applying from stdin!"));
1813 goto out_wimlib_free;
1815 ret = wim_reference_globs(wim, &refglobs, open_flags);
1817 goto out_wimlib_free;
1822 /* Interpret a regular file or block device target as an NTFS
1826 if (tstat(target, &stbuf)) {
1827 if (errno != ENOENT) {
1828 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1831 goto out_wimlib_free;
1834 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1835 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1841 ret = wimlib_extract_image(wim, image, target, extract_flags);
1843 set_fd_to_binary_mode(STDIN_FILENO);
1844 ret = wimlib_extract_image_from_pipe_with_progress(
1849 imagex_progress_func,
1853 imagex_printf(T("Done applying WIM image.\n"));
1854 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1856 do_resource_not_found_warning(wimfile, &info, &refglobs);
1858 imagex_error(T( "If you are applying an image "
1859 "from a split pipable WIM,\n"
1860 " make sure you have "
1861 "concatenated together all parts."));
1863 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1864 do_metadata_not_found_warning(wimfile, &info);
1869 string_list_destroy(&refglobs);
1873 usage(CMD_APPLY, stderr);
1875 goto out_free_refglobs;
1879 * Create a WIM image from a directory tree, NTFS volume, or multiple files or
1880 * directory trees. 'wimcapture': create a new WIM file containing the desired
1881 * image. 'wimappend': add a new image to an existing WIM file; or, with
1882 * '--create' behave like 'wimcapture' if the WIM file doesn't exist.
1885 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1888 bool create = false;
1889 bool appending = (cmd == CMD_APPEND);
1891 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1892 WIMLIB_ADD_FLAG_WINCONFIG |
1893 WIMLIB_ADD_FLAG_VERBOSE |
1894 WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1895 int write_flags = 0;
1896 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1897 uint32_t chunk_size = UINT32_MAX;
1898 uint32_t solid_chunk_size = UINT32_MAX;
1899 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1900 const tchar *wimfile;
1903 STRING_LIST(image_properties);
1906 STRING_LIST(base_wimfiles);
1907 WIMStruct **base_wims;
1909 WIMStruct *template_wim = NULL;
1910 const tchar *template_wimfile = NULL;
1911 const tchar *template_image_name_or_num = NULL;
1912 int template_image = WIMLIB_NO_IMAGE;
1915 unsigned num_threads = 0;
1920 tchar *config_file = NULL;
1922 bool source_list = false;
1923 size_t source_list_nchars = 0;
1924 tchar *source_list_contents;
1925 bool capture_sources_malloced;
1926 struct wimlib_capture_source *capture_sources;
1928 bool name_defaulted;
1930 for_opt(c, capture_or_append_options) {
1932 case IMAGEX_BOOT_OPTION:
1933 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1935 case IMAGEX_CHECK_OPTION:
1936 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1938 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
1939 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1941 case IMAGEX_NOCHECK_OPTION:
1942 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1944 case IMAGEX_CONFIG_OPTION:
1945 config_file = optarg;
1946 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1948 case IMAGEX_COMPRESS_OPTION:
1949 compression_type = get_compression_type(optarg, false);
1950 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1953 case IMAGEX_CHUNK_SIZE_OPTION:
1954 chunk_size = parse_chunk_size(optarg);
1955 if (chunk_size == UINT32_MAX)
1958 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1959 solid_chunk_size = parse_chunk_size(optarg);
1960 if (solid_chunk_size == UINT32_MAX)
1963 case IMAGEX_SOLID_COMPRESS_OPTION:
1964 solid_ctype = get_compression_type(optarg, true);
1965 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1968 case IMAGEX_SOLID_OPTION:
1969 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1971 case IMAGEX_NO_SOLID_SORT_OPTION:
1972 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1974 case IMAGEX_FLAGS_OPTION: {
1975 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1976 tsprintf(p, T("FLAGS=%"TS), optarg);
1977 ret = string_list_append(&image_properties, p);
1982 case IMAGEX_IMAGE_PROPERTY_OPTION:
1983 ret = append_image_property_argument(&image_properties);
1987 case IMAGEX_DEREFERENCE_OPTION:
1988 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1990 case IMAGEX_VERBOSE_OPTION:
1991 /* No longer does anything. */
1993 case IMAGEX_THREADS_OPTION:
1994 num_threads = parse_num_threads(optarg);
1995 if (num_threads == UINT_MAX)
1998 case IMAGEX_REBUILD_OPTION:
1999 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2001 case IMAGEX_UNIX_DATA_OPTION:
2002 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
2004 case IMAGEX_SOURCE_LIST_OPTION:
2007 case IMAGEX_NO_ACLS_OPTION:
2008 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
2010 case IMAGEX_STRICT_ACLS_OPTION:
2011 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
2013 case IMAGEX_RPFIX_OPTION:
2014 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
2016 case IMAGEX_NORPFIX_OPTION:
2017 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
2019 case IMAGEX_PIPABLE_OPTION:
2020 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2022 case IMAGEX_NOT_PIPABLE_OPTION:
2023 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2025 case IMAGEX_UPDATE_OF_OPTION:
2026 if (template_image_name_or_num) {
2027 imagex_error(T("'--update-of' can only be "
2028 "specified one time!"));
2032 colon = tstrrchr(optarg, T(':'));
2035 template_wimfile = optarg;
2037 template_image_name_or_num = colon + 1;
2039 template_wimfile = NULL;
2040 template_image_name_or_num = optarg;
2044 imagex_printf(T("[WARNING] '--update-of' is unreliable on Windows!\n"));
2047 case IMAGEX_DELTA_FROM_OPTION:
2048 ret = string_list_append(&base_wimfiles, optarg);
2051 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2053 case IMAGEX_WIMBOOT_OPTION:
2054 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2056 case IMAGEX_UNSAFE_COMPACT_OPTION:
2057 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2059 case IMAGEX_SNAPSHOT_OPTION:
2060 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2062 case IMAGEX_CREATE_OPTION:
2063 if (cmd == CMD_CAPTURE) {
2064 imagex_error(T("'--create' is only valid for 'wimappend', not 'wimcapture'"));
2076 if (argc < 2 || argc > 4)
2082 /* Set default compression type and parameters. */
2085 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2086 /* No compression type specified. Use the default. */
2088 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2089 /* With --wimboot, default to XPRESS compression. */
2090 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2091 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2092 /* With --solid, default to LZMS compression. (However,
2093 * this will not affect solid resources!) */
2094 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2096 /* Otherwise, default to LZX compression. */
2097 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2101 if (!tstrcmp(wimfile, T("-"))) {
2102 /* Writing captured WIM to standard output. */
2106 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2107 imagex_error("Can't write a non-pipable WIM to "
2108 "standard output! Specify --pipable\n"
2109 " if you want to create a pipable WIM "
2110 "(but read the docs first).");
2114 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2117 imagex_error(T("Using standard output for append does "
2118 "not make sense."));
2121 wim_fd = STDOUT_FILENO;
2123 imagex_output_to_stderr();
2124 set_fd_to_binary_mode(wim_fd);
2128 /* Check for 'wimappend --create' acting as wimcapture */
2129 if (create && tstat(wimfile, &stbuf) != 0 && errno == ENOENT) {
2133 /* Ignore '--update-of' for the target WIMFILE */
2134 if (template_image_name_or_num &&
2135 (!template_wimfile ||
2136 !tstrcmp(template_wimfile, wimfile)))
2138 template_image_name_or_num = NULL;
2139 template_wimfile = NULL;
2144 if ((write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) && !appending) {
2145 imagex_error(T("'--unsafe-compact' is only valid for append!"));
2149 /* If template image was specified using --update-of=IMAGE rather
2150 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2151 if (template_image_name_or_num && !template_wimfile) {
2152 if (base_wimfiles.num_strings == 1) {
2153 /* Capturing delta WIM based on single WIM: default to
2155 template_wimfile = base_wimfiles.strings[0];
2156 } else if (appending) {
2157 /* Appending to WIM: default to WIM being appended to.
2159 template_wimfile = wimfile;
2161 /* Capturing a normal (non-delta) WIM, so the WIM file
2162 * *must* be explicitly specified. */
2163 if (base_wimfiles.num_strings > 1) {
2164 imagex_error(T("For capture of delta WIM "
2165 "based on multiple existing "
2167 " '--update-of' must "
2168 "specify WIMFILE:IMAGE!"));
2170 imagex_error(T("For capture of non-delta WIM, "
2171 "'--update-of' must specify "
2180 name_defaulted = false;
2182 /* Set default name to SOURCE argument, omitting any directory
2183 * prefixes and trailing slashes. This requires making a copy
2184 * of @source. Leave some free characters at the end in case we
2185 * append a number to keep the name unique. */
2186 size_t source_name_len;
2188 source_name_len = tstrlen(source);
2189 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2190 name = tbasename(tstrcpy(source_copy, source));
2191 name_defaulted = true;
2194 /* Image description (if given). */
2196 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2197 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2198 ret = string_list_append(&image_properties, p);
2204 /* Set up capture sources in source list mode */
2205 if (source[0] == T('-') && source[1] == T('\0')) {
2206 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2208 source_list_contents = file_get_text_contents(source,
2209 &source_list_nchars);
2211 if (!source_list_contents)
2214 capture_sources = parse_source_list(&source_list_contents,
2217 if (!capture_sources) {
2219 goto out_free_source_list_contents;
2221 capture_sources_malloced = true;
2223 /* Set up capture source in non-source-list mode. */
2224 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2225 capture_sources[0].fs_source_path = source;
2226 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2227 capture_sources[0].reserved = 0;
2229 capture_sources_malloced = false;
2230 source_list_contents = NULL;
2233 /* Open the existing WIM, or create a new one. */
2235 ret = wimlib_open_wim_with_progress(wimfile,
2236 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2238 imagex_progress_func,
2241 goto out_free_capture_sources;
2243 ret = wimlib_create_new_wim(compression_type, &wim);
2245 goto out_free_capture_sources;
2246 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2249 /* Set chunk size if non-default. */
2250 if (chunk_size != UINT32_MAX) {
2251 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2254 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2256 int ctype = compression_type;
2259 struct wimlib_wim_info info;
2260 wimlib_get_wim_info(wim, &info);
2261 ctype = info.compression_type;
2264 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2265 ret = wimlib_set_output_chunk_size(wim, 4096);
2270 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2271 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2275 if (solid_chunk_size != UINT32_MAX) {
2276 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2282 /* Detect if source is regular file or block device and set NTFS volume
2287 if (tstat(source, &stbuf) == 0) {
2288 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2289 imagex_printf(T("Capturing WIM image from NTFS "
2290 "filesystem on \"%"TS"\"\n"), source);
2291 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2294 if (errno != ENOENT) {
2295 imagex_error_with_errno(T("Failed to stat "
2296 "\"%"TS"\""), source);
2304 /* If the user did not specify an image name, and the basename of the
2305 * source already exists as an image name in the WIM file, append a
2306 * suffix to make it unique. */
2307 if (appending && name_defaulted) {
2308 unsigned long conflict_idx;
2309 tchar *name_end = tstrchr(name, T('\0'));
2310 for (conflict_idx = 1;
2311 wimlib_image_name_in_use(wim, name);
2314 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2318 /* If capturing a delta WIM, reference resources from the base WIMs
2319 * before adding the new image. */
2320 if (base_wimfiles.num_strings) {
2321 base_wims = calloc(base_wimfiles.num_strings,
2322 sizeof(base_wims[0]));
2323 if (base_wims == NULL) {
2324 imagex_error(T("Out of memory!"));
2329 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2330 ret = wimlib_open_wim_with_progress(
2331 base_wimfiles.strings[i], open_flags,
2332 &base_wims[i], imagex_progress_func, NULL);
2334 goto out_free_base_wims;
2338 ret = wimlib_reference_resources(wim, base_wims,
2339 base_wimfiles.num_strings, 0);
2341 goto out_free_base_wims;
2343 if (base_wimfiles.num_strings == 1) {
2344 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2345 base_wimfiles.strings[0]);
2347 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2348 base_wimfiles.num_strings);
2355 /* If capturing or appending as an update of an existing (template) image,
2356 * open the WIM if needed and parse the image index. */
2357 if (template_image_name_or_num) {
2359 if (appending && !tstrcmp(template_wimfile, wimfile)) {
2362 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2363 if (!tstrcmp(template_wimfile,
2364 base_wimfiles.strings[i])) {
2365 template_wim = base_wims[i];
2371 if (!template_wim) {
2372 ret = wimlib_open_wim_with_progress(template_wimfile,
2375 imagex_progress_func,
2378 goto out_free_base_wims;
2381 template_image = wimlib_resolve_image(template_wim,
2382 template_image_name_or_num);
2384 if (template_image_name_or_num[0] == T('-')) {
2387 struct wimlib_wim_info info;
2389 wimlib_get_wim_info(template_wim, &info);
2390 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2391 if (n >= 1 && n <= info.image_count &&
2393 tmp != template_image_name_or_num + 1)
2395 template_image = info.image_count - (n - 1);
2398 ret = verify_image_exists_and_is_single(template_image,
2399 template_image_name_or_num,
2402 goto out_free_template_wim;
2405 ret = wimlib_add_image_multisource(wim,
2412 goto out_free_template_wim;
2414 if (image_properties.num_strings || template_image_name_or_num) {
2415 /* User asked to set additional image properties, or an image on
2416 * which the added one is to be based has been specified with
2418 struct wimlib_wim_info info;
2420 wimlib_get_wim_info(wim, &info);
2422 ret = apply_image_properties(&image_properties, wim,
2423 info.image_count, NULL);
2425 goto out_free_template_wim;
2427 /* Reference template image if the user provided one. */
2428 if (template_image_name_or_num) {
2429 imagex_printf(T("Using image %d "
2430 "from \"%"TS"\" as template\n"),
2431 template_image, template_wimfile);
2432 ret = wimlib_reference_template_image(wim,
2438 goto out_free_template_wim;
2442 /* Write the new WIM or overwrite the existing WIM with the new image
2445 ret = wimlib_overwrite(wim, write_flags, num_threads);
2446 } else if (wimfile) {
2447 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2448 write_flags, num_threads);
2450 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2451 write_flags, num_threads);
2453 out_free_template_wim:
2454 /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
2455 if (template_wim == wim)
2456 goto out_free_base_wims;
2457 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2458 if (template_wim == base_wims[i])
2459 goto out_free_base_wims;
2460 wimlib_free(template_wim);
2462 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2463 wimlib_free(base_wims[i]);
2467 out_free_capture_sources:
2468 if (capture_sources_malloced)
2469 free(capture_sources);
2470 out_free_source_list_contents:
2471 free(source_list_contents);
2473 string_list_destroy(&image_properties);
2474 string_list_destroy(&base_wimfiles);
2484 /* Remove image(s) from a WIM. */
2486 imagex_delete(int argc, tchar **argv, int cmd)
2489 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2490 int write_flags = 0;
2491 const tchar *wimfile;
2492 const tchar *image_num_or_name;
2497 for_opt(c, delete_options) {
2499 case IMAGEX_CHECK_OPTION:
2500 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2502 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2503 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2505 case IMAGEX_SOFT_OPTION:
2506 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2508 case IMAGEX_UNSAFE_COMPACT_OPTION:
2509 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2520 imagex_error(T("Must specify a WIM file"));
2522 imagex_error(T("Must specify an image"));
2526 image_num_or_name = argv[1];
2528 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2529 imagex_progress_func, NULL);
2533 image = wimlib_resolve_image(wim, image_num_or_name);
2535 ret = verify_image_exists(image, image_num_or_name, wimfile);
2537 goto out_wimlib_free;
2539 ret = wimlib_delete_image(wim, image);
2541 imagex_error(T("Failed to delete image from \"%"TS"\""),
2543 goto out_wimlib_free;
2546 ret = wimlib_overwrite(wim, write_flags, 0);
2548 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2549 "deleted"), wimfile);
2557 usage(CMD_DELETE, stderr);
2562 struct print_dentry_options {
2567 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2569 tprintf(T("%"TS"\n"), dentry->full_path);
2572 static const struct {
2575 } file_attr_flags[] = {
2576 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2577 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2578 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2579 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2580 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2581 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2582 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2583 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2584 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2585 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2586 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2587 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2588 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2589 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2590 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2593 #define TIMESTR_MAX 100
2596 print_time(const tchar *type, const struct wimlib_timespec *wts,
2599 tchar timestr[TIMESTR_MAX];
2603 if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2604 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2609 tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2610 timestr[TIMESTR_MAX - 1] = '\0';
2612 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2615 static void print_byte_field(const uint8_t field[], size_t len)
2618 tprintf(T("%02hhx"), *field++);
2622 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2624 tchar attr_string[256];
2627 tputs(T("WIM Information:"));
2628 tputs(T("----------------"));
2629 tprintf(T("Path: %"TS"\n"), wimfile);
2630 tprintf(T("GUID: 0x"));
2631 print_byte_field(info->guid, sizeof(info->guid));
2633 tprintf(T("Version: %u\n"), info->wim_version);
2634 tprintf(T("Image Count: %d\n"), info->image_count);
2635 tprintf(T("Compression: %"TS"\n"),
2636 wimlib_get_compression_type_string(info->compression_type));
2637 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2639 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2640 tprintf(T("Boot Index: %d\n"), info->boot_index);
2641 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2643 attr_string[0] = T('\0');
2646 tstrcat(attr_string, T("Pipable, "));
2648 if (info->has_integrity_table)
2649 tstrcat(attr_string, T("Integrity info, "));
2651 if (info->has_rpfix)
2652 tstrcat(attr_string, T("Relative path junction, "));
2654 if (info->resource_only)
2655 tstrcat(attr_string, T("Resource only, "));
2657 if (info->metadata_only)
2658 tstrcat(attr_string, T("Metadata only, "));
2660 if (info->is_marked_readonly)
2661 tstrcat(attr_string, T("Readonly, "));
2663 p = tstrchr(attr_string, T('\0'));
2664 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2667 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2671 print_resource(const struct wimlib_resource_entry *resource,
2674 tprintf(T("Hash = 0x"));
2675 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2678 if (!resource->is_missing) {
2679 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2680 resource->uncompressed_size);
2681 if (resource->packed) {
2682 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2683 "bytes @ offset %"PRIu64"\n"),
2684 resource->raw_resource_uncompressed_size,
2685 resource->raw_resource_compressed_size,
2686 resource->raw_resource_offset_in_wim);
2688 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2691 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2692 resource->compressed_size);
2694 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2698 tprintf(T("Part Number = %u\n"), resource->part_number);
2699 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2701 tprintf(T("Flags = "));
2702 if (resource->is_compressed)
2703 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2704 if (resource->is_metadata)
2705 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2706 if (resource->is_free)
2707 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2708 if (resource->is_spanned)
2709 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2710 if (resource->packed)
2711 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2719 print_blobs(WIMStruct *wim)
2721 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2726 default_print_security_descriptor(const uint8_t *sd, size_t size)
2728 tprintf(T("Security Descriptor = "));
2729 print_byte_field(sd, size);
2735 is_null_guid(const uint8_t *guid)
2737 static const uint8_t null_guid[WIMLIB_GUID_LEN];
2739 return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2743 print_guid(const tchar *label, const uint8_t *guid)
2745 if (is_null_guid(guid))
2747 tprintf(T("%-20"TS"= 0x"), label);
2748 print_byte_field(guid, WIMLIB_GUID_LEN);
2753 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2756 "----------------------------------------------------------------------------\n"));
2757 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2758 if (dentry->dos_name)
2759 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2760 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2761 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2762 if (file_attr_flags[i].flag & dentry->attributes)
2763 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2764 file_attr_flags[i].name);
2766 if (dentry->security_descriptor) {
2767 print_security_descriptor(dentry->security_descriptor,
2768 dentry->security_descriptor_size);
2771 print_time(T("Creation Time"),
2772 &dentry->creation_time, dentry->creation_time_high);
2773 print_time(T("Last Write Time"),
2774 &dentry->last_write_time, dentry->last_write_time_high);
2775 print_time(T("Last Access Time"),
2776 &dentry->last_access_time, dentry->last_access_time_high);
2779 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2780 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2782 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2783 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2785 if (dentry->unix_mode != 0) {
2786 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2787 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2788 dentry->unix_uid, dentry->unix_gid,
2789 dentry->unix_mode, dentry->unix_rdev);
2792 if (!is_null_guid(dentry->object_id.object_id)) {
2793 print_guid(T("Object ID"), dentry->object_id.object_id);
2794 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2795 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2796 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2799 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2800 if (dentry->streams[i].stream_name) {
2801 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2802 dentry->streams[i].stream_name);
2803 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2804 tprintf(T("\tRaw encrypted data stream:\n"));
2805 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2806 tprintf(T("\tReparse point stream:\n"));
2808 tprintf(T("\tUnnamed data stream:\n"));
2810 print_resource(&dentry->streams[i].resource, NULL);
2815 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2817 const struct print_dentry_options *options = _options;
2818 if (!options->detailed)
2819 print_dentry_full_path(dentry);
2821 print_dentry_detailed(dentry);
2825 /* Print the files contained in an image(s) in a WIM file. */
2827 imagex_dir(int argc, tchar **argv, int cmd)
2829 const tchar *wimfile;
2830 WIMStruct *wim = NULL;
2833 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2835 struct print_dentry_options options = {
2838 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2840 STRING_LIST(refglobs);
2842 for_opt(c, dir_options) {
2844 case IMAGEX_PATH_OPTION:
2847 case IMAGEX_DETAILED_OPTION:
2848 options.detailed = true;
2850 case IMAGEX_ONE_FILE_ONLY_OPTION:
2851 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2853 case IMAGEX_REF_OPTION:
2854 ret = string_list_append(&refglobs, optarg);
2856 goto out_free_refglobs;
2866 imagex_error(T("Must specify a WIM file"));
2870 imagex_error(T("Too many arguments"));
2875 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2876 imagex_progress_func, NULL);
2878 goto out_free_refglobs;
2881 image = wimlib_resolve_image(wim, argv[1]);
2882 ret = verify_image_exists(image, argv[1], wimfile);
2884 goto out_wimlib_free;
2886 /* No image specified; default to image 1, but only if the WIM
2887 * contains exactly one image. */
2889 struct wimlib_wim_info info;
2891 wimlib_get_wim_info(wim, &info);
2892 if (info.image_count != 1) {
2893 imagex_error(T("\"%"TS"\" contains %d images; Please "
2894 "select one (or all)."),
2895 wimfile, info.image_count);
2902 if (refglobs.num_strings) {
2903 ret = wim_reference_globs(wim, &refglobs, 0);
2905 goto out_wimlib_free;
2908 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2909 print_dentry, &options);
2910 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2911 struct wimlib_wim_info info;
2913 wimlib_get_wim_info(wim, &info);
2914 do_metadata_not_found_warning(wimfile, &info);
2919 string_list_destroy(&refglobs);
2923 usage(CMD_DIR, stderr);
2925 goto out_free_refglobs;
2928 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2931 imagex_export(int argc, tchar **argv, int cmd)
2935 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2936 int write_flags = 0;
2937 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2938 const tchar *src_wimfile;
2939 const tchar *src_image_num_or_name;
2940 const tchar *dest_wimfile;
2942 const tchar *dest_name;
2943 const tchar *dest_desc;
2945 struct wimlib_wim_info src_info;
2946 WIMStruct *dest_wim;
2951 STRING_LIST(refglobs);
2952 unsigned num_threads = 0;
2953 uint32_t chunk_size = UINT32_MAX;
2954 uint32_t solid_chunk_size = UINT32_MAX;
2955 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2957 for_opt(c, export_options) {
2959 case IMAGEX_BOOT_OPTION:
2960 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2962 case IMAGEX_CHECK_OPTION:
2963 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2965 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2966 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2968 case IMAGEX_NOCHECK_OPTION:
2969 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2971 case IMAGEX_COMPRESS_OPTION:
2972 compression_type = get_compression_type(optarg, false);
2973 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2976 case IMAGEX_RECOMPRESS_OPTION:
2977 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2979 case IMAGEX_SOLID_OPTION:
2980 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2982 case IMAGEX_NO_SOLID_SORT_OPTION:
2983 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2985 case IMAGEX_CHUNK_SIZE_OPTION:
2986 chunk_size = parse_chunk_size(optarg);
2987 if (chunk_size == UINT32_MAX)
2990 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2991 solid_chunk_size = parse_chunk_size(optarg);
2992 if (solid_chunk_size == UINT32_MAX)
2995 case IMAGEX_SOLID_COMPRESS_OPTION:
2996 solid_ctype = get_compression_type(optarg, true);
2997 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3000 case IMAGEX_REF_OPTION:
3001 ret = string_list_append(&refglobs, optarg);
3003 goto out_free_refglobs;
3005 case IMAGEX_THREADS_OPTION:
3006 num_threads = parse_num_threads(optarg);
3007 if (num_threads == UINT_MAX)
3010 case IMAGEX_REBUILD_OPTION:
3011 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3013 case IMAGEX_PIPABLE_OPTION:
3014 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3016 case IMAGEX_NOT_PIPABLE_OPTION:
3017 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3019 case IMAGEX_WIMBOOT_OPTION:
3020 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
3022 case IMAGEX_UNSAFE_COMPACT_OPTION:
3023 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3031 if (argc < 3 || argc > 5)
3033 src_wimfile = argv[0];
3034 src_image_num_or_name = argv[1];
3035 dest_wimfile = argv[2];
3036 dest_name = (argc >= 4) ? argv[3] : NULL;
3037 dest_desc = (argc >= 5) ? argv[4] : NULL;
3038 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
3039 imagex_progress_func, NULL);
3041 goto out_free_refglobs;
3043 wimlib_get_wim_info(src_wim, &src_info);
3045 /* Determine if the destination is an existing file or not. If so, we
3046 * try to append the exported image(s) to it; otherwise, we create a new
3047 * WIM containing the exported image(s). Furthermore, determine if we
3048 * need to write a pipable WIM directly to standard output. */
3050 if (tstrcmp(dest_wimfile, T("-")) == 0) {
3052 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
3053 imagex_error("Can't write a non-pipable WIM to "
3054 "standard output! Specify --pipable\n"
3055 " if you want to create a pipable WIM "
3056 "(but read the docs first).");
3058 goto out_free_src_wim;
3061 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3063 dest_wimfile = NULL;
3064 dest_wim_fd = STDOUT_FILENO;
3065 imagex_output_to_stderr();
3066 set_fd_to_binary_mode(dest_wim_fd);
3069 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
3071 /* Destination file exists. */
3073 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
3074 imagex_error(T("\"%"TS"\" is not a regular file "
3075 "or block device"), dest_wimfile);
3077 goto out_free_src_wim;
3079 ret = wimlib_open_wim_with_progress(dest_wimfile,
3081 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
3083 imagex_progress_func,
3086 goto out_free_src_wim;
3088 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3089 /* The user specified a compression type, but we're
3090 * exporting to an existing WIM. Make sure the
3091 * specified compression type is the same as the
3092 * compression type of the existing destination WIM. */
3093 struct wimlib_wim_info dest_info;
3095 wimlib_get_wim_info(dest_wim, &dest_info);
3096 if (compression_type != dest_info.compression_type) {
3097 imagex_error(T("Cannot specify a compression type that is "
3098 "not the same as that used in the "
3099 "destination WIM"));
3101 goto out_free_dest_wim;
3107 if (errno != ENOENT) {
3108 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3111 goto out_free_src_wim;
3114 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3115 imagex_error(T("'--unsafe-compact' is only valid when "
3116 "exporting to an existing WIM file!"));
3118 goto out_free_src_wim;
3121 /* dest_wimfile is not an existing file, so create a new WIM. */
3123 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3124 /* The user did not specify a compression type; default
3125 * to that of the source WIM, unless --solid or
3126 * --wimboot was specified. */
3128 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3129 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3130 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3131 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3133 compression_type = src_info.compression_type;
3135 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3137 goto out_free_src_wim;
3139 wimlib_register_progress_function(dest_wim,
3140 imagex_progress_func, NULL);
3142 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3143 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3145 /* For --wimboot export, use small XPRESS chunks. */
3146 wimlib_set_output_chunk_size(dest_wim, 4096);
3147 } else if (compression_type == src_info.compression_type &&
3148 chunk_size == UINT32_MAX)
3150 /* Use same chunk size if compression type is the same. */
3151 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3155 if (chunk_size != UINT32_MAX) {
3156 /* Set destination chunk size. */
3157 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3159 goto out_free_dest_wim;
3161 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3162 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3164 goto out_free_dest_wim;
3166 if (solid_chunk_size != UINT32_MAX) {
3167 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3169 goto out_free_dest_wim;
3172 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3173 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3175 goto out_free_dest_wim;
3177 if (refglobs.num_strings) {
3178 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3180 goto out_free_dest_wim;
3183 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3184 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3186 imagex_error(T("--boot specified for all-images export, but source WIM "
3187 "has no bootable image."));
3189 goto out_free_dest_wim;
3192 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3193 dest_desc, export_flags);
3195 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3196 do_resource_not_found_warning(src_wimfile,
3197 &src_info, &refglobs);
3198 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3199 do_metadata_not_found_warning(src_wimfile, &src_info);
3201 goto out_free_dest_wim;
3205 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3206 else if (dest_wimfile)
3207 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3208 write_flags, num_threads);
3210 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3211 WIMLIB_ALL_IMAGES, write_flags,
3214 wimlib_free(dest_wim);
3216 wimlib_free(src_wim);
3218 string_list_destroy(&refglobs);
3222 usage(CMD_EXPORT, stderr);
3225 goto out_free_refglobs;
3228 /* Extract files or directories from a WIM image */
3230 imagex_extract(int argc, tchar **argv, int cmd)
3237 const tchar *wimfile;
3238 const tchar *image_num_or_name;
3239 tchar *dest_dir = T(".");
3240 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3241 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3242 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3243 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3245 STRING_LIST(refglobs);
3247 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3249 for_opt(c, extract_options) {
3251 case IMAGEX_CHECK_OPTION:
3252 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3254 case IMAGEX_VERBOSE_OPTION:
3255 /* No longer does anything. */
3257 case IMAGEX_REF_OPTION:
3258 ret = string_list_append(&refglobs, optarg);
3260 goto out_free_refglobs;
3262 case IMAGEX_UNIX_DATA_OPTION:
3263 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3265 case IMAGEX_NO_ACLS_OPTION:
3266 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3268 case IMAGEX_STRICT_ACLS_OPTION:
3269 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3271 case IMAGEX_NO_ATTRIBUTES_OPTION:
3272 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3274 case IMAGEX_DEST_DIR_OPTION:
3277 case IMAGEX_TO_STDOUT_OPTION:
3278 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3279 imagex_suppress_output();
3280 set_fd_to_binary_mode(STDOUT_FILENO);
3282 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3283 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3284 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3286 case IMAGEX_NO_GLOBS_OPTION:
3287 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3289 case IMAGEX_NULLGLOB_OPTION:
3290 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3292 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3293 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3295 case IMAGEX_WIMBOOT_OPTION:
3296 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3298 case IMAGEX_COMPACT_OPTION:
3299 ret = set_compact_mode(optarg, &extract_flags);
3301 goto out_free_refglobs;
3303 case IMAGEX_RECOVER_DATA_OPTION:
3304 extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
3316 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3317 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3319 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3324 image_num_or_name = argv[1];
3329 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3330 imagex_progress_func, NULL);
3332 goto out_free_refglobs;
3334 image = wimlib_resolve_image(wim, image_num_or_name);
3335 ret = verify_image_exists_and_is_single(image,
3339 goto out_wimlib_free;
3341 if (refglobs.num_strings) {
3342 ret = wim_reference_globs(wim, &refglobs, open_flags);
3344 goto out_wimlib_free;
3350 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3353 while (argc != 0 && ret == 0) {
3357 num_paths < argc && argv[num_paths][0] != T('@');
3362 ret = wimlib_extract_paths(wim, image, dest_dir,
3363 (const tchar **)argv,
3365 extract_flags | notlist_extract_flags);
3369 const tchar *listfile = argv[0] + 1;
3371 if (!tstrcmp(listfile, T("-"))) {
3372 tputs(T("Reading pathlist file from standard input..."));
3376 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3377 listfile, extract_flags);
3384 imagex_printf(T("Done extracting files.\n"));
3385 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3386 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3387 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3388 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3389 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3392 T("Note: You can use the '--nullglob' "
3393 "option to ignore missing files.\n"));
3395 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3396 "files and directories\n"
3397 " are in the WIM image.\n"),
3398 get_cmd_string(CMD_DIR, false));
3399 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3400 struct wimlib_wim_info info;
3402 wimlib_get_wim_info(wim, &info);
3403 do_resource_not_found_warning(wimfile, &info, &refglobs);
3404 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3405 struct wimlib_wim_info info;
3407 wimlib_get_wim_info(wim, &info);
3408 do_metadata_not_found_warning(wimfile, &info);
3413 string_list_destroy(&refglobs);
3417 usage(CMD_EXTRACT, stderr);
3420 goto out_free_refglobs;
3423 /* Prints information about a WIM file; also can mark an image as bootable,
3424 * change the name of an image, or change the description of an image. */
3426 imagex_info(int argc, tchar **argv, int cmd)
3430 bool header = false;
3433 bool short_header = true;
3434 const tchar *xml_out_file = NULL;
3435 const tchar *wimfile;
3436 const tchar *image_num_or_name;
3437 STRING_LIST(image_properties);
3442 int write_flags = 0;
3443 struct wimlib_wim_info info;
3445 for_opt(c, info_options) {
3447 case IMAGEX_BOOT_OPTION:
3450 case IMAGEX_CHECK_OPTION:
3451 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3453 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3454 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3456 case IMAGEX_NOCHECK_OPTION:
3457 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3459 case IMAGEX_HEADER_OPTION:
3461 short_header = false;
3463 case IMAGEX_BLOBS_OPTION:
3465 short_header = false;
3467 case IMAGEX_XML_OPTION:
3469 short_header = false;
3471 case IMAGEX_EXTRACT_XML_OPTION:
3472 xml_out_file = optarg;
3473 short_header = false;
3475 case IMAGEX_IMAGE_PROPERTY_OPTION:
3476 ret = append_image_property_argument(&image_properties);
3487 if (argc < 1 || argc > 4)
3491 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3495 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3496 tsprintf(p, T("NAME=%"TS), argv[2]);
3497 ret = string_list_append(&image_properties, p);
3504 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3505 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3506 ret = string_list_append(&image_properties, p);
3511 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3512 imagex_progress_func, NULL);
3516 wimlib_get_wim_info(wim, &info);
3518 image = wimlib_resolve_image(wim, image_num_or_name);
3519 ret = WIMLIB_ERR_INVALID_IMAGE;
3520 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3521 verify_image_exists(image, image_num_or_name, wimfile);
3523 imagex_error(T("If you would like to set the boot "
3524 "index to 0, specify image \"0\" with "
3525 "the --boot flag."));
3527 goto out_wimlib_free;
3530 if (boot && info.image_count == 0) {
3531 imagex_error(T("--boot is meaningless on a WIM with no images"));
3532 goto out_wimlib_free;
3535 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3537 imagex_error(T("Cannot specify the --boot flag "
3538 "without specifying a specific "
3539 "image in a multi-image WIM"));
3540 goto out_wimlib_free;
3542 if (image_properties.num_strings) {
3543 imagex_error(T("Can't change image properties without "
3544 "specifying a specific image in a "
3545 "multi-image WIM"));
3546 goto out_wimlib_free;
3550 /* Operations that print information are separated from operations that
3551 * recreate the WIM file. */
3552 if (!image_properties.num_strings && !boot) {
3554 /* Read-only operations */
3556 if (image == WIMLIB_NO_IMAGE) {
3557 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3558 image_num_or_name, wimfile);
3559 goto out_wimlib_free;
3562 if (image == WIMLIB_ALL_IMAGES && short_header)
3563 print_wim_information(wimfile, &info);
3566 wimlib_print_header(wim);
3569 if (info.total_parts != 1) {
3570 tfprintf(stderr, T("Warning: Only showing the blobs "
3571 "for part %d of a %d-part WIM.\n"),
3572 info.part_number, info.total_parts);
3578 ret = wimlib_extract_xml_data(wim, stdout);
3580 goto out_wimlib_free;
3586 fp = tfopen(xml_out_file, T("wb"));
3588 imagex_error_with_errno(T("Failed to open the "
3589 "file \"%"TS"\" for "
3593 goto out_wimlib_free;
3595 ret = wimlib_extract_xml_data(wim, fp);
3597 imagex_error(T("Failed to close the file "
3603 goto out_wimlib_free;
3607 wimlib_print_available_images(wim, image);
3611 /* Modification operations */
3612 bool any_property_changes;
3614 if (image == WIMLIB_ALL_IMAGES)
3617 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3618 imagex_error(T("Cannot change image properties "
3619 "when using image 0"));
3621 goto out_wimlib_free;
3625 if (image == info.boot_index) {
3626 imagex_printf(T("Image %d is already marked as "
3627 "bootable.\n"), image);
3630 imagex_printf(T("Marking image %d as bootable.\n"),
3632 info.boot_index = image;
3633 ret = wimlib_set_wim_info(wim, &info,
3634 WIMLIB_CHANGE_BOOT_INDEX);
3636 goto out_wimlib_free;
3640 ret = apply_image_properties(&image_properties, wim, image,
3641 &any_property_changes);
3643 goto out_wimlib_free;
3645 /* Only call wimlib_overwrite() if something actually needs to
3647 if (boot || any_property_changes ||
3648 ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
3649 !info.has_integrity_table) ||
3650 ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
3651 info.has_integrity_table))
3653 ret = wimlib_overwrite(wim, write_flags, 1);
3655 imagex_printf(T("The file \"%"TS"\" was not modified "
3656 "because nothing needed to be done.\n"),
3664 string_list_destroy(&image_properties);
3668 usage(CMD_INFO, stderr);
3673 /* Join split WIMs into one part WIM */
3675 imagex_join(int argc, tchar **argv, int cmd)
3678 int swm_open_flags = 0;
3679 int wim_write_flags = 0;
3680 const tchar *output_path;
3683 for_opt(c, join_options) {
3685 case IMAGEX_CHECK_OPTION:
3686 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3688 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3689 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3699 imagex_error(T("Must specify one or more split WIM (.swm) "
3703 output_path = argv[0];
3704 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3709 imagex_progress_func,
3715 usage(CMD_JOIN, stderr);
3720 #if WIM_MOUNTING_SUPPORTED
3722 /* Mounts a WIM image. */
3724 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3727 int mount_flags = 0;
3729 const tchar *staging_dir = NULL;
3730 const tchar *wimfile;
3733 struct wimlib_wim_info info;
3737 STRING_LIST(refglobs);
3739 if (cmd == CMD_MOUNTRW) {
3740 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3741 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3744 for_opt(c, mount_options) {
3746 case IMAGEX_ALLOW_OTHER_OPTION:
3747 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3749 case IMAGEX_CHECK_OPTION:
3750 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3752 case IMAGEX_DEBUG_OPTION:
3753 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3755 case IMAGEX_STREAMS_INTERFACE_OPTION:
3756 if (!tstrcasecmp(optarg, T("none")))
3757 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3758 else if (!tstrcasecmp(optarg, T("xattr")))
3759 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3760 else if (!tstrcasecmp(optarg, T("windows")))
3761 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3763 imagex_error(T("Unknown stream interface \"%"TS"\""),
3768 case IMAGEX_REF_OPTION:
3769 ret = string_list_append(&refglobs, optarg);
3771 goto out_free_refglobs;
3773 case IMAGEX_STAGING_DIR_OPTION:
3774 staging_dir = optarg;
3776 case IMAGEX_UNIX_DATA_OPTION:
3777 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3785 if (argc != 2 && argc != 3)
3790 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3791 imagex_progress_func, NULL);
3793 goto out_free_refglobs;
3795 wimlib_get_wim_info(wim, &info);
3798 /* Image explicitly specified. */
3799 image = wimlib_resolve_image(wim, argv[1]);
3801 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3805 /* No image specified; default to image 1, but only if the WIM
3806 * contains exactly one image. */
3808 if (info.image_count != 1) {
3809 imagex_error(T("\"%"TS"\" contains %d images; Please "
3810 "select one."), wimfile, info.image_count);
3818 if (refglobs.num_strings) {
3819 ret = wim_reference_globs(wim, &refglobs, open_flags);
3824 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3826 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3827 do_metadata_not_found_warning(wimfile, &info);
3829 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3831 image, wimfile, dir);
3837 string_list_destroy(&refglobs);
3843 goto out_free_refglobs;
3845 #endif /* WIM_MOUNTING_SUPPORTED */
3847 /* Rebuild a WIM file */
3849 imagex_optimize(int argc, tchar **argv, int cmd)
3852 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3853 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3854 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3855 uint32_t chunk_size = UINT32_MAX;
3856 uint32_t solid_chunk_size = UINT32_MAX;
3857 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3860 const tchar *wimfile;
3863 unsigned num_threads = 0;
3865 for_opt(c, optimize_options) {
3867 case IMAGEX_CHECK_OPTION:
3868 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3870 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3871 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3873 case IMAGEX_NOCHECK_OPTION:
3874 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3876 case IMAGEX_COMPRESS_OPTION:
3877 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3878 compression_type = get_compression_type(optarg, false);
3879 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3882 case IMAGEX_RECOMPRESS_OPTION:
3883 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3885 case IMAGEX_CHUNK_SIZE_OPTION:
3886 chunk_size = parse_chunk_size(optarg);
3887 if (chunk_size == UINT32_MAX)
3890 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3891 solid_chunk_size = parse_chunk_size(optarg);
3892 if (solid_chunk_size == UINT32_MAX)
3895 case IMAGEX_SOLID_COMPRESS_OPTION:
3896 solid_ctype = get_compression_type(optarg, true);
3897 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3900 case IMAGEX_SOLID_OPTION:
3901 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3902 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3904 case IMAGEX_NO_SOLID_SORT_OPTION:
3905 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3907 case IMAGEX_THREADS_OPTION:
3908 num_threads = parse_num_threads(optarg);
3909 if (num_threads == UINT_MAX)
3912 case IMAGEX_PIPABLE_OPTION:
3913 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3915 case IMAGEX_NOT_PIPABLE_OPTION:
3916 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3918 case IMAGEX_UNSAFE_COMPACT_OPTION:
3919 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3933 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3934 imagex_progress_func, NULL);
3938 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3939 /* Change compression type. */
3940 ret = wimlib_set_output_compression_type(wim, compression_type);
3942 goto out_wimlib_free;
3945 if (chunk_size != UINT32_MAX) {
3946 /* Change chunk size. */
3947 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3949 goto out_wimlib_free;
3951 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3952 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3954 goto out_wimlib_free;
3956 if (solid_chunk_size != UINT32_MAX) {
3957 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3959 goto out_wimlib_free;
3962 old_size = file_get_size(wimfile);
3963 tprintf(T("\"%"TS"\" original size: "), wimfile);
3965 tputs(T("Unknown"));
3967 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3969 ret = wimlib_overwrite(wim, write_flags, num_threads);
3971 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3972 goto out_wimlib_free;
3975 new_size = file_get_size(wimfile);
3976 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3978 tputs(T("Unknown"));
3980 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3982 tfputs(T("Space saved: "), stdout);
3983 if (new_size != -1 && old_size != -1) {
3984 tprintf(T("%lld KiB\n"),
3985 ((long long)old_size - (long long)new_size) >> 10);
3987 tputs(T("Unknown"));
3996 usage(CMD_OPTIMIZE, stderr);
4002 /* Split a WIM into a spanned set */
4004 imagex_split(int argc, tchar **argv, int cmd)
4008 int write_flags = 0;
4014 for_opt(c, split_options) {
4016 case IMAGEX_CHECK_OPTION:
4017 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4019 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4020 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4032 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
4033 if (tmp == argv[2] || *tmp) {
4034 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
4035 imagex_error(T("The part size must be an integer or "
4036 "floating-point number of megabytes."));
4039 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
4040 imagex_progress_func, NULL);
4044 ret = wimlib_split(wim, argv[1], part_size, write_flags);
4046 tprintf(T("\nFinished splitting \"%"TS"\"\n"), argv[0]);
4052 usage(CMD_SPLIT, stderr);
4058 #if WIM_MOUNTING_SUPPORTED
4059 /* Unmounts a mounted WIM image. */
4061 imagex_unmount(int argc, tchar **argv, int cmd)
4064 int unmount_flags = 0;
4067 for_opt(c, unmount_options) {
4069 case IMAGEX_COMMIT_OPTION:
4070 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
4072 case IMAGEX_CHECK_OPTION:
4073 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
4075 case IMAGEX_REBUILD_OPTION:
4076 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
4078 case IMAGEX_LAZY_OPTION:
4079 case IMAGEX_FORCE_OPTION:
4080 /* Now, unmount is lazy by default. However, committing
4081 * the image will fail with
4082 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
4083 * file descriptors on the WIM image. The
4084 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
4085 * descriptors to be closed. */
4086 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4088 case IMAGEX_NEW_IMAGE_OPTION:
4089 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4100 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4101 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4102 imagex_error(T("--new-image is meaningless "
4103 "without --commit also specified!"));
4108 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4109 imagex_progress_func, NULL);
4111 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4112 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4114 "\tNote: Use --commit --force to force changes "
4115 "to be committed, regardless\n"
4116 "\t of open files.\n"));
4123 usage(CMD_UNMOUNT, stderr);
4128 #endif /* WIM_MOUNTING_SUPPORTED */
4131 * Add, delete, or rename files in a WIM image.
4134 imagex_update(int argc, tchar **argv, int cmd)
4136 const tchar *wimfile;
4140 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4141 int write_flags = 0;
4142 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4143 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4144 WIMLIB_ADD_FLAG_VERBOSE |
4145 WIMLIB_ADD_FLAG_WINCONFIG;
4146 int default_delete_flags = 0;
4147 unsigned num_threads = 0;
4149 tchar *cmd_file_contents;
4150 size_t cmd_file_nchars;
4151 struct wimlib_update_command *cmds;
4153 tchar *command_str = NULL;
4154 tchar *config_file = NULL;
4155 tchar *wimboot_config = NULL;
4157 for_opt(c, update_options) {
4159 /* Generic or write options */
4160 case IMAGEX_THREADS_OPTION:
4161 num_threads = parse_num_threads(optarg);
4162 if (num_threads == UINT_MAX)
4165 case IMAGEX_CHECK_OPTION:
4166 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4168 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4169 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4171 case IMAGEX_REBUILD_OPTION:
4172 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4174 case IMAGEX_COMMAND_OPTION:
4176 imagex_error(T("--command may only be specified "
4177 "one time. Please provide\n"
4178 " the update commands "
4179 "on standard input instead."));
4182 command_str = tstrdup(optarg);
4184 imagex_error(T("Out of memory!"));
4188 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4189 wimboot_config = optarg;
4191 /* Default delete options */
4192 case IMAGEX_FORCE_OPTION:
4193 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4195 case IMAGEX_RECURSIVE_OPTION:
4196 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4199 /* Global add option */
4200 case IMAGEX_CONFIG_OPTION:
4201 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4202 config_file = optarg;
4205 /* Default add options */
4206 case IMAGEX_VERBOSE_OPTION:
4207 /* No longer does anything. */
4209 case IMAGEX_DEREFERENCE_OPTION:
4210 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4212 case IMAGEX_UNIX_DATA_OPTION:
4213 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4215 case IMAGEX_NO_ACLS_OPTION:
4216 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4218 case IMAGEX_STRICT_ACLS_OPTION:
4219 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4221 case IMAGEX_NO_REPLACE_OPTION:
4222 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4224 case IMAGEX_UNSAFE_COMPACT_OPTION:
4225 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4234 if (argc != 1 && argc != 2)
4238 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4239 imagex_progress_func, NULL);
4241 goto out_free_command_str;
4244 /* Image explicitly specified. */
4245 image = wimlib_resolve_image(wim, argv[1]);
4246 ret = verify_image_exists_and_is_single(image, argv[1],
4249 goto out_wimlib_free;
4251 /* No image specified; default to image 1, but only if the WIM
4252 * contains exactly one image. */
4253 struct wimlib_wim_info info;
4255 wimlib_get_wim_info(wim, &info);
4256 if (info.image_count != 1) {
4257 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4258 wimfile, info.image_count);
4265 /* Read update commands from standard input, or the command string if
4268 cmd_file_contents = NULL;
4269 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4273 goto out_free_cmd_file_contents;
4275 } else if (!wimboot_config) {
4276 if (isatty(STDIN_FILENO)) {
4277 tputs(T("Reading update commands from standard input..."));
4278 recommend_man_page(CMD_UPDATE, stdout);
4280 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4281 if (!cmd_file_contents) {
4283 goto out_wimlib_free;
4286 /* Parse the update commands */
4287 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4291 goto out_free_cmd_file_contents;
4294 cmd_file_contents = NULL;
4299 /* Set default flags and capture config on the update commands */
4300 for (size_t i = 0; i < num_cmds; i++) {
4301 switch (cmds[i].op) {
4302 case WIMLIB_UPDATE_OP_ADD:
4303 cmds[i].add.add_flags |= default_add_flags;
4304 cmds[i].add.config_file = config_file;
4306 case WIMLIB_UPDATE_OP_DELETE:
4307 cmds[i].delete_.delete_flags |= default_delete_flags;
4314 /* Execute the update commands */
4315 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4319 if (wimboot_config) {
4320 /* --wimboot-config=FILE is short for an
4321 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4323 struct wimlib_update_command cmd;
4325 cmd.op = WIMLIB_UPDATE_OP_ADD;
4326 cmd.add.fs_source_path = wimboot_config;
4327 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4328 cmd.add.config_file = NULL;
4329 cmd.add.add_flags = 0;
4331 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4336 /* Overwrite the updated WIM */
4337 ret = wimlib_overwrite(wim, write_flags, num_threads);
4340 out_free_cmd_file_contents:
4341 free(cmd_file_contents);
4344 out_free_command_str:
4349 usage(CMD_UPDATE, stderr);
4352 goto out_free_command_str;
4355 /* Verify a WIM file. */
4357 imagex_verify(int argc, tchar **argv, int cmd)
4360 const tchar *wimfile;
4362 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4363 int verify_flags = 0;
4364 STRING_LIST(refglobs);
4367 for_opt(c, verify_options) {
4369 case IMAGEX_REF_OPTION:
4370 ret = string_list_append(&refglobs, optarg);
4372 goto out_free_refglobs;
4374 case IMAGEX_NOCHECK_OPTION:
4375 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4387 imagex_error(T("Must specify a WIM file!"));
4389 imagex_error(T("At most one WIM file can be specified!"));
4395 ret = wimlib_open_wim_with_progress(wimfile,
4398 imagex_progress_func,
4401 goto out_free_refglobs;
4403 ret = wim_reference_globs(wim, &refglobs, open_flags);
4405 goto out_wimlib_free;
4407 ret = wimlib_verify_wim(wim, verify_flags);
4409 tputc(T('\n'), stderr);
4410 imagex_error(T("\"%"TS"\" failed verification!"),
4412 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4413 refglobs.num_strings == 0)
4415 imagex_printf(T("Note: if this WIM file is not standalone, "
4416 "use the --ref option to specify the other parts.\n"));
4419 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4426 string_list_destroy(&refglobs);
4430 usage(CMD_VERIFY, stderr);
4432 goto out_free_refglobs;
4435 struct imagex_command {
4437 int (*func)(int argc, tchar **argv, int cmd);
4440 static const struct imagex_command imagex_commands[] = {
4441 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4442 [CMD_APPLY] = {T("apply"), imagex_apply},
4443 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4444 [CMD_DELETE] = {T("delete"), imagex_delete},
4445 [CMD_DIR ] = {T("dir"), imagex_dir},
4446 [CMD_EXPORT] = {T("export"), imagex_export},
4447 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4448 [CMD_INFO] = {T("info"), imagex_info},
4449 [CMD_JOIN] = {T("join"), imagex_join},
4450 #if WIM_MOUNTING_SUPPORTED
4451 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4452 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4454 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4455 [CMD_SPLIT] = {T("split"), imagex_split},
4456 #if WIM_MOUNTING_SUPPORTED
4457 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4459 [CMD_UPDATE] = {T("update"), imagex_update},
4460 [CMD_VERIFY] = {T("verify"), imagex_verify},
4465 /* Can be a directory or source list file. But source list file is probably
4466 * a rare use case, so just say directory. */
4467 # define SOURCE_STR T("DIRECTORY")
4469 /* Can only be a directory */
4470 # define TARGET_STR T("DIRECTORY")
4473 /* Can be a directory, NTFS volume, or source list file. */
4474 # define SOURCE_STR T("SOURCE")
4476 /* Can be a directory or NTFS volume. */
4477 # define TARGET_STR T("TARGET")
4481 static const tchar * const usage_strings[] = {
4484 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4485 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4486 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4487 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4488 " [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4489 " [--dereference] [--snapshot] [--create]\n"
4493 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4494 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4495 " [--no-attributes] [--rpfix] [--norpfix]\n"
4496 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4497 " [--compact=FORMAT] [--recover-data]\n"
4501 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4502 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4503 " [--config=FILE] [--threads=NUM_THREADS]\n"
4504 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4505 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4506 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4511 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4515 " %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4519 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4520 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4521 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4522 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4523 " [--wimboot] [--solid]\n"
4527 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4528 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4529 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4530 " [--no-attributes] [--include-invalid-names] [--no-globs]\n"
4531 " [--nullglob] [--preserve-dir-structure] [--recover-data]\n"
4535 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4536 " [--boot] [--check] [--nocheck] [--xml]\n"
4537 " [--extract-xml FILE] [--header] [--blobs]\n"
4538 " [--image-property NAME=VALUE]\n"
4542 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4544 #if WIM_MOUNTING_SUPPORTED
4547 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4548 " [--check] [--streams-interface=INTERFACE]\n"
4549 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4553 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4554 " [--check] [--streams-interface=INTERFACE]\n"
4555 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4561 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4562 " [--check] [--nocheck] [--solid]\n"
4567 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4569 #if WIM_MOUNTING_SUPPORTED
4572 " %"TS" DIRECTORY\n"
4573 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4578 " %"TS" WIMFILE [IMAGE]\n"
4579 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4580 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4581 " [--command=STRING] [--wimboot-config=FILE]\n"
4586 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4590 static const tchar *invocation_name;
4591 static int invocation_cmd = CMD_NONE;
4593 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4595 static tchar buf[50];
4597 if (cmd == CMD_NONE)
4598 return T("wimlib-imagex");
4600 if (only_short_form || invocation_cmd != CMD_NONE) {
4601 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4603 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4604 imagex_commands[cmd].name);
4612 static const tchar * const fmt =
4614 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4615 "Copyright (C) 2012-2023 Eric Biggers\n"
4616 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4617 "This is free software: you are free to change and redistribute it.\n"
4618 "There is NO WARRANTY, to the extent permitted by law.\n"
4620 "Report bugs to "PACKAGE_BUGREPORT".\n"
4622 tfprintf(stdout, fmt, wimlib_get_version_string());
4626 do_common_options(int *argc_p, tchar **argv, int cmd)
4632 for (i = 1; i < argc; i++) {
4634 if (p[0] == T('-') && p[1] == T('-')) {
4636 if (!tstrcmp(p, T("help"))) {
4637 if (cmd == CMD_NONE)
4642 } else if (!tstrcmp(p, T("version"))) {
4645 } else if (!tstrcmp(p, T("quiet"))) {
4646 imagex_suppress_output();
4647 memmove(&argv[i], &argv[i + 1],
4648 (argc - i) * sizeof(argv[i]));
4651 } else if (!*p) /* reached "--", no more options */
4660 print_usage_string(int cmd, FILE *fp)
4662 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4666 recommend_man_page(int cmd, FILE *fp)
4668 const tchar *format_str;
4670 format_str = T("Some uncommon options are not listed;\n"
4671 "See %"TS".pdf in the doc directory for more details.\n");
4673 format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4675 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4679 usage(int cmd, FILE *fp)
4681 tfprintf(fp, T("Usage:\n"));
4682 print_usage_string(cmd, fp);
4683 tfprintf(fp, T("\n"));
4684 recommend_man_page(cmd, fp);
4690 tfprintf(fp, T("Usage:\n"));
4691 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4692 print_usage_string(cmd, fp);
4693 tfprintf(fp, T("\n"));
4695 static const tchar * const extra =
4698 " %"TS" --version\n"
4701 tfprintf(fp, extra, invocation_name, invocation_name);
4703 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4704 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4705 "For some commands IMAGE may be \"all\".\n"
4707 recommend_man_page(CMD_NONE, fp);
4711 int wmain(int argc, wchar_t **argv);
4715 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4716 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4717 * something else), while on Windows the command arguments will be UTF-16LE
4718 * encoded 'wchar_t' strings. */
4720 main(int argc, tchar **argv)
4726 imagex_info_file = stdout;
4727 invocation_name = tbasename(argv[0]);
4730 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4731 if (igcase != NULL) {
4732 if (!tstrcmp(igcase, T("no")) ||
4733 !tstrcmp(igcase, T("0")))
4734 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4735 else if (!tstrcmp(igcase, T("yes")) ||
4736 !tstrcmp(igcase, T("1")))
4737 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4740 "WARNING: Ignoring unknown setting of "
4741 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4746 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4748 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4749 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4750 for (int i = 0; i < CMD_MAX; i++) {
4751 if (!tstrcmp(invocation_name + 3,
4752 imagex_commands[i].name))
4761 /* Unless already known from the invocation name, determine which
4762 * command was specified. */
4763 if (cmd == CMD_NONE) {
4765 imagex_error(T("No command specified!\n"));
4769 for (int i = 0; i < CMD_MAX; i++) {
4770 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4775 if (cmd != CMD_NONE) {
4781 /* Handle common options. May exit early (for --help or --version). */
4782 do_common_options(&argc, argv, cmd);
4784 /* Bail if a valid command was not specified. */
4785 if (cmd == CMD_NONE) {
4786 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4791 /* Enable warning and error messages in wimlib to be more user-friendly.
4793 wimlib_set_print_errors(true);
4795 /* Initialize wimlib. */
4796 ret = wimlib_global_init(init_flags);
4798 goto out_check_status;
4800 /* Call the command handler function. */
4801 ret = imagex_commands[cmd].func(argc, argv, cmd);
4803 /* Check for error writing to standard output, especially since for some
4804 * commands, writing to standard output is part of the program's actual
4805 * behavior and not just for informational purposes. */
4806 if (ferror(stdout) || fclose(stdout)) {
4807 imagex_error_with_errno(T("error writing to standard output"));
4812 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4813 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4814 * error code from which an error message can be printed. */
4816 imagex_error(T("Exiting with error code %d:\n"
4818 wimlib_get_error_string(ret));
4819 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4820 imagex_error_with_errno(T("errno"));
4822 /* Make wimlib free any resources it's holding (although this is not
4823 * strictly necessary because the process is ending anyway). */
4824 wimlib_global_cleanup();