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 if (cmd != CMD_APPEND) {
1962 imagex_error(T("'--unsafe-compact' is only "
1963 "valid for append!"));
1966 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
1975 if (argc < 2 || argc > 4)
1981 /* Set default compression type and parameters. */
1984 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1985 /* No compression type specified. Use the default. */
1987 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1988 /* With --wimboot, default to XPRESS compression. */
1989 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1990 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1991 /* With --solid, default to LZMS compression. (However,
1992 * this will not affect solid resources!) */
1993 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1995 /* Otherwise, default to LZX compression. */
1996 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2000 if (!tstrcmp(wimfile, T("-"))) {
2001 /* Writing captured WIM to standard output. */
2003 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2004 imagex_error("Can't write a non-pipable WIM to "
2005 "standard output! Specify --pipable\n"
2006 " if you want to create a pipable WIM "
2007 "(but read the docs first).");
2011 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2013 if (cmd == CMD_APPEND) {
2014 imagex_error(T("Using standard output for append does "
2015 "not make sense."));
2018 wim_fd = STDOUT_FILENO;
2020 imagex_info_file = stderr;
2021 set_fd_to_binary_mode(wim_fd);
2024 /* If template image was specified using --update-of=IMAGE rather
2025 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2026 if (template_image_name_or_num && !template_wimfile) {
2027 if (base_wimfiles.num_strings == 1) {
2028 /* Capturing delta WIM based on single WIM: default to
2030 template_wimfile = base_wimfiles.strings[0];
2031 } else if (cmd == CMD_APPEND) {
2032 /* Appending to WIM: default to WIM being appended to.
2034 template_wimfile = wimfile;
2036 /* Capturing a normal (non-delta) WIM, so the WIM file
2037 * *must* be explicitly specified. */
2038 if (base_wimfiles.num_strings > 1) {
2039 imagex_error(T("For capture of delta WIM "
2040 "based on multiple existing "
2042 " '--update-of' must "
2043 "specify WIMFILE:IMAGE!"));
2045 imagex_error(T("For capture of non-delta WIM, "
2046 "'--update-of' must specify "
2055 name_defaulted = false;
2057 /* Set default name to SOURCE argument, omitting any directory
2058 * prefixes and trailing slashes. This requires making a copy
2059 * of @source. Leave some free characters at the end in case we
2060 * append a number to keep the name unique. */
2061 size_t source_name_len;
2063 source_name_len = tstrlen(source);
2064 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2065 name = tbasename(tstrcpy(source_copy, source));
2066 name_defaulted = true;
2068 /* Image description defaults to NULL if not given. */
2075 /* Set up capture sources in source list mode */
2076 if (source[0] == T('-') && source[1] == T('\0')) {
2077 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2079 source_list_contents = file_get_text_contents(source,
2080 &source_list_nchars);
2082 if (!source_list_contents)
2085 capture_sources = parse_source_list(&source_list_contents,
2088 if (!capture_sources) {
2090 goto out_free_source_list_contents;
2092 capture_sources_malloced = true;
2094 /* Set up capture source in non-source-list mode. */
2095 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2096 capture_sources[0].fs_source_path = source;
2097 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2098 capture_sources[0].reserved = 0;
2100 capture_sources_malloced = false;
2101 source_list_contents = NULL;
2104 /* Open the existing WIM, or create a new one. */
2105 if (cmd == CMD_APPEND) {
2106 ret = wimlib_open_wim_with_progress(wimfile,
2107 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2109 imagex_progress_func,
2112 goto out_free_capture_sources;
2114 ret = wimlib_create_new_wim(compression_type, &wim);
2116 goto out_free_capture_sources;
2117 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2120 /* Set chunk size if non-default. */
2121 if (chunk_size != UINT32_MAX) {
2122 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2125 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2126 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2127 ret = wimlib_set_output_chunk_size(wim, 4096);
2131 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2132 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2136 if (solid_chunk_size != UINT32_MAX) {
2137 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2143 /* Detect if source is regular file or block device and set NTFS volume
2148 if (tstat(source, &stbuf) == 0) {
2149 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2150 imagex_printf(T("Capturing WIM image from NTFS "
2151 "filesystem on \"%"TS"\"\n"), source);
2152 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2155 if (errno != ENOENT) {
2156 imagex_error_with_errno(T("Failed to stat "
2157 "\"%"TS"\""), source);
2165 /* If the user did not specify an image name, and the basename of the
2166 * source already exists as an image name in the WIM file, append a
2167 * suffix to make it unique. */
2168 if (cmd == CMD_APPEND && name_defaulted) {
2169 unsigned long conflict_idx;
2170 tchar *name_end = tstrchr(name, T('\0'));
2171 for (conflict_idx = 1;
2172 wimlib_image_name_in_use(wim, name);
2175 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2179 /* If capturing a delta WIM, reference resources from the base WIMs
2180 * before adding the new image. */
2181 if (base_wimfiles.num_strings) {
2182 base_wims = calloc(base_wimfiles.num_strings,
2183 sizeof(base_wims[0]));
2184 if (base_wims == NULL) {
2185 imagex_error(T("Out of memory!"));
2190 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2191 ret = wimlib_open_wim_with_progress(
2192 base_wimfiles.strings[i], open_flags,
2193 &base_wims[i], imagex_progress_func, NULL);
2195 goto out_free_base_wims;
2199 ret = wimlib_reference_resources(wim, base_wims,
2200 base_wimfiles.num_strings, 0);
2202 goto out_free_base_wims;
2204 if (base_wimfiles.num_strings == 1) {
2205 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2206 base_wimfiles.strings[0]);
2208 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2209 base_wimfiles.num_strings);
2216 /* If capturing or appending as an update of an existing (template) image,
2217 * open the WIM if needed and parse the image index. */
2218 if (template_image_name_or_num) {
2221 if (base_wimfiles.num_strings == 1 &&
2222 template_wimfile == base_wimfiles.strings[0]) {
2223 template_wim = base_wims[0];
2224 } else if (template_wimfile == wimfile) {
2227 ret = wimlib_open_wim_with_progress(template_wimfile,
2230 imagex_progress_func,
2233 goto out_free_base_wims;
2236 template_image = wimlib_resolve_image(template_wim,
2237 template_image_name_or_num);
2239 if (template_image_name_or_num[0] == T('-')) {
2242 struct wimlib_wim_info info;
2244 wimlib_get_wim_info(template_wim, &info);
2245 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2246 if (n >= 1 && n <= info.image_count &&
2248 tmp != template_image_name_or_num + 1)
2250 template_image = info.image_count - (n - 1);
2253 ret = verify_image_exists_and_is_single(template_image,
2254 template_image_name_or_num,
2257 goto out_free_template_wim;
2259 template_wim = NULL;
2262 ret = wimlib_add_image_multisource(wim,
2269 goto out_free_template_wim;
2271 if (desc || flags_element || template_image_name_or_num) {
2272 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2273 * on which the added one is to be based has been specified with
2274 * --update-of. Get the index of the image we just
2275 * added, then use it to call the appropriate functions. */
2276 struct wimlib_wim_info info;
2278 wimlib_get_wim_info(wim, &info);
2281 ret = wimlib_set_image_descripton(wim,
2285 goto out_free_template_wim;
2288 if (flags_element) {
2289 ret = wimlib_set_image_flags(wim, info.image_count,
2292 goto out_free_template_wim;
2295 /* Reference template image if the user provided one. */
2296 if (template_image_name_or_num) {
2297 imagex_printf(T("Using image %d "
2298 "from \"%"TS"\" as template\n"),
2299 template_image, template_wimfile);
2300 ret = wimlib_reference_template_image(wim,
2306 goto out_free_template_wim;
2310 /* Write the new WIM or overwrite the existing WIM with the new image
2312 if (cmd == CMD_APPEND) {
2313 ret = wimlib_overwrite(wim, write_flags, num_threads);
2314 } else if (wimfile) {
2315 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2316 write_flags, num_threads);
2318 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2319 write_flags, num_threads);
2321 out_free_template_wim:
2322 /* template_wim may alias base_wims[0] or wim. */
2323 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2324 template_wim != wim)
2325 wimlib_free(template_wim);
2327 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2328 wimlib_free(base_wims[i]);
2332 out_free_capture_sources:
2333 if (capture_sources_malloced)
2334 free(capture_sources);
2335 out_free_source_list_contents:
2336 free(source_list_contents);
2337 out_free_base_wimfiles:
2338 string_set_destroy(&base_wimfiles);
2345 goto out_free_base_wimfiles;
2348 /* Remove image(s) from a WIM. */
2350 imagex_delete(int argc, tchar **argv, int cmd)
2353 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2354 int write_flags = 0;
2355 const tchar *wimfile;
2356 const tchar *image_num_or_name;
2361 for_opt(c, delete_options) {
2363 case IMAGEX_CHECK_OPTION:
2364 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2365 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2367 case IMAGEX_SOFT_OPTION:
2368 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2370 case IMAGEX_UNSAFE_COMPACT_OPTION:
2371 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2382 imagex_error(T("Must specify a WIM file"));
2384 imagex_error(T("Must specify an image"));
2388 image_num_or_name = argv[1];
2390 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2391 imagex_progress_func, NULL);
2395 image = wimlib_resolve_image(wim, image_num_or_name);
2397 ret = verify_image_exists(image, image_num_or_name, wimfile);
2399 goto out_wimlib_free;
2401 ret = wimlib_delete_image(wim, image);
2403 imagex_error(T("Failed to delete image from \"%"TS"\""),
2405 goto out_wimlib_free;
2408 ret = wimlib_overwrite(wim, write_flags, 0);
2410 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2411 "deleted"), wimfile);
2419 usage(CMD_DELETE, stderr);
2424 struct print_dentry_options {
2429 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2431 tprintf(T("%"TS"\n"), dentry->full_path);
2434 static const struct {
2437 } file_attr_flags[] = {
2438 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2439 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2440 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2441 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2442 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2443 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2444 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2445 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2446 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2447 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2448 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2449 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2450 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2451 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2452 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2455 #define TIMESTR_MAX 100
2458 timespec_to_string(const struct timespec *spec, tchar *buf)
2460 time_t t = spec->tv_sec;
2463 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2464 buf[TIMESTR_MAX - 1] = '\0';
2468 print_time(const tchar *type, const struct timespec *spec)
2470 tchar timestr[TIMESTR_MAX];
2472 timespec_to_string(spec, timestr);
2474 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2477 static void print_byte_field(const uint8_t field[], size_t len)
2480 tprintf(T("%02hhx"), *field++);
2484 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2486 tchar attr_string[256];
2489 tputs(T("WIM Information:"));
2490 tputs(T("----------------"));
2491 tprintf(T("Path: %"TS"\n"), wimfile);
2492 tprintf(T("GUID: 0x"));
2493 print_byte_field(info->guid, sizeof(info->guid));
2495 tprintf(T("Version: %u\n"), info->wim_version);
2496 tprintf(T("Image Count: %d\n"), info->image_count);
2497 tprintf(T("Compression: %"TS"\n"),
2498 wimlib_get_compression_type_string(info->compression_type));
2499 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2501 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2502 tprintf(T("Boot Index: %d\n"), info->boot_index);
2503 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2505 attr_string[0] = T('\0');
2508 tstrcat(attr_string, T("Pipable, "));
2510 if (info->has_integrity_table)
2511 tstrcat(attr_string, T("Integrity info, "));
2513 if (info->has_rpfix)
2514 tstrcat(attr_string, T("Relative path junction, "));
2516 if (info->resource_only)
2517 tstrcat(attr_string, T("Resource only, "));
2519 if (info->metadata_only)
2520 tstrcat(attr_string, T("Metadata only, "));
2522 if (info->is_marked_readonly)
2523 tstrcat(attr_string, T("Readonly, "));
2525 p = tstrchr(attr_string, T('\0'));
2526 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2529 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2533 print_resource(const struct wimlib_resource_entry *resource,
2536 tprintf(T("Hash = 0x"));
2537 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2540 if (!resource->is_missing) {
2541 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2542 resource->uncompressed_size);
2543 if (resource->packed) {
2544 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2545 "bytes @ offset %"PRIu64"\n"),
2546 resource->raw_resource_uncompressed_size,
2547 resource->raw_resource_compressed_size,
2548 resource->raw_resource_offset_in_wim);
2550 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2553 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2554 resource->compressed_size);
2556 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2560 tprintf(T("Part Number = %u\n"), resource->part_number);
2561 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2563 tprintf(T("Flags = "));
2564 if (resource->is_compressed)
2565 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2566 if (resource->is_metadata)
2567 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2568 if (resource->is_free)
2569 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2570 if (resource->is_spanned)
2571 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2572 if (resource->packed)
2573 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2581 print_blobs(WIMStruct *wim)
2583 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2587 default_print_security_descriptor(const uint8_t *sd, size_t size)
2589 tprintf(T("Security Descriptor = "));
2590 print_byte_field(sd, size);
2595 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2599 "----------------------------------------------------------------------------\n"));
2600 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2601 if (dentry->dos_name)
2602 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2603 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2604 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2605 if (file_attr_flags[i].flag & dentry->attributes)
2606 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2607 file_attr_flags[i].name);
2609 if (dentry->security_descriptor) {
2610 print_security_descriptor(dentry->security_descriptor,
2611 dentry->security_descriptor_size);
2614 print_time(T("Creation Time"), &dentry->creation_time);
2615 print_time(T("Last Write Time"), &dentry->last_write_time);
2616 print_time(T("Last Access Time"), &dentry->last_access_time);
2619 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2620 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2622 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2623 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2625 if (dentry->unix_mode != 0) {
2626 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2627 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2628 dentry->unix_uid, dentry->unix_gid,
2629 dentry->unix_mode, dentry->unix_rdev);
2632 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2633 if (dentry->streams[i].stream_name) {
2634 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2635 dentry->streams[i].stream_name);
2636 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2637 tprintf(T("\tRaw encrypted data stream:\n"));
2638 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2639 tprintf(T("\tReparse point stream:\n"));
2641 tprintf(T("\tUnnamed data stream:\n"));
2643 print_resource(&dentry->streams[i].resource, NULL);
2648 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2650 const struct print_dentry_options *options = _options;
2651 if (!options->detailed)
2652 print_dentry_full_path(dentry);
2654 print_dentry_detailed(dentry);
2658 /* Print the files contained in an image(s) in a WIM file. */
2660 imagex_dir(int argc, tchar **argv, int cmd)
2662 const tchar *wimfile;
2663 WIMStruct *wim = NULL;
2666 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2668 struct print_dentry_options options = {
2671 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2673 STRING_SET(refglobs);
2675 for_opt(c, dir_options) {
2677 case IMAGEX_PATH_OPTION:
2680 case IMAGEX_DETAILED_OPTION:
2681 options.detailed = true;
2683 case IMAGEX_ONE_FILE_ONLY_OPTION:
2684 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2686 case IMAGEX_REF_OPTION:
2687 ret = string_set_append(&refglobs, optarg);
2689 goto out_free_refglobs;
2699 imagex_error(T("Must specify a WIM file"));
2703 imagex_error(T("Too many arguments"));
2708 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2709 imagex_progress_func, NULL);
2711 goto out_free_refglobs;
2714 image = wimlib_resolve_image(wim, argv[1]);
2715 ret = verify_image_exists(image, argv[1], wimfile);
2717 goto out_wimlib_free;
2719 /* No image specified; default to image 1, but only if the WIM
2720 * contains exactly one image. */
2722 struct wimlib_wim_info info;
2724 wimlib_get_wim_info(wim, &info);
2725 if (info.image_count != 1) {
2726 imagex_error(T("\"%"TS"\" contains %d images; Please "
2727 "select one (or all)."),
2728 wimfile, info.image_count);
2735 if (refglobs.num_strings) {
2736 ret = wim_reference_globs(wim, &refglobs, 0);
2738 goto out_wimlib_free;
2741 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2742 print_dentry, &options);
2743 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2744 struct wimlib_wim_info info;
2746 wimlib_get_wim_info(wim, &info);
2747 do_metadata_not_found_warning(wimfile, &info);
2752 string_set_destroy(&refglobs);
2756 usage(CMD_DIR, stderr);
2758 goto out_free_refglobs;
2761 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2764 imagex_export(int argc, tchar **argv, int cmd)
2768 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2769 int write_flags = 0;
2770 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2771 const tchar *src_wimfile;
2772 const tchar *src_image_num_or_name;
2773 const tchar *dest_wimfile;
2775 const tchar *dest_name;
2776 const tchar *dest_desc;
2778 struct wimlib_wim_info src_info;
2779 WIMStruct *dest_wim;
2784 STRING_SET(refglobs);
2785 unsigned num_threads = 0;
2786 uint32_t chunk_size = UINT32_MAX;
2787 uint32_t solid_chunk_size = UINT32_MAX;
2788 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2790 for_opt(c, export_options) {
2792 case IMAGEX_BOOT_OPTION:
2793 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2795 case IMAGEX_CHECK_OPTION:
2796 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2797 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2799 case IMAGEX_NOCHECK_OPTION:
2800 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2802 case IMAGEX_COMPRESS_OPTION:
2803 compression_type = get_compression_type(optarg);
2804 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2807 case IMAGEX_COMPRESS_SLOW_OPTION:
2808 set_compress_slow();
2809 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2811 case IMAGEX_RECOMPRESS_OPTION:
2812 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2814 case IMAGEX_SOLID_OPTION:
2815 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2817 case IMAGEX_NO_SOLID_SORT_OPTION:
2818 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2820 case IMAGEX_CHUNK_SIZE_OPTION:
2821 chunk_size = parse_chunk_size(optarg);
2822 if (chunk_size == UINT32_MAX)
2825 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2826 solid_chunk_size = parse_chunk_size(optarg);
2827 if (solid_chunk_size == UINT32_MAX)
2830 case IMAGEX_SOLID_COMPRESS_OPTION:
2831 solid_ctype = get_compression_type(optarg);
2832 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2835 case IMAGEX_REF_OPTION:
2836 ret = string_set_append(&refglobs, optarg);
2838 goto out_free_refglobs;
2840 case IMAGEX_THREADS_OPTION:
2841 num_threads = parse_num_threads(optarg);
2842 if (num_threads == UINT_MAX)
2845 case IMAGEX_REBUILD_OPTION:
2846 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2848 case IMAGEX_PIPABLE_OPTION:
2849 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2851 case IMAGEX_NOT_PIPABLE_OPTION:
2852 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2854 case IMAGEX_WIMBOOT_OPTION:
2855 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2857 case IMAGEX_UNSAFE_COMPACT_OPTION:
2858 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2866 if (argc < 3 || argc > 5)
2868 src_wimfile = argv[0];
2869 src_image_num_or_name = argv[1];
2870 dest_wimfile = argv[2];
2871 dest_name = (argc >= 4) ? argv[3] : NULL;
2872 dest_desc = (argc >= 5) ? argv[4] : NULL;
2873 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2874 imagex_progress_func, NULL);
2876 goto out_free_refglobs;
2878 wimlib_get_wim_info(src_wim, &src_info);
2880 /* Determine if the destination is an existing file or not. If so, we
2881 * try to append the exported image(s) to it; otherwise, we create a new
2882 * WIM containing the exported image(s). Furthermore, determine if we
2883 * need to write a pipable WIM directly to standard output. */
2885 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2887 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2888 imagex_error("Can't write a non-pipable WIM to "
2889 "standard output! Specify --pipable\n"
2890 " if you want to create a pipable WIM "
2891 "(but read the docs first).");
2893 goto out_free_src_wim;
2896 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2898 dest_wimfile = NULL;
2899 dest_wim_fd = STDOUT_FILENO;
2900 imagex_info_file = stderr;
2901 set_fd_to_binary_mode(dest_wim_fd);
2904 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2906 /* Destination file exists. */
2908 if (!S_ISREG(stbuf.st_mode)) {
2909 imagex_error(T("\"%"TS"\" is not a regular file"),
2912 goto out_free_src_wim;
2914 ret = wimlib_open_wim_with_progress(dest_wimfile,
2916 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2918 imagex_progress_func,
2921 goto out_free_src_wim;
2923 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2924 /* The user specified a compression type, but we're
2925 * exporting to an existing WIM. Make sure the
2926 * specified compression type is the same as the
2927 * compression type of the existing destination WIM. */
2928 struct wimlib_wim_info dest_info;
2930 wimlib_get_wim_info(dest_wim, &dest_info);
2931 if (compression_type != dest_info.compression_type) {
2932 imagex_error(T("Cannot specify a compression type that is "
2933 "not the same as that used in the "
2934 "destination WIM"));
2936 goto out_free_dest_wim;
2942 if (errno != ENOENT) {
2943 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2946 goto out_free_src_wim;
2949 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
2950 imagex_error(T("'--unsafe-compact' is only valid when "
2951 "exporting to an existing WIM file!"));
2953 goto out_free_src_wim;
2956 /* dest_wimfile is not an existing file, so create a new WIM. */
2958 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2959 /* The user did not specify a compression type; default
2960 * to that of the source WIM, unless --solid or
2961 * --wimboot was specified. */
2963 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2964 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2965 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2966 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2968 compression_type = src_info.compression_type;
2970 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2972 goto out_free_src_wim;
2974 wimlib_register_progress_function(dest_wim,
2975 imagex_progress_func, NULL);
2977 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2978 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2980 /* For --wimboot export, use small XPRESS chunks. */
2981 wimlib_set_output_chunk_size(dest_wim, 4096);
2982 } else if (compression_type == src_info.compression_type &&
2983 chunk_size == UINT32_MAX)
2985 /* Use same chunk size if compression type is the same. */
2986 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2990 if (chunk_size != UINT32_MAX) {
2991 /* Set destination chunk size. */
2992 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2994 goto out_free_dest_wim;
2996 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2997 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2999 goto out_free_dest_wim;
3001 if (solid_chunk_size != UINT32_MAX) {
3002 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3004 goto out_free_dest_wim;
3007 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3008 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3010 goto out_free_dest_wim;
3012 if (refglobs.num_strings) {
3013 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3015 goto out_free_dest_wim;
3018 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3019 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3021 imagex_error(T("--boot specified for all-images export, but source WIM "
3022 "has no bootable image."));
3024 goto out_free_dest_wim;
3027 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3028 dest_desc, export_flags);
3030 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3031 do_resource_not_found_warning(src_wimfile,
3032 &src_info, &refglobs);
3033 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3034 do_metadata_not_found_warning(src_wimfile, &src_info);
3036 goto out_free_dest_wim;
3040 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3041 else if (dest_wimfile)
3042 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3043 write_flags, num_threads);
3045 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3046 WIMLIB_ALL_IMAGES, write_flags,
3049 wimlib_free(dest_wim);
3051 wimlib_free(src_wim);
3053 string_set_destroy(&refglobs);
3057 usage(CMD_EXPORT, stderr);
3060 goto out_free_refglobs;
3063 /* Extract files or directories from a WIM image */
3065 imagex_extract(int argc, tchar **argv, int cmd)
3072 const tchar *wimfile;
3073 const tchar *image_num_or_name;
3074 tchar *dest_dir = T(".");
3075 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3076 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3077 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3078 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3080 STRING_SET(refglobs);
3082 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3084 for_opt(c, extract_options) {
3086 case IMAGEX_CHECK_OPTION:
3087 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3089 case IMAGEX_VERBOSE_OPTION:
3090 /* No longer does anything. */
3092 case IMAGEX_REF_OPTION:
3093 ret = string_set_append(&refglobs, optarg);
3095 goto out_free_refglobs;
3097 case IMAGEX_UNIX_DATA_OPTION:
3098 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3100 case IMAGEX_NO_ACLS_OPTION:
3101 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3103 case IMAGEX_STRICT_ACLS_OPTION:
3104 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3106 case IMAGEX_NO_ATTRIBUTES_OPTION:
3107 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3109 case IMAGEX_DEST_DIR_OPTION:
3112 case IMAGEX_TO_STDOUT_OPTION:
3113 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3114 imagex_info_file = stderr;
3115 imagex_be_quiet = true;
3116 set_fd_to_binary_mode(STDOUT_FILENO);
3118 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3119 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3120 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3122 case IMAGEX_NO_GLOBS_OPTION:
3123 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3125 case IMAGEX_NULLGLOB_OPTION:
3126 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3128 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3129 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3131 case IMAGEX_WIMBOOT_OPTION:
3132 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3134 case IMAGEX_COMPACT_OPTION:
3135 ret = set_compact_mode(optarg, &extract_flags);
3137 goto out_free_refglobs;
3149 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3150 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3152 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3157 image_num_or_name = argv[1];
3162 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3163 imagex_progress_func, NULL);
3165 goto out_free_refglobs;
3167 image = wimlib_resolve_image(wim, image_num_or_name);
3168 ret = verify_image_exists_and_is_single(image,
3172 goto out_wimlib_free;
3174 if (refglobs.num_strings) {
3175 ret = wim_reference_globs(wim, &refglobs, open_flags);
3177 goto out_wimlib_free;
3183 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3186 while (argc != 0 && ret == 0) {
3190 num_paths < argc && argv[num_paths][0] != T('@');
3195 ret = wimlib_extract_paths(wim, image, dest_dir,
3196 (const tchar **)argv,
3198 extract_flags | notlist_extract_flags);
3202 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3211 if (!imagex_be_quiet)
3212 imagex_printf(T("Done extracting files.\n"));
3213 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3214 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3215 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3216 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3217 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3220 T("Note: You can use the '--nullglob' "
3221 "option to ignore missing files.\n"));
3223 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3224 "files and directories\n"
3225 " are in the WIM image.\n"),
3226 get_cmd_string(CMD_DIR, false));
3227 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3228 struct wimlib_wim_info info;
3230 wimlib_get_wim_info(wim, &info);
3231 do_resource_not_found_warning(wimfile, &info, &refglobs);
3232 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3233 struct wimlib_wim_info info;
3235 wimlib_get_wim_info(wim, &info);
3236 do_metadata_not_found_warning(wimfile, &info);
3241 string_set_destroy(&refglobs);
3245 usage(CMD_EXTRACT, stderr);
3248 goto out_free_refglobs;
3251 /* Prints information about a WIM file; also can mark an image as bootable,
3252 * change the name of an image, or change the description of an image. */
3254 imagex_info(int argc, tchar **argv, int cmd)
3259 bool nocheck = false;
3260 bool header = false;
3263 bool short_header = true;
3264 const tchar *xml_out_file = NULL;
3265 const tchar *wimfile;
3266 const tchar *image_num_or_name;
3267 const tchar *new_name;
3268 const tchar *new_desc;
3273 struct wimlib_wim_info info;
3275 for_opt(c, info_options) {
3277 case IMAGEX_BOOT_OPTION:
3280 case IMAGEX_CHECK_OPTION:
3283 case IMAGEX_NOCHECK_OPTION:
3286 case IMAGEX_HEADER_OPTION:
3288 short_header = false;
3290 case IMAGEX_BLOBS_OPTION:
3292 short_header = false;
3294 case IMAGEX_XML_OPTION:
3296 short_header = false;
3298 case IMAGEX_EXTRACT_XML_OPTION:
3299 xml_out_file = optarg;
3300 short_header = false;
3302 case IMAGEX_METADATA_OPTION:
3303 imagex_error(T("The --metadata option has been removed. "
3304 "Use 'wimdir --detail' instead."));
3313 if (argc < 1 || argc > 4)
3317 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3318 new_name = (argc >= 3) ? argv[2] : NULL;
3319 new_desc = (argc >= 4) ? argv[3] : NULL;
3321 if (check && nocheck) {
3322 imagex_error(T("Can't specify both --check and --nocheck"));
3327 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3329 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3330 imagex_progress_func, NULL);
3334 wimlib_get_wim_info(wim, &info);
3336 image = wimlib_resolve_image(wim, image_num_or_name);
3337 ret = WIMLIB_ERR_INVALID_IMAGE;
3338 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3339 verify_image_exists(image, image_num_or_name, wimfile);
3341 imagex_error(T("If you would like to set the boot "
3342 "index to 0, specify image \"0\" with "
3343 "the --boot flag."));
3345 goto out_wimlib_free;
3348 if (boot && info.image_count == 0) {
3349 imagex_error(T("--boot is meaningless on a WIM with no images"));
3350 goto out_wimlib_free;
3353 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3355 imagex_error(T("Cannot specify the --boot flag "
3356 "without specifying a specific "
3357 "image in a multi-image WIM"));
3358 goto out_wimlib_free;
3361 imagex_error(T("Cannot specify the NEW_NAME "
3362 "without specifying a specific "
3363 "image in a multi-image WIM"));
3364 goto out_wimlib_free;
3368 /* Operations that print information are separated from operations that
3369 * recreate the WIM file. */
3370 if (!new_name && !boot) {
3372 /* Read-only operations */
3374 if (image == WIMLIB_NO_IMAGE) {
3375 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3376 image_num_or_name, wimfile);
3377 goto out_wimlib_free;
3380 if (image == WIMLIB_ALL_IMAGES && short_header)
3381 print_wim_information(wimfile, &info);
3384 wimlib_print_header(wim);
3387 if (info.total_parts != 1) {
3388 tfprintf(stderr, T("Warning: Only showing the blobs "
3389 "for part %d of a %d-part WIM.\n"),
3390 info.part_number, info.total_parts);
3396 ret = wimlib_extract_xml_data(wim, stdout);
3398 goto out_wimlib_free;
3404 fp = tfopen(xml_out_file, T("wb"));
3406 imagex_error_with_errno(T("Failed to open the "
3407 "file \"%"TS"\" for "
3411 goto out_wimlib_free;
3413 ret = wimlib_extract_xml_data(wim, fp);
3415 imagex_error(T("Failed to close the file "
3421 goto out_wimlib_free;
3425 wimlib_print_available_images(wim, image);
3430 /* Modification operations */
3432 if (image == WIMLIB_ALL_IMAGES)
3435 if (image == WIMLIB_NO_IMAGE && new_name) {
3436 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3437 "when using image 0"), new_name);
3439 goto out_wimlib_free;
3443 if (image == info.boot_index) {
3444 imagex_printf(T("Image %d is already marked as "
3445 "bootable.\n"), image);
3448 imagex_printf(T("Marking image %d as bootable.\n"),
3450 info.boot_index = image;
3451 ret = wimlib_set_wim_info(wim, &info,
3452 WIMLIB_CHANGE_BOOT_INDEX);
3454 goto out_wimlib_free;
3458 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3460 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3464 imagex_printf(T("Changing the name of image %d to "
3465 "\"%"TS"\".\n"), image, new_name);
3466 ret = wimlib_set_image_name(wim, image, new_name);
3468 goto out_wimlib_free;
3472 const tchar *old_desc;
3473 old_desc = wimlib_get_image_description(wim, image);
3474 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3475 imagex_printf(T("The description of image %d is already "
3476 "\"%"TS"\".\n"), image, new_desc);
3479 imagex_printf(T("Changing the description of image %d "
3480 "to \"%"TS"\".\n"), image, new_desc);
3481 ret = wimlib_set_image_descripton(wim, image,
3484 goto out_wimlib_free;
3488 /* Only call wimlib_overwrite() if something actually needs to
3490 if (boot || new_name || new_desc ||
3491 (check && !info.has_integrity_table) ||
3492 (nocheck && info.has_integrity_table))
3494 int write_flags = 0;
3497 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3499 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3500 ret = wimlib_overwrite(wim, write_flags, 1);
3502 imagex_printf(T("The file \"%"TS"\" was not modified "
3503 "because nothing needed to be done.\n"),
3514 usage(CMD_INFO, stderr);
3520 /* Join split WIMs into one part WIM */
3522 imagex_join(int argc, tchar **argv, int cmd)
3525 int swm_open_flags = 0;
3526 int wim_write_flags = 0;
3527 const tchar *output_path;
3530 for_opt(c, join_options) {
3532 case IMAGEX_CHECK_OPTION:
3533 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3534 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3544 imagex_error(T("Must specify one or more split WIM (.swm) "
3548 output_path = argv[0];
3549 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3554 imagex_progress_func,
3560 usage(CMD_JOIN, stderr);
3565 #if WIM_MOUNTING_SUPPORTED
3567 /* Mounts a WIM image. */
3569 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3572 int mount_flags = 0;
3574 const tchar *staging_dir = NULL;
3575 const tchar *wimfile;
3578 struct wimlib_wim_info info;
3582 STRING_SET(refglobs);
3584 if (cmd == CMD_MOUNTRW) {
3585 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3586 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3589 for_opt(c, mount_options) {
3591 case IMAGEX_ALLOW_OTHER_OPTION:
3592 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3594 case IMAGEX_CHECK_OPTION:
3595 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3597 case IMAGEX_DEBUG_OPTION:
3598 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3600 case IMAGEX_STREAMS_INTERFACE_OPTION:
3601 if (!tstrcasecmp(optarg, T("none")))
3602 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3603 else if (!tstrcasecmp(optarg, T("xattr")))
3604 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3605 else if (!tstrcasecmp(optarg, T("windows")))
3606 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3608 imagex_error(T("Unknown stream interface \"%"TS"\""),
3613 case IMAGEX_REF_OPTION:
3614 ret = string_set_append(&refglobs, optarg);
3616 goto out_free_refglobs;
3618 case IMAGEX_STAGING_DIR_OPTION:
3619 staging_dir = optarg;
3621 case IMAGEX_UNIX_DATA_OPTION:
3622 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3630 if (argc != 2 && argc != 3)
3635 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3636 imagex_progress_func, NULL);
3638 goto out_free_refglobs;
3640 wimlib_get_wim_info(wim, &info);
3643 /* Image explicitly specified. */
3644 image = wimlib_resolve_image(wim, argv[1]);
3646 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3650 /* No image specified; default to image 1, but only if the WIM
3651 * contains exactly one image. */
3653 if (info.image_count != 1) {
3654 imagex_error(T("\"%"TS"\" contains %d images; Please "
3655 "select one."), wimfile, info.image_count);
3663 if (refglobs.num_strings) {
3664 ret = wim_reference_globs(wim, &refglobs, open_flags);
3669 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3671 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3672 do_metadata_not_found_warning(wimfile, &info);
3674 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3676 image, wimfile, dir);
3682 string_set_destroy(&refglobs);
3688 goto out_free_refglobs;
3690 #endif /* WIM_MOUNTING_SUPPORTED */
3692 /* Rebuild a WIM file */
3694 imagex_optimize(int argc, tchar **argv, int cmd)
3697 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3698 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3699 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3700 uint32_t chunk_size = UINT32_MAX;
3701 uint32_t solid_chunk_size = UINT32_MAX;
3702 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3705 const tchar *wimfile;
3708 unsigned num_threads = 0;
3710 for_opt(c, optimize_options) {
3712 case IMAGEX_CHECK_OPTION:
3713 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3714 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3716 case IMAGEX_NOCHECK_OPTION:
3717 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3719 case IMAGEX_COMPRESS_OPTION:
3720 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3721 compression_type = get_compression_type(optarg);
3722 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3725 case IMAGEX_COMPRESS_SLOW_OPTION:
3726 set_compress_slow();
3727 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3729 case IMAGEX_RECOMPRESS_OPTION:
3730 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3732 case IMAGEX_CHUNK_SIZE_OPTION:
3733 chunk_size = parse_chunk_size(optarg);
3734 if (chunk_size == UINT32_MAX)
3737 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3738 solid_chunk_size = parse_chunk_size(optarg);
3739 if (solid_chunk_size == UINT32_MAX)
3742 case IMAGEX_SOLID_COMPRESS_OPTION:
3743 solid_ctype = get_compression_type(optarg);
3744 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3747 case IMAGEX_SOLID_OPTION:
3748 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3749 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3751 case IMAGEX_NO_SOLID_SORT_OPTION:
3752 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3754 case IMAGEX_THREADS_OPTION:
3755 num_threads = parse_num_threads(optarg);
3756 if (num_threads == UINT_MAX)
3759 case IMAGEX_PIPABLE_OPTION:
3760 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3762 case IMAGEX_NOT_PIPABLE_OPTION:
3763 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3765 case IMAGEX_UNSAFE_COMPACT_OPTION:
3766 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3780 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3781 imagex_progress_func, NULL);
3785 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3786 /* Change compression type. */
3787 ret = wimlib_set_output_compression_type(wim, compression_type);
3789 goto out_wimlib_free;
3792 if (chunk_size != UINT32_MAX) {
3793 /* Change chunk size. */
3794 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3796 goto out_wimlib_free;
3798 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3799 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3801 goto out_wimlib_free;
3803 if (solid_chunk_size != UINT32_MAX) {
3804 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3806 goto out_wimlib_free;
3809 old_size = file_get_size(wimfile);
3810 tprintf(T("\"%"TS"\" original size: "), wimfile);
3812 tputs(T("Unknown"));
3814 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3816 ret = wimlib_overwrite(wim, write_flags, num_threads);
3818 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3819 goto out_wimlib_free;
3822 new_size = file_get_size(wimfile);
3823 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3825 tputs(T("Unknown"));
3827 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3829 tfputs(T("Space saved: "), stdout);
3830 if (new_size != -1 && old_size != -1) {
3831 tprintf(T("%lld KiB\n"),
3832 ((long long)old_size - (long long)new_size) >> 10);
3834 tputs(T("Unknown"));
3843 usage(CMD_OPTIMIZE, stderr);
3849 /* Split a WIM into a spanned set */
3851 imagex_split(int argc, tchar **argv, int cmd)
3855 int write_flags = 0;
3856 unsigned long part_size;
3861 for_opt(c, split_options) {
3863 case IMAGEX_CHECK_OPTION:
3864 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3865 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3877 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3878 if (tmp == argv[2] || *tmp) {
3879 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3880 imagex_error(T("The part size must be an integer or "
3881 "floating-point number of megabytes."));
3884 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3885 imagex_progress_func, NULL);
3889 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3895 usage(CMD_SPLIT, stderr);
3901 #if WIM_MOUNTING_SUPPORTED
3902 /* Unmounts a mounted WIM image. */
3904 imagex_unmount(int argc, tchar **argv, int cmd)
3907 int unmount_flags = 0;
3910 for_opt(c, unmount_options) {
3912 case IMAGEX_COMMIT_OPTION:
3913 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3915 case IMAGEX_CHECK_OPTION:
3916 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3918 case IMAGEX_REBUILD_OPTION:
3919 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3921 case IMAGEX_LAZY_OPTION:
3922 case IMAGEX_FORCE_OPTION:
3923 /* Now, unmount is lazy by default. However, committing
3924 * the image will fail with
3925 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3926 * file descriptors on the WIM image. The
3927 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3928 * descriptors to be closed. */
3929 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3931 case IMAGEX_NEW_IMAGE_OPTION:
3932 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3943 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3944 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3945 imagex_error(T("--new-image is meaningless "
3946 "without --commit also specified!"));
3951 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3952 imagex_progress_func, NULL);
3954 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3955 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3957 "\tNote: Use --commit --force to force changes "
3958 "to be committed, regardless\n"
3959 "\t of open files.\n"));
3966 usage(CMD_UNMOUNT, stderr);
3971 #endif /* WIM_MOUNTING_SUPPORTED */
3974 * Add, delete, or rename files in a WIM image.
3977 imagex_update(int argc, tchar **argv, int cmd)
3979 const tchar *wimfile;
3983 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3984 int write_flags = 0;
3985 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3986 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3987 WIMLIB_ADD_FLAG_VERBOSE |
3988 WIMLIB_ADD_FLAG_WINCONFIG;
3989 int default_delete_flags = 0;
3990 unsigned num_threads = 0;
3992 tchar *cmd_file_contents;
3993 size_t cmd_file_nchars;
3994 struct wimlib_update_command *cmds;
3996 tchar *command_str = NULL;
3997 tchar *config_file = NULL;
3998 tchar *wimboot_config = NULL;
4000 for_opt(c, update_options) {
4002 /* Generic or write options */
4003 case IMAGEX_THREADS_OPTION:
4004 num_threads = parse_num_threads(optarg);
4005 if (num_threads == UINT_MAX)
4008 case IMAGEX_CHECK_OPTION:
4009 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4010 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4012 case IMAGEX_REBUILD_OPTION:
4013 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4015 case IMAGEX_COMMAND_OPTION:
4017 imagex_error(T("--command may only be specified "
4018 "one time. Please provide\n"
4019 " the update commands "
4020 "on standard input instead."));
4023 command_str = tstrdup(optarg);
4025 imagex_error(T("Out of memory!"));
4029 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4030 wimboot_config = optarg;
4032 /* Default delete options */
4033 case IMAGEX_FORCE_OPTION:
4034 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4036 case IMAGEX_RECURSIVE_OPTION:
4037 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4040 /* Global add option */
4041 case IMAGEX_CONFIG_OPTION:
4042 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4043 config_file = optarg;
4046 /* Default add options */
4047 case IMAGEX_VERBOSE_OPTION:
4048 /* No longer does anything. */
4050 case IMAGEX_DEREFERENCE_OPTION:
4051 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4053 case IMAGEX_UNIX_DATA_OPTION:
4054 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4056 case IMAGEX_NO_ACLS_OPTION:
4057 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4059 case IMAGEX_STRICT_ACLS_OPTION:
4060 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4062 case IMAGEX_NO_REPLACE_OPTION:
4063 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4065 case IMAGEX_UNSAFE_COMPACT_OPTION:
4066 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4075 if (argc != 1 && argc != 2)
4079 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4080 imagex_progress_func, NULL);
4082 goto out_free_command_str;
4085 /* Image explicitly specified. */
4086 image = wimlib_resolve_image(wim, argv[1]);
4087 ret = verify_image_exists_and_is_single(image, argv[1],
4090 goto out_wimlib_free;
4092 /* No image specified; default to image 1, but only if the WIM
4093 * contains exactly one image. */
4094 struct wimlib_wim_info info;
4096 wimlib_get_wim_info(wim, &info);
4097 if (info.image_count != 1) {
4098 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4099 wimfile, info.image_count);
4106 /* Read update commands from standard input, or the command string if
4109 cmd_file_contents = NULL;
4110 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4114 goto out_free_cmd_file_contents;
4116 } else if (!wimboot_config) {
4117 if (isatty(STDIN_FILENO)) {
4118 tputs(T("Reading update commands from standard input..."));
4119 recommend_man_page(CMD_UPDATE, stdout);
4121 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4122 if (!cmd_file_contents) {
4124 goto out_wimlib_free;
4127 /* Parse the update commands */
4128 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4132 goto out_free_cmd_file_contents;
4135 cmd_file_contents = NULL;
4140 /* Set default flags and capture config on the update commands */
4141 for (size_t i = 0; i < num_cmds; i++) {
4142 switch (cmds[i].op) {
4143 case WIMLIB_UPDATE_OP_ADD:
4144 cmds[i].add.add_flags |= default_add_flags;
4145 cmds[i].add.config_file = config_file;
4147 case WIMLIB_UPDATE_OP_DELETE:
4148 cmds[i].delete_.delete_flags |= default_delete_flags;
4155 /* Execute the update commands */
4156 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4160 if (wimboot_config) {
4161 /* --wimboot-config=FILE is short for an
4162 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4164 struct wimlib_update_command cmd;
4166 cmd.op = WIMLIB_UPDATE_OP_ADD;
4167 cmd.add.fs_source_path = wimboot_config;
4168 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4169 cmd.add.config_file = NULL;
4170 cmd.add.add_flags = 0;
4172 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4177 /* Overwrite the updated WIM */
4178 ret = wimlib_overwrite(wim, write_flags, num_threads);
4181 out_free_cmd_file_contents:
4182 free(cmd_file_contents);
4185 out_free_command_str:
4190 usage(CMD_UPDATE, stderr);
4193 goto out_free_command_str;
4196 /* Verify a WIM file. */
4198 imagex_verify(int argc, tchar **argv, int cmd)
4201 const tchar *wimfile;
4203 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4204 int verify_flags = 0;
4205 STRING_SET(refglobs);
4208 for_opt(c, verify_options) {
4210 case IMAGEX_REF_OPTION:
4211 ret = string_set_append(&refglobs, optarg);
4213 goto out_free_refglobs;
4215 case IMAGEX_NOCHECK_OPTION:
4216 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4228 imagex_error(T("Must specify a WIM file!"));
4230 imagex_error(T("At most one WIM file can be specified!"));
4236 ret = wimlib_open_wim_with_progress(wimfile,
4239 imagex_progress_func,
4242 goto out_free_refglobs;
4244 ret = wim_reference_globs(wim, &refglobs, open_flags);
4246 goto out_wimlib_free;
4248 ret = wimlib_verify_wim(wim, verify_flags);
4250 tputc(T('\n'), stderr);
4251 imagex_error(T("\"%"TS"\" failed verification!"),
4253 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4254 refglobs.num_strings == 0)
4256 imagex_printf(T("Note: if this WIM file is not standalone, "
4257 "use the --ref option to specify the other parts.\n"));
4260 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4267 string_set_destroy(&refglobs);
4271 usage(CMD_VERIFY, stderr);
4273 goto out_free_refglobs;
4276 struct imagex_command {
4278 int (*func)(int argc, tchar **argv, int cmd);
4281 static const struct imagex_command imagex_commands[] = {
4282 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4283 [CMD_APPLY] = {T("apply"), imagex_apply},
4284 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4285 [CMD_DELETE] = {T("delete"), imagex_delete},
4286 [CMD_DIR ] = {T("dir"), imagex_dir},
4287 [CMD_EXPORT] = {T("export"), imagex_export},
4288 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4289 [CMD_INFO] = {T("info"), imagex_info},
4290 [CMD_JOIN] = {T("join"), imagex_join},
4291 #if WIM_MOUNTING_SUPPORTED
4292 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4293 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4295 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4296 [CMD_SPLIT] = {T("split"), imagex_split},
4297 #if WIM_MOUNTING_SUPPORTED
4298 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4300 [CMD_UPDATE] = {T("update"), imagex_update},
4301 [CMD_VERIFY] = {T("verify"), imagex_verify},
4306 /* Can be a directory or source list file. But source list file is probably
4307 * a rare use case, so just say directory. */
4308 # define SOURCE_STR T("DIRECTORY")
4310 /* Can only be a directory */
4311 # define TARGET_STR T("DIRECTORY")
4314 /* Can be a directory, NTFS volume, or source list file. */
4315 # define SOURCE_STR T("SOURCE")
4317 /* Can be a directory or NTFS volume. */
4318 # define TARGET_STR T("TARGET")
4322 static const tchar *usage_strings[] = {
4325 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4326 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4327 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4328 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4329 " [--wimboot] [--unix-data] [--dereference]\n"
4333 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4334 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4335 " [--no-attributes] [--rpfix] [--norpfix]\n"
4336 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4337 " [--compact=FORMAT]\n"
4341 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4342 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4343 " [--config=FILE] [--threads=NUM_THREADS]\n"
4344 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4345 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4346 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4350 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4354 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4358 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4359 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4360 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4361 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4362 " [--wimboot] [--solid]\n"
4366 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4367 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4368 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4369 " [--no-attributes] [--include-invalid-names]\n"
4370 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4374 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4375 " [--boot] [--check] [--nocheck] [--xml]\n"
4376 " [--extract-xml FILE] [--header] [--blobs]\n"
4380 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4382 #if WIM_MOUNTING_SUPPORTED
4385 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4386 " [--check] [--streams-interface=INTERFACE]\n"
4387 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4391 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4392 " [--check] [--streams-interface=INTERFACE]\n"
4393 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4399 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4400 " [--check] [--nocheck] [--solid]\n"
4405 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4407 #if WIM_MOUNTING_SUPPORTED
4410 " %"TS" DIRECTORY\n"
4411 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4416 " %"TS" WIMFILE [IMAGE]\n"
4417 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4418 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4419 " [--command=STRING] [--wimboot-config=FILE]\n"
4424 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4428 static const tchar *invocation_name;
4429 static int invocation_cmd = CMD_NONE;
4431 static const tchar *get_cmd_string(int cmd, bool nospace)
4433 static tchar buf[50];
4434 if (cmd == CMD_NONE) {
4435 return T("wimlib-imagex");
4436 } else if (invocation_cmd != CMD_NONE) {
4437 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4439 const tchar *format;
4442 format = T("%"TS"-%"TS"");
4444 format = T("%"TS" %"TS"");
4445 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4453 static const tchar *s =
4455 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4456 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4457 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4458 "This is free software: you are free to change and redistribute it.\n"
4459 "There is NO WARRANTY, to the extent permitted by law.\n"
4461 "Report bugs to "PACKAGE_BUGREPORT".\n"
4468 help_or_version(int argc, tchar **argv, int cmd)
4473 for (i = 1; i < argc; i++) {
4475 if (p[0] == T('-') && p[1] == T('-')) {
4477 if (!tstrcmp(p, T("help"))) {
4478 if (cmd == CMD_NONE)
4483 } else if (!tstrcmp(p, T("version"))) {
4492 print_usage_string(int cmd, FILE *fp)
4494 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4498 recommend_man_page(int cmd, FILE *fp)
4500 const tchar *format_str;
4502 format_str = T("Some uncommon options are not listed;\n"
4503 "See %"TS".pdf in the doc directory for more details.\n");
4505 format_str = T("Some uncommon options are not listed;\n"
4506 "Try `man %"TS"' for more details.\n");
4508 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4512 usage(int cmd, FILE *fp)
4514 tfprintf(fp, T("Usage:\n"));
4515 print_usage_string(cmd, fp);
4516 tfprintf(fp, T("\n"));
4517 recommend_man_page(cmd, fp);
4523 tfprintf(fp, T("Usage:\n"));
4524 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4525 print_usage_string(cmd, fp);
4526 tfprintf(fp, T("\n"));
4528 static const tchar *extra =
4531 " %"TS" --version\n"
4534 tfprintf(fp, extra, invocation_name, invocation_name);
4536 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4537 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4538 "For some commands IMAGE may be \"all\".\n"
4540 recommend_man_page(CMD_NONE, fp);
4543 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4544 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4545 * something else), while on Windows the command arguments will be UTF-16LE
4546 * encoded 'wchar_t' strings. */
4549 wmain(int argc, wchar_t **argv, wchar_t **envp)
4551 main(int argc, char **argv)
4558 imagex_info_file = stdout;
4559 invocation_name = tbasename(argv[0]);
4562 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4563 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4567 setlocale(LC_ALL, "");
4568 codeset = nl_langinfo(CODESET);
4569 if (!strstr(codeset, "UTF-8") &&
4570 !strstr(codeset, "UTF8") &&
4571 !strstr(codeset, "utf-8") &&
4572 !strstr(codeset, "utf8"))
4575 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4576 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4577 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4578 " to any value to force wimlib to use UTF-8.\n",
4584 #endif /* !__WIN32__ */
4587 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4588 if (igcase != NULL) {
4589 if (!tstrcmp(igcase, T("no")) ||
4590 !tstrcmp(igcase, T("0")))
4591 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4592 else if (!tstrcmp(igcase, T("yes")) ||
4593 !tstrcmp(igcase, T("1")))
4594 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4597 "WARNING: Ignoring unknown setting of "
4598 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4603 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4605 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4606 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4607 for (int i = 0; i < CMD_MAX; i++) {
4608 if (!tstrcmp(invocation_name + 3,
4609 imagex_commands[i].name))
4618 /* Unless already known from the invocation name, determine which
4619 * command was specified. */
4620 if (cmd == CMD_NONE) {
4622 imagex_error(T("No command specified!\n"));
4626 for (int i = 0; i < CMD_MAX; i++) {
4627 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4632 if (cmd != CMD_NONE) {
4638 /* Handle --help and --version. --help can be either for the program as
4639 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4640 * CMD_NONE). Note: help_or_version() will not return if a --help or
4641 * --version argument was found. */
4642 help_or_version(argc, argv, cmd);
4644 /* Bail if a valid command was not specified. */
4645 if (cmd == CMD_NONE) {
4646 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4651 /* Enable warning and error messages in wimlib to be more user-friendly.
4653 wimlib_set_print_errors(true);
4655 /* Initialize wimlib. */
4656 ret = wimlib_global_init(init_flags);
4658 goto out_check_status;
4660 /* Call the command handler function. */
4661 ret = imagex_commands[cmd].func(argc, argv, cmd);
4663 /* Check for error writing to standard output, especially since for some
4664 * commands, writing to standard output is part of the program's actual
4665 * behavior and not just for informational purposes. */
4666 if (ferror(stdout) || fclose(stdout)) {
4667 imagex_error_with_errno(T("error writing to standard output"));
4672 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4673 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4674 * error code from which an error message can be printed. */
4676 imagex_error(T("Exiting with error code %d:\n"
4678 wimlib_get_error_string(ret));
4679 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4680 imagex_error_with_errno(T("errno"));
4682 /* Make wimlib free any resources it's holding (although this is not
4683 * strictly necessary because the process is ending anyway). */
4684 wimlib_global_cleanup();