4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # include "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
147 IMAGEX_CHUNK_SIZE_OPTION,
148 IMAGEX_COMMAND_OPTION,
149 IMAGEX_COMMIT_OPTION,
150 IMAGEX_COMPACT_OPTION,
151 IMAGEX_COMPRESS_OPTION,
152 IMAGEX_COMPRESS_SLOW_OPTION,
153 IMAGEX_CONFIG_OPTION,
155 IMAGEX_DELTA_FROM_OPTION,
156 IMAGEX_DEREFERENCE_OPTION,
157 IMAGEX_DEST_DIR_OPTION,
158 IMAGEX_DETAILED_OPTION,
159 IMAGEX_EXTRACT_XML_OPTION,
162 IMAGEX_HEADER_OPTION,
163 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
165 IMAGEX_METADATA_OPTION,
166 IMAGEX_NEW_IMAGE_OPTION,
167 IMAGEX_NOCHECK_OPTION,
168 IMAGEX_NORPFIX_OPTION,
169 IMAGEX_NOT_PIPABLE_OPTION,
170 IMAGEX_NO_ACLS_OPTION,
171 IMAGEX_NO_ATTRIBUTES_OPTION,
172 IMAGEX_NO_GLOBS_OPTION,
173 IMAGEX_NO_REPLACE_OPTION,
174 IMAGEX_NO_SOLID_SORT_OPTION,
175 IMAGEX_NULLGLOB_OPTION,
176 IMAGEX_ONE_FILE_ONLY_OPTION,
178 IMAGEX_PIPABLE_OPTION,
179 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
180 IMAGEX_REBUILD_OPTION,
181 IMAGEX_RECOMPRESS_OPTION,
182 IMAGEX_RECURSIVE_OPTION,
184 IMAGEX_RESUME_OPTION,
187 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
188 IMAGEX_SOLID_COMPRESS_OPTION,
190 IMAGEX_SOURCE_LIST_OPTION,
191 IMAGEX_STAGING_DIR_OPTION,
192 IMAGEX_STREAMS_INTERFACE_OPTION,
193 IMAGEX_STRICT_ACLS_OPTION,
194 IMAGEX_THREADS_OPTION,
195 IMAGEX_TO_STDOUT_OPTION,
196 IMAGEX_UNIX_DATA_OPTION,
197 IMAGEX_UNSAFE_COMPACT_OPTION,
198 IMAGEX_UPDATE_OF_OPTION,
199 IMAGEX_VERBOSE_OPTION,
200 IMAGEX_WIMBOOT_CONFIG_OPTION,
201 IMAGEX_WIMBOOT_OPTION,
205 static const struct option apply_options[] = {
206 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
207 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
208 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
209 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
210 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
211 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
212 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
213 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
214 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
215 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
216 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
218 /* --resume is undocumented for now as it needs improvement. */
219 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
220 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
221 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
225 static const struct option capture_or_append_options[] = {
226 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
227 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
228 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
229 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
230 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
231 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
232 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
233 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
234 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
235 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
236 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
237 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
238 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
239 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
240 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
241 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
242 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
243 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
244 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
245 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
246 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
247 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
248 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
249 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
250 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
251 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
252 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
253 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
254 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
255 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
256 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
257 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
258 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
262 static const struct option delete_options[] = {
263 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
264 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
265 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
269 static const struct option dir_options[] = {
270 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
271 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
272 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
273 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
277 static const struct option export_options[] = {
278 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
279 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
280 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
281 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
282 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
283 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
284 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
285 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
286 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
287 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
288 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
289 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
290 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
291 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
292 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
293 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
294 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
295 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
296 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
297 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
298 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
299 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
303 static const struct option extract_options[] = {
304 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
305 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
306 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
307 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
308 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
309 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
310 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
311 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
312 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
313 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
314 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
315 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
316 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
317 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
318 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
319 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
320 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
324 static const struct option info_options[] = {
325 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
326 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
327 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
328 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
329 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
330 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
331 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
332 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
333 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
334 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
338 static const struct option join_options[] = {
339 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
343 static const struct option mount_options[] = {
344 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
345 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
346 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
347 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
348 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
349 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
350 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
354 static const struct option optimize_options[] = {
355 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
356 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
357 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
358 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
359 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
360 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
361 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
362 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
363 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
364 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
365 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
366 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
367 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
368 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
369 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
370 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
371 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
372 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
373 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
377 static const struct option split_options[] = {
378 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
382 static const struct option unmount_options[] = {
383 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
384 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
385 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
386 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
387 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
388 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
392 static const struct option update_options[] = {
393 /* Careful: some of the options here set the defaults for update
394 * commands, but the flags given to an actual update command (and not to
395 * `imagex update' itself are also handled in
396 * update_command_add_option(). */
397 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
398 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
399 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
400 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
401 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
403 /* Default delete options */
404 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
405 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
407 /* Global add option */
408 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
410 /* Default add options */
411 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
412 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
413 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
414 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
415 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
416 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
417 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
418 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
423 static const struct option verify_options[] = {
424 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
425 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
431 # define _format_attribute(type, format_str, args_start) \
432 __attribute__((format(type, format_str, args_start)))
434 # define _format_attribute(type, format_str, args_start)
437 /* Print formatted error message to stderr. */
438 static void _format_attribute(printf, 1, 2)
439 imagex_error(const tchar *format, ...)
442 va_start(va, format);
443 tfputs(T("ERROR: "), stderr);
444 tvfprintf(stderr, format, va);
445 tputc(T('\n'), stderr);
449 /* Print formatted error message to stderr. */
450 static void _format_attribute(printf, 1, 2)
451 imagex_error_with_errno(const tchar *format, ...)
453 int errno_save = errno;
455 va_start(va, format);
456 tfputs(T("ERROR: "), stderr);
457 tvfprintf(stderr, format, va);
458 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
463 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
465 if (image == WIMLIB_NO_IMAGE) {
466 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
467 " Please specify a 1-based image index or "
468 "image name. To list the images\n"
469 " contained in the WIM archive, run\n"
471 " %"TS" \"%"TS"\"\n"),
472 image_name, wim_name,
473 get_cmd_string(CMD_INFO, false), wim_name);
474 return WIMLIB_ERR_INVALID_IMAGE;
480 verify_image_is_single(int image)
482 if (image == WIMLIB_ALL_IMAGES) {
483 imagex_error(T("Cannot specify all images for this action!"));
484 return WIMLIB_ERR_INVALID_IMAGE;
490 verify_image_exists_and_is_single(int image, const tchar *image_name,
491 const tchar *wim_name)
494 ret = verify_image_exists(image, image_name, wim_name);
496 ret = verify_image_is_single(image);
501 print_available_compression_types(FILE *fp)
503 static const tchar *s =
505 "Available compression types:\n"
508 " xpress (alias: \"fast\")\n"
509 " lzx (alias: \"maximum\") (default for capture)\n"
510 " lzms (alias: \"recovery\")\n"
516 /* Parse the argument to --compress */
518 get_compression_type(tchar *optarg)
521 unsigned int compression_level = 0;
524 plevel = tstrchr(optarg, T(':'));
530 ultmp = tstrtoul(plevel, &ptmp, 10);
531 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
532 imagex_error(T("Compression level must be a positive integer! "
533 "e.g. --compress=lzx:80"));
534 return WIMLIB_COMPRESSION_TYPE_INVALID;
536 compression_level = ultmp;
539 if (!tstrcasecmp(optarg, T("maximum")) ||
540 !tstrcasecmp(optarg, T("lzx")) ||
541 !tstrcasecmp(optarg, T("max")))
542 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
543 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
544 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
545 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
546 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
547 else if (!tstrcasecmp(optarg, T("none")))
548 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
550 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
551 print_available_compression_types(stderr);
552 return WIMLIB_COMPRESSION_TYPE_INVALID;
555 if (compression_level != 0)
556 wimlib_set_default_compression_level(ctype, compression_level);
560 /* Parse the argument to --compact */
562 set_compact_mode(const tchar *arg, int *extract_flags)
565 if (!tstrcasecmp(arg, T("xpress4k")))
566 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
567 else if (!tstrcasecmp(arg, T("xpress8k")))
568 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
569 else if (!tstrcasecmp(arg, T("xpress16k")))
570 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
571 else if (!tstrcasecmp(arg, T("lzx")))
572 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
575 *extract_flags |= flag;
580 "\"%"TS"\" is not a recognized System Compression format. The options are:"
582 " --compact=xpress4k\n"
583 " --compact=xpress8k\n"
584 " --compact=xpress16k\n"
592 set_compress_slow(void)
595 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
596 " Use the '--compress=TYPE:LEVEL' option instead.\n");
598 wimlib_set_default_compression_level(-1, 100);
602 const tchar **strings;
603 unsigned num_strings;
604 unsigned num_alloc_strings;
607 #define STRING_SET_INITIALIZER \
608 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
610 #define STRING_SET(_strings) \
611 struct string_set _strings = STRING_SET_INITIALIZER
614 string_set_append(struct string_set *set, const tchar *glob)
616 unsigned num_alloc_strings = set->num_alloc_strings;
618 if (set->num_strings == num_alloc_strings) {
619 const tchar **new_strings;
621 num_alloc_strings += 4;
622 new_strings = realloc(set->strings,
623 sizeof(set->strings[0]) * num_alloc_strings);
625 imagex_error(T("Out of memory!"));
628 set->strings = new_strings;
629 set->num_alloc_strings = num_alloc_strings;
631 set->strings[set->num_strings++] = glob;
636 string_set_destroy(struct string_set *set)
642 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
644 return wimlib_reference_resource_files(wim, set->strings,
646 WIMLIB_REF_FLAG_GLOB_ENABLE,
651 do_resource_not_found_warning(const tchar *wimfile,
652 const struct wimlib_wim_info *info,
653 const struct string_set *refglobs)
655 if (info->total_parts > 1) {
656 if (refglobs->num_strings == 0) {
657 imagex_error(T("\"%"TS"\" is part of a split WIM. "
658 "Use --ref to specify the other parts."),
661 imagex_error(T("Perhaps the '--ref' argument did not "
662 "specify all other parts of the split "
666 imagex_error(T("If this is a delta WIM, use the --ref argument "
667 "to specify the WIM(s) on which it is based."));
672 do_metadata_not_found_warning(const tchar *wimfile,
673 const struct wimlib_wim_info *info)
675 if (info->part_number != 1) {
676 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
677 " You must specify the first part."),
682 /* Returns the size of a file given its name, or -1 if the file does not exist
683 * or its size cannot be determined. */
685 file_get_size(const tchar *filename)
688 if (tstat(filename, &st) == 0)
695 PARSE_STRING_SUCCESS = 0,
696 PARSE_STRING_FAILURE = 1,
697 PARSE_STRING_NONE = 2,
701 * Parses a string token from an array of characters.
703 * Tokens are either whitespace-delimited, or double or single-quoted.
705 * @line_p: Pointer to the pointer to the line of data. Will be updated
706 * to point past the string token iff the return value is
707 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
710 * @len_p: @len_p initially stores the length of the line of data, which may
711 * be 0, and it will be updated to the number of bytes remaining in
712 * the line iff the return value is PARSE_STRING_SUCCESS.
714 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
715 * parsed string token will be returned here.
717 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
718 * PARSE_STRING_FAILURE if the data was invalid due to a missing
719 * closing quote; or PARSE_STRING_NONE if the line ended before the
720 * beginning of a string token was found.
723 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
726 tchar *line = *line_p;
730 /* Skip leading whitespace */
733 return PARSE_STRING_NONE;
734 if (!istspace(*line) && *line != T('\0'))
740 if (quote_char == T('"') || quote_char == T('\'')) {
745 line = tmemchr(line, quote_char, len);
747 imagex_error(T("Missing closing quote: %"TS), fn - 1);
748 return PARSE_STRING_FAILURE;
751 /* Unquoted string. Go until whitespace. Line is terminated
752 * by '\0', so no need to check 'len'. */
756 } while (!istspace(*line) && *line != T('\0'));
763 return PARSE_STRING_SUCCESS;
766 /* Parses a line of data (not an empty line or comment) in the source list file
767 * format. (See the man page for 'wimlib-imagex capture' for details on this
768 * format and the meaning.)
770 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
771 * len == 0. The data in @line will be modified by this function call.
773 * @len: Length of the line of data.
775 * @source: On success, the capture source and target described by the line is
776 * written into this destination. Note that it will contain pointers
777 * to data in the @line array.
779 * Returns true if the line was valid; false otherwise. */
781 parse_source_list_line(tchar *line, size_t len,
782 struct wimlib_capture_source *source)
786 ret = parse_string(&line, &len, &source->fs_source_path);
787 if (ret != PARSE_STRING_SUCCESS)
789 ret = parse_string(&line, &len, &source->wim_target_path);
790 if (ret == PARSE_STRING_NONE)
791 source->wim_target_path = source->fs_source_path;
792 return ret != PARSE_STRING_FAILURE;
795 /* Returns %true if the given line of length @len > 0 is a comment or empty line
796 * in the source list file format. */
798 is_comment_line(const tchar *line, size_t len)
801 if (*line == T('#') || *line == T(';'))
803 if (!istspace(*line) && *line != T('\0'))
813 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
816 tchar *contents = *contents_p;
817 size_t nchars = *nchars_p;
820 for (i = 0; i < nchars; i++)
821 if (contents[i] == T('\n'))
824 /* Handle last line not terminated by a newline */
825 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
826 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
828 imagex_error(T("Out of memory!"));
831 contents[nchars] = T('\n');
832 *contents_p = contents;
840 /* Parses a file in the source list format. (See the man page for
841 * 'wimlib-imagex capture' for details on this format and the meaning.)
843 * @source_list_contents: Contents of the source list file. Note that this
844 * buffer will be modified to save memory allocations,
845 * and cannot be freed until the returned array of
846 * wimlib_capture_source's has also been freed.
848 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
851 * @nsources_ret: On success, the length of the returned array is
854 * Returns: An array of `struct wimlib_capture_source's that can be passed to
855 * the wimlib_add_image_multisource() function to specify how a WIM image is to
857 static struct wimlib_capture_source *
858 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
859 size_t *nsources_ret)
863 struct wimlib_capture_source *sources;
866 nlines = text_file_count_lines(source_list_contents_p,
867 &source_list_nchars);
871 /* Always allocate at least 1 slot, just in case the implementation of
872 * calloc() returns NULL if 0 bytes are requested. */
873 sources = calloc(nlines ?: 1, sizeof(*sources));
875 imagex_error(T("out of memory"));
878 p = *source_list_contents_p;
880 for (i = 0; i < nlines; i++) {
881 /* XXX: Could use rawmemchr() here instead, but it may not be
882 * available on all platforms. */
883 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
884 size_t len = endp - p + 1;
886 if (!is_comment_line(p, len)) {
887 if (!parse_source_list_line(p, len, &sources[j++])) {
899 /* Reads the contents of a file into memory. */
901 file_get_contents(const tchar *filename, size_t *len_ret)
908 if (tstat(filename, &stbuf) != 0) {
909 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
914 fp = tfopen(filename, T("rb"));
916 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
920 buf = malloc(len ? len : 1);
922 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
923 "contents of file \"%"TS"\""), len, filename);
926 if (fread(buf, 1, len, fp) != len) {
927 imagex_error_with_errno(T("Failed to read %zu bytes from the "
928 "file \"%"TS"\""), len, filename);
942 /* Read standard input until EOF and return the full contents in a malloc()ed
943 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
946 stdin_get_contents(size_t *len_ret)
948 /* stdin can, of course, be a pipe or other non-seekable file, so the
949 * total length of the data cannot be pre-determined */
951 size_t newlen = 1024;
955 char *p = realloc(buf, newlen);
956 size_t bytes_read, bytes_to_read;
958 imagex_error(T("out of memory while reading stdin"));
962 bytes_to_read = newlen - pos;
963 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
965 if (bytes_read != bytes_to_read) {
970 imagex_error_with_errno(T("error reading stdin"));
984 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
987 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
989 *num_tchars_ret = num_bytes;
991 #else /* !__WIN32__ */
992 /* On Windows, translate the text to UTF-16LE */
996 if (num_bytes >= 2 &&
997 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
998 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1000 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1001 * with something that looks like an ASCII character encoded as
1002 * a UTF-16LE code unit. Assume the file is encoded as
1003 * UTF-16LE. This is not a 100% reliable check. */
1004 num_wchars = num_bytes / 2;
1005 text_wstr = (wchar_t*)text;
1007 /* File does not look like UTF-16LE. Assume it is encoded in
1008 * the current Windows code page. I think these are always
1009 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1010 * should work as expected. */
1011 text_wstr = win32_mbs_to_wcs(text,
1016 *num_tchars_ret = num_wchars;
1018 #endif /* __WIN32__ */
1022 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1027 contents = file_get_contents(filename, &num_bytes);
1030 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1034 stdin_get_text_contents(size_t *num_tchars_ret)
1039 contents = stdin_get_contents(&num_bytes);
1042 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1045 #define TO_PERCENT(numerator, denominator) \
1046 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1048 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1049 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1050 #define KIBIBYTE_MIN_NBYTES 10000ULL
1053 get_unit(uint64_t total_bytes, const tchar **name_ret)
1055 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1056 *name_ret = T("GiB");
1058 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1059 *name_ret = T("MiB");
1061 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1062 *name_ret = T("KiB");
1065 *name_ret = T("bytes");
1070 static struct wimlib_progress_info_scan last_scan_progress;
1073 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1075 uint64_t prev_count, cur_count;
1077 prev_count = last_scan_progress.num_nondirs_scanned +
1078 last_scan_progress.num_dirs_scanned;
1079 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1081 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1082 cur_count % 128 == 0)
1084 unsigned unit_shift;
1085 const tchar *unit_name;
1087 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1088 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1089 "%"PRIu64" directories) "),
1090 scan->num_bytes_scanned >> unit_shift,
1092 scan->num_nondirs_scanned,
1093 scan->num_dirs_scanned);
1094 last_scan_progress = *scan;
1097 /* Progress callback function passed to various wimlib functions. */
1098 static enum wimlib_progress_status
1099 imagex_progress_func(enum wimlib_progress_msg msg,
1100 union wimlib_progress_info *info,
1101 void *_ignored_context)
1103 unsigned percent_done;
1104 unsigned unit_shift;
1105 const tchar *unit_name;
1107 if (imagex_be_quiet)
1108 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1110 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1112 static bool first = true;
1114 imagex_printf(T("Writing %"TS"-compressed data "
1115 "using %u thread%"TS"\n"),
1116 wimlib_get_compression_type_string(
1117 info->write_streams.compression_type),
1118 info->write_streams.num_threads,
1119 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1123 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1124 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1125 info->write_streams.total_bytes);
1127 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1128 "written (%u%% done)"),
1129 info->write_streams.completed_bytes >> unit_shift,
1131 info->write_streams.total_bytes >> unit_shift,
1134 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1135 imagex_printf(T("\n"));
1137 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1138 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1139 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1140 imagex_printf(T("\n"));
1142 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1143 info->scan.wim_target_path);
1145 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1147 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1148 switch (info->scan.status) {
1149 case WIMLIB_SCAN_DENTRY_OK:
1150 report_scan_progress(&info->scan, false);
1152 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1153 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1155 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1156 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1157 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1159 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1160 /* Symlink fixups are enabled by default. This is
1161 * mainly intended for Windows, which for some reason
1162 * uses absolute junctions (with drive letters!) in the
1163 * default installation. On UNIX-like systems, warn the
1164 * user when fixing the target of an absolute symbolic
1165 * link, so they know to disable this if they want. */
1167 imagex_printf(T("\nWARNING: Adjusted target of "
1168 "absolute symbolic link \"%"TS"\"\n"
1169 " (Use --norpfix to capture "
1170 "absolute symbolic links as-is)\n"),
1171 info->scan.cur_path);
1178 case WIMLIB_PROGRESS_MSG_SCAN_END:
1179 report_scan_progress(&info->scan, true);
1180 imagex_printf(T("\n"));
1182 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1183 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1184 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1185 info->integrity.total_bytes);
1186 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1187 "of %"PRIu64" %"TS" (%u%%) done"),
1188 info->integrity.filename,
1189 info->integrity.completed_bytes >> unit_shift,
1191 info->integrity.total_bytes >> unit_shift,
1194 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1195 imagex_printf(T("\n"));
1197 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1198 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1199 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1200 info->integrity.total_bytes);
1201 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1202 "of %"PRIu64" %"TS" (%u%%) done"),
1203 info->integrity.completed_bytes >> unit_shift,
1205 info->integrity.total_bytes >> unit_shift,
1208 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1209 imagex_printf(T("\n"));
1211 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1212 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1213 "to %"TS" \"%"TS"\"\n"),
1214 info->extract.image,
1215 info->extract.image_name,
1216 info->extract.wimfile_name,
1217 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1218 T("NTFS volume") : T("directory")),
1219 info->extract.target);
1221 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1222 if (info->extract.end_file_count >= 2000) {
1223 percent_done = TO_PERCENT(info->extract.current_file_count,
1224 info->extract.end_file_count);
1225 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1226 info->extract.current_file_count,
1227 info->extract.end_file_count, percent_done);
1228 if (info->extract.current_file_count == info->extract.end_file_count)
1229 imagex_printf(T("\n"));
1232 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1233 percent_done = TO_PERCENT(info->extract.completed_bytes,
1234 info->extract.total_bytes);
1235 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1236 imagex_printf(T("\rExtracting file data: "
1237 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1238 info->extract.completed_bytes >> unit_shift,
1240 info->extract.total_bytes >> unit_shift,
1243 if (info->extract.completed_bytes >= info->extract.total_bytes)
1244 imagex_printf(T("\n"));
1246 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1247 if (info->extract.end_file_count >= 2000) {
1248 percent_done = TO_PERCENT(info->extract.current_file_count,
1249 info->extract.end_file_count);
1250 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1251 info->extract.current_file_count,
1252 info->extract.end_file_count, percent_done);
1253 if (info->extract.current_file_count == info->extract.end_file_count)
1254 imagex_printf(T("\n"));
1257 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1258 if (info->extract.total_parts != 1) {
1259 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1260 info->extract.part_number,
1261 info->extract.total_parts);
1264 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1265 percent_done = TO_PERCENT(info->split.completed_bytes,
1266 info->split.total_bytes);
1267 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1268 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1269 "%"PRIu64" %"TS" (%u%%) written\n"),
1270 info->split.part_name,
1271 info->split.cur_part_number,
1272 info->split.total_parts,
1273 info->split.completed_bytes >> unit_shift,
1275 info->split.total_bytes >> unit_shift,
1279 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1280 if (info->split.completed_bytes == info->split.total_bytes) {
1281 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1282 info->split.cur_part_number,
1283 info->split.total_parts);
1286 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1287 switch (info->update.command->op) {
1288 case WIMLIB_UPDATE_OP_DELETE:
1289 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1290 info->update.command->delete_.wim_path);
1292 case WIMLIB_UPDATE_OP_RENAME:
1293 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1294 info->update.command->rename.wim_source_path,
1295 info->update.command->rename.wim_target_path);
1297 case WIMLIB_UPDATE_OP_ADD:
1302 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1303 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1304 info->replace.path_in_wim);
1306 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1307 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1308 info->wimboot_exclude.path_in_wim);
1310 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1311 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1312 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1313 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1314 info->unmount.mounted_wim,
1315 info->unmount.mounted_image);
1317 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1318 info->unmount.mounted_wim,
1319 info->unmount.mounted_image);
1320 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1324 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1325 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1326 info->verify_image.current_image,
1327 info->verify_image.total_images);
1329 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1330 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1331 info->verify_streams.total_bytes);
1332 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1333 imagex_printf(T("\rVerifying file data: "
1334 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1335 info->verify_streams.completed_bytes >> unit_shift,
1337 info->verify_streams.total_bytes >> unit_shift,
1340 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1341 imagex_printf(T("\n"));
1346 fflush(imagex_info_file);
1347 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1351 parse_num_threads(const tchar *optarg)
1354 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1355 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1356 imagex_error(T("Number of threads must be a non-negative integer!"));
1364 parse_chunk_size(const tchar *optarg)
1367 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1368 if (chunk_size == 0) {
1369 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1370 " with optional K, M, or G suffix"));
1374 if (*tmp == T('k') || *tmp == T('K')) {
1377 } else if (*tmp == T('m') || *tmp == T('M')) {
1380 } else if (*tmp == T('g') || *tmp == T('G')) {
1384 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1385 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1389 if (chunk_size >= UINT32_MAX) {
1390 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1398 * Parse an option passed to an update command.
1400 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1403 * @option: Text string for the option (beginning with --)
1405 * @cmd: `struct wimlib_update_command' that is being constructed for
1408 * Returns true if the option was recognized; false if not.
1411 update_command_add_option(int op, const tchar *option,
1412 struct wimlib_update_command *cmd)
1414 bool recognized = true;
1416 case WIMLIB_UPDATE_OP_ADD:
1417 if (!tstrcmp(option, T("--verbose")))
1418 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1419 else if (!tstrcmp(option, T("--unix-data")))
1420 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1421 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1422 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1423 else if (!tstrcmp(option, T("--strict-acls")))
1424 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1425 else if (!tstrcmp(option, T("--dereference")))
1426 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1427 else if (!tstrcmp(option, T("--no-replace")))
1428 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1432 case WIMLIB_UPDATE_OP_DELETE:
1433 if (!tstrcmp(option, T("--force")))
1434 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1435 else if (!tstrcmp(option, T("--recursive")))
1436 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1447 /* How many nonoption arguments each `imagex update' command expects */
1448 static const unsigned update_command_num_nonoptions[] = {
1449 [WIMLIB_UPDATE_OP_ADD] = 2,
1450 [WIMLIB_UPDATE_OP_DELETE] = 1,
1451 [WIMLIB_UPDATE_OP_RENAME] = 2,
1455 update_command_add_nonoption(int op, const tchar *nonoption,
1456 struct wimlib_update_command *cmd,
1457 unsigned num_nonoptions)
1460 case WIMLIB_UPDATE_OP_ADD:
1461 if (num_nonoptions == 0)
1462 cmd->add.fs_source_path = (tchar*)nonoption;
1464 cmd->add.wim_target_path = (tchar*)nonoption;
1466 case WIMLIB_UPDATE_OP_DELETE:
1467 cmd->delete_.wim_path = (tchar*)nonoption;
1469 case WIMLIB_UPDATE_OP_RENAME:
1470 if (num_nonoptions == 0)
1471 cmd->rename.wim_source_path = (tchar*)nonoption;
1473 cmd->rename.wim_target_path = (tchar*)nonoption;
1479 * Parse a command passed on stdin to `imagex update'.
1481 * @line: Text of the command.
1482 * @len: Length of the line, including a null terminator
1485 * @command: A `struct wimlib_update_command' to fill in from the parsed
1488 * @line_number: Line number of the command, for diagnostics.
1490 * Returns true on success; returns false on parse error.
1493 parse_update_command(tchar *line, size_t len,
1494 struct wimlib_update_command *command,
1498 tchar *command_name;
1500 size_t num_nonoptions;
1502 /* Get the command name ("add", "delete", "rename") */
1503 ret = parse_string(&line, &len, &command_name);
1504 if (ret != PARSE_STRING_SUCCESS)
1507 if (!tstrcasecmp(command_name, T("add"))) {
1508 op = WIMLIB_UPDATE_OP_ADD;
1509 } else if (!tstrcasecmp(command_name, T("delete"))) {
1510 op = WIMLIB_UPDATE_OP_DELETE;
1511 } else if (!tstrcasecmp(command_name, T("rename"))) {
1512 op = WIMLIB_UPDATE_OP_RENAME;
1514 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1515 command_name, line_number);
1520 /* Parse additional options and non-options as needed */
1525 ret = parse_string(&line, &len, &next_string);
1526 if (ret == PARSE_STRING_NONE) /* End of line */
1528 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1530 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1532 if (!update_command_add_option(op, next_string, command))
1534 imagex_error(T("Unrecognized option \"%"TS"\" to "
1535 "update command \"%"TS"\" on line %zu"),
1536 next_string, command_name, line_number);
1542 if (num_nonoptions == update_command_num_nonoptions[op])
1544 imagex_error(T("Unexpected argument \"%"TS"\" in "
1545 "update command on line %zu\n"
1546 " (The \"%"TS"\" command only "
1547 "takes %zu nonoption arguments!)\n"),
1548 next_string, line_number,
1549 command_name, num_nonoptions);
1552 update_command_add_nonoption(op, next_string,
1553 command, num_nonoptions);
1558 if (num_nonoptions != update_command_num_nonoptions[op]) {
1559 imagex_error(T("Not enough arguments to update command "
1560 "\"%"TS"\" on line %zu"), command_name, line_number);
1566 static struct wimlib_update_command *
1567 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1568 size_t *num_cmds_ret)
1572 struct wimlib_update_command *cmds;
1575 nlines = text_file_count_lines(cmd_file_contents_p,
1580 /* Always allocate at least 1 slot, just in case the implementation of
1581 * calloc() returns NULL if 0 bytes are requested. */
1582 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1584 imagex_error(T("out of memory"));
1587 p = *cmd_file_contents_p;
1589 for (i = 0; i < nlines; i++) {
1590 /* XXX: Could use rawmemchr() here instead, but it may not be
1591 * available on all platforms. */
1592 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1593 size_t len = endp - p + 1;
1595 if (!is_comment_line(p, len)) {
1596 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1607 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1608 * one image from a WIM file to an NTFS volume. */
1610 imagex_apply(int argc, tchar **argv, int cmd)
1614 int image = WIMLIB_NO_IMAGE;
1616 struct wimlib_wim_info info;
1618 const tchar *wimfile;
1619 const tchar *target;
1620 const tchar *image_num_or_name = NULL;
1621 int extract_flags = 0;
1623 STRING_SET(refglobs);
1625 for_opt(c, apply_options) {
1627 case IMAGEX_CHECK_OPTION:
1628 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1630 case IMAGEX_VERBOSE_OPTION:
1631 /* No longer does anything. */
1633 case IMAGEX_REF_OPTION:
1634 ret = string_set_append(&refglobs, optarg);
1636 goto out_free_refglobs;
1638 case IMAGEX_UNIX_DATA_OPTION:
1639 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1641 case IMAGEX_NO_ACLS_OPTION:
1642 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1644 case IMAGEX_STRICT_ACLS_OPTION:
1645 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1647 case IMAGEX_NO_ATTRIBUTES_OPTION:
1648 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1650 case IMAGEX_NORPFIX_OPTION:
1651 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1653 case IMAGEX_RPFIX_OPTION:
1654 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1656 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1657 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1658 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1660 case IMAGEX_RESUME_OPTION:
1661 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1663 case IMAGEX_WIMBOOT_OPTION:
1664 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1666 case IMAGEX_COMPACT_OPTION:
1667 ret = set_compact_mode(optarg, &extract_flags);
1669 goto out_free_refglobs;
1677 if (argc != 2 && argc != 3)
1682 if (!tstrcmp(wimfile, T("-"))) {
1683 /* Attempt to apply pipable WIM from standard input. */
1685 image_num_or_name = NULL;
1688 image_num_or_name = argv[1];
1693 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1694 imagex_progress_func, NULL);
1696 goto out_free_refglobs;
1698 wimlib_get_wim_info(wim, &info);
1701 /* Image explicitly specified. */
1702 image_num_or_name = argv[1];
1703 image = wimlib_resolve_image(wim, image_num_or_name);
1704 ret = verify_image_exists(image, image_num_or_name, wimfile);
1706 goto out_wimlib_free;
1709 /* No image specified; default to image 1, but only if the WIM
1710 * contains exactly one image. */
1712 if (info.image_count != 1) {
1713 imagex_error(T("\"%"TS"\" contains %d images; "
1714 "Please select one (or all)."),
1715 wimfile, info.image_count);
1724 if (refglobs.num_strings) {
1726 imagex_error(T("Can't specify --ref when applying from stdin!"));
1728 goto out_wimlib_free;
1730 ret = wim_reference_globs(wim, &refglobs, open_flags);
1732 goto out_wimlib_free;
1737 /* Interpret a regular file or block device target as an NTFS
1741 if (tstat(target, &stbuf)) {
1742 if (errno != ENOENT) {
1743 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1746 goto out_wimlib_free;
1749 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1750 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1756 ret = wimlib_extract_image(wim, image, target, extract_flags);
1758 set_fd_to_binary_mode(STDIN_FILENO);
1759 ret = wimlib_extract_image_from_pipe_with_progress(
1764 imagex_progress_func,
1768 imagex_printf(T("Done applying WIM image.\n"));
1769 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1771 do_resource_not_found_warning(wimfile, &info, &refglobs);
1773 imagex_error(T( "If you are applying an image "
1774 "from a split pipable WIM,\n"
1775 " make sure you have "
1776 "concatenated together all parts."));
1778 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1779 do_metadata_not_found_warning(wimfile, &info);
1784 string_set_destroy(&refglobs);
1788 usage(CMD_APPLY, stderr);
1790 goto out_free_refglobs;
1793 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1794 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1795 * the desired image. 'wimlib-imagex append': add a new image to an existing
1798 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1802 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1803 WIMLIB_ADD_FLAG_WINCONFIG |
1804 WIMLIB_ADD_FLAG_VERBOSE;
1805 int write_flags = 0;
1806 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1807 uint32_t chunk_size = UINT32_MAX;
1808 uint32_t solid_chunk_size = UINT32_MAX;
1809 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1810 const tchar *wimfile;
1814 const tchar *flags_element = NULL;
1817 STRING_SET(base_wimfiles);
1818 WIMStruct **base_wims;
1820 WIMStruct *template_wim;
1821 const tchar *template_wimfile = NULL;
1822 const tchar *template_image_name_or_num = NULL;
1823 int template_image = WIMLIB_NO_IMAGE;
1826 unsigned num_threads = 0;
1831 tchar *config_file = NULL;
1833 bool source_list = false;
1834 size_t source_list_nchars = 0;
1835 tchar *source_list_contents;
1836 bool capture_sources_malloced;
1837 struct wimlib_capture_source *capture_sources;
1839 bool name_defaulted;
1841 for_opt(c, capture_or_append_options) {
1843 case IMAGEX_BOOT_OPTION:
1844 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1846 case IMAGEX_CHECK_OPTION:
1847 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1848 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1850 case IMAGEX_NOCHECK_OPTION:
1851 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1853 case IMAGEX_CONFIG_OPTION:
1854 config_file = optarg;
1855 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1857 case IMAGEX_COMPRESS_OPTION:
1858 compression_type = get_compression_type(optarg);
1859 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1862 case IMAGEX_COMPRESS_SLOW_OPTION:
1863 set_compress_slow();
1865 case IMAGEX_CHUNK_SIZE_OPTION:
1866 chunk_size = parse_chunk_size(optarg);
1867 if (chunk_size == UINT32_MAX)
1870 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1871 solid_chunk_size = parse_chunk_size(optarg);
1872 if (solid_chunk_size == UINT32_MAX)
1875 case IMAGEX_SOLID_COMPRESS_OPTION:
1876 solid_ctype = get_compression_type(optarg);
1877 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1880 case IMAGEX_SOLID_OPTION:
1881 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1883 case IMAGEX_NO_SOLID_SORT_OPTION:
1884 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1886 case IMAGEX_FLAGS_OPTION:
1887 flags_element = optarg;
1889 case IMAGEX_DEREFERENCE_OPTION:
1890 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1892 case IMAGEX_VERBOSE_OPTION:
1893 /* No longer does anything. */
1895 case IMAGEX_THREADS_OPTION:
1896 num_threads = parse_num_threads(optarg);
1897 if (num_threads == UINT_MAX)
1900 case IMAGEX_REBUILD_OPTION:
1901 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1903 case IMAGEX_UNIX_DATA_OPTION:
1904 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1906 case IMAGEX_SOURCE_LIST_OPTION:
1909 case IMAGEX_NO_ACLS_OPTION:
1910 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1912 case IMAGEX_STRICT_ACLS_OPTION:
1913 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1915 case IMAGEX_RPFIX_OPTION:
1916 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1918 case IMAGEX_NORPFIX_OPTION:
1919 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1921 case IMAGEX_PIPABLE_OPTION:
1922 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1924 case IMAGEX_NOT_PIPABLE_OPTION:
1925 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1927 case IMAGEX_UPDATE_OF_OPTION:
1928 if (template_image_name_or_num) {
1929 imagex_error(T("'--update-of' can only be "
1930 "specified one time!"));
1934 colon = tstrrchr(optarg, T(':'));
1937 template_wimfile = optarg;
1939 template_image_name_or_num = colon + 1;
1941 template_wimfile = NULL;
1942 template_image_name_or_num = optarg;
1946 case IMAGEX_DELTA_FROM_OPTION:
1947 if (cmd != CMD_CAPTURE) {
1948 imagex_error(T("'--delta-from' is only "
1949 "valid for capture!"));
1952 ret = string_set_append(&base_wimfiles, optarg);
1954 goto out_free_base_wimfiles;
1955 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1957 case IMAGEX_WIMBOOT_OPTION:
1958 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1960 case IMAGEX_UNSAFE_COMPACT_OPTION:
1961 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
1970 if (argc < 2 || argc > 4)
1976 /* Set default compression type and parameters. */
1979 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1980 /* No compression type specified. Use the default. */
1982 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1983 /* With --wimboot, default to XPRESS compression. */
1984 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1985 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1986 /* With --solid, default to LZMS compression. (However,
1987 * this will not affect solid resources!) */
1988 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1990 /* Otherwise, default to LZX compression. */
1991 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1995 if (!tstrcmp(wimfile, T("-"))) {
1996 /* Writing captured WIM to standard output. */
1998 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1999 imagex_error("Can't write a non-pipable WIM to "
2000 "standard output! Specify --pipable\n"
2001 " if you want to create a pipable WIM "
2002 "(but read the docs first).");
2006 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2008 if (cmd == CMD_APPEND) {
2009 imagex_error(T("Using standard output for append does "
2010 "not make sense."));
2013 wim_fd = STDOUT_FILENO;
2015 imagex_info_file = stderr;
2016 set_fd_to_binary_mode(wim_fd);
2019 /* If template image was specified using --update-of=IMAGE rather
2020 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2021 if (template_image_name_or_num && !template_wimfile) {
2022 if (base_wimfiles.num_strings == 1) {
2023 /* Capturing delta WIM based on single WIM: default to
2025 template_wimfile = base_wimfiles.strings[0];
2026 } else if (cmd == CMD_APPEND) {
2027 /* Appending to WIM: default to WIM being appended to.
2029 template_wimfile = wimfile;
2031 /* Capturing a normal (non-delta) WIM, so the WIM file
2032 * *must* be explicitly specified. */
2033 if (base_wimfiles.num_strings > 1) {
2034 imagex_error(T("For capture of delta WIM "
2035 "based on multiple existing "
2037 " '--update-of' must "
2038 "specify WIMFILE:IMAGE!"));
2040 imagex_error(T("For capture of non-delta WIM, "
2041 "'--update-of' must specify "
2050 name_defaulted = false;
2052 /* Set default name to SOURCE argument, omitting any directory
2053 * prefixes and trailing slashes. This requires making a copy
2054 * of @source. Leave some free characters at the end in case we
2055 * append a number to keep the name unique. */
2056 size_t source_name_len;
2058 source_name_len = tstrlen(source);
2059 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2060 name = tbasename(tstrcpy(source_copy, source));
2061 name_defaulted = true;
2063 /* Image description defaults to NULL if not given. */
2070 /* Set up capture sources in source list mode */
2071 if (source[0] == T('-') && source[1] == T('\0')) {
2072 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2074 source_list_contents = file_get_text_contents(source,
2075 &source_list_nchars);
2077 if (!source_list_contents)
2080 capture_sources = parse_source_list(&source_list_contents,
2083 if (!capture_sources) {
2085 goto out_free_source_list_contents;
2087 capture_sources_malloced = true;
2089 /* Set up capture source in non-source-list mode. */
2090 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2091 capture_sources[0].fs_source_path = source;
2092 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2093 capture_sources[0].reserved = 0;
2095 capture_sources_malloced = false;
2096 source_list_contents = NULL;
2099 /* Open the existing WIM, or create a new one. */
2100 if (cmd == CMD_APPEND) {
2101 ret = wimlib_open_wim_with_progress(wimfile,
2102 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2104 imagex_progress_func,
2107 goto out_free_capture_sources;
2109 ret = wimlib_create_new_wim(compression_type, &wim);
2111 goto out_free_capture_sources;
2112 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2115 /* Set chunk size if non-default. */
2116 if (chunk_size != UINT32_MAX) {
2117 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2120 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2121 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2122 ret = wimlib_set_output_chunk_size(wim, 4096);
2126 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2127 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2131 if (solid_chunk_size != UINT32_MAX) {
2132 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2138 /* Detect if source is regular file or block device and set NTFS volume
2143 if (tstat(source, &stbuf) == 0) {
2144 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2145 imagex_printf(T("Capturing WIM image from NTFS "
2146 "filesystem on \"%"TS"\"\n"), source);
2147 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2150 if (errno != ENOENT) {
2151 imagex_error_with_errno(T("Failed to stat "
2152 "\"%"TS"\""), source);
2160 /* If the user did not specify an image name, and the basename of the
2161 * source already exists as an image name in the WIM file, append a
2162 * suffix to make it unique. */
2163 if (cmd == CMD_APPEND && name_defaulted) {
2164 unsigned long conflict_idx;
2165 tchar *name_end = tstrchr(name, T('\0'));
2166 for (conflict_idx = 1;
2167 wimlib_image_name_in_use(wim, name);
2170 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2174 /* If capturing a delta WIM, reference resources from the base WIMs
2175 * before adding the new image. */
2176 if (base_wimfiles.num_strings) {
2177 base_wims = calloc(base_wimfiles.num_strings,
2178 sizeof(base_wims[0]));
2179 if (base_wims == NULL) {
2180 imagex_error(T("Out of memory!"));
2185 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2186 ret = wimlib_open_wim_with_progress(
2187 base_wimfiles.strings[i], open_flags,
2188 &base_wims[i], imagex_progress_func, NULL);
2190 goto out_free_base_wims;
2194 ret = wimlib_reference_resources(wim, base_wims,
2195 base_wimfiles.num_strings, 0);
2197 goto out_free_base_wims;
2199 if (base_wimfiles.num_strings == 1) {
2200 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2201 base_wimfiles.strings[0]);
2203 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2204 base_wimfiles.num_strings);
2211 /* If capturing or appending as an update of an existing (template) image,
2212 * open the WIM if needed and parse the image index. */
2213 if (template_image_name_or_num) {
2216 if (base_wimfiles.num_strings == 1 &&
2217 template_wimfile == base_wimfiles.strings[0]) {
2218 template_wim = base_wims[0];
2219 } else if (template_wimfile == wimfile) {
2222 ret = wimlib_open_wim_with_progress(template_wimfile,
2225 imagex_progress_func,
2228 goto out_free_base_wims;
2231 template_image = wimlib_resolve_image(template_wim,
2232 template_image_name_or_num);
2234 if (template_image_name_or_num[0] == T('-')) {
2237 struct wimlib_wim_info info;
2239 wimlib_get_wim_info(template_wim, &info);
2240 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2241 if (n >= 1 && n <= info.image_count &&
2243 tmp != template_image_name_or_num + 1)
2245 template_image = info.image_count - (n - 1);
2248 ret = verify_image_exists_and_is_single(template_image,
2249 template_image_name_or_num,
2252 goto out_free_template_wim;
2254 template_wim = NULL;
2257 ret = wimlib_add_image_multisource(wim,
2264 goto out_free_template_wim;
2266 if (desc || flags_element || template_image_name_or_num) {
2267 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2268 * on which the added one is to be based has been specified with
2269 * --update-of. Get the index of the image we just
2270 * added, then use it to call the appropriate functions. */
2271 struct wimlib_wim_info info;
2273 wimlib_get_wim_info(wim, &info);
2276 ret = wimlib_set_image_descripton(wim,
2280 goto out_free_template_wim;
2283 if (flags_element) {
2284 ret = wimlib_set_image_flags(wim, info.image_count,
2287 goto out_free_template_wim;
2290 /* Reference template image if the user provided one. */
2291 if (template_image_name_or_num) {
2292 imagex_printf(T("Using image %d "
2293 "from \"%"TS"\" as template\n"),
2294 template_image, template_wimfile);
2295 ret = wimlib_reference_template_image(wim,
2301 goto out_free_template_wim;
2305 /* Write the new WIM or overwrite the existing WIM with the new image
2307 if (cmd == CMD_APPEND) {
2308 ret = wimlib_overwrite(wim, write_flags, num_threads);
2309 } else if (wimfile) {
2310 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2311 write_flags, num_threads);
2313 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2314 write_flags, num_threads);
2316 out_free_template_wim:
2317 /* template_wim may alias base_wims[0] or wim. */
2318 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2319 template_wim != wim)
2320 wimlib_free(template_wim);
2322 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2323 wimlib_free(base_wims[i]);
2327 out_free_capture_sources:
2328 if (capture_sources_malloced)
2329 free(capture_sources);
2330 out_free_source_list_contents:
2331 free(source_list_contents);
2332 out_free_base_wimfiles:
2333 string_set_destroy(&base_wimfiles);
2340 goto out_free_base_wimfiles;
2343 /* Remove image(s) from a WIM. */
2345 imagex_delete(int argc, tchar **argv, int cmd)
2348 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2349 int write_flags = 0;
2350 const tchar *wimfile;
2351 const tchar *image_num_or_name;
2356 for_opt(c, delete_options) {
2358 case IMAGEX_CHECK_OPTION:
2359 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2360 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2362 case IMAGEX_SOFT_OPTION:
2363 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2365 case IMAGEX_UNSAFE_COMPACT_OPTION:
2366 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2377 imagex_error(T("Must specify a WIM file"));
2379 imagex_error(T("Must specify an image"));
2383 image_num_or_name = argv[1];
2385 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2386 imagex_progress_func, NULL);
2390 image = wimlib_resolve_image(wim, image_num_or_name);
2392 ret = verify_image_exists(image, image_num_or_name, wimfile);
2394 goto out_wimlib_free;
2396 ret = wimlib_delete_image(wim, image);
2398 imagex_error(T("Failed to delete image from \"%"TS"\""),
2400 goto out_wimlib_free;
2403 ret = wimlib_overwrite(wim, write_flags, 0);
2405 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2406 "deleted"), wimfile);
2414 usage(CMD_DELETE, stderr);
2419 struct print_dentry_options {
2424 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2426 tprintf(T("%"TS"\n"), dentry->full_path);
2429 static const struct {
2432 } file_attr_flags[] = {
2433 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2434 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2435 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2436 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2437 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2438 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2439 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2440 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2441 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2442 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2443 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2444 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2445 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2446 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2447 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2450 #define TIMESTR_MAX 100
2453 timespec_to_string(const struct timespec *spec, tchar *buf)
2455 time_t t = spec->tv_sec;
2458 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2459 buf[TIMESTR_MAX - 1] = '\0';
2463 print_time(const tchar *type, const struct timespec *spec)
2465 tchar timestr[TIMESTR_MAX];
2467 timespec_to_string(spec, timestr);
2469 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2472 static void print_byte_field(const uint8_t field[], size_t len)
2475 tprintf(T("%02hhx"), *field++);
2479 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2481 tchar attr_string[256];
2484 tputs(T("WIM Information:"));
2485 tputs(T("----------------"));
2486 tprintf(T("Path: %"TS"\n"), wimfile);
2487 tprintf(T("GUID: 0x"));
2488 print_byte_field(info->guid, sizeof(info->guid));
2490 tprintf(T("Version: %u\n"), info->wim_version);
2491 tprintf(T("Image Count: %d\n"), info->image_count);
2492 tprintf(T("Compression: %"TS"\n"),
2493 wimlib_get_compression_type_string(info->compression_type));
2494 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2496 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2497 tprintf(T("Boot Index: %d\n"), info->boot_index);
2498 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2500 attr_string[0] = T('\0');
2503 tstrcat(attr_string, T("Pipable, "));
2505 if (info->has_integrity_table)
2506 tstrcat(attr_string, T("Integrity info, "));
2508 if (info->has_rpfix)
2509 tstrcat(attr_string, T("Relative path junction, "));
2511 if (info->resource_only)
2512 tstrcat(attr_string, T("Resource only, "));
2514 if (info->metadata_only)
2515 tstrcat(attr_string, T("Metadata only, "));
2517 if (info->is_marked_readonly)
2518 tstrcat(attr_string, T("Readonly, "));
2520 p = tstrchr(attr_string, T('\0'));
2521 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2524 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2528 print_resource(const struct wimlib_resource_entry *resource,
2531 tprintf(T("Hash = 0x"));
2532 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2535 if (!resource->is_missing) {
2536 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2537 resource->uncompressed_size);
2538 if (resource->packed) {
2539 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2540 "bytes @ offset %"PRIu64"\n"),
2541 resource->raw_resource_uncompressed_size,
2542 resource->raw_resource_compressed_size,
2543 resource->raw_resource_offset_in_wim);
2545 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2548 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2549 resource->compressed_size);
2551 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2555 tprintf(T("Part Number = %u\n"), resource->part_number);
2556 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2558 tprintf(T("Flags = "));
2559 if (resource->is_compressed)
2560 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2561 if (resource->is_metadata)
2562 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2563 if (resource->is_free)
2564 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2565 if (resource->is_spanned)
2566 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2567 if (resource->packed)
2568 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2576 print_blobs(WIMStruct *wim)
2578 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2582 default_print_security_descriptor(const uint8_t *sd, size_t size)
2584 tprintf(T("Security Descriptor = "));
2585 print_byte_field(sd, size);
2590 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2594 "----------------------------------------------------------------------------\n"));
2595 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2596 if (dentry->dos_name)
2597 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2598 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2599 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2600 if (file_attr_flags[i].flag & dentry->attributes)
2601 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2602 file_attr_flags[i].name);
2604 if (dentry->security_descriptor) {
2605 print_security_descriptor(dentry->security_descriptor,
2606 dentry->security_descriptor_size);
2609 print_time(T("Creation Time"), &dentry->creation_time);
2610 print_time(T("Last Write Time"), &dentry->last_write_time);
2611 print_time(T("Last Access Time"), &dentry->last_access_time);
2614 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2615 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2617 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2618 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2620 if (dentry->unix_mode != 0) {
2621 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2622 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2623 dentry->unix_uid, dentry->unix_gid,
2624 dentry->unix_mode, dentry->unix_rdev);
2627 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2628 if (dentry->streams[i].stream_name) {
2629 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2630 dentry->streams[i].stream_name);
2631 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2632 tprintf(T("\tRaw encrypted data stream:\n"));
2633 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2634 tprintf(T("\tReparse point stream:\n"));
2636 tprintf(T("\tUnnamed data stream:\n"));
2638 print_resource(&dentry->streams[i].resource, NULL);
2643 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2645 const struct print_dentry_options *options = _options;
2646 if (!options->detailed)
2647 print_dentry_full_path(dentry);
2649 print_dentry_detailed(dentry);
2653 /* Print the files contained in an image(s) in a WIM file. */
2655 imagex_dir(int argc, tchar **argv, int cmd)
2657 const tchar *wimfile;
2658 WIMStruct *wim = NULL;
2661 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2663 struct print_dentry_options options = {
2666 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2668 STRING_SET(refglobs);
2670 for_opt(c, dir_options) {
2672 case IMAGEX_PATH_OPTION:
2675 case IMAGEX_DETAILED_OPTION:
2676 options.detailed = true;
2678 case IMAGEX_ONE_FILE_ONLY_OPTION:
2679 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2681 case IMAGEX_REF_OPTION:
2682 ret = string_set_append(&refglobs, optarg);
2684 goto out_free_refglobs;
2694 imagex_error(T("Must specify a WIM file"));
2698 imagex_error(T("Too many arguments"));
2703 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2704 imagex_progress_func, NULL);
2706 goto out_free_refglobs;
2709 image = wimlib_resolve_image(wim, argv[1]);
2710 ret = verify_image_exists(image, argv[1], wimfile);
2712 goto out_wimlib_free;
2714 /* No image specified; default to image 1, but only if the WIM
2715 * contains exactly one image. */
2717 struct wimlib_wim_info info;
2719 wimlib_get_wim_info(wim, &info);
2720 if (info.image_count != 1) {
2721 imagex_error(T("\"%"TS"\" contains %d images; Please "
2722 "select one (or all)."),
2723 wimfile, info.image_count);
2730 if (refglobs.num_strings) {
2731 ret = wim_reference_globs(wim, &refglobs, 0);
2733 goto out_wimlib_free;
2736 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2737 print_dentry, &options);
2738 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2739 struct wimlib_wim_info info;
2741 wimlib_get_wim_info(wim, &info);
2742 do_metadata_not_found_warning(wimfile, &info);
2747 string_set_destroy(&refglobs);
2751 usage(CMD_DIR, stderr);
2753 goto out_free_refglobs;
2756 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2759 imagex_export(int argc, tchar **argv, int cmd)
2763 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2764 int write_flags = 0;
2765 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2766 const tchar *src_wimfile;
2767 const tchar *src_image_num_or_name;
2768 const tchar *dest_wimfile;
2770 const tchar *dest_name;
2771 const tchar *dest_desc;
2773 struct wimlib_wim_info src_info;
2774 WIMStruct *dest_wim;
2779 STRING_SET(refglobs);
2780 unsigned num_threads = 0;
2781 uint32_t chunk_size = UINT32_MAX;
2782 uint32_t solid_chunk_size = UINT32_MAX;
2783 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2785 for_opt(c, export_options) {
2787 case IMAGEX_BOOT_OPTION:
2788 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2790 case IMAGEX_CHECK_OPTION:
2791 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2792 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2794 case IMAGEX_NOCHECK_OPTION:
2795 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2797 case IMAGEX_COMPRESS_OPTION:
2798 compression_type = get_compression_type(optarg);
2799 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2802 case IMAGEX_COMPRESS_SLOW_OPTION:
2803 set_compress_slow();
2804 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2806 case IMAGEX_RECOMPRESS_OPTION:
2807 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2809 case IMAGEX_SOLID_OPTION:
2810 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2812 case IMAGEX_NO_SOLID_SORT_OPTION:
2813 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2815 case IMAGEX_CHUNK_SIZE_OPTION:
2816 chunk_size = parse_chunk_size(optarg);
2817 if (chunk_size == UINT32_MAX)
2820 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2821 solid_chunk_size = parse_chunk_size(optarg);
2822 if (solid_chunk_size == UINT32_MAX)
2825 case IMAGEX_SOLID_COMPRESS_OPTION:
2826 solid_ctype = get_compression_type(optarg);
2827 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2830 case IMAGEX_REF_OPTION:
2831 ret = string_set_append(&refglobs, optarg);
2833 goto out_free_refglobs;
2835 case IMAGEX_THREADS_OPTION:
2836 num_threads = parse_num_threads(optarg);
2837 if (num_threads == UINT_MAX)
2840 case IMAGEX_REBUILD_OPTION:
2841 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2843 case IMAGEX_PIPABLE_OPTION:
2844 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2846 case IMAGEX_NOT_PIPABLE_OPTION:
2847 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2849 case IMAGEX_WIMBOOT_OPTION:
2850 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2852 case IMAGEX_UNSAFE_COMPACT_OPTION:
2853 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2861 if (argc < 3 || argc > 5)
2863 src_wimfile = argv[0];
2864 src_image_num_or_name = argv[1];
2865 dest_wimfile = argv[2];
2866 dest_name = (argc >= 4) ? argv[3] : NULL;
2867 dest_desc = (argc >= 5) ? argv[4] : NULL;
2868 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2869 imagex_progress_func, NULL);
2871 goto out_free_refglobs;
2873 wimlib_get_wim_info(src_wim, &src_info);
2875 /* Determine if the destination is an existing file or not. If so, we
2876 * try to append the exported image(s) to it; otherwise, we create a new
2877 * WIM containing the exported image(s). Furthermore, determine if we
2878 * need to write a pipable WIM directly to standard output. */
2880 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2882 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2883 imagex_error("Can't write a non-pipable WIM to "
2884 "standard output! Specify --pipable\n"
2885 " if you want to create a pipable WIM "
2886 "(but read the docs first).");
2888 goto out_free_src_wim;
2891 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2893 dest_wimfile = NULL;
2894 dest_wim_fd = STDOUT_FILENO;
2895 imagex_info_file = stderr;
2896 set_fd_to_binary_mode(dest_wim_fd);
2899 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2901 /* Destination file exists. */
2903 if (!S_ISREG(stbuf.st_mode)) {
2904 imagex_error(T("\"%"TS"\" is not a regular file"),
2907 goto out_free_src_wim;
2909 ret = wimlib_open_wim_with_progress(dest_wimfile,
2911 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2913 imagex_progress_func,
2916 goto out_free_src_wim;
2918 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2919 /* The user specified a compression type, but we're
2920 * exporting to an existing WIM. Make sure the
2921 * specified compression type is the same as the
2922 * compression type of the existing destination WIM. */
2923 struct wimlib_wim_info dest_info;
2925 wimlib_get_wim_info(dest_wim, &dest_info);
2926 if (compression_type != dest_info.compression_type) {
2927 imagex_error(T("Cannot specify a compression type that is "
2928 "not the same as that used in the "
2929 "destination WIM"));
2931 goto out_free_dest_wim;
2937 if (errno != ENOENT) {
2938 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2941 goto out_free_src_wim;
2944 /* dest_wimfile is not an existing file, so create a new WIM. */
2946 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2947 /* The user did not specify a compression type; default
2948 * to that of the source WIM, unless --solid or
2949 * --wimboot was specified. */
2951 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2952 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2953 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2954 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2956 compression_type = src_info.compression_type;
2958 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2960 goto out_free_src_wim;
2962 wimlib_register_progress_function(dest_wim,
2963 imagex_progress_func, NULL);
2965 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2966 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2968 /* For --wimboot export, use small XPRESS chunks. */
2969 wimlib_set_output_chunk_size(dest_wim, 4096);
2970 } else if (compression_type == src_info.compression_type &&
2971 chunk_size == UINT32_MAX)
2973 /* Use same chunk size if compression type is the same. */
2974 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2978 if (chunk_size != UINT32_MAX) {
2979 /* Set destination chunk size. */
2980 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2982 goto out_free_dest_wim;
2984 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2985 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2987 goto out_free_dest_wim;
2989 if (solid_chunk_size != UINT32_MAX) {
2990 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2992 goto out_free_dest_wim;
2995 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2996 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2998 goto out_free_dest_wim;
3000 if (refglobs.num_strings) {
3001 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3003 goto out_free_dest_wim;
3006 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3007 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3009 imagex_error(T("--boot specified for all-images export, but source WIM "
3010 "has no bootable image."));
3012 goto out_free_dest_wim;
3015 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3016 dest_desc, export_flags);
3018 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3019 do_resource_not_found_warning(src_wimfile,
3020 &src_info, &refglobs);
3021 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3022 do_metadata_not_found_warning(src_wimfile, &src_info);
3024 goto out_free_dest_wim;
3028 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3029 else if (dest_wimfile)
3030 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3031 write_flags, num_threads);
3033 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3034 WIMLIB_ALL_IMAGES, write_flags,
3037 wimlib_free(dest_wim);
3039 wimlib_free(src_wim);
3041 string_set_destroy(&refglobs);
3045 usage(CMD_EXPORT, stderr);
3048 goto out_free_refglobs;
3051 /* Extract files or directories from a WIM image */
3053 imagex_extract(int argc, tchar **argv, int cmd)
3060 const tchar *wimfile;
3061 const tchar *image_num_or_name;
3062 tchar *dest_dir = T(".");
3063 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3064 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3065 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3066 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3068 STRING_SET(refglobs);
3070 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3072 for_opt(c, extract_options) {
3074 case IMAGEX_CHECK_OPTION:
3075 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3077 case IMAGEX_VERBOSE_OPTION:
3078 /* No longer does anything. */
3080 case IMAGEX_REF_OPTION:
3081 ret = string_set_append(&refglobs, optarg);
3083 goto out_free_refglobs;
3085 case IMAGEX_UNIX_DATA_OPTION:
3086 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3088 case IMAGEX_NO_ACLS_OPTION:
3089 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3091 case IMAGEX_STRICT_ACLS_OPTION:
3092 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3094 case IMAGEX_NO_ATTRIBUTES_OPTION:
3095 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3097 case IMAGEX_DEST_DIR_OPTION:
3100 case IMAGEX_TO_STDOUT_OPTION:
3101 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3102 imagex_info_file = stderr;
3103 imagex_be_quiet = true;
3104 set_fd_to_binary_mode(STDOUT_FILENO);
3106 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3107 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3108 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3110 case IMAGEX_NO_GLOBS_OPTION:
3111 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3113 case IMAGEX_NULLGLOB_OPTION:
3114 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3116 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3117 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3119 case IMAGEX_WIMBOOT_OPTION:
3120 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3122 case IMAGEX_COMPACT_OPTION:
3123 ret = set_compact_mode(optarg, &extract_flags);
3125 goto out_free_refglobs;
3137 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3138 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3140 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3145 image_num_or_name = argv[1];
3150 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3151 imagex_progress_func, NULL);
3153 goto out_free_refglobs;
3155 image = wimlib_resolve_image(wim, image_num_or_name);
3156 ret = verify_image_exists_and_is_single(image,
3160 goto out_wimlib_free;
3162 if (refglobs.num_strings) {
3163 ret = wim_reference_globs(wim, &refglobs, open_flags);
3165 goto out_wimlib_free;
3171 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3174 while (argc != 0 && ret == 0) {
3178 num_paths < argc && argv[num_paths][0] != T('@');
3183 ret = wimlib_extract_paths(wim, image, dest_dir,
3184 (const tchar **)argv,
3186 extract_flags | notlist_extract_flags);
3190 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3199 if (!imagex_be_quiet)
3200 imagex_printf(T("Done extracting files.\n"));
3201 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3202 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3203 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3204 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3205 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3208 T("Note: You can use the '--nullglob' "
3209 "option to ignore missing files.\n"));
3211 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3212 "files and directories\n"
3213 " are in the WIM image.\n"),
3214 get_cmd_string(CMD_DIR, false));
3215 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3216 struct wimlib_wim_info info;
3218 wimlib_get_wim_info(wim, &info);
3219 do_resource_not_found_warning(wimfile, &info, &refglobs);
3220 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3221 struct wimlib_wim_info info;
3223 wimlib_get_wim_info(wim, &info);
3224 do_metadata_not_found_warning(wimfile, &info);
3229 string_set_destroy(&refglobs);
3233 usage(CMD_EXTRACT, stderr);
3236 goto out_free_refglobs;
3239 /* Prints information about a WIM file; also can mark an image as bootable,
3240 * change the name of an image, or change the description of an image. */
3242 imagex_info(int argc, tchar **argv, int cmd)
3247 bool nocheck = false;
3248 bool header = false;
3251 bool short_header = true;
3252 const tchar *xml_out_file = NULL;
3253 const tchar *wimfile;
3254 const tchar *image_num_or_name;
3255 const tchar *new_name;
3256 const tchar *new_desc;
3261 struct wimlib_wim_info info;
3263 for_opt(c, info_options) {
3265 case IMAGEX_BOOT_OPTION:
3268 case IMAGEX_CHECK_OPTION:
3271 case IMAGEX_NOCHECK_OPTION:
3274 case IMAGEX_HEADER_OPTION:
3276 short_header = false;
3278 case IMAGEX_BLOBS_OPTION:
3280 short_header = false;
3282 case IMAGEX_XML_OPTION:
3284 short_header = false;
3286 case IMAGEX_EXTRACT_XML_OPTION:
3287 xml_out_file = optarg;
3288 short_header = false;
3290 case IMAGEX_METADATA_OPTION:
3291 imagex_error(T("The --metadata option has been removed. "
3292 "Use 'wimdir --detail' instead."));
3301 if (argc < 1 || argc > 4)
3305 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3306 new_name = (argc >= 3) ? argv[2] : NULL;
3307 new_desc = (argc >= 4) ? argv[3] : NULL;
3309 if (check && nocheck) {
3310 imagex_error(T("Can't specify both --check and --nocheck"));
3315 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3317 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3318 imagex_progress_func, NULL);
3322 wimlib_get_wim_info(wim, &info);
3324 image = wimlib_resolve_image(wim, image_num_or_name);
3325 ret = WIMLIB_ERR_INVALID_IMAGE;
3326 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3327 verify_image_exists(image, image_num_or_name, wimfile);
3329 imagex_error(T("If you would like to set the boot "
3330 "index to 0, specify image \"0\" with "
3331 "the --boot flag."));
3333 goto out_wimlib_free;
3336 if (boot && info.image_count == 0) {
3337 imagex_error(T("--boot is meaningless on a WIM with no images"));
3338 goto out_wimlib_free;
3341 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3343 imagex_error(T("Cannot specify the --boot flag "
3344 "without specifying a specific "
3345 "image in a multi-image WIM"));
3346 goto out_wimlib_free;
3349 imagex_error(T("Cannot specify the NEW_NAME "
3350 "without specifying a specific "
3351 "image in a multi-image WIM"));
3352 goto out_wimlib_free;
3356 /* Operations that print information are separated from operations that
3357 * recreate the WIM file. */
3358 if (!new_name && !boot) {
3360 /* Read-only operations */
3362 if (image == WIMLIB_NO_IMAGE) {
3363 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3364 image_num_or_name, wimfile);
3365 goto out_wimlib_free;
3368 if (image == WIMLIB_ALL_IMAGES && short_header)
3369 print_wim_information(wimfile, &info);
3372 wimlib_print_header(wim);
3375 if (info.total_parts != 1) {
3376 tfprintf(stderr, T("Warning: Only showing the blobs "
3377 "for part %d of a %d-part WIM.\n"),
3378 info.part_number, info.total_parts);
3384 ret = wimlib_extract_xml_data(wim, stdout);
3386 goto out_wimlib_free;
3392 fp = tfopen(xml_out_file, T("wb"));
3394 imagex_error_with_errno(T("Failed to open the "
3395 "file \"%"TS"\" for "
3399 goto out_wimlib_free;
3401 ret = wimlib_extract_xml_data(wim, fp);
3403 imagex_error(T("Failed to close the file "
3409 goto out_wimlib_free;
3413 wimlib_print_available_images(wim, image);
3418 /* Modification operations */
3420 if (image == WIMLIB_ALL_IMAGES)
3423 if (image == WIMLIB_NO_IMAGE && new_name) {
3424 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3425 "when using image 0"), new_name);
3427 goto out_wimlib_free;
3431 if (image == info.boot_index) {
3432 imagex_printf(T("Image %d is already marked as "
3433 "bootable.\n"), image);
3436 imagex_printf(T("Marking image %d as bootable.\n"),
3438 info.boot_index = image;
3439 ret = wimlib_set_wim_info(wim, &info,
3440 WIMLIB_CHANGE_BOOT_INDEX);
3442 goto out_wimlib_free;
3446 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3448 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3452 imagex_printf(T("Changing the name of image %d to "
3453 "\"%"TS"\".\n"), image, new_name);
3454 ret = wimlib_set_image_name(wim, image, new_name);
3456 goto out_wimlib_free;
3460 const tchar *old_desc;
3461 old_desc = wimlib_get_image_description(wim, image);
3462 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3463 imagex_printf(T("The description of image %d is already "
3464 "\"%"TS"\".\n"), image, new_desc);
3467 imagex_printf(T("Changing the description of image %d "
3468 "to \"%"TS"\".\n"), image, new_desc);
3469 ret = wimlib_set_image_descripton(wim, image,
3472 goto out_wimlib_free;
3476 /* Only call wimlib_overwrite() if something actually needs to
3478 if (boot || new_name || new_desc ||
3479 (check && !info.has_integrity_table) ||
3480 (nocheck && info.has_integrity_table))
3482 int write_flags = 0;
3485 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3487 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3488 ret = wimlib_overwrite(wim, write_flags, 1);
3490 imagex_printf(T("The file \"%"TS"\" was not modified "
3491 "because nothing needed to be done.\n"),
3502 usage(CMD_INFO, stderr);
3508 /* Join split WIMs into one part WIM */
3510 imagex_join(int argc, tchar **argv, int cmd)
3513 int swm_open_flags = 0;
3514 int wim_write_flags = 0;
3515 const tchar *output_path;
3518 for_opt(c, join_options) {
3520 case IMAGEX_CHECK_OPTION:
3521 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3522 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3532 imagex_error(T("Must specify one or more split WIM (.swm) "
3536 output_path = argv[0];
3537 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3542 imagex_progress_func,
3548 usage(CMD_JOIN, stderr);
3553 #if WIM_MOUNTING_SUPPORTED
3555 /* Mounts a WIM image. */
3557 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3560 int mount_flags = 0;
3562 const tchar *staging_dir = NULL;
3563 const tchar *wimfile;
3566 struct wimlib_wim_info info;
3570 STRING_SET(refglobs);
3572 if (cmd == CMD_MOUNTRW) {
3573 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3574 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3577 for_opt(c, mount_options) {
3579 case IMAGEX_ALLOW_OTHER_OPTION:
3580 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3582 case IMAGEX_CHECK_OPTION:
3583 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3585 case IMAGEX_DEBUG_OPTION:
3586 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3588 case IMAGEX_STREAMS_INTERFACE_OPTION:
3589 if (!tstrcasecmp(optarg, T("none")))
3590 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3591 else if (!tstrcasecmp(optarg, T("xattr")))
3592 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3593 else if (!tstrcasecmp(optarg, T("windows")))
3594 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3596 imagex_error(T("Unknown stream interface \"%"TS"\""),
3601 case IMAGEX_REF_OPTION:
3602 ret = string_set_append(&refglobs, optarg);
3604 goto out_free_refglobs;
3606 case IMAGEX_STAGING_DIR_OPTION:
3607 staging_dir = optarg;
3609 case IMAGEX_UNIX_DATA_OPTION:
3610 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3618 if (argc != 2 && argc != 3)
3623 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3624 imagex_progress_func, NULL);
3626 goto out_free_refglobs;
3628 wimlib_get_wim_info(wim, &info);
3631 /* Image explicitly specified. */
3632 image = wimlib_resolve_image(wim, argv[1]);
3634 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3638 /* No image specified; default to image 1, but only if the WIM
3639 * contains exactly one image. */
3641 if (info.image_count != 1) {
3642 imagex_error(T("\"%"TS"\" contains %d images; Please "
3643 "select one."), wimfile, info.image_count);
3651 if (refglobs.num_strings) {
3652 ret = wim_reference_globs(wim, &refglobs, open_flags);
3657 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3659 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3660 do_metadata_not_found_warning(wimfile, &info);
3662 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3664 image, wimfile, dir);
3670 string_set_destroy(&refglobs);
3676 goto out_free_refglobs;
3678 #endif /* WIM_MOUNTING_SUPPORTED */
3680 /* Rebuild a WIM file */
3682 imagex_optimize(int argc, tchar **argv, int cmd)
3685 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3686 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3687 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3688 uint32_t chunk_size = UINT32_MAX;
3689 uint32_t solid_chunk_size = UINT32_MAX;
3690 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3693 const tchar *wimfile;
3696 unsigned num_threads = 0;
3698 for_opt(c, optimize_options) {
3700 case IMAGEX_CHECK_OPTION:
3701 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3702 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3704 case IMAGEX_NOCHECK_OPTION:
3705 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3707 case IMAGEX_COMPRESS_OPTION:
3708 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3709 compression_type = get_compression_type(optarg);
3710 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3713 case IMAGEX_COMPRESS_SLOW_OPTION:
3714 set_compress_slow();
3715 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3717 case IMAGEX_RECOMPRESS_OPTION:
3718 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3720 case IMAGEX_CHUNK_SIZE_OPTION:
3721 chunk_size = parse_chunk_size(optarg);
3722 if (chunk_size == UINT32_MAX)
3725 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3726 solid_chunk_size = parse_chunk_size(optarg);
3727 if (solid_chunk_size == UINT32_MAX)
3730 case IMAGEX_SOLID_COMPRESS_OPTION:
3731 solid_ctype = get_compression_type(optarg);
3732 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3735 case IMAGEX_SOLID_OPTION:
3736 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3737 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3739 case IMAGEX_NO_SOLID_SORT_OPTION:
3740 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3742 case IMAGEX_THREADS_OPTION:
3743 num_threads = parse_num_threads(optarg);
3744 if (num_threads == UINT_MAX)
3747 case IMAGEX_PIPABLE_OPTION:
3748 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3750 case IMAGEX_NOT_PIPABLE_OPTION:
3751 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3753 case IMAGEX_UNSAFE_COMPACT_OPTION:
3754 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3768 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3769 imagex_progress_func, NULL);
3773 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3774 /* Change compression type. */
3775 ret = wimlib_set_output_compression_type(wim, compression_type);
3777 goto out_wimlib_free;
3780 if (chunk_size != UINT32_MAX) {
3781 /* Change chunk size. */
3782 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3784 goto out_wimlib_free;
3786 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3787 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3789 goto out_wimlib_free;
3791 if (solid_chunk_size != UINT32_MAX) {
3792 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3794 goto out_wimlib_free;
3797 old_size = file_get_size(wimfile);
3798 tprintf(T("\"%"TS"\" original size: "), wimfile);
3800 tputs(T("Unknown"));
3802 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3804 ret = wimlib_overwrite(wim, write_flags, num_threads);
3806 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3807 goto out_wimlib_free;
3810 new_size = file_get_size(wimfile);
3811 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3813 tputs(T("Unknown"));
3815 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3817 tfputs(T("Space saved: "), stdout);
3818 if (new_size != -1 && old_size != -1) {
3819 tprintf(T("%lld KiB\n"),
3820 ((long long)old_size - (long long)new_size) >> 10);
3822 tputs(T("Unknown"));
3831 usage(CMD_OPTIMIZE, stderr);
3837 /* Split a WIM into a spanned set */
3839 imagex_split(int argc, tchar **argv, int cmd)
3843 int write_flags = 0;
3844 unsigned long part_size;
3849 for_opt(c, split_options) {
3851 case IMAGEX_CHECK_OPTION:
3852 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3853 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3865 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3866 if (tmp == argv[2] || *tmp) {
3867 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3868 imagex_error(T("The part size must be an integer or "
3869 "floating-point number of megabytes."));
3872 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3873 imagex_progress_func, NULL);
3877 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3883 usage(CMD_SPLIT, stderr);
3889 #if WIM_MOUNTING_SUPPORTED
3890 /* Unmounts a mounted WIM image. */
3892 imagex_unmount(int argc, tchar **argv, int cmd)
3895 int unmount_flags = 0;
3898 for_opt(c, unmount_options) {
3900 case IMAGEX_COMMIT_OPTION:
3901 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3903 case IMAGEX_CHECK_OPTION:
3904 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3906 case IMAGEX_REBUILD_OPTION:
3907 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3909 case IMAGEX_LAZY_OPTION:
3910 case IMAGEX_FORCE_OPTION:
3911 /* Now, unmount is lazy by default. However, committing
3912 * the image will fail with
3913 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3914 * file descriptors on the WIM image. The
3915 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3916 * descriptors to be closed. */
3917 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3919 case IMAGEX_NEW_IMAGE_OPTION:
3920 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3931 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3932 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3933 imagex_error(T("--new-image is meaningless "
3934 "without --commit also specified!"));
3939 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3940 imagex_progress_func, NULL);
3942 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3943 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3945 "\tNote: Use --commit --force to force changes "
3946 "to be committed, regardless\n"
3947 "\t of open files.\n"));
3954 usage(CMD_UNMOUNT, stderr);
3959 #endif /* WIM_MOUNTING_SUPPORTED */
3962 * Add, delete, or rename files in a WIM image.
3965 imagex_update(int argc, tchar **argv, int cmd)
3967 const tchar *wimfile;
3971 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3972 int write_flags = 0;
3973 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3974 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3975 WIMLIB_ADD_FLAG_VERBOSE |
3976 WIMLIB_ADD_FLAG_WINCONFIG;
3977 int default_delete_flags = 0;
3978 unsigned num_threads = 0;
3980 tchar *cmd_file_contents;
3981 size_t cmd_file_nchars;
3982 struct wimlib_update_command *cmds;
3984 tchar *command_str = NULL;
3985 tchar *config_file = NULL;
3986 tchar *wimboot_config = NULL;
3988 for_opt(c, update_options) {
3990 /* Generic or write options */
3991 case IMAGEX_THREADS_OPTION:
3992 num_threads = parse_num_threads(optarg);
3993 if (num_threads == UINT_MAX)
3996 case IMAGEX_CHECK_OPTION:
3997 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3998 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4000 case IMAGEX_REBUILD_OPTION:
4001 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4003 case IMAGEX_COMMAND_OPTION:
4005 imagex_error(T("--command may only be specified "
4006 "one time. Please provide\n"
4007 " the update commands "
4008 "on standard input instead."));
4011 command_str = tstrdup(optarg);
4013 imagex_error(T("Out of memory!"));
4017 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4018 wimboot_config = optarg;
4020 /* Default delete options */
4021 case IMAGEX_FORCE_OPTION:
4022 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4024 case IMAGEX_RECURSIVE_OPTION:
4025 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4028 /* Global add option */
4029 case IMAGEX_CONFIG_OPTION:
4030 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4031 config_file = optarg;
4034 /* Default add options */
4035 case IMAGEX_VERBOSE_OPTION:
4036 /* No longer does anything. */
4038 case IMAGEX_DEREFERENCE_OPTION:
4039 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4041 case IMAGEX_UNIX_DATA_OPTION:
4042 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4044 case IMAGEX_NO_ACLS_OPTION:
4045 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4047 case IMAGEX_STRICT_ACLS_OPTION:
4048 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4050 case IMAGEX_NO_REPLACE_OPTION:
4051 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4053 case IMAGEX_UNSAFE_COMPACT_OPTION:
4054 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4063 if (argc != 1 && argc != 2)
4067 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4068 imagex_progress_func, NULL);
4070 goto out_free_command_str;
4073 /* Image explicitly specified. */
4074 image = wimlib_resolve_image(wim, argv[1]);
4075 ret = verify_image_exists_and_is_single(image, argv[1],
4078 goto out_wimlib_free;
4080 /* No image specified; default to image 1, but only if the WIM
4081 * contains exactly one image. */
4082 struct wimlib_wim_info info;
4084 wimlib_get_wim_info(wim, &info);
4085 if (info.image_count != 1) {
4086 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4087 wimfile, info.image_count);
4094 /* Read update commands from standard input, or the command string if
4097 cmd_file_contents = NULL;
4098 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4102 goto out_free_cmd_file_contents;
4104 } else if (!wimboot_config) {
4105 if (isatty(STDIN_FILENO)) {
4106 tputs(T("Reading update commands from standard input..."));
4107 recommend_man_page(CMD_UPDATE, stdout);
4109 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4110 if (!cmd_file_contents) {
4112 goto out_wimlib_free;
4115 /* Parse the update commands */
4116 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4120 goto out_free_cmd_file_contents;
4123 cmd_file_contents = NULL;
4128 /* Set default flags and capture config on the update commands */
4129 for (size_t i = 0; i < num_cmds; i++) {
4130 switch (cmds[i].op) {
4131 case WIMLIB_UPDATE_OP_ADD:
4132 cmds[i].add.add_flags |= default_add_flags;
4133 cmds[i].add.config_file = config_file;
4135 case WIMLIB_UPDATE_OP_DELETE:
4136 cmds[i].delete_.delete_flags |= default_delete_flags;
4143 /* Execute the update commands */
4144 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4148 if (wimboot_config) {
4149 /* --wimboot-config=FILE is short for an
4150 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4152 struct wimlib_update_command cmd;
4154 cmd.op = WIMLIB_UPDATE_OP_ADD;
4155 cmd.add.fs_source_path = wimboot_config;
4156 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4157 cmd.add.config_file = NULL;
4158 cmd.add.add_flags = 0;
4160 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4165 /* Overwrite the updated WIM */
4166 ret = wimlib_overwrite(wim, write_flags, num_threads);
4169 out_free_cmd_file_contents:
4170 free(cmd_file_contents);
4173 out_free_command_str:
4178 usage(CMD_UPDATE, stderr);
4181 goto out_free_command_str;
4184 /* Verify a WIM file. */
4186 imagex_verify(int argc, tchar **argv, int cmd)
4189 const tchar *wimfile;
4191 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4192 int verify_flags = 0;
4193 STRING_SET(refglobs);
4196 for_opt(c, verify_options) {
4198 case IMAGEX_REF_OPTION:
4199 ret = string_set_append(&refglobs, optarg);
4201 goto out_free_refglobs;
4203 case IMAGEX_NOCHECK_OPTION:
4204 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4216 imagex_error(T("Must specify a WIM file!"));
4218 imagex_error(T("At most one WIM file can be specified!"));
4224 ret = wimlib_open_wim_with_progress(wimfile,
4227 imagex_progress_func,
4230 goto out_free_refglobs;
4232 ret = wim_reference_globs(wim, &refglobs, open_flags);
4234 goto out_wimlib_free;
4236 ret = wimlib_verify_wim(wim, verify_flags);
4238 tputc(T('\n'), stderr);
4239 imagex_error(T("\"%"TS"\" failed verification!"),
4241 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4242 refglobs.num_strings == 0)
4244 imagex_printf(T("Note: if this WIM file is not standalone, "
4245 "use the --ref option to specify the other parts.\n"));
4248 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4255 string_set_destroy(&refglobs);
4259 usage(CMD_VERIFY, stderr);
4261 goto out_free_refglobs;
4264 struct imagex_command {
4266 int (*func)(int argc, tchar **argv, int cmd);
4269 static const struct imagex_command imagex_commands[] = {
4270 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4271 [CMD_APPLY] = {T("apply"), imagex_apply},
4272 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4273 [CMD_DELETE] = {T("delete"), imagex_delete},
4274 [CMD_DIR ] = {T("dir"), imagex_dir},
4275 [CMD_EXPORT] = {T("export"), imagex_export},
4276 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4277 [CMD_INFO] = {T("info"), imagex_info},
4278 [CMD_JOIN] = {T("join"), imagex_join},
4279 #if WIM_MOUNTING_SUPPORTED
4280 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4281 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4283 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4284 [CMD_SPLIT] = {T("split"), imagex_split},
4285 #if WIM_MOUNTING_SUPPORTED
4286 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4288 [CMD_UPDATE] = {T("update"), imagex_update},
4289 [CMD_VERIFY] = {T("verify"), imagex_verify},
4294 /* Can be a directory or source list file. But source list file is probably
4295 * a rare use case, so just say directory. */
4296 # define SOURCE_STR T("DIRECTORY")
4298 /* Can only be a directory */
4299 # define TARGET_STR T("DIRECTORY")
4302 /* Can be a directory, NTFS volume, or source list file. */
4303 # define SOURCE_STR T("SOURCE")
4305 /* Can be a directory or NTFS volume. */
4306 # define TARGET_STR T("TARGET")
4310 static const tchar *usage_strings[] = {
4313 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4314 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4315 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4316 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4317 " [--wimboot] [--unix-data] [--dereference]\n"
4321 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4322 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4323 " [--no-attributes] [--rpfix] [--norpfix]\n"
4324 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4325 " [--compact=FORMAT]\n"
4329 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4330 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4331 " [--config=FILE] [--threads=NUM_THREADS]\n"
4332 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4333 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4334 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4338 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4342 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4346 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4347 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4348 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4349 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4350 " [--wimboot] [--solid]\n"
4354 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4355 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4356 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4357 " [--no-attributes] [--include-invalid-names]\n"
4358 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4362 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4363 " [--boot] [--check] [--nocheck] [--xml]\n"
4364 " [--extract-xml FILE] [--header] [--blobs]\n"
4368 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4370 #if WIM_MOUNTING_SUPPORTED
4373 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4374 " [--check] [--streams-interface=INTERFACE]\n"
4375 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4379 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4380 " [--check] [--streams-interface=INTERFACE]\n"
4381 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4387 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4388 " [--check] [--nocheck] [--solid]\n"
4393 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4395 #if WIM_MOUNTING_SUPPORTED
4398 " %"TS" DIRECTORY\n"
4399 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4404 " %"TS" WIMFILE [IMAGE]\n"
4405 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4406 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4407 " [--command=STRING] [--wimboot-config=FILE]\n"
4412 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4416 static const tchar *invocation_name;
4417 static int invocation_cmd = CMD_NONE;
4419 static const tchar *get_cmd_string(int cmd, bool nospace)
4421 static tchar buf[50];
4422 if (cmd == CMD_NONE) {
4423 return T("wimlib-imagex");
4424 } else if (invocation_cmd != CMD_NONE) {
4425 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4427 const tchar *format;
4430 format = T("%"TS"-%"TS"");
4432 format = T("%"TS" %"TS"");
4433 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4441 static const tchar *s =
4443 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4444 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4445 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4446 "This is free software: you are free to change and redistribute it.\n"
4447 "There is NO WARRANTY, to the extent permitted by law.\n"
4449 "Report bugs to "PACKAGE_BUGREPORT".\n"
4456 help_or_version(int argc, tchar **argv, int cmd)
4461 for (i = 1; i < argc; i++) {
4463 if (p[0] == T('-') && p[1] == T('-')) {
4465 if (!tstrcmp(p, T("help"))) {
4466 if (cmd == CMD_NONE)
4471 } else if (!tstrcmp(p, T("version"))) {
4480 print_usage_string(int cmd, FILE *fp)
4482 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4486 recommend_man_page(int cmd, FILE *fp)
4488 const tchar *format_str;
4490 format_str = T("Some uncommon options are not listed;\n"
4491 "See %"TS".pdf in the doc directory for more details.\n");
4493 format_str = T("Some uncommon options are not listed;\n"
4494 "Try `man %"TS"' for more details.\n");
4496 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4500 usage(int cmd, FILE *fp)
4502 tfprintf(fp, T("Usage:\n"));
4503 print_usage_string(cmd, fp);
4504 tfprintf(fp, T("\n"));
4505 recommend_man_page(cmd, fp);
4511 tfprintf(fp, T("Usage:\n"));
4512 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4513 print_usage_string(cmd, fp);
4514 tfprintf(fp, T("\n"));
4516 static const tchar *extra =
4519 " %"TS" --version\n"
4522 tfprintf(fp, extra, invocation_name, invocation_name);
4524 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4525 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4526 "For some commands IMAGE may be \"all\".\n"
4528 recommend_man_page(CMD_NONE, fp);
4531 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4532 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4533 * something else), while on Windows the command arguments will be UTF-16LE
4534 * encoded 'wchar_t' strings. */
4537 wmain(int argc, wchar_t **argv, wchar_t **envp)
4539 main(int argc, char **argv)
4546 imagex_info_file = stdout;
4547 invocation_name = tbasename(argv[0]);
4550 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4551 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4555 setlocale(LC_ALL, "");
4556 codeset = nl_langinfo(CODESET);
4557 if (!strstr(codeset, "UTF-8") &&
4558 !strstr(codeset, "UTF8") &&
4559 !strstr(codeset, "utf-8") &&
4560 !strstr(codeset, "utf8"))
4563 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4564 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4565 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4566 " to any value to force wimlib to use UTF-8.\n",
4572 #endif /* !__WIN32__ */
4575 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4576 if (igcase != NULL) {
4577 if (!tstrcmp(igcase, T("no")) ||
4578 !tstrcmp(igcase, T("0")))
4579 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4580 else if (!tstrcmp(igcase, T("yes")) ||
4581 !tstrcmp(igcase, T("1")))
4582 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4585 "WARNING: Ignoring unknown setting of "
4586 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4591 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4593 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4594 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4595 for (int i = 0; i < CMD_MAX; i++) {
4596 if (!tstrcmp(invocation_name + 3,
4597 imagex_commands[i].name))
4606 /* Unless already known from the invocation name, determine which
4607 * command was specified. */
4608 if (cmd == CMD_NONE) {
4610 imagex_error(T("No command specified!\n"));
4614 for (int i = 0; i < CMD_MAX; i++) {
4615 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4620 if (cmd != CMD_NONE) {
4626 /* Handle --help and --version. --help can be either for the program as
4627 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4628 * CMD_NONE). Note: help_or_version() will not return if a --help or
4629 * --version argument was found. */
4630 help_or_version(argc, argv, cmd);
4632 /* Bail if a valid command was not specified. */
4633 if (cmd == CMD_NONE) {
4634 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4639 /* Enable warning and error messages in wimlib to be more user-friendly.
4641 wimlib_set_print_errors(true);
4643 /* Initialize wimlib. */
4644 ret = wimlib_global_init(init_flags);
4646 goto out_check_status;
4648 /* Call the command handler function. */
4649 ret = imagex_commands[cmd].func(argc, argv, cmd);
4651 /* Check for error writing to standard output, especially since for some
4652 * commands, writing to standard output is part of the program's actual
4653 * behavior and not just for informational purposes. */
4654 if (ferror(stdout) || fclose(stdout)) {
4655 imagex_error_with_errno(T("error writing to standard output"));
4660 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4661 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4662 * error code from which an error message can be printed. */
4664 imagex_error(T("Exiting with error code %d:\n"
4666 wimlib_get_error_string(ret));
4667 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4668 imagex_error_with_errno(T("errno"));
4670 /* Make wimlib free any resources it's holding (although this is not
4671 * strictly necessary because the process is ending anyway). */
4672 wimlib_global_cleanup();