4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright 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 <https://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 * wimupdate itself) are also handled in update_command_add_option(). */
420 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
421 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
422 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
423 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
424 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
425 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
426 {T("ref"), required_argument, NULL, IMAGEX_REF_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 #define TO_PERCENT(numerator, denominator) \
972 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
974 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
975 #define MEBIBYTE_MIN_NBYTES 10000000ULL
976 #define KIBIBYTE_MIN_NBYTES 10000ULL
979 get_unit(uint64_t total_bytes, const tchar **name_ret)
981 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
982 *name_ret = T("GiB");
984 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
985 *name_ret = T("MiB");
987 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
988 *name_ret = T("KiB");
991 *name_ret = T("bytes");
996 static struct wimlib_progress_info_scan last_scan_progress;
999 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1001 uint64_t prev_count, cur_count;
1003 prev_count = last_scan_progress.num_nondirs_scanned +
1004 last_scan_progress.num_dirs_scanned;
1005 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1007 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1008 cur_count % 128 == 0)
1010 unsigned unit_shift;
1011 const tchar *unit_name;
1013 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1014 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1015 "%"PRIu64" directories) "),
1016 scan->num_bytes_scanned >> unit_shift,
1018 scan->num_nondirs_scanned,
1019 scan->num_dirs_scanned);
1020 last_scan_progress = *scan;
1024 static struct wimlib_progress_info_split last_split_progress;
1027 report_split_progress(uint64_t bytes_completed_in_part)
1029 uint64_t completed_bytes = last_split_progress.completed_bytes +
1030 bytes_completed_in_part;
1031 unsigned percent_done = TO_PERCENT(completed_bytes,
1032 last_split_progress.total_bytes);
1033 unsigned unit_shift;
1034 const tchar *unit_name;
1036 unit_shift = get_unit(last_split_progress.total_bytes, &unit_name);
1037 imagex_printf(T("\rSplitting WIM: %"PRIu64" %"TS" of "
1038 "%"PRIu64" %"TS" (%u%%) written, part %u of %u"),
1039 completed_bytes >> unit_shift,
1041 last_split_progress.total_bytes >> unit_shift,
1044 last_split_progress.cur_part_number,
1045 last_split_progress.total_parts);
1048 /* Progress callback function passed to various wimlib functions. */
1049 static enum wimlib_progress_status
1050 imagex_progress_func(enum wimlib_progress_msg msg,
1051 union wimlib_progress_info *info,
1052 void *_ignored_context)
1054 unsigned percent_done;
1055 unsigned unit_shift;
1056 const tchar *unit_name;
1059 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1060 if (last_split_progress.total_bytes != 0) {
1061 /* wimlib_split() in progress; use the split-specific
1062 * progress message. */
1063 report_split_progress(info->write_streams.completed_compressed_bytes);
1067 static bool started;
1069 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1070 imagex_printf(T("Using %"TS" compression "
1071 "with %u thread%"TS"\n"),
1072 wimlib_get_compression_type_string(
1073 info->write_streams.compression_type),
1074 info->write_streams.num_threads,
1075 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1080 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1081 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1082 info->write_streams.total_bytes);
1084 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1085 info->write_streams.completed_bytes >> unit_shift,
1087 info->write_streams.total_bytes >> unit_shift,
1090 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1091 imagex_printf(T("\n"));
1093 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1094 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1095 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1096 imagex_printf(T("\n"));
1098 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1099 info->scan.wim_target_path);
1101 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1103 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1104 switch (info->scan.status) {
1105 case WIMLIB_SCAN_DENTRY_OK:
1106 report_scan_progress(&info->scan, false);
1108 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1109 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1111 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1112 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1113 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1115 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1116 /* Symlink fixups are enabled by default. This is
1117 * mainly intended for Windows, which for some reason
1118 * uses absolute junctions (with drive letters!) in the
1119 * default installation. On UNIX-like systems, warn the
1120 * user when fixing the target of an absolute symbolic
1121 * link, so they know to disable this if they want. */
1123 imagex_printf(T("\nWARNING: Adjusted target of "
1124 "absolute symbolic link \"%"TS"\"\n"
1125 " (Use --norpfix to capture "
1126 "absolute symbolic links as-is)\n"),
1127 info->scan.cur_path);
1134 case WIMLIB_PROGRESS_MSG_SCAN_END:
1135 report_scan_progress(&info->scan, true);
1136 imagex_printf(T("\n"));
1138 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1139 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1140 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1141 info->integrity.total_bytes);
1142 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1143 "of %"PRIu64" %"TS" (%u%%) done"),
1144 info->integrity.filename,
1145 info->integrity.completed_bytes >> unit_shift,
1147 info->integrity.total_bytes >> unit_shift,
1150 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1151 imagex_printf(T("\n"));
1153 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1154 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1155 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1156 info->integrity.total_bytes);
1157 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1158 "of %"PRIu64" %"TS" (%u%%) done"),
1159 info->integrity.completed_bytes >> unit_shift,
1161 info->integrity.total_bytes >> unit_shift,
1164 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1165 imagex_printf(T("\n"));
1167 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1168 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1169 "to %"TS" \"%"TS"\"\n"),
1170 info->extract.image,
1171 info->extract.image_name,
1172 info->extract.wimfile_name,
1173 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1174 T("NTFS volume") : T("directory")),
1175 info->extract.target);
1177 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1178 if (info->extract.end_file_count >= 2000) {
1179 percent_done = TO_PERCENT(info->extract.current_file_count,
1180 info->extract.end_file_count);
1181 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1182 info->extract.current_file_count,
1183 info->extract.end_file_count, percent_done);
1184 if (info->extract.current_file_count == info->extract.end_file_count)
1185 imagex_printf(T("\n"));
1188 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1189 percent_done = TO_PERCENT(info->extract.completed_bytes,
1190 info->extract.total_bytes);
1191 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1192 imagex_printf(T("\rExtracting file data: "
1193 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1194 info->extract.completed_bytes >> unit_shift,
1196 info->extract.total_bytes >> unit_shift,
1199 if (info->extract.completed_bytes >= info->extract.total_bytes)
1200 imagex_printf(T("\n"));
1202 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1203 if (info->extract.end_file_count >= 2000) {
1204 percent_done = TO_PERCENT(info->extract.current_file_count,
1205 info->extract.end_file_count);
1206 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1207 info->extract.current_file_count,
1208 info->extract.end_file_count, percent_done);
1209 if (info->extract.current_file_count == info->extract.end_file_count)
1210 imagex_printf(T("\n"));
1213 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1214 if (info->extract.total_parts != 1) {
1215 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1216 info->extract.part_number,
1217 info->extract.total_parts);
1220 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1221 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1222 last_split_progress = info->split;
1223 report_split_progress(0);
1225 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1226 switch (info->update.command->op) {
1227 case WIMLIB_UPDATE_OP_DELETE:
1228 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1229 info->update.command->delete_.wim_path);
1231 case WIMLIB_UPDATE_OP_RENAME:
1232 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1233 info->update.command->rename.wim_source_path,
1234 info->update.command->rename.wim_target_path);
1236 case WIMLIB_UPDATE_OP_ADD:
1241 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1242 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1243 info->replace.path_in_wim);
1245 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1246 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1247 info->wimboot_exclude.path_in_wim);
1249 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1250 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1251 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1252 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1253 info->unmount.mounted_wim,
1254 info->unmount.mounted_image);
1256 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1257 info->unmount.mounted_wim,
1258 info->unmount.mounted_image);
1259 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1263 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1264 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1265 info->verify_image.current_image,
1266 info->verify_image.total_images);
1268 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1269 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1270 info->verify_streams.total_bytes);
1271 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1272 imagex_printf(T("\rVerifying file data: "
1273 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1274 info->verify_streams.completed_bytes >> unit_shift,
1276 info->verify_streams.total_bytes >> unit_shift,
1279 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1280 imagex_printf(T("\n"));
1285 imagex_flush_output();
1286 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1290 parse_num_threads(const tchar *optarg)
1293 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1294 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1295 imagex_error(T("Number of threads must be a non-negative integer!"));
1303 parse_chunk_size(const tchar *optarg)
1306 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1307 if (chunk_size == 0) {
1308 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1309 " with optional K, M, or G suffix"));
1313 if (*tmp == T('k') || *tmp == T('K')) {
1316 } else if (*tmp == T('m') || *tmp == T('M')) {
1319 } else if (*tmp == T('g') || *tmp == T('G')) {
1323 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1324 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1328 if (chunk_size >= UINT32_MAX) {
1329 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1337 * Parse an option passed to an update command.
1339 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1342 * @option: Text string for the option (beginning with --)
1344 * @cmd: `struct wimlib_update_command' that is being constructed for
1347 * Returns true if the option was recognized; false if not.
1350 update_command_add_option(int op, const tchar *option,
1351 struct wimlib_update_command *cmd)
1353 bool recognized = true;
1355 case WIMLIB_UPDATE_OP_ADD:
1356 if (!tstrcmp(option, T("--verbose")))
1357 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1358 else if (!tstrcmp(option, T("--unix-data")))
1359 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1360 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1361 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1362 else if (!tstrcmp(option, T("--strict-acls")))
1363 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1364 else if (!tstrcmp(option, T("--dereference")))
1365 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1366 else if (!tstrcmp(option, T("--no-replace")))
1367 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1371 case WIMLIB_UPDATE_OP_DELETE:
1372 if (!tstrcmp(option, T("--force")))
1373 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1374 else if (!tstrcmp(option, T("--recursive")))
1375 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1386 /* How many nonoption arguments each wimupdate command expects */
1387 static const unsigned update_command_num_nonoptions[] = {
1388 [WIMLIB_UPDATE_OP_ADD] = 2,
1389 [WIMLIB_UPDATE_OP_DELETE] = 1,
1390 [WIMLIB_UPDATE_OP_RENAME] = 2,
1394 update_command_add_nonoption(int op, const tchar *nonoption,
1395 struct wimlib_update_command *cmd,
1396 unsigned num_nonoptions)
1399 case WIMLIB_UPDATE_OP_ADD:
1400 if (num_nonoptions == 0)
1401 cmd->add.fs_source_path = (tchar*)nonoption;
1403 cmd->add.wim_target_path = (tchar*)nonoption;
1405 case WIMLIB_UPDATE_OP_DELETE:
1406 cmd->delete_.wim_path = (tchar*)nonoption;
1408 case WIMLIB_UPDATE_OP_RENAME:
1409 if (num_nonoptions == 0)
1410 cmd->rename.wim_source_path = (tchar*)nonoption;
1412 cmd->rename.wim_target_path = (tchar*)nonoption;
1418 * Parse a command passed on stdin to wimupdate.
1420 * @line: Text of the command.
1421 * @len: Length of the line, including a null terminator
1424 * @command: A `struct wimlib_update_command' to fill in from the parsed
1427 * @line_number: Line number of the command, for diagnostics.
1429 * Returns true on success; returns false on parse error.
1432 parse_update_command(tchar *line, size_t len,
1433 struct wimlib_update_command *command,
1437 tchar *command_name;
1439 size_t num_nonoptions;
1441 /* Get the command name ("add", "delete", "rename") */
1442 ret = parse_string(&line, &len, &command_name);
1443 if (ret != PARSE_STRING_SUCCESS)
1446 if (!tstrcasecmp(command_name, T("add"))) {
1447 op = WIMLIB_UPDATE_OP_ADD;
1448 } else if (!tstrcasecmp(command_name, T("delete"))) {
1449 op = WIMLIB_UPDATE_OP_DELETE;
1450 } else if (!tstrcasecmp(command_name, T("rename"))) {
1451 op = WIMLIB_UPDATE_OP_RENAME;
1453 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1454 command_name, line_number);
1459 /* Parse additional options and non-options as needed */
1464 ret = parse_string(&line, &len, &next_string);
1465 if (ret == PARSE_STRING_NONE) /* End of line */
1467 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1469 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1471 if (!update_command_add_option(op, next_string, command))
1473 imagex_error(T("Unrecognized option \"%"TS"\" to "
1474 "update command \"%"TS"\" on line %zu"),
1475 next_string, command_name, line_number);
1481 if (num_nonoptions == update_command_num_nonoptions[op])
1483 imagex_error(T("Unexpected argument \"%"TS"\" in "
1484 "update command on line %zu\n"
1485 " (The \"%"TS"\" command only "
1486 "takes %zu nonoption arguments!)\n"),
1487 next_string, line_number,
1488 command_name, num_nonoptions);
1491 update_command_add_nonoption(op, next_string,
1492 command, num_nonoptions);
1497 if (num_nonoptions != update_command_num_nonoptions[op]) {
1498 imagex_error(T("Not enough arguments to update command "
1499 "\"%"TS"\" on line %zu"), command_name, line_number);
1505 static struct wimlib_update_command *
1506 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1507 size_t *num_cmds_ret)
1511 struct wimlib_update_command *cmds;
1514 nlines = text_file_count_lines(cmd_file_contents_p,
1519 /* Always allocate at least 1 slot, just in case the implementation of
1520 * calloc() returns NULL if 0 bytes are requested. */
1521 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1523 imagex_error(T("out of memory"));
1526 p = *cmd_file_contents_p;
1528 for (i = 0; i < nlines; i++) {
1529 /* XXX: Could use rawmemchr() here instead, but it may not be
1530 * available on all platforms. */
1531 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1532 size_t len = endp - p + 1;
1534 if (!is_comment_line(p, len)) {
1535 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1546 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1547 * one image from a WIM file to an NTFS volume. */
1549 imagex_apply(int argc, tchar **argv, int cmd)
1553 int image = WIMLIB_NO_IMAGE;
1555 struct wimlib_wim_info info;
1557 const tchar *wimfile;
1558 const tchar *target;
1559 const tchar *image_num_or_name = NULL;
1560 int extract_flags = 0;
1562 STRING_LIST(refglobs);
1564 for_opt(c, apply_options) {
1566 case IMAGEX_CHECK_OPTION:
1567 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1569 case IMAGEX_VERBOSE_OPTION:
1570 /* No longer does anything. */
1572 case IMAGEX_REF_OPTION:
1573 ret = string_list_append(&refglobs, optarg);
1575 goto out_free_refglobs;
1577 case IMAGEX_UNIX_DATA_OPTION:
1578 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1580 case IMAGEX_NO_ACLS_OPTION:
1581 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1583 case IMAGEX_STRICT_ACLS_OPTION:
1584 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1586 case IMAGEX_NO_ATTRIBUTES_OPTION:
1587 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1589 case IMAGEX_NORPFIX_OPTION:
1590 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1592 case IMAGEX_RPFIX_OPTION:
1593 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1595 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1596 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1597 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1599 case IMAGEX_WIMBOOT_OPTION:
1600 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1602 case IMAGEX_COMPACT_OPTION:
1603 ret = set_compact_mode(optarg, &extract_flags);
1605 goto out_free_refglobs;
1607 case IMAGEX_RECOVER_DATA_OPTION:
1608 extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
1616 if (argc != 2 && argc != 3)
1621 if (!tstrcmp(wimfile, T("-"))) {
1622 /* Attempt to apply pipable WIM from standard input. */
1624 image_num_or_name = NULL;
1627 image_num_or_name = argv[1];
1632 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1633 imagex_progress_func, NULL);
1635 goto out_free_refglobs;
1637 wimlib_get_wim_info(wim, &info);
1640 /* Image explicitly specified. */
1641 image_num_or_name = argv[1];
1642 image = wimlib_resolve_image(wim, image_num_or_name);
1643 ret = verify_image_exists(image, image_num_or_name, wimfile);
1645 goto out_wimlib_free;
1648 /* No image specified; default to image 1, but only if the WIM
1649 * contains exactly one image. */
1651 if (info.image_count != 1) {
1652 imagex_error(T("\"%"TS"\" contains %d images; "
1653 "Please select one (or all)."),
1654 wimfile, info.image_count);
1663 if (refglobs.num_strings) {
1665 imagex_error(T("Can't specify --ref when applying from stdin!"));
1667 goto out_wimlib_free;
1669 ret = wim_reference_globs(wim, &refglobs, open_flags);
1671 goto out_wimlib_free;
1676 /* Interpret a regular file or block device target as an NTFS
1680 if (tstat(target, &stbuf)) {
1681 if (errno != ENOENT) {
1682 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1685 goto out_wimlib_free;
1688 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1689 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1695 ret = wimlib_extract_image(wim, image, target, extract_flags);
1697 set_fd_to_binary_mode(STDIN_FILENO);
1698 ret = wimlib_extract_image_from_pipe_with_progress(
1703 imagex_progress_func,
1707 imagex_printf(T("Done applying WIM image.\n"));
1708 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1710 do_resource_not_found_warning(wimfile, &info, &refglobs);
1712 imagex_error(T( "If you are applying an image "
1713 "from a split pipable WIM,\n"
1714 " make sure you have "
1715 "concatenated together all parts."));
1717 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1718 do_metadata_not_found_warning(wimfile, &info);
1723 string_list_destroy(&refglobs);
1727 usage(CMD_APPLY, stderr);
1729 goto out_free_refglobs;
1733 * Create a WIM image from a directory tree, NTFS volume, or multiple files or
1734 * directory trees. 'wimcapture': create a new WIM file containing the desired
1735 * image. 'wimappend': add a new image to an existing WIM file; or, with
1736 * '--create' behave like 'wimcapture' if the WIM file doesn't exist.
1739 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1742 bool create = false;
1743 bool appending = (cmd == CMD_APPEND);
1745 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1746 WIMLIB_ADD_FLAG_WINCONFIG |
1747 WIMLIB_ADD_FLAG_VERBOSE |
1748 WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1749 int write_flags = 0;
1750 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1751 uint32_t chunk_size = UINT32_MAX;
1752 uint32_t solid_chunk_size = UINT32_MAX;
1753 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1754 const tchar *wimfile;
1757 STRING_LIST(image_properties);
1760 STRING_LIST(base_wimfiles);
1761 WIMStruct **base_wims;
1763 WIMStruct *template_wim = NULL;
1764 const tchar *template_wimfile = NULL;
1765 const tchar *template_image_name_or_num = NULL;
1766 int template_image = WIMLIB_NO_IMAGE;
1769 unsigned num_threads = 0;
1774 tchar *config_file = NULL;
1776 bool source_list = false;
1777 size_t source_list_nchars = 0;
1778 tchar *source_list_contents;
1779 bool capture_sources_malloced;
1780 struct wimlib_capture_source *capture_sources;
1782 bool name_defaulted;
1784 for_opt(c, capture_or_append_options) {
1786 case IMAGEX_BOOT_OPTION:
1787 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1789 case IMAGEX_CHECK_OPTION:
1790 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1792 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
1793 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1795 case IMAGEX_NOCHECK_OPTION:
1796 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1798 case IMAGEX_CONFIG_OPTION:
1799 config_file = optarg;
1800 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1802 case IMAGEX_COMPRESS_OPTION:
1803 compression_type = get_compression_type(optarg, false);
1804 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1807 case IMAGEX_CHUNK_SIZE_OPTION:
1808 chunk_size = parse_chunk_size(optarg);
1809 if (chunk_size == UINT32_MAX)
1812 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1813 solid_chunk_size = parse_chunk_size(optarg);
1814 if (solid_chunk_size == UINT32_MAX)
1817 case IMAGEX_SOLID_COMPRESS_OPTION:
1818 solid_ctype = get_compression_type(optarg, true);
1819 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1822 case IMAGEX_SOLID_OPTION:
1823 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1825 case IMAGEX_NO_SOLID_SORT_OPTION:
1826 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1828 case IMAGEX_FLAGS_OPTION: {
1829 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1830 tsprintf(p, T("FLAGS=%"TS), optarg);
1831 ret = string_list_append(&image_properties, p);
1836 case IMAGEX_IMAGE_PROPERTY_OPTION:
1837 ret = append_image_property_argument(&image_properties);
1841 case IMAGEX_DEREFERENCE_OPTION:
1842 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1844 case IMAGEX_VERBOSE_OPTION:
1845 /* No longer does anything. */
1847 case IMAGEX_THREADS_OPTION:
1848 num_threads = parse_num_threads(optarg);
1849 if (num_threads == UINT_MAX)
1852 case IMAGEX_REBUILD_OPTION:
1853 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1855 case IMAGEX_UNIX_DATA_OPTION:
1856 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1858 case IMAGEX_SOURCE_LIST_OPTION:
1861 case IMAGEX_NO_ACLS_OPTION:
1862 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1864 case IMAGEX_STRICT_ACLS_OPTION:
1865 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1867 case IMAGEX_RPFIX_OPTION:
1868 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1870 case IMAGEX_NORPFIX_OPTION:
1871 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1873 case IMAGEX_PIPABLE_OPTION:
1874 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1876 case IMAGEX_NOT_PIPABLE_OPTION:
1877 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1879 case IMAGEX_UPDATE_OF_OPTION:
1880 if (template_image_name_or_num) {
1881 imagex_error(T("'--update-of' can only be "
1882 "specified one time!"));
1886 colon = tstrrchr(optarg, T(':'));
1889 template_wimfile = optarg;
1891 template_image_name_or_num = colon + 1;
1893 template_wimfile = NULL;
1894 template_image_name_or_num = optarg;
1898 imagex_printf(T("[WARNING] '--update-of' is unreliable on Windows!\n"));
1901 case IMAGEX_DELTA_FROM_OPTION:
1902 ret = string_list_append(&base_wimfiles, optarg);
1905 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1907 case IMAGEX_WIMBOOT_OPTION:
1908 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1910 case IMAGEX_UNSAFE_COMPACT_OPTION:
1911 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
1913 case IMAGEX_SNAPSHOT_OPTION:
1914 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
1916 case IMAGEX_CREATE_OPTION:
1917 if (cmd == CMD_CAPTURE) {
1918 imagex_error(T("'--create' is only valid for 'wimappend', not 'wimcapture'"));
1930 if (argc < 2 || argc > 4)
1936 /* Set default compression type and parameters. */
1939 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1940 /* No compression type specified. Use the default. */
1942 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1943 /* With --wimboot, default to XPRESS compression. */
1944 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1945 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1946 /* With --solid, default to LZMS compression. (However,
1947 * this will not affect solid resources!) */
1948 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1950 /* Otherwise, default to LZX compression. */
1951 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1955 if (!tstrcmp(wimfile, T("-"))) {
1956 /* Writing captured WIM to standard output. */
1960 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1961 imagex_error("Can't write a non-pipable WIM to "
1962 "standard output! Specify --pipable\n"
1963 " if you want to create a pipable WIM "
1964 "(but read the docs first).");
1968 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1971 imagex_error(T("Using standard output for append does "
1972 "not make sense."));
1975 wim_fd = STDOUT_FILENO;
1977 imagex_output_to_stderr();
1978 set_fd_to_binary_mode(wim_fd);
1982 /* Check for 'wimappend --create' acting as wimcapture */
1983 if (create && tstat(wimfile, &stbuf) != 0 && errno == ENOENT) {
1987 /* Ignore '--update-of' for the target WIMFILE */
1988 if (template_image_name_or_num &&
1989 (!template_wimfile ||
1990 !tstrcmp(template_wimfile, wimfile)))
1992 template_image_name_or_num = NULL;
1993 template_wimfile = NULL;
1998 if ((write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) && !appending) {
1999 imagex_error(T("'--unsafe-compact' is only valid for append!"));
2003 /* If template image was specified using --update-of=IMAGE rather
2004 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2005 if (template_image_name_or_num && !template_wimfile) {
2006 if (base_wimfiles.num_strings == 1) {
2007 /* Capturing delta WIM based on single WIM: default to
2009 template_wimfile = base_wimfiles.strings[0];
2010 } else if (appending) {
2011 /* Appending to WIM: default to WIM being appended to.
2013 template_wimfile = wimfile;
2015 /* Capturing a normal (non-delta) WIM, so the WIM file
2016 * *must* be explicitly specified. */
2017 if (base_wimfiles.num_strings > 1) {
2018 imagex_error(T("For capture of delta WIM "
2019 "based on multiple existing "
2021 " '--update-of' must "
2022 "specify WIMFILE:IMAGE!"));
2024 imagex_error(T("For capture of non-delta WIM, "
2025 "'--update-of' must specify "
2034 name_defaulted = false;
2036 /* Set default name to SOURCE argument, omitting any directory
2037 * prefixes and trailing slashes. This requires making a copy
2038 * of @source. Leave some free characters at the end in case we
2039 * append a number to keep the name unique. */
2040 size_t source_name_len;
2042 source_name_len = tstrlen(source);
2043 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2044 name = tbasename(tstrcpy(source_copy, source));
2045 name_defaulted = true;
2048 /* Image description (if given). */
2050 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2051 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2052 ret = string_list_append(&image_properties, p);
2058 /* Set up capture sources in source list mode */
2059 if (wimlib_load_text_file(source, &source_list_contents,
2060 &source_list_nchars) != 0)
2063 capture_sources = parse_source_list(&source_list_contents,
2066 if (!capture_sources) {
2068 goto out_free_source_list_contents;
2070 capture_sources_malloced = true;
2072 /* Set up capture source in non-source-list mode. */
2073 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2074 capture_sources[0].fs_source_path = source;
2075 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2076 capture_sources[0].reserved = 0;
2078 capture_sources_malloced = false;
2079 source_list_contents = NULL;
2082 /* Open the existing WIM, or create a new one. */
2084 ret = wimlib_open_wim_with_progress(wimfile,
2085 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2087 imagex_progress_func,
2090 goto out_free_capture_sources;
2092 ret = wimlib_create_new_wim(compression_type, &wim);
2094 goto out_free_capture_sources;
2095 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2098 /* Set chunk size if non-default. */
2099 if (chunk_size != UINT32_MAX) {
2100 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2103 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2105 int ctype = compression_type;
2108 struct wimlib_wim_info info;
2109 wimlib_get_wim_info(wim, &info);
2110 ctype = info.compression_type;
2113 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2114 ret = wimlib_set_output_chunk_size(wim, 4096);
2119 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2120 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2124 if (solid_chunk_size != UINT32_MAX) {
2125 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2131 /* Detect if source is regular file or block device and set NTFS volume
2136 if (tstat(source, &stbuf) == 0) {
2137 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2138 imagex_printf(T("Capturing WIM image from NTFS "
2139 "filesystem on \"%"TS"\"\n"), source);
2140 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2143 if (errno != ENOENT) {
2144 imagex_error_with_errno(T("Failed to stat "
2145 "\"%"TS"\""), source);
2153 /* If the user did not specify an image name, and the basename of the
2154 * source already exists as an image name in the WIM file, append a
2155 * suffix to make it unique. */
2156 if (appending && name_defaulted) {
2157 unsigned long conflict_idx;
2158 tchar *name_end = tstrchr(name, T('\0'));
2159 for (conflict_idx = 1;
2160 wimlib_image_name_in_use(wim, name);
2163 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2167 /* If capturing a delta WIM, reference resources from the base WIMs
2168 * before adding the new image. */
2169 if (base_wimfiles.num_strings) {
2170 base_wims = calloc(base_wimfiles.num_strings,
2171 sizeof(base_wims[0]));
2172 if (base_wims == NULL) {
2173 imagex_error(T("Out of memory!"));
2178 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2179 ret = wimlib_open_wim_with_progress(
2180 base_wimfiles.strings[i], open_flags,
2181 &base_wims[i], imagex_progress_func, NULL);
2183 goto out_free_base_wims;
2187 ret = wimlib_reference_resources(wim, base_wims,
2188 base_wimfiles.num_strings, 0);
2190 goto out_free_base_wims;
2192 if (base_wimfiles.num_strings == 1) {
2193 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2194 base_wimfiles.strings[0]);
2196 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2197 base_wimfiles.num_strings);
2204 /* If capturing or appending as an update of an existing (template) image,
2205 * open the WIM if needed and parse the image index. */
2206 if (template_image_name_or_num) {
2208 if (appending && !tstrcmp(template_wimfile, wimfile)) {
2211 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2212 if (!tstrcmp(template_wimfile,
2213 base_wimfiles.strings[i])) {
2214 template_wim = base_wims[i];
2220 if (!template_wim) {
2221 ret = wimlib_open_wim_with_progress(template_wimfile,
2224 imagex_progress_func,
2227 goto out_free_base_wims;
2230 template_image = wimlib_resolve_image(template_wim,
2231 template_image_name_or_num);
2233 if (template_image_name_or_num[0] == T('-')) {
2236 struct wimlib_wim_info info;
2238 wimlib_get_wim_info(template_wim, &info);
2239 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2240 if (n >= 1 && n <= info.image_count &&
2242 tmp != template_image_name_or_num + 1)
2244 template_image = info.image_count - (n - 1);
2247 ret = verify_image_exists_and_is_single(template_image,
2248 template_image_name_or_num,
2251 goto out_free_template_wim;
2254 ret = wimlib_add_image_multisource(wim,
2261 goto out_free_template_wim;
2263 if (image_properties.num_strings || template_image_name_or_num) {
2264 /* User asked to set additional image properties, or an image on
2265 * which the added one is to be based has been specified with
2267 struct wimlib_wim_info info;
2269 wimlib_get_wim_info(wim, &info);
2271 ret = apply_image_properties(&image_properties, wim,
2272 info.image_count, NULL);
2274 goto out_free_template_wim;
2276 /* Reference template image if the user provided one. */
2277 if (template_image_name_or_num) {
2278 imagex_printf(T("Using image %d "
2279 "from \"%"TS"\" as template\n"),
2280 template_image, template_wimfile);
2281 ret = wimlib_reference_template_image(wim,
2287 goto out_free_template_wim;
2291 /* Write the new WIM or overwrite the existing WIM with the new image
2294 ret = wimlib_overwrite(wim, write_flags, num_threads);
2295 } else if (wimfile) {
2296 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2297 write_flags, num_threads);
2299 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2300 write_flags, num_threads);
2302 out_free_template_wim:
2303 /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
2304 if (template_wim == wim)
2305 goto out_free_base_wims;
2306 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2307 if (template_wim == base_wims[i])
2308 goto out_free_base_wims;
2309 wimlib_free(template_wim);
2311 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2312 wimlib_free(base_wims[i]);
2316 out_free_capture_sources:
2317 if (capture_sources_malloced)
2318 free(capture_sources);
2319 out_free_source_list_contents:
2320 free(source_list_contents);
2322 string_list_destroy(&image_properties);
2323 string_list_destroy(&base_wimfiles);
2333 /* Remove image(s) from a WIM. */
2335 imagex_delete(int argc, tchar **argv, int cmd)
2338 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2339 int write_flags = 0;
2340 const tchar *wimfile;
2341 const tchar *image_num_or_name;
2346 for_opt(c, delete_options) {
2348 case IMAGEX_CHECK_OPTION:
2349 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2351 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2352 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2354 case IMAGEX_SOFT_OPTION:
2355 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2357 case IMAGEX_UNSAFE_COMPACT_OPTION:
2358 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2369 imagex_error(T("Must specify a WIM file"));
2371 imagex_error(T("Must specify an image"));
2375 image_num_or_name = argv[1];
2377 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2378 imagex_progress_func, NULL);
2382 image = wimlib_resolve_image(wim, image_num_or_name);
2384 ret = verify_image_exists(image, image_num_or_name, wimfile);
2386 goto out_wimlib_free;
2388 ret = wimlib_delete_image(wim, image);
2390 imagex_error(T("Failed to delete image from \"%"TS"\""),
2392 goto out_wimlib_free;
2395 ret = wimlib_overwrite(wim, write_flags, 0);
2397 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2398 "deleted"), wimfile);
2406 usage(CMD_DELETE, stderr);
2411 struct print_dentry_options {
2416 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2418 tprintf(T("%"TS"\n"), dentry->full_path);
2421 static const struct {
2424 } file_attr_flags[] = {
2425 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2426 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2427 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2428 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2429 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2430 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2431 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2432 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2433 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2434 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2435 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2436 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2437 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2438 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2439 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2442 #define TIMESTR_MAX 100
2445 print_time(const tchar *type, const struct wimlib_timespec *wts,
2448 tchar timestr[TIMESTR_MAX];
2452 if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2453 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2458 tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2459 timestr[TIMESTR_MAX - 1] = '\0';
2461 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2464 static void print_byte_field(const uint8_t field[], size_t len)
2467 tprintf(T("%02hhx"), *field++);
2471 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2473 tchar attr_string[256];
2476 tputs(T("WIM Information:"));
2477 tputs(T("----------------"));
2478 tprintf(T("Path: %"TS"\n"), wimfile);
2479 tprintf(T("GUID: 0x"));
2480 print_byte_field(info->guid, sizeof(info->guid));
2482 tprintf(T("Version: %u\n"), info->wim_version);
2483 tprintf(T("Image Count: %d\n"), info->image_count);
2484 tprintf(T("Compression: %"TS"\n"),
2485 wimlib_get_compression_type_string(info->compression_type));
2486 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2488 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2489 tprintf(T("Boot Index: %d\n"), info->boot_index);
2490 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2492 attr_string[0] = T('\0');
2495 tstrcat(attr_string, T("Pipable, "));
2497 if (info->has_integrity_table)
2498 tstrcat(attr_string, T("Integrity info, "));
2500 if (info->has_rpfix)
2501 tstrcat(attr_string, T("Relative path junction, "));
2503 if (info->resource_only)
2504 tstrcat(attr_string, T("Resource only, "));
2506 if (info->metadata_only)
2507 tstrcat(attr_string, T("Metadata only, "));
2509 if (info->is_marked_readonly)
2510 tstrcat(attr_string, T("Readonly, "));
2512 p = tstrchr(attr_string, T('\0'));
2513 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2516 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2520 print_resource(const struct wimlib_resource_entry *resource,
2523 tprintf(T("Hash = 0x"));
2524 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2527 if (!resource->is_missing) {
2528 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2529 resource->uncompressed_size);
2530 if (resource->packed) {
2531 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2532 "bytes @ offset %"PRIu64"\n"),
2533 resource->raw_resource_uncompressed_size,
2534 resource->raw_resource_compressed_size,
2535 resource->raw_resource_offset_in_wim);
2537 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2540 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2541 resource->compressed_size);
2543 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2547 tprintf(T("Part Number = %u\n"), resource->part_number);
2548 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2550 tprintf(T("Flags = "));
2551 if (resource->is_compressed)
2552 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2553 if (resource->is_metadata)
2554 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2555 if (resource->is_free)
2556 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2557 if (resource->is_spanned)
2558 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2559 if (resource->packed)
2560 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2568 print_blobs(WIMStruct *wim)
2570 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2575 default_print_security_descriptor(const uint8_t *sd, size_t size)
2577 tprintf(T("Security Descriptor = "));
2578 print_byte_field(sd, size);
2584 is_null_guid(const uint8_t *guid)
2586 static const uint8_t null_guid[WIMLIB_GUID_LEN];
2588 return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2592 print_guid(const tchar *label, const uint8_t *guid)
2594 if (is_null_guid(guid))
2596 tprintf(T("%-20"TS"= 0x"), label);
2597 print_byte_field(guid, WIMLIB_GUID_LEN);
2602 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2605 "----------------------------------------------------------------------------\n"));
2606 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2607 if (dentry->dos_name)
2608 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2609 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2610 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2611 if (file_attr_flags[i].flag & dentry->attributes)
2612 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2613 file_attr_flags[i].name);
2615 if (dentry->security_descriptor) {
2616 print_security_descriptor(dentry->security_descriptor,
2617 dentry->security_descriptor_size);
2620 print_time(T("Creation Time"),
2621 &dentry->creation_time, dentry->creation_time_high);
2622 print_time(T("Last Write Time"),
2623 &dentry->last_write_time, dentry->last_write_time_high);
2624 print_time(T("Last Access Time"),
2625 &dentry->last_access_time, dentry->last_access_time_high);
2628 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2629 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2631 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2632 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2634 if (dentry->unix_mode != 0) {
2635 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2636 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2637 dentry->unix_uid, dentry->unix_gid,
2638 dentry->unix_mode, dentry->unix_rdev);
2641 if (!is_null_guid(dentry->object_id.object_id)) {
2642 print_guid(T("Object ID"), dentry->object_id.object_id);
2643 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2644 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2645 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2648 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2649 if (dentry->streams[i].stream_name) {
2650 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2651 dentry->streams[i].stream_name);
2652 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2653 tprintf(T("\tRaw encrypted data stream:\n"));
2654 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2655 tprintf(T("\tReparse point stream:\n"));
2657 tprintf(T("\tUnnamed data stream:\n"));
2659 print_resource(&dentry->streams[i].resource, NULL);
2664 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2666 const struct print_dentry_options *options = _options;
2667 if (!options->detailed)
2668 print_dentry_full_path(dentry);
2670 print_dentry_detailed(dentry);
2674 /* Print the files contained in an image(s) in a WIM file. */
2676 imagex_dir(int argc, tchar **argv, int cmd)
2678 const tchar *wimfile;
2679 WIMStruct *wim = NULL;
2682 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2684 struct print_dentry_options options = {
2687 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2689 STRING_LIST(refglobs);
2691 for_opt(c, dir_options) {
2693 case IMAGEX_PATH_OPTION:
2696 case IMAGEX_DETAILED_OPTION:
2697 options.detailed = true;
2699 case IMAGEX_ONE_FILE_ONLY_OPTION:
2700 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2702 case IMAGEX_REF_OPTION:
2703 ret = string_list_append(&refglobs, optarg);
2705 goto out_free_refglobs;
2715 imagex_error(T("Must specify a WIM file"));
2719 imagex_error(T("Too many arguments"));
2724 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2725 imagex_progress_func, NULL);
2727 goto out_free_refglobs;
2730 image = wimlib_resolve_image(wim, argv[1]);
2731 ret = verify_image_exists(image, argv[1], wimfile);
2733 goto out_wimlib_free;
2735 /* No image specified; default to image 1, but only if the WIM
2736 * contains exactly one image. */
2738 struct wimlib_wim_info info;
2740 wimlib_get_wim_info(wim, &info);
2741 if (info.image_count != 1) {
2742 imagex_error(T("\"%"TS"\" contains %d images; Please "
2743 "select one (or all)."),
2744 wimfile, info.image_count);
2751 if (refglobs.num_strings) {
2752 ret = wim_reference_globs(wim, &refglobs, 0);
2754 goto out_wimlib_free;
2757 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2758 print_dentry, &options);
2759 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2760 struct wimlib_wim_info info;
2762 wimlib_get_wim_info(wim, &info);
2763 do_metadata_not_found_warning(wimfile, &info);
2768 string_list_destroy(&refglobs);
2772 usage(CMD_DIR, stderr);
2774 goto out_free_refglobs;
2777 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2780 imagex_export(int argc, tchar **argv, int cmd)
2784 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2785 int write_flags = 0;
2786 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2787 const tchar *src_wimfile;
2788 const tchar *src_image_num_or_name;
2789 const tchar *dest_wimfile;
2791 const tchar *dest_name;
2792 const tchar *dest_desc;
2794 struct wimlib_wim_info src_info;
2795 WIMStruct *dest_wim;
2800 STRING_LIST(refglobs);
2801 unsigned num_threads = 0;
2802 uint32_t chunk_size = UINT32_MAX;
2803 uint32_t solid_chunk_size = UINT32_MAX;
2804 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2806 for_opt(c, export_options) {
2808 case IMAGEX_BOOT_OPTION:
2809 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2811 case IMAGEX_CHECK_OPTION:
2812 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2814 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2815 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2817 case IMAGEX_NOCHECK_OPTION:
2818 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2820 case IMAGEX_COMPRESS_OPTION:
2821 compression_type = get_compression_type(optarg, false);
2822 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2825 case IMAGEX_RECOMPRESS_OPTION:
2826 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2828 case IMAGEX_SOLID_OPTION:
2829 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2831 case IMAGEX_NO_SOLID_SORT_OPTION:
2832 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2834 case IMAGEX_CHUNK_SIZE_OPTION:
2835 chunk_size = parse_chunk_size(optarg);
2836 if (chunk_size == UINT32_MAX)
2839 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2840 solid_chunk_size = parse_chunk_size(optarg);
2841 if (solid_chunk_size == UINT32_MAX)
2844 case IMAGEX_SOLID_COMPRESS_OPTION:
2845 solid_ctype = get_compression_type(optarg, true);
2846 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2849 case IMAGEX_REF_OPTION:
2850 ret = string_list_append(&refglobs, optarg);
2852 goto out_free_refglobs;
2854 case IMAGEX_THREADS_OPTION:
2855 num_threads = parse_num_threads(optarg);
2856 if (num_threads == UINT_MAX)
2859 case IMAGEX_REBUILD_OPTION:
2860 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2862 case IMAGEX_PIPABLE_OPTION:
2863 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2865 case IMAGEX_NOT_PIPABLE_OPTION:
2866 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2868 case IMAGEX_WIMBOOT_OPTION:
2869 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2871 case IMAGEX_UNSAFE_COMPACT_OPTION:
2872 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2880 if (argc < 3 || argc > 5)
2882 src_wimfile = argv[0];
2883 src_image_num_or_name = argv[1];
2884 dest_wimfile = argv[2];
2885 dest_name = (argc >= 4) ? argv[3] : NULL;
2886 dest_desc = (argc >= 5) ? argv[4] : NULL;
2887 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2888 imagex_progress_func, NULL);
2890 goto out_free_refglobs;
2892 wimlib_get_wim_info(src_wim, &src_info);
2894 /* Determine if the destination is an existing file or not. If so, we
2895 * try to append the exported image(s) to it; otherwise, we create a new
2896 * WIM containing the exported image(s). Furthermore, determine if we
2897 * need to write a pipable WIM directly to standard output. */
2899 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2901 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2902 imagex_error("Can't write a non-pipable WIM to "
2903 "standard output! Specify --pipable\n"
2904 " if you want to create a pipable WIM "
2905 "(but read the docs first).");
2907 goto out_free_src_wim;
2910 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2912 dest_wimfile = NULL;
2913 dest_wim_fd = STDOUT_FILENO;
2914 imagex_output_to_stderr();
2915 set_fd_to_binary_mode(dest_wim_fd);
2918 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2920 /* Destination file exists. */
2922 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
2923 imagex_error(T("\"%"TS"\" is not a regular file "
2924 "or block device"), dest_wimfile);
2926 goto out_free_src_wim;
2928 ret = wimlib_open_wim_with_progress(dest_wimfile,
2930 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2932 imagex_progress_func,
2935 goto out_free_src_wim;
2937 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2938 /* The user specified a compression type, but we're
2939 * exporting to an existing WIM. Make sure the
2940 * specified compression type is the same as the
2941 * compression type of the existing destination WIM. */
2942 struct wimlib_wim_info dest_info;
2944 wimlib_get_wim_info(dest_wim, &dest_info);
2945 if (compression_type != dest_info.compression_type) {
2946 imagex_error(T("Cannot specify a compression type that is "
2947 "not the same as that used in the "
2948 "destination WIM"));
2950 goto out_free_dest_wim;
2956 if (errno != ENOENT) {
2957 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2960 goto out_free_src_wim;
2963 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
2964 imagex_error(T("'--unsafe-compact' is only valid when "
2965 "exporting to an existing WIM file!"));
2967 goto out_free_src_wim;
2970 /* dest_wimfile is not an existing file, so create a new WIM. */
2972 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2973 /* The user did not specify a compression type; default
2974 * to that of the source WIM, unless --solid or
2975 * --wimboot was specified. */
2977 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2978 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2979 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2980 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2982 compression_type = src_info.compression_type;
2984 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2986 goto out_free_src_wim;
2988 wimlib_register_progress_function(dest_wim,
2989 imagex_progress_func, NULL);
2991 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2992 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2994 /* For --wimboot export, use small XPRESS chunks. */
2995 wimlib_set_output_chunk_size(dest_wim, 4096);
2996 } else if (compression_type == src_info.compression_type &&
2997 chunk_size == UINT32_MAX)
2999 /* Use same chunk size if compression type is the same. */
3000 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3004 if (chunk_size != UINT32_MAX) {
3005 /* Set destination chunk size. */
3006 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3008 goto out_free_dest_wim;
3010 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3011 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3013 goto out_free_dest_wim;
3015 if (solid_chunk_size != UINT32_MAX) {
3016 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3018 goto out_free_dest_wim;
3021 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3022 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3024 goto out_free_dest_wim;
3026 if (refglobs.num_strings) {
3027 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3029 goto out_free_dest_wim;
3032 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3033 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3035 imagex_error(T("--boot specified for all-images export, but source WIM "
3036 "has no bootable image."));
3038 goto out_free_dest_wim;
3041 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3042 dest_desc, export_flags);
3044 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3045 do_resource_not_found_warning(src_wimfile,
3046 &src_info, &refglobs);
3047 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3048 do_metadata_not_found_warning(src_wimfile, &src_info);
3050 goto out_free_dest_wim;
3054 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3055 else if (dest_wimfile)
3056 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3057 write_flags, num_threads);
3059 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3060 WIMLIB_ALL_IMAGES, write_flags,
3063 wimlib_free(dest_wim);
3065 wimlib_free(src_wim);
3067 string_list_destroy(&refglobs);
3071 usage(CMD_EXPORT, stderr);
3074 goto out_free_refglobs;
3077 /* Extract files or directories from a WIM image */
3079 imagex_extract(int argc, tchar **argv, int cmd)
3086 const tchar *wimfile;
3087 const tchar *image_num_or_name;
3088 tchar *dest_dir = T(".");
3089 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3090 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3091 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3092 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3094 STRING_LIST(refglobs);
3096 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3098 for_opt(c, extract_options) {
3100 case IMAGEX_CHECK_OPTION:
3101 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3103 case IMAGEX_VERBOSE_OPTION:
3104 /* No longer does anything. */
3106 case IMAGEX_REF_OPTION:
3107 ret = string_list_append(&refglobs, optarg);
3109 goto out_free_refglobs;
3111 case IMAGEX_UNIX_DATA_OPTION:
3112 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3114 case IMAGEX_NO_ACLS_OPTION:
3115 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3117 case IMAGEX_STRICT_ACLS_OPTION:
3118 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3120 case IMAGEX_NO_ATTRIBUTES_OPTION:
3121 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3123 case IMAGEX_DEST_DIR_OPTION:
3126 case IMAGEX_TO_STDOUT_OPTION:
3127 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3128 imagex_suppress_output();
3129 set_fd_to_binary_mode(STDOUT_FILENO);
3131 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3132 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3133 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3135 case IMAGEX_NO_GLOBS_OPTION:
3136 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3138 case IMAGEX_NULLGLOB_OPTION:
3139 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3141 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3142 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3144 case IMAGEX_WIMBOOT_OPTION:
3145 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3147 case IMAGEX_COMPACT_OPTION:
3148 ret = set_compact_mode(optarg, &extract_flags);
3150 goto out_free_refglobs;
3152 case IMAGEX_RECOVER_DATA_OPTION:
3153 extract_flags |= WIMLIB_EXTRACT_FLAG_RECOVER_DATA;
3165 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3166 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3168 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3173 image_num_or_name = argv[1];
3178 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3179 imagex_progress_func, NULL);
3181 goto out_free_refglobs;
3183 image = wimlib_resolve_image(wim, image_num_or_name);
3184 ret = verify_image_exists_and_is_single(image,
3188 goto out_wimlib_free;
3190 if (refglobs.num_strings) {
3191 ret = wim_reference_globs(wim, &refglobs, open_flags);
3193 goto out_wimlib_free;
3199 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3202 while (argc != 0 && ret == 0) {
3206 num_paths < argc && argv[num_paths][0] != T('@');
3211 ret = wimlib_extract_paths(wim, image, dest_dir,
3212 (const tchar **)argv,
3214 extract_flags | notlist_extract_flags);
3218 const tchar *listfile = argv[0] + 1;
3220 if (!tstrcmp(listfile, T("-"))) {
3221 tputs(T("Reading pathlist file from standard input..."));
3225 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3226 listfile, extract_flags);
3233 imagex_printf(T("Done extracting files.\n"));
3234 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3235 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3236 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3237 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3238 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3241 T("Note: You can use the '--nullglob' "
3242 "option to ignore missing files.\n"));
3244 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3245 "files and directories\n"
3246 " are in the WIM image.\n"),
3247 get_cmd_string(CMD_DIR, false));
3248 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3249 struct wimlib_wim_info info;
3251 wimlib_get_wim_info(wim, &info);
3252 do_resource_not_found_warning(wimfile, &info, &refglobs);
3253 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3254 struct wimlib_wim_info info;
3256 wimlib_get_wim_info(wim, &info);
3257 do_metadata_not_found_warning(wimfile, &info);
3262 string_list_destroy(&refglobs);
3266 usage(CMD_EXTRACT, stderr);
3269 goto out_free_refglobs;
3272 /* Prints information about a WIM file; also can mark an image as bootable,
3273 * change the name of an image, or change the description of an image. */
3275 imagex_info(int argc, tchar **argv, int cmd)
3279 bool header = false;
3282 bool short_header = true;
3283 const tchar *xml_out_file = NULL;
3284 const tchar *wimfile;
3285 const tchar *image_num_or_name;
3286 STRING_LIST(image_properties);
3291 int write_flags = 0;
3292 struct wimlib_wim_info info;
3294 for_opt(c, info_options) {
3296 case IMAGEX_BOOT_OPTION:
3299 case IMAGEX_CHECK_OPTION:
3300 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3302 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3303 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3305 case IMAGEX_NOCHECK_OPTION:
3306 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3308 case IMAGEX_HEADER_OPTION:
3310 short_header = false;
3312 case IMAGEX_BLOBS_OPTION:
3314 short_header = false;
3316 case IMAGEX_XML_OPTION:
3318 short_header = false;
3320 case IMAGEX_EXTRACT_XML_OPTION:
3321 xml_out_file = optarg;
3322 short_header = false;
3324 case IMAGEX_IMAGE_PROPERTY_OPTION:
3325 ret = append_image_property_argument(&image_properties);
3336 if (argc < 1 || argc > 4)
3340 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3344 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3345 tsprintf(p, T("NAME=%"TS), argv[2]);
3346 ret = string_list_append(&image_properties, p);
3353 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3354 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3355 ret = string_list_append(&image_properties, p);
3360 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3361 imagex_progress_func, NULL);
3365 wimlib_get_wim_info(wim, &info);
3367 image = wimlib_resolve_image(wim, image_num_or_name);
3368 ret = WIMLIB_ERR_INVALID_IMAGE;
3369 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3370 verify_image_exists(image, image_num_or_name, wimfile);
3372 imagex_error(T("If you would like to set the boot "
3373 "index to 0, specify image \"0\" with "
3374 "the --boot flag."));
3376 goto out_wimlib_free;
3379 if (boot && info.image_count == 0) {
3380 imagex_error(T("--boot is meaningless on a WIM with no images"));
3381 goto out_wimlib_free;
3384 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3386 imagex_error(T("Cannot specify the --boot flag "
3387 "without specifying a specific "
3388 "image in a multi-image WIM"));
3389 goto out_wimlib_free;
3391 if (image_properties.num_strings) {
3392 imagex_error(T("Can't change image properties without "
3393 "specifying a specific image in a "
3394 "multi-image WIM"));
3395 goto out_wimlib_free;
3399 /* Operations that print information are separated from operations that
3400 * recreate the WIM file. */
3401 if (!image_properties.num_strings && !boot) {
3403 /* Read-only operations */
3405 if (image == WIMLIB_NO_IMAGE) {
3406 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3407 image_num_or_name, wimfile);
3408 goto out_wimlib_free;
3411 if (image == WIMLIB_ALL_IMAGES && short_header)
3412 print_wim_information(wimfile, &info);
3415 wimlib_print_header(wim);
3418 if (info.total_parts != 1) {
3419 tfprintf(stderr, T("Warning: Only showing the blobs "
3420 "for part %d of a %d-part WIM.\n"),
3421 info.part_number, info.total_parts);
3427 ret = wimlib_extract_xml_data(wim, stdout);
3429 goto out_wimlib_free;
3435 fp = tfopen(xml_out_file, T("wb"));
3437 imagex_error_with_errno(T("Failed to open the "
3438 "file \"%"TS"\" for "
3442 goto out_wimlib_free;
3444 ret = wimlib_extract_xml_data(wim, fp);
3446 imagex_error(T("Failed to close the file "
3452 goto out_wimlib_free;
3456 wimlib_print_available_images(wim, image);
3460 /* Modification operations */
3461 bool any_property_changes;
3463 if (image == WIMLIB_ALL_IMAGES)
3466 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3467 imagex_error(T("Cannot change image properties "
3468 "when using image 0"));
3470 goto out_wimlib_free;
3474 if (image == info.boot_index) {
3475 imagex_printf(T("Image %d is already marked as "
3476 "bootable.\n"), image);
3479 imagex_printf(T("Marking image %d as bootable.\n"),
3481 info.boot_index = image;
3482 ret = wimlib_set_wim_info(wim, &info,
3483 WIMLIB_CHANGE_BOOT_INDEX);
3485 goto out_wimlib_free;
3489 ret = apply_image_properties(&image_properties, wim, image,
3490 &any_property_changes);
3492 goto out_wimlib_free;
3494 /* Only call wimlib_overwrite() if something actually needs to
3496 if (boot || any_property_changes ||
3497 ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
3498 !info.has_integrity_table) ||
3499 ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
3500 info.has_integrity_table))
3502 ret = wimlib_overwrite(wim, write_flags, 1);
3504 imagex_printf(T("The file \"%"TS"\" was not modified "
3505 "because nothing needed to be done.\n"),
3513 string_list_destroy(&image_properties);
3517 usage(CMD_INFO, stderr);
3522 /* Join split WIMs into one part WIM */
3524 imagex_join(int argc, tchar **argv, int cmd)
3527 int swm_open_flags = 0;
3528 int wim_write_flags = 0;
3529 const tchar *output_path;
3532 for_opt(c, join_options) {
3534 case IMAGEX_CHECK_OPTION:
3535 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3537 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3538 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3548 imagex_error(T("Must specify one or more split WIM (.swm) "
3552 output_path = argv[0];
3553 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3558 imagex_progress_func,
3564 usage(CMD_JOIN, stderr);
3569 #if WIM_MOUNTING_SUPPORTED
3571 /* Mounts a WIM image. */
3573 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3576 int mount_flags = 0;
3578 const tchar *staging_dir = NULL;
3579 const tchar *wimfile;
3582 struct wimlib_wim_info info;
3586 STRING_LIST(refglobs);
3588 if (cmd == CMD_MOUNTRW) {
3589 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3590 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3593 for_opt(c, mount_options) {
3595 case IMAGEX_ALLOW_OTHER_OPTION:
3596 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3598 case IMAGEX_CHECK_OPTION:
3599 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3601 case IMAGEX_DEBUG_OPTION:
3602 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3604 case IMAGEX_STREAMS_INTERFACE_OPTION:
3605 if (!tstrcasecmp(optarg, T("none")))
3606 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3607 else if (!tstrcasecmp(optarg, T("xattr")))
3608 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3609 else if (!tstrcasecmp(optarg, T("windows")))
3610 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3612 imagex_error(T("Unknown stream interface \"%"TS"\""),
3617 case IMAGEX_REF_OPTION:
3618 ret = string_list_append(&refglobs, optarg);
3620 goto out_free_refglobs;
3622 case IMAGEX_STAGING_DIR_OPTION:
3623 staging_dir = optarg;
3625 case IMAGEX_UNIX_DATA_OPTION:
3626 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3634 if (argc != 2 && argc != 3)
3639 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3640 imagex_progress_func, NULL);
3642 goto out_free_refglobs;
3644 wimlib_get_wim_info(wim, &info);
3647 /* Image explicitly specified. */
3648 image = wimlib_resolve_image(wim, argv[1]);
3650 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3654 /* No image specified; default to image 1, but only if the WIM
3655 * contains exactly one image. */
3657 if (info.image_count != 1) {
3658 imagex_error(T("\"%"TS"\" contains %d images; Please "
3659 "select one."), wimfile, info.image_count);
3667 if (refglobs.num_strings) {
3668 ret = wim_reference_globs(wim, &refglobs, open_flags);
3673 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3675 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3676 do_metadata_not_found_warning(wimfile, &info);
3678 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3680 image, wimfile, dir);
3686 string_list_destroy(&refglobs);
3692 goto out_free_refglobs;
3694 #endif /* WIM_MOUNTING_SUPPORTED */
3696 /* Rebuild a WIM file */
3698 imagex_optimize(int argc, tchar **argv, int cmd)
3701 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3702 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3703 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3704 uint32_t chunk_size = UINT32_MAX;
3705 uint32_t solid_chunk_size = UINT32_MAX;
3706 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3709 struct wimlib_wim_info info;
3710 const tchar *wimfile;
3713 unsigned num_threads = 0;
3715 for_opt(c, optimize_options) {
3717 case IMAGEX_CHECK_OPTION:
3718 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3720 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3721 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3723 case IMAGEX_NOCHECK_OPTION:
3724 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3726 case IMAGEX_COMPRESS_OPTION:
3727 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3728 compression_type = get_compression_type(optarg, false);
3729 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3732 case IMAGEX_RECOMPRESS_OPTION:
3733 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3735 case IMAGEX_CHUNK_SIZE_OPTION:
3736 chunk_size = parse_chunk_size(optarg);
3737 if (chunk_size == UINT32_MAX)
3740 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3741 solid_chunk_size = parse_chunk_size(optarg);
3742 if (solid_chunk_size == UINT32_MAX)
3745 case IMAGEX_SOLID_COMPRESS_OPTION:
3746 solid_ctype = get_compression_type(optarg, true);
3747 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3750 case IMAGEX_SOLID_OPTION:
3751 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3752 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3753 /* Reset the non-solid compression type to LZMS. */
3754 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3755 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3757 case IMAGEX_NO_SOLID_SORT_OPTION:
3758 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3760 case IMAGEX_THREADS_OPTION:
3761 num_threads = parse_num_threads(optarg);
3762 if (num_threads == UINT_MAX)
3765 case IMAGEX_PIPABLE_OPTION:
3766 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3768 case IMAGEX_NOT_PIPABLE_OPTION:
3769 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3771 case IMAGEX_UNSAFE_COMPACT_OPTION:
3772 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3786 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3787 imagex_progress_func, NULL);
3791 wimlib_get_wim_info(wim, &info);
3793 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID &&
3794 compression_type != info.compression_type) {
3795 /* Change compression type. */
3796 ret = wimlib_set_output_compression_type(wim, compression_type);
3798 goto out_wimlib_free;
3800 /* Reset the chunk size. */
3801 if (chunk_size == UINT32_MAX)
3805 if (chunk_size != UINT32_MAX) {
3806 /* Change chunk size. */
3807 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3809 goto out_wimlib_free;
3811 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3812 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3814 goto out_wimlib_free;
3816 if (solid_chunk_size != UINT32_MAX) {
3817 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3819 goto out_wimlib_free;
3822 old_size = file_get_size(wimfile);
3823 tprintf(T("\"%"TS"\" original size: "), wimfile);
3825 tputs(T("Unknown"));
3827 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3829 ret = wimlib_overwrite(wim, write_flags, num_threads);
3831 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3832 goto out_wimlib_free;
3835 new_size = file_get_size(wimfile);
3836 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3838 tputs(T("Unknown"));
3840 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3842 tfputs(T("Space saved: "), stdout);
3843 if (new_size != -1 && old_size != -1) {
3844 tprintf(T("%lld KiB\n"),
3845 ((long long)old_size - (long long)new_size) >> 10);
3847 tputs(T("Unknown"));
3856 usage(CMD_OPTIMIZE, stderr);
3862 /* Split a WIM into a spanned set */
3864 imagex_split(int argc, tchar **argv, int cmd)
3868 int write_flags = 0;
3874 for_opt(c, split_options) {
3876 case IMAGEX_CHECK_OPTION:
3877 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3879 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3880 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3892 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3893 if (tmp == argv[2] || *tmp) {
3894 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3895 imagex_error(T("The part size must be an integer or "
3896 "floating-point number of megabytes."));
3899 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3900 imagex_progress_func, NULL);
3904 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3906 tprintf(T("\nFinished splitting \"%"TS"\"\n"), argv[0]);
3912 usage(CMD_SPLIT, stderr);
3918 #if WIM_MOUNTING_SUPPORTED
3919 /* Unmounts a mounted WIM image. */
3921 imagex_unmount(int argc, tchar **argv, int cmd)
3924 int unmount_flags = 0;
3927 for_opt(c, unmount_options) {
3929 case IMAGEX_COMMIT_OPTION:
3930 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3932 case IMAGEX_CHECK_OPTION:
3933 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3935 case IMAGEX_REBUILD_OPTION:
3936 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3938 case IMAGEX_LAZY_OPTION:
3939 case IMAGEX_FORCE_OPTION:
3940 /* Now, unmount is lazy by default. However, committing
3941 * the image will fail with
3942 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3943 * file descriptors on the WIM image. The
3944 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3945 * descriptors to be closed. */
3946 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3948 case IMAGEX_NEW_IMAGE_OPTION:
3949 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3960 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3961 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3962 imagex_error(T("--new-image is meaningless "
3963 "without --commit also specified!"));
3968 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3969 imagex_progress_func, NULL);
3971 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3972 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3974 "\tNote: Use --commit --force to force changes "
3975 "to be committed, regardless\n"
3976 "\t of open files.\n"));
3983 usage(CMD_UNMOUNT, stderr);
3988 #endif /* WIM_MOUNTING_SUPPORTED */
3991 * Add, delete, or rename files in a WIM image.
3994 imagex_update(int argc, tchar **argv, int cmd)
3996 const tchar *wimfile;
4000 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4001 int write_flags = 0;
4002 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4003 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4004 WIMLIB_ADD_FLAG_VERBOSE |
4005 WIMLIB_ADD_FLAG_WINCONFIG;
4006 int default_delete_flags = 0;
4007 unsigned num_threads = 0;
4008 STRING_LIST(refglobs);
4010 tchar *cmd_file_contents;
4011 size_t cmd_file_nchars;
4012 struct wimlib_update_command *cmds;
4014 tchar *command_str = NULL;
4015 tchar *config_file = NULL;
4016 tchar *wimboot_config = NULL;
4018 for_opt(c, update_options) {
4020 /* Generic or write options */
4021 case IMAGEX_THREADS_OPTION:
4022 num_threads = parse_num_threads(optarg);
4023 if (num_threads == UINT_MAX)
4026 case IMAGEX_CHECK_OPTION:
4027 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4029 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4030 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4032 case IMAGEX_REBUILD_OPTION:
4033 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4035 case IMAGEX_COMMAND_OPTION:
4037 imagex_error(T("--command may only be specified "
4038 "one time. Please provide\n"
4039 " the update commands "
4040 "on standard input instead."));
4043 command_str = tstrdup(optarg);
4045 imagex_error(T("Out of memory!"));
4049 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4050 wimboot_config = optarg;
4052 case IMAGEX_REF_OPTION:
4053 ret = string_list_append(&refglobs, optarg);
4056 /* assume delta WIM */
4057 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
4059 /* Default delete options */
4060 case IMAGEX_FORCE_OPTION:
4061 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4063 case IMAGEX_RECURSIVE_OPTION:
4064 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4067 /* Global add option */
4068 case IMAGEX_CONFIG_OPTION:
4069 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4070 config_file = optarg;
4073 /* Default add options */
4074 case IMAGEX_VERBOSE_OPTION:
4075 /* No longer does anything. */
4077 case IMAGEX_DEREFERENCE_OPTION:
4078 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4080 case IMAGEX_UNIX_DATA_OPTION:
4081 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4083 case IMAGEX_NO_ACLS_OPTION:
4084 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4086 case IMAGEX_STRICT_ACLS_OPTION:
4087 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4089 case IMAGEX_NO_REPLACE_OPTION:
4090 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4092 case IMAGEX_UNSAFE_COMPACT_OPTION:
4093 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4102 if (argc != 1 && argc != 2)
4106 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4107 imagex_progress_func, NULL);
4112 /* Image explicitly specified. */
4113 image = wimlib_resolve_image(wim, argv[1]);
4114 ret = verify_image_exists_and_is_single(image, argv[1],
4117 goto out_wimlib_free;
4119 /* No image specified; default to image 1, but only if the WIM
4120 * contains exactly one image. */
4121 struct wimlib_wim_info info;
4123 wimlib_get_wim_info(wim, &info);
4124 if (info.image_count != 1) {
4125 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4126 wimfile, info.image_count);
4133 ret = wim_reference_globs(wim, &refglobs, open_flags);
4135 goto out_wimlib_free;
4137 /* Read update commands from standard input, or the command string if
4140 cmd_file_contents = NULL;
4141 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4145 goto out_free_cmd_file_contents;
4147 } else if (!wimboot_config) {
4148 if (isatty(STDIN_FILENO)) {
4149 tputs(T("Reading update commands from standard input..."));
4150 recommend_man_page(CMD_UPDATE, stdout);
4152 if (wimlib_load_text_file(NULL, &cmd_file_contents,
4153 &cmd_file_nchars) != 0) {
4155 goto out_wimlib_free;
4158 /* Parse the update commands */
4159 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4163 goto out_free_cmd_file_contents;
4166 cmd_file_contents = NULL;
4171 /* Set default flags and capture config on the update commands */
4172 for (size_t i = 0; i < num_cmds; i++) {
4173 switch (cmds[i].op) {
4174 case WIMLIB_UPDATE_OP_ADD:
4175 cmds[i].add.add_flags |= default_add_flags;
4176 cmds[i].add.config_file = config_file;
4178 case WIMLIB_UPDATE_OP_DELETE:
4179 cmds[i].delete_.delete_flags |= default_delete_flags;
4186 /* Execute the update commands */
4187 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4191 if (wimboot_config) {
4192 /* --wimboot-config=FILE is short for an
4193 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4195 struct wimlib_update_command cmd;
4197 cmd.op = WIMLIB_UPDATE_OP_ADD;
4198 cmd.add.fs_source_path = wimboot_config;
4199 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4200 cmd.add.config_file = NULL;
4201 cmd.add.add_flags = 0;
4203 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4208 /* Overwrite the updated WIM */
4209 ret = wimlib_overwrite(wim, write_flags, num_threads);
4212 out_free_cmd_file_contents:
4213 free(cmd_file_contents);
4218 string_list_destroy(&refglobs);
4222 usage(CMD_UPDATE, stderr);
4228 /* Verify a WIM file. */
4230 imagex_verify(int argc, tchar **argv, int cmd)
4233 const tchar *wimfile;
4235 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4236 int verify_flags = 0;
4237 STRING_LIST(refglobs);
4240 for_opt(c, verify_options) {
4242 case IMAGEX_REF_OPTION:
4243 ret = string_list_append(&refglobs, optarg);
4245 goto out_free_refglobs;
4247 case IMAGEX_NOCHECK_OPTION:
4248 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4260 imagex_error(T("Must specify a WIM file!"));
4262 imagex_error(T("At most one WIM file can be specified!"));
4268 ret = wimlib_open_wim_with_progress(wimfile,
4271 imagex_progress_func,
4274 goto out_free_refglobs;
4276 ret = wim_reference_globs(wim, &refglobs, open_flags);
4278 goto out_wimlib_free;
4280 ret = wimlib_verify_wim(wim, verify_flags);
4282 tputc(T('\n'), stderr);
4283 imagex_error(T("\"%"TS"\" failed verification!"),
4285 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4286 refglobs.num_strings == 0)
4288 imagex_printf(T("Note: if this WIM file is not standalone, "
4289 "use the --ref option to specify the other parts.\n"));
4292 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4299 string_list_destroy(&refglobs);
4303 usage(CMD_VERIFY, stderr);
4305 goto out_free_refglobs;
4308 struct imagex_command {
4310 int (*func)(int argc, tchar **argv, int cmd);
4313 static const struct imagex_command imagex_commands[] = {
4314 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4315 [CMD_APPLY] = {T("apply"), imagex_apply},
4316 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4317 [CMD_DELETE] = {T("delete"), imagex_delete},
4318 [CMD_DIR ] = {T("dir"), imagex_dir},
4319 [CMD_EXPORT] = {T("export"), imagex_export},
4320 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4321 [CMD_INFO] = {T("info"), imagex_info},
4322 [CMD_JOIN] = {T("join"), imagex_join},
4323 #if WIM_MOUNTING_SUPPORTED
4324 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4325 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4327 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4328 [CMD_SPLIT] = {T("split"), imagex_split},
4329 #if WIM_MOUNTING_SUPPORTED
4330 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4332 [CMD_UPDATE] = {T("update"), imagex_update},
4333 [CMD_VERIFY] = {T("verify"), imagex_verify},
4338 /* Can be a directory or source list file. But source list file is probably
4339 * a rare use case, so just say directory. */
4340 # define SOURCE_STR T("DIRECTORY")
4342 /* Can only be a directory */
4343 # define TARGET_STR T("DIRECTORY")
4346 /* Can be a directory, NTFS volume, or source list file. */
4347 # define SOURCE_STR T("SOURCE")
4349 /* Can be a directory or NTFS volume. */
4350 # define TARGET_STR T("TARGET")
4354 static const tchar * const usage_strings[] = {
4357 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4358 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4359 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4360 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4361 " [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4362 " [--dereference] [--snapshot] [--create]\n"
4366 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4367 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4368 " [--no-attributes] [--rpfix] [--norpfix]\n"
4369 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4370 " [--compact=FORMAT] [--recover-data]\n"
4374 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4375 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4376 " [--config=FILE] [--threads=NUM_THREADS]\n"
4377 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4378 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4379 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4384 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4388 " %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4392 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4393 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4394 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4395 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4396 " [--wimboot] [--solid]\n"
4400 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4401 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4402 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4403 " [--no-attributes] [--include-invalid-names] [--no-globs]\n"
4404 " [--nullglob] [--preserve-dir-structure] [--recover-data]\n"
4408 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4409 " [--boot] [--check] [--nocheck] [--xml]\n"
4410 " [--extract-xml FILE] [--header] [--blobs]\n"
4411 " [--image-property NAME=VALUE]\n"
4415 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4417 #if WIM_MOUNTING_SUPPORTED
4420 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4421 " [--check] [--streams-interface=INTERFACE]\n"
4422 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4426 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4427 " [--check] [--streams-interface=INTERFACE]\n"
4428 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4434 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4435 " [--check] [--nocheck] [--solid]\n"
4440 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4442 #if WIM_MOUNTING_SUPPORTED
4445 " %"TS" DIRECTORY\n"
4446 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4451 " %"TS" WIMFILE [IMAGE]\n"
4452 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4453 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4454 " [--command=STRING] [--wimboot-config=FILE]\n"
4459 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4463 static const tchar *invocation_name;
4464 static int invocation_cmd = CMD_NONE;
4466 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4468 static tchar buf[50];
4470 if (cmd == CMD_NONE)
4471 return T("wimlib-imagex");
4473 if (only_short_form || invocation_cmd != CMD_NONE) {
4474 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4476 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4477 imagex_commands[cmd].name);
4485 static const tchar * const fmt =
4487 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4488 "Copyright 2012-2023 Eric Biggers\n"
4489 "License GPLv3+; GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.\n"
4490 "This is free software: you are free to change and redistribute it.\n"
4491 "There is NO WARRANTY, to the extent permitted by law.\n"
4493 "Report bugs to "PACKAGE_BUGREPORT".\n"
4495 tfprintf(stdout, fmt, wimlib_get_version_string());
4499 do_common_options(int *argc_p, tchar **argv, int cmd)
4505 for (i = 1; i < argc; i++) {
4507 if (p[0] == T('-') && p[1] == T('-')) {
4509 if (!tstrcmp(p, T("help"))) {
4510 if (cmd == CMD_NONE)
4515 } else if (!tstrcmp(p, T("version"))) {
4518 } else if (!tstrcmp(p, T("quiet"))) {
4519 imagex_suppress_output();
4520 memmove(&argv[i], &argv[i + 1],
4521 (argc - i) * sizeof(argv[i]));
4524 } else if (!*p) /* reached "--", no more options */
4533 print_usage_string(int cmd, FILE *fp)
4535 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4539 recommend_man_page(int cmd, FILE *fp)
4541 const tchar *format_str;
4543 format_str = T("Some uncommon options are not listed;\n"
4544 "See %"TS".pdf in the doc directory for more details.\n");
4546 format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4548 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4552 usage(int cmd, FILE *fp)
4554 tfprintf(fp, T("Usage:\n"));
4555 print_usage_string(cmd, fp);
4556 tfprintf(fp, T("\n"));
4557 recommend_man_page(cmd, fp);
4563 tfprintf(fp, T("Usage:\n"));
4564 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4565 print_usage_string(cmd, fp);
4566 tfprintf(fp, T("\n"));
4568 static const tchar * const extra =
4571 " %"TS" --version\n"
4574 tfprintf(fp, extra, invocation_name, invocation_name);
4576 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4577 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4578 "For some commands IMAGE may be \"all\".\n"
4580 recommend_man_page(CMD_NONE, fp);
4584 int wmain(int argc, wchar_t **argv);
4588 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4589 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4590 * something else), while on Windows the command arguments will be UTF-16LE
4591 * encoded 'wchar_t' strings. */
4593 main(int argc, tchar **argv)
4599 imagex_info_file = stdout;
4600 invocation_name = tbasename(argv[0]);
4603 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4604 if (igcase != NULL) {
4605 if (!tstrcmp(igcase, T("no")) ||
4606 !tstrcmp(igcase, T("0")))
4607 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4608 else if (!tstrcmp(igcase, T("yes")) ||
4609 !tstrcmp(igcase, T("1")))
4610 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4613 "WARNING: Ignoring unknown setting of "
4614 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4619 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4621 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4622 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4623 for (int i = 0; i < CMD_MAX; i++) {
4624 if (!tstrcmp(invocation_name + 3,
4625 imagex_commands[i].name))
4634 /* Unless already known from the invocation name, determine which
4635 * command was specified. */
4636 if (cmd == CMD_NONE) {
4638 imagex_error(T("No command specified!\n"));
4642 for (int i = 0; i < CMD_MAX; i++) {
4643 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4648 if (cmd != CMD_NONE) {
4654 /* Handle common options. May exit early (for --help or --version). */
4655 do_common_options(&argc, argv, cmd);
4657 /* Bail if a valid command was not specified. */
4658 if (cmd == CMD_NONE) {
4659 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4664 /* Enable warning and error messages in wimlib to be more user-friendly.
4666 wimlib_set_print_errors(true);
4668 /* Initialize wimlib. */
4669 ret = wimlib_global_init(init_flags);
4671 goto out_check_status;
4673 /* Call the command handler function. */
4674 ret = imagex_commands[cmd].func(argc, argv, cmd);
4676 /* Check for error writing to standard output, especially since for some
4677 * commands, writing to standard output is part of the program's actual
4678 * behavior and not just for informational purposes. */
4679 if (ferror(stdout) || fclose(stdout)) {
4680 imagex_error_with_errno(T("error writing to standard output"));
4685 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4686 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4687 * error code from which an error message can be printed. */
4689 imagex_error(T("Exiting with error code %d:\n"
4691 wimlib_get_error_string(ret));
4692 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4693 imagex_error_with_errno(T("errno"));
4695 /* Make wimlib free any resources it's holding (although this is not
4696 * strictly necessary because the process is ending anyway). */
4697 wimlib_global_cleanup();