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_UPDATE_OF_OPTION,
198 IMAGEX_VERBOSE_OPTION,
199 IMAGEX_WIMBOOT_CONFIG_OPTION,
200 IMAGEX_WIMBOOT_OPTION,
204 static const struct option apply_options[] = {
205 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
206 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
207 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
208 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
209 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
210 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
211 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
212 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
213 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
214 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
215 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
217 /* --resume is undocumented for now as it needs improvement. */
218 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
219 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
220 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
224 static const struct option capture_or_append_options[] = {
225 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
226 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
227 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
228 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
229 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
230 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
231 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
232 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
233 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
234 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
235 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
236 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
237 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
238 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
239 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
240 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
241 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
242 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
243 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
244 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
245 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
246 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
247 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
248 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
249 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
250 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
251 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
252 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
253 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
254 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
255 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
256 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
260 static const struct option delete_options[] = {
261 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
262 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
266 static const struct option dir_options[] = {
267 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
268 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
269 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
270 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
274 static const struct option export_options[] = {
275 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
276 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
277 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
278 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
279 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
280 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
281 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
282 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
283 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
284 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
285 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
286 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
287 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
288 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
289 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
290 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
291 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
292 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
293 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
294 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
295 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
299 static const struct option extract_options[] = {
300 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
301 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
302 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
303 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
304 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
305 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
306 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
307 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
308 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
309 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
310 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
311 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
312 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
313 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
314 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
315 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
316 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
320 static const struct option info_options[] = {
321 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
322 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
323 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
324 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
325 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
326 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
327 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
328 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
329 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
330 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
334 static const struct option join_options[] = {
335 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
339 static const struct option mount_options[] = {
340 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
341 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
342 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
343 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
344 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
345 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
346 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
350 static const struct option optimize_options[] = {
351 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
352 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
353 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
354 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
355 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
356 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
357 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
358 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
359 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
360 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
361 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
362 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
363 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
364 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
365 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
366 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
367 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
368 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
372 static const struct option split_options[] = {
373 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
377 static const struct option unmount_options[] = {
378 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
379 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
380 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
381 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
382 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
383 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
387 static const struct option update_options[] = {
388 /* Careful: some of the options here set the defaults for update
389 * commands, but the flags given to an actual update command (and not to
390 * `imagex update' itself are also handled in
391 * update_command_add_option(). */
392 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
393 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
394 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
395 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
396 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
398 /* Default delete options */
399 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
400 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
402 /* Global add option */
403 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
405 /* Default add options */
406 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
407 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
408 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
409 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
410 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
411 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
412 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
417 static const struct option verify_options[] = {
418 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
419 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
425 # define _format_attribute(type, format_str, args_start) \
426 __attribute__((format(type, format_str, args_start)))
428 # define _format_attribute(type, format_str, args_start)
431 /* Print formatted error message to stderr. */
432 static void _format_attribute(printf, 1, 2)
433 imagex_error(const tchar *format, ...)
436 va_start(va, format);
437 tfputs(T("ERROR: "), stderr);
438 tvfprintf(stderr, format, va);
439 tputc(T('\n'), stderr);
443 /* Print formatted error message to stderr. */
444 static void _format_attribute(printf, 1, 2)
445 imagex_error_with_errno(const tchar *format, ...)
447 int errno_save = errno;
449 va_start(va, format);
450 tfputs(T("ERROR: "), stderr);
451 tvfprintf(stderr, format, va);
452 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
457 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
459 if (image == WIMLIB_NO_IMAGE) {
460 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
461 " Please specify a 1-based image index or "
462 "image name. To list the images\n"
463 " contained in the WIM archive, run\n"
465 " %"TS" \"%"TS"\"\n"),
466 image_name, wim_name,
467 get_cmd_string(CMD_INFO, false), wim_name);
468 return WIMLIB_ERR_INVALID_IMAGE;
474 verify_image_is_single(int image)
476 if (image == WIMLIB_ALL_IMAGES) {
477 imagex_error(T("Cannot specify all images for this action!"));
478 return WIMLIB_ERR_INVALID_IMAGE;
484 verify_image_exists_and_is_single(int image, const tchar *image_name,
485 const tchar *wim_name)
488 ret = verify_image_exists(image, image_name, wim_name);
490 ret = verify_image_is_single(image);
495 print_available_compression_types(FILE *fp)
497 static const tchar *s =
499 "Available compression types:\n"
502 " xpress (alias: \"fast\")\n"
503 " lzx (alias: \"maximum\") (default for capture)\n"
504 " lzms (alias: \"recovery\")\n"
510 /* Parse the argument to --compress */
512 get_compression_type(tchar *optarg)
515 unsigned int compression_level = 0;
518 plevel = tstrchr(optarg, T(':'));
524 ultmp = tstrtoul(plevel, &ptmp, 10);
525 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
526 imagex_error(T("Compression level must be a positive integer! "
527 "e.g. --compress=lzx:80"));
528 return WIMLIB_COMPRESSION_TYPE_INVALID;
530 compression_level = ultmp;
533 if (!tstrcasecmp(optarg, T("maximum")) ||
534 !tstrcasecmp(optarg, T("lzx")) ||
535 !tstrcasecmp(optarg, T("max")))
536 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
537 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
538 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
539 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
540 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
541 else if (!tstrcasecmp(optarg, T("none")))
542 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
544 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
545 print_available_compression_types(stderr);
546 return WIMLIB_COMPRESSION_TYPE_INVALID;
549 if (compression_level != 0)
550 wimlib_set_default_compression_level(ctype, compression_level);
554 /* Parse the argument to --compact */
556 set_compact_mode(const tchar *arg, int *extract_flags)
559 if (!tstrcasecmp(arg, T("xpress4k")))
560 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
561 else if (!tstrcasecmp(arg, T("xpress8k")))
562 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
563 else if (!tstrcasecmp(arg, T("xpress16k")))
564 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
565 else if (!tstrcasecmp(arg, T("lzx")))
566 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
569 *extract_flags |= flag;
574 "\"%"TS"\" is not a recognized System Compression format. The options are:"
576 " --compact=xpress4k\n"
577 " --compact=xpress8k\n"
578 " --compact=xpress16k\n"
586 set_compress_slow(void)
589 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
590 " Use the '--compress=TYPE:LEVEL' option instead.\n");
592 wimlib_set_default_compression_level(-1, 100);
596 const tchar **strings;
597 unsigned num_strings;
598 unsigned num_alloc_strings;
601 #define STRING_SET_INITIALIZER \
602 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
604 #define STRING_SET(_strings) \
605 struct string_set _strings = STRING_SET_INITIALIZER
608 string_set_append(struct string_set *set, const tchar *glob)
610 unsigned num_alloc_strings = set->num_alloc_strings;
612 if (set->num_strings == num_alloc_strings) {
613 const tchar **new_strings;
615 num_alloc_strings += 4;
616 new_strings = realloc(set->strings,
617 sizeof(set->strings[0]) * num_alloc_strings);
619 imagex_error(T("Out of memory!"));
622 set->strings = new_strings;
623 set->num_alloc_strings = num_alloc_strings;
625 set->strings[set->num_strings++] = glob;
630 string_set_destroy(struct string_set *set)
636 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
638 return wimlib_reference_resource_files(wim, set->strings,
640 WIMLIB_REF_FLAG_GLOB_ENABLE,
645 do_resource_not_found_warning(const tchar *wimfile,
646 const struct wimlib_wim_info *info,
647 const struct string_set *refglobs)
649 if (info->total_parts > 1) {
650 if (refglobs->num_strings == 0) {
651 imagex_error(T("\"%"TS"\" is part of a split WIM. "
652 "Use --ref to specify the other parts."),
655 imagex_error(T("Perhaps the '--ref' argument did not "
656 "specify all other parts of the split "
660 imagex_error(T("If this is a delta WIM, use the --ref argument "
661 "to specify the WIM(s) on which it is based."));
665 /* Returns the size of a file given its name, or -1 if the file does not exist
666 * or its size cannot be determined. */
668 file_get_size(const tchar *filename)
671 if (tstat(filename, &st) == 0)
678 PARSE_STRING_SUCCESS = 0,
679 PARSE_STRING_FAILURE = 1,
680 PARSE_STRING_NONE = 2,
684 * Parses a string token from an array of characters.
686 * Tokens are either whitespace-delimited, or double or single-quoted.
688 * @line_p: Pointer to the pointer to the line of data. Will be updated
689 * to point past the string token iff the return value is
690 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
693 * @len_p: @len_p initially stores the length of the line of data, which may
694 * be 0, and it will be updated to the number of bytes remaining in
695 * the line iff the return value is PARSE_STRING_SUCCESS.
697 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
698 * parsed string token will be returned here.
700 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
701 * PARSE_STRING_FAILURE if the data was invalid due to a missing
702 * closing quote; or PARSE_STRING_NONE if the line ended before the
703 * beginning of a string token was found.
706 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
709 tchar *line = *line_p;
713 /* Skip leading whitespace */
716 return PARSE_STRING_NONE;
717 if (!istspace(*line) && *line != T('\0'))
723 if (quote_char == T('"') || quote_char == T('\'')) {
728 line = tmemchr(line, quote_char, len);
730 imagex_error(T("Missing closing quote: %"TS), fn - 1);
731 return PARSE_STRING_FAILURE;
734 /* Unquoted string. Go until whitespace. Line is terminated
735 * by '\0', so no need to check 'len'. */
739 } while (!istspace(*line) && *line != T('\0'));
746 return PARSE_STRING_SUCCESS;
749 /* Parses a line of data (not an empty line or comment) in the source list file
750 * format. (See the man page for 'wimlib-imagex capture' for details on this
751 * format and the meaning.)
753 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
754 * len == 0. The data in @line will be modified by this function call.
756 * @len: Length of the line of data.
758 * @source: On success, the capture source and target described by the line is
759 * written into this destination. Note that it will contain pointers
760 * to data in the @line array.
762 * Returns true if the line was valid; false otherwise. */
764 parse_source_list_line(tchar *line, size_t len,
765 struct wimlib_capture_source *source)
769 ret = parse_string(&line, &len, &source->fs_source_path);
770 if (ret != PARSE_STRING_SUCCESS)
772 ret = parse_string(&line, &len, &source->wim_target_path);
773 if (ret == PARSE_STRING_NONE)
774 source->wim_target_path = source->fs_source_path;
775 return ret != PARSE_STRING_FAILURE;
778 /* Returns %true if the given line of length @len > 0 is a comment or empty line
779 * in the source list file format. */
781 is_comment_line(const tchar *line, size_t len)
784 if (*line == T('#') || *line == T(';'))
786 if (!istspace(*line) && *line != T('\0'))
796 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
799 tchar *contents = *contents_p;
800 size_t nchars = *nchars_p;
803 for (i = 0; i < nchars; i++)
804 if (contents[i] == T('\n'))
807 /* Handle last line not terminated by a newline */
808 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
809 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
811 imagex_error(T("Out of memory!"));
814 contents[nchars] = T('\n');
815 *contents_p = contents;
823 /* Parses a file in the source list format. (See the man page for
824 * 'wimlib-imagex capture' for details on this format and the meaning.)
826 * @source_list_contents: Contents of the source list file. Note that this
827 * buffer will be modified to save memory allocations,
828 * and cannot be freed until the returned array of
829 * wimlib_capture_source's has also been freed.
831 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
834 * @nsources_ret: On success, the length of the returned array is
837 * Returns: An array of `struct wimlib_capture_source's that can be passed to
838 * the wimlib_add_image_multisource() function to specify how a WIM image is to
840 static struct wimlib_capture_source *
841 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
842 size_t *nsources_ret)
846 struct wimlib_capture_source *sources;
849 nlines = text_file_count_lines(source_list_contents_p,
850 &source_list_nchars);
854 /* Always allocate at least 1 slot, just in case the implementation of
855 * calloc() returns NULL if 0 bytes are requested. */
856 sources = calloc(nlines ?: 1, sizeof(*sources));
858 imagex_error(T("out of memory"));
861 p = *source_list_contents_p;
863 for (i = 0; i < nlines; i++) {
864 /* XXX: Could use rawmemchr() here instead, but it may not be
865 * available on all platforms. */
866 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
867 size_t len = endp - p + 1;
869 if (!is_comment_line(p, len)) {
870 if (!parse_source_list_line(p, len, &sources[j++])) {
882 /* Reads the contents of a file into memory. */
884 file_get_contents(const tchar *filename, size_t *len_ret)
891 if (tstat(filename, &stbuf) != 0) {
892 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
897 fp = tfopen(filename, T("rb"));
899 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
903 buf = malloc(len ? len : 1);
905 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
906 "contents of file \"%"TS"\""), len, filename);
909 if (fread(buf, 1, len, fp) != len) {
910 imagex_error_with_errno(T("Failed to read %zu bytes from the "
911 "file \"%"TS"\""), len, filename);
925 /* Read standard input until EOF and return the full contents in a malloc()ed
926 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
929 stdin_get_contents(size_t *len_ret)
931 /* stdin can, of course, be a pipe or other non-seekable file, so the
932 * total length of the data cannot be pre-determined */
934 size_t newlen = 1024;
938 char *p = realloc(buf, newlen);
939 size_t bytes_read, bytes_to_read;
941 imagex_error(T("out of memory while reading stdin"));
945 bytes_to_read = newlen - pos;
946 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
948 if (bytes_read != bytes_to_read) {
953 imagex_error_with_errno(T("error reading stdin"));
967 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
970 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
972 *num_tchars_ret = num_bytes;
974 #else /* !__WIN32__ */
975 /* On Windows, translate the text to UTF-16LE */
979 if (num_bytes >= 2 &&
980 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
981 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
983 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
984 * with something that looks like an ASCII character encoded as
985 * a UTF-16LE code unit. Assume the file is encoded as
986 * UTF-16LE. This is not a 100% reliable check. */
987 num_wchars = num_bytes / 2;
988 text_wstr = (wchar_t*)text;
990 /* File does not look like UTF-16LE. Assume it is encoded in
991 * the current Windows code page. I think these are always
992 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
993 * should work as expected. */
994 text_wstr = win32_mbs_to_wcs(text,
999 *num_tchars_ret = num_wchars;
1001 #endif /* __WIN32__ */
1005 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1010 contents = file_get_contents(filename, &num_bytes);
1013 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1017 stdin_get_text_contents(size_t *num_tchars_ret)
1022 contents = stdin_get_contents(&num_bytes);
1025 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1028 #define TO_PERCENT(numerator, denominator) \
1029 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1031 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1032 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1033 #define KIBIBYTE_MIN_NBYTES 10000ULL
1036 get_unit(uint64_t total_bytes, const tchar **name_ret)
1038 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1039 *name_ret = T("GiB");
1041 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1042 *name_ret = T("MiB");
1044 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1045 *name_ret = T("KiB");
1048 *name_ret = T("bytes");
1053 static struct wimlib_progress_info_scan last_scan_progress;
1056 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1058 uint64_t prev_count, cur_count;
1060 prev_count = last_scan_progress.num_nondirs_scanned +
1061 last_scan_progress.num_dirs_scanned;
1062 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1064 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1065 cur_count % 128 == 0)
1067 unsigned unit_shift;
1068 const tchar *unit_name;
1070 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1071 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1072 "%"PRIu64" directories) "),
1073 scan->num_bytes_scanned >> unit_shift,
1075 scan->num_nondirs_scanned,
1076 scan->num_dirs_scanned);
1077 last_scan_progress = *scan;
1080 /* Progress callback function passed to various wimlib functions. */
1081 static enum wimlib_progress_status
1082 imagex_progress_func(enum wimlib_progress_msg msg,
1083 union wimlib_progress_info *info,
1084 void *_ignored_context)
1086 unsigned percent_done;
1087 unsigned unit_shift;
1088 const tchar *unit_name;
1090 if (imagex_be_quiet)
1091 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1093 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1095 static bool first = true;
1097 imagex_printf(T("Writing %"TS"-compressed data "
1098 "using %u thread%"TS"\n"),
1099 wimlib_get_compression_type_string(
1100 info->write_streams.compression_type),
1101 info->write_streams.num_threads,
1102 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1106 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1107 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1108 info->write_streams.total_bytes);
1110 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1111 "written (%u%% done)"),
1112 info->write_streams.completed_bytes >> unit_shift,
1114 info->write_streams.total_bytes >> unit_shift,
1117 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1118 imagex_printf(T("\n"));
1120 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1121 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1122 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1123 imagex_printf(T("\n"));
1125 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1126 info->scan.wim_target_path);
1128 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1130 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1131 switch (info->scan.status) {
1132 case WIMLIB_SCAN_DENTRY_OK:
1133 report_scan_progress(&info->scan, false);
1135 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1136 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1138 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1139 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1140 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1142 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1143 /* Symlink fixups are enabled by default. This is
1144 * mainly intended for Windows, which for some reason
1145 * uses absolute junctions (with drive letters!) in the
1146 * default installation. On UNIX-like systems, warn the
1147 * user when fixing the target of an absolute symbolic
1148 * link, so they know to disable this if they want. */
1150 imagex_printf(T("\nWARNING: Adjusted target of "
1151 "absolute symbolic link \"%"TS"\"\n"
1152 " (Use --norpfix to capture "
1153 "absolute symbolic links as-is)\n"),
1154 info->scan.cur_path);
1161 case WIMLIB_PROGRESS_MSG_SCAN_END:
1162 report_scan_progress(&info->scan, true);
1163 imagex_printf(T("\n"));
1165 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1166 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1167 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1168 info->integrity.total_bytes);
1169 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1170 "of %"PRIu64" %"TS" (%u%%) done"),
1171 info->integrity.filename,
1172 info->integrity.completed_bytes >> unit_shift,
1174 info->integrity.total_bytes >> unit_shift,
1177 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1178 imagex_printf(T("\n"));
1180 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1181 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1182 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1183 info->integrity.total_bytes);
1184 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1185 "of %"PRIu64" %"TS" (%u%%) done"),
1186 info->integrity.completed_bytes >> unit_shift,
1188 info->integrity.total_bytes >> unit_shift,
1191 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1192 imagex_printf(T("\n"));
1194 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1195 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1196 "to %"TS" \"%"TS"\"\n"),
1197 info->extract.image,
1198 info->extract.image_name,
1199 info->extract.wimfile_name,
1200 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1201 T("NTFS volume") : T("directory")),
1202 info->extract.target);
1204 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1205 if (info->extract.end_file_count >= 2000) {
1206 percent_done = TO_PERCENT(info->extract.current_file_count,
1207 info->extract.end_file_count);
1208 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1209 info->extract.current_file_count,
1210 info->extract.end_file_count, percent_done);
1211 if (info->extract.current_file_count == info->extract.end_file_count)
1212 imagex_printf(T("\n"));
1215 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1216 percent_done = TO_PERCENT(info->extract.completed_bytes,
1217 info->extract.total_bytes);
1218 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1219 imagex_printf(T("\rExtracting file data: "
1220 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1221 info->extract.completed_bytes >> unit_shift,
1223 info->extract.total_bytes >> unit_shift,
1226 if (info->extract.completed_bytes >= info->extract.total_bytes)
1227 imagex_printf(T("\n"));
1229 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1230 if (info->extract.end_file_count >= 2000) {
1231 percent_done = TO_PERCENT(info->extract.current_file_count,
1232 info->extract.end_file_count);
1233 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1234 info->extract.current_file_count,
1235 info->extract.end_file_count, percent_done);
1236 if (info->extract.current_file_count == info->extract.end_file_count)
1237 imagex_printf(T("\n"));
1240 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1241 if (info->extract.total_parts != 1) {
1242 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1243 info->extract.part_number,
1244 info->extract.total_parts);
1247 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1248 percent_done = TO_PERCENT(info->split.completed_bytes,
1249 info->split.total_bytes);
1250 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1251 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1252 "%"PRIu64" %"TS" (%u%%) written\n"),
1253 info->split.part_name,
1254 info->split.cur_part_number,
1255 info->split.total_parts,
1256 info->split.completed_bytes >> unit_shift,
1258 info->split.total_bytes >> unit_shift,
1262 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1263 if (info->split.completed_bytes == info->split.total_bytes) {
1264 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1265 info->split.cur_part_number,
1266 info->split.total_parts);
1269 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1270 switch (info->update.command->op) {
1271 case WIMLIB_UPDATE_OP_DELETE:
1272 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1273 info->update.command->delete_.wim_path);
1275 case WIMLIB_UPDATE_OP_RENAME:
1276 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1277 info->update.command->rename.wim_source_path,
1278 info->update.command->rename.wim_target_path);
1280 case WIMLIB_UPDATE_OP_ADD:
1285 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1286 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1287 info->replace.path_in_wim);
1289 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1290 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1291 info->wimboot_exclude.path_in_wim);
1293 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1294 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1295 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1296 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1297 info->unmount.mounted_wim,
1298 info->unmount.mounted_image);
1300 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1301 info->unmount.mounted_wim,
1302 info->unmount.mounted_image);
1303 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1307 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1308 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1309 info->verify_image.current_image,
1310 info->verify_image.total_images);
1312 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1313 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1314 info->verify_streams.total_bytes);
1315 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1316 imagex_printf(T("\rVerifying file data: "
1317 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1318 info->verify_streams.completed_bytes >> unit_shift,
1320 info->verify_streams.total_bytes >> unit_shift,
1323 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1324 imagex_printf(T("\n"));
1329 fflush(imagex_info_file);
1330 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1334 parse_num_threads(const tchar *optarg)
1337 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1338 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1339 imagex_error(T("Number of threads must be a non-negative integer!"));
1347 parse_chunk_size(const tchar *optarg)
1350 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1351 if (chunk_size == 0) {
1352 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1353 " with optional K, M, or G suffix"));
1357 if (*tmp == T('k') || *tmp == T('K')) {
1360 } else if (*tmp == T('m') || *tmp == T('M')) {
1363 } else if (*tmp == T('g') || *tmp == T('G')) {
1367 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1368 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1372 if (chunk_size >= UINT32_MAX) {
1373 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1381 * Parse an option passed to an update command.
1383 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1386 * @option: Text string for the option (beginning with --)
1388 * @cmd: `struct wimlib_update_command' that is being constructed for
1391 * Returns true if the option was recognized; false if not.
1394 update_command_add_option(int op, const tchar *option,
1395 struct wimlib_update_command *cmd)
1397 bool recognized = true;
1399 case WIMLIB_UPDATE_OP_ADD:
1400 if (!tstrcmp(option, T("--verbose")))
1401 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1402 else if (!tstrcmp(option, T("--unix-data")))
1403 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1404 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1405 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1406 else if (!tstrcmp(option, T("--strict-acls")))
1407 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1408 else if (!tstrcmp(option, T("--dereference")))
1409 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1410 else if (!tstrcmp(option, T("--no-replace")))
1411 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1415 case WIMLIB_UPDATE_OP_DELETE:
1416 if (!tstrcmp(option, T("--force")))
1417 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1418 else if (!tstrcmp(option, T("--recursive")))
1419 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1430 /* How many nonoption arguments each `imagex update' command expects */
1431 static const unsigned update_command_num_nonoptions[] = {
1432 [WIMLIB_UPDATE_OP_ADD] = 2,
1433 [WIMLIB_UPDATE_OP_DELETE] = 1,
1434 [WIMLIB_UPDATE_OP_RENAME] = 2,
1438 update_command_add_nonoption(int op, const tchar *nonoption,
1439 struct wimlib_update_command *cmd,
1440 unsigned num_nonoptions)
1443 case WIMLIB_UPDATE_OP_ADD:
1444 if (num_nonoptions == 0)
1445 cmd->add.fs_source_path = (tchar*)nonoption;
1447 cmd->add.wim_target_path = (tchar*)nonoption;
1449 case WIMLIB_UPDATE_OP_DELETE:
1450 cmd->delete_.wim_path = (tchar*)nonoption;
1452 case WIMLIB_UPDATE_OP_RENAME:
1453 if (num_nonoptions == 0)
1454 cmd->rename.wim_source_path = (tchar*)nonoption;
1456 cmd->rename.wim_target_path = (tchar*)nonoption;
1462 * Parse a command passed on stdin to `imagex update'.
1464 * @line: Text of the command.
1465 * @len: Length of the line, including a null terminator
1468 * @command: A `struct wimlib_update_command' to fill in from the parsed
1471 * @line_number: Line number of the command, for diagnostics.
1473 * Returns true on success; returns false on parse error.
1476 parse_update_command(tchar *line, size_t len,
1477 struct wimlib_update_command *command,
1481 tchar *command_name;
1483 size_t num_nonoptions;
1485 /* Get the command name ("add", "delete", "rename") */
1486 ret = parse_string(&line, &len, &command_name);
1487 if (ret != PARSE_STRING_SUCCESS)
1490 if (!tstrcasecmp(command_name, T("add"))) {
1491 op = WIMLIB_UPDATE_OP_ADD;
1492 } else if (!tstrcasecmp(command_name, T("delete"))) {
1493 op = WIMLIB_UPDATE_OP_DELETE;
1494 } else if (!tstrcasecmp(command_name, T("rename"))) {
1495 op = WIMLIB_UPDATE_OP_RENAME;
1497 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1498 command_name, line_number);
1503 /* Parse additional options and non-options as needed */
1508 ret = parse_string(&line, &len, &next_string);
1509 if (ret == PARSE_STRING_NONE) /* End of line */
1511 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1513 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1515 if (!update_command_add_option(op, next_string, command))
1517 imagex_error(T("Unrecognized option \"%"TS"\" to "
1518 "update command \"%"TS"\" on line %zu"),
1519 next_string, command_name, line_number);
1525 if (num_nonoptions == update_command_num_nonoptions[op])
1527 imagex_error(T("Unexpected argument \"%"TS"\" in "
1528 "update command on line %zu\n"
1529 " (The \"%"TS"\" command only "
1530 "takes %zu nonoption arguments!)\n"),
1531 next_string, line_number,
1532 command_name, num_nonoptions);
1535 update_command_add_nonoption(op, next_string,
1536 command, num_nonoptions);
1541 if (num_nonoptions != update_command_num_nonoptions[op]) {
1542 imagex_error(T("Not enough arguments to update command "
1543 "\"%"TS"\" on line %zu"), command_name, line_number);
1549 static struct wimlib_update_command *
1550 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1551 size_t *num_cmds_ret)
1555 struct wimlib_update_command *cmds;
1558 nlines = text_file_count_lines(cmd_file_contents_p,
1563 /* Always allocate at least 1 slot, just in case the implementation of
1564 * calloc() returns NULL if 0 bytes are requested. */
1565 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1567 imagex_error(T("out of memory"));
1570 p = *cmd_file_contents_p;
1572 for (i = 0; i < nlines; i++) {
1573 /* XXX: Could use rawmemchr() here instead, but it may not be
1574 * available on all platforms. */
1575 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1576 size_t len = endp - p + 1;
1578 if (!is_comment_line(p, len)) {
1579 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1590 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1591 * one image from a WIM file to an NTFS volume. */
1593 imagex_apply(int argc, tchar **argv, int cmd)
1597 int image = WIMLIB_NO_IMAGE;
1599 struct wimlib_wim_info info;
1601 const tchar *wimfile;
1602 const tchar *target;
1603 const tchar *image_num_or_name = NULL;
1604 int extract_flags = 0;
1606 STRING_SET(refglobs);
1608 for_opt(c, apply_options) {
1610 case IMAGEX_CHECK_OPTION:
1611 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1613 case IMAGEX_VERBOSE_OPTION:
1614 /* No longer does anything. */
1616 case IMAGEX_REF_OPTION:
1617 ret = string_set_append(&refglobs, optarg);
1619 goto out_free_refglobs;
1621 case IMAGEX_UNIX_DATA_OPTION:
1622 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1624 case IMAGEX_NO_ACLS_OPTION:
1625 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1627 case IMAGEX_STRICT_ACLS_OPTION:
1628 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1630 case IMAGEX_NO_ATTRIBUTES_OPTION:
1631 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1633 case IMAGEX_NORPFIX_OPTION:
1634 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1636 case IMAGEX_RPFIX_OPTION:
1637 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1639 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1640 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1641 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1643 case IMAGEX_RESUME_OPTION:
1644 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1646 case IMAGEX_WIMBOOT_OPTION:
1647 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1649 case IMAGEX_COMPACT_OPTION:
1650 ret = set_compact_mode(optarg, &extract_flags);
1652 goto out_free_refglobs;
1660 if (argc != 2 && argc != 3)
1665 if (!tstrcmp(wimfile, T("-"))) {
1666 /* Attempt to apply pipable WIM from standard input. */
1668 image_num_or_name = NULL;
1671 image_num_or_name = argv[1];
1676 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1677 imagex_progress_func, NULL);
1679 goto out_free_refglobs;
1681 wimlib_get_wim_info(wim, &info);
1684 /* Image explicitly specified. */
1685 image_num_or_name = argv[1];
1686 image = wimlib_resolve_image(wim, image_num_or_name);
1687 ret = verify_image_exists(image, image_num_or_name, wimfile);
1689 goto out_wimlib_free;
1692 /* No image specified; default to image 1, but only if the WIM
1693 * contains exactly one image. */
1695 if (info.image_count != 1) {
1696 imagex_error(T("\"%"TS"\" contains %d images; "
1697 "Please select one (or all)."),
1698 wimfile, info.image_count);
1707 if (refglobs.num_strings) {
1709 imagex_error(T("Can't specify --ref when applying from stdin!"));
1711 goto out_wimlib_free;
1713 ret = wim_reference_globs(wim, &refglobs, open_flags);
1715 goto out_wimlib_free;
1720 /* Interpret a regular file or block device target as an NTFS
1724 if (tstat(target, &stbuf)) {
1725 if (errno != ENOENT) {
1726 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1729 goto out_wimlib_free;
1732 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1733 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1739 ret = wimlib_extract_image(wim, image, target, extract_flags);
1741 set_fd_to_binary_mode(STDIN_FILENO);
1742 ret = wimlib_extract_image_from_pipe_with_progress(
1747 imagex_progress_func,
1751 imagex_printf(T("Done applying WIM image.\n"));
1752 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1754 do_resource_not_found_warning(wimfile, &info, &refglobs);
1756 imagex_error(T( "If you are applying an image "
1757 "from a split pipable WIM,\n"
1758 " make sure you have "
1759 "concatenated together all parts."));
1765 string_set_destroy(&refglobs);
1769 usage(CMD_APPLY, stderr);
1771 goto out_free_refglobs;
1774 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1775 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1776 * the desired image. 'wimlib-imagex append': add a new image to an existing
1779 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1783 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1784 WIMLIB_ADD_FLAG_WINCONFIG |
1785 WIMLIB_ADD_FLAG_VERBOSE;
1786 int write_flags = 0;
1787 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1788 uint32_t chunk_size = UINT32_MAX;
1789 uint32_t solid_chunk_size = UINT32_MAX;
1790 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1791 const tchar *wimfile;
1795 const tchar *flags_element = NULL;
1798 STRING_SET(base_wimfiles);
1799 WIMStruct **base_wims;
1801 WIMStruct *template_wim;
1802 const tchar *template_wimfile = NULL;
1803 const tchar *template_image_name_or_num = NULL;
1804 int template_image = WIMLIB_NO_IMAGE;
1807 unsigned num_threads = 0;
1812 tchar *config_file = NULL;
1814 bool source_list = false;
1815 size_t source_list_nchars = 0;
1816 tchar *source_list_contents;
1817 bool capture_sources_malloced;
1818 struct wimlib_capture_source *capture_sources;
1820 bool name_defaulted;
1822 for_opt(c, capture_or_append_options) {
1824 case IMAGEX_BOOT_OPTION:
1825 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1827 case IMAGEX_CHECK_OPTION:
1828 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1829 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1831 case IMAGEX_NOCHECK_OPTION:
1832 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1834 case IMAGEX_CONFIG_OPTION:
1835 config_file = optarg;
1836 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1838 case IMAGEX_COMPRESS_OPTION:
1839 compression_type = get_compression_type(optarg);
1840 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1843 case IMAGEX_COMPRESS_SLOW_OPTION:
1844 set_compress_slow();
1846 case IMAGEX_CHUNK_SIZE_OPTION:
1847 chunk_size = parse_chunk_size(optarg);
1848 if (chunk_size == UINT32_MAX)
1851 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1852 solid_chunk_size = parse_chunk_size(optarg);
1853 if (solid_chunk_size == UINT32_MAX)
1856 case IMAGEX_SOLID_COMPRESS_OPTION:
1857 solid_ctype = get_compression_type(optarg);
1858 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1861 case IMAGEX_SOLID_OPTION:
1862 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1864 case IMAGEX_NO_SOLID_SORT_OPTION:
1865 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1867 case IMAGEX_FLAGS_OPTION:
1868 flags_element = optarg;
1870 case IMAGEX_DEREFERENCE_OPTION:
1871 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1873 case IMAGEX_VERBOSE_OPTION:
1874 /* No longer does anything. */
1876 case IMAGEX_THREADS_OPTION:
1877 num_threads = parse_num_threads(optarg);
1878 if (num_threads == UINT_MAX)
1881 case IMAGEX_REBUILD_OPTION:
1882 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1884 case IMAGEX_UNIX_DATA_OPTION:
1885 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1887 case IMAGEX_SOURCE_LIST_OPTION:
1890 case IMAGEX_NO_ACLS_OPTION:
1891 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1893 case IMAGEX_STRICT_ACLS_OPTION:
1894 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1896 case IMAGEX_RPFIX_OPTION:
1897 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1899 case IMAGEX_NORPFIX_OPTION:
1900 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1902 case IMAGEX_PIPABLE_OPTION:
1903 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1905 case IMAGEX_NOT_PIPABLE_OPTION:
1906 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1908 case IMAGEX_UPDATE_OF_OPTION:
1909 if (template_image_name_or_num) {
1910 imagex_error(T("'--update-of' can only be "
1911 "specified one time!"));
1915 colon = tstrrchr(optarg, T(':'));
1918 template_wimfile = optarg;
1920 template_image_name_or_num = colon + 1;
1922 template_wimfile = NULL;
1923 template_image_name_or_num = optarg;
1927 case IMAGEX_DELTA_FROM_OPTION:
1928 if (cmd != CMD_CAPTURE) {
1929 imagex_error(T("'--delta-from' is only "
1930 "valid for capture!"));
1933 ret = string_set_append(&base_wimfiles, optarg);
1935 goto out_free_base_wimfiles;
1936 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1938 case IMAGEX_WIMBOOT_OPTION:
1939 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1948 if (argc < 2 || argc > 4)
1954 /* Set default compression type and parameters. */
1957 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1958 /* No compression type specified. Use the default. */
1960 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1961 /* With --wimboot, default to XPRESS compression. */
1962 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1963 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1964 /* With --solid, default to LZMS compression. (However,
1965 * this will not affect solid resources!) */
1966 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1968 /* Otherwise, default to LZX compression. */
1969 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1973 if (!tstrcmp(wimfile, T("-"))) {
1974 /* Writing captured WIM to standard output. */
1976 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1977 imagex_error("Can't write a non-pipable WIM to "
1978 "standard output! Specify --pipable\n"
1979 " if you want to create a pipable WIM "
1980 "(but read the docs first).");
1984 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1986 if (cmd == CMD_APPEND) {
1987 imagex_error(T("Using standard output for append does "
1988 "not make sense."));
1991 wim_fd = STDOUT_FILENO;
1993 imagex_info_file = stderr;
1994 set_fd_to_binary_mode(wim_fd);
1997 /* If template image was specified using --update-of=IMAGE rather
1998 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1999 if (template_image_name_or_num && !template_wimfile) {
2000 if (base_wimfiles.num_strings == 1) {
2001 /* Capturing delta WIM based on single WIM: default to
2003 template_wimfile = base_wimfiles.strings[0];
2004 } else if (cmd == CMD_APPEND) {
2005 /* Appending to WIM: default to WIM being appended to.
2007 template_wimfile = wimfile;
2009 /* Capturing a normal (non-delta) WIM, so the WIM file
2010 * *must* be explicitly specified. */
2011 if (base_wimfiles.num_strings > 1) {
2012 imagex_error(T("For capture of delta WIM "
2013 "based on multiple existing "
2015 " '--update-of' must "
2016 "specify WIMFILE:IMAGE!"));
2018 imagex_error(T("For capture of non-delta WIM, "
2019 "'--update-of' must specify "
2028 name_defaulted = false;
2030 /* Set default name to SOURCE argument, omitting any directory
2031 * prefixes and trailing slashes. This requires making a copy
2032 * of @source. Leave some free characters at the end in case we
2033 * append a number to keep the name unique. */
2034 size_t source_name_len;
2036 source_name_len = tstrlen(source);
2037 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2038 name = tbasename(tstrcpy(source_copy, source));
2039 name_defaulted = true;
2041 /* Image description defaults to NULL if not given. */
2048 /* Set up capture sources in source list mode */
2049 if (source[0] == T('-') && source[1] == T('\0')) {
2050 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2052 source_list_contents = file_get_text_contents(source,
2053 &source_list_nchars);
2055 if (!source_list_contents)
2058 capture_sources = parse_source_list(&source_list_contents,
2061 if (!capture_sources) {
2063 goto out_free_source_list_contents;
2065 capture_sources_malloced = true;
2067 /* Set up capture source in non-source-list mode. */
2068 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2069 capture_sources[0].fs_source_path = source;
2070 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2071 capture_sources[0].reserved = 0;
2073 capture_sources_malloced = false;
2074 source_list_contents = NULL;
2077 /* Open the existing WIM, or create a new one. */
2078 if (cmd == CMD_APPEND) {
2079 ret = wimlib_open_wim_with_progress(wimfile,
2080 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2082 imagex_progress_func,
2085 goto out_free_capture_sources;
2087 ret = wimlib_create_new_wim(compression_type, &wim);
2089 goto out_free_capture_sources;
2090 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2093 /* Set chunk size if non-default. */
2094 if (chunk_size != UINT32_MAX) {
2095 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2098 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2099 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2100 ret = wimlib_set_output_chunk_size(wim, 4096);
2104 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2105 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2109 if (solid_chunk_size != UINT32_MAX) {
2110 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2116 /* Detect if source is regular file or block device and set NTFS volume
2121 if (tstat(source, &stbuf) == 0) {
2122 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2123 imagex_printf(T("Capturing WIM image from NTFS "
2124 "filesystem on \"%"TS"\"\n"), source);
2125 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2128 if (errno != ENOENT) {
2129 imagex_error_with_errno(T("Failed to stat "
2130 "\"%"TS"\""), source);
2138 /* If the user did not specify an image name, and the basename of the
2139 * source already exists as an image name in the WIM file, append a
2140 * suffix to make it unique. */
2141 if (cmd == CMD_APPEND && name_defaulted) {
2142 unsigned long conflict_idx;
2143 tchar *name_end = tstrchr(name, T('\0'));
2144 for (conflict_idx = 1;
2145 wimlib_image_name_in_use(wim, name);
2148 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2152 /* If capturing a delta WIM, reference resources from the base WIMs
2153 * before adding the new image. */
2154 if (base_wimfiles.num_strings) {
2155 base_wims = calloc(base_wimfiles.num_strings,
2156 sizeof(base_wims[0]));
2157 if (base_wims == NULL) {
2158 imagex_error(T("Out of memory!"));
2163 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2164 ret = wimlib_open_wim_with_progress(
2165 base_wimfiles.strings[i], open_flags,
2166 &base_wims[i], imagex_progress_func, NULL);
2168 goto out_free_base_wims;
2172 ret = wimlib_reference_resources(wim, base_wims,
2173 base_wimfiles.num_strings, 0);
2175 goto out_free_base_wims;
2177 if (base_wimfiles.num_strings == 1) {
2178 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2179 base_wimfiles.strings[0]);
2181 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2182 base_wimfiles.num_strings);
2189 /* If capturing or appending as an update of an existing (template) image,
2190 * open the WIM if needed and parse the image index. */
2191 if (template_image_name_or_num) {
2194 if (base_wimfiles.num_strings == 1 &&
2195 template_wimfile == base_wimfiles.strings[0]) {
2196 template_wim = base_wims[0];
2197 } else if (template_wimfile == wimfile) {
2200 ret = wimlib_open_wim_with_progress(template_wimfile,
2203 imagex_progress_func,
2206 goto out_free_base_wims;
2209 template_image = wimlib_resolve_image(template_wim,
2210 template_image_name_or_num);
2212 if (template_image_name_or_num[0] == T('-')) {
2215 struct wimlib_wim_info info;
2217 wimlib_get_wim_info(template_wim, &info);
2218 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2219 if (n >= 1 && n <= info.image_count &&
2221 tmp != template_image_name_or_num + 1)
2223 template_image = info.image_count - (n - 1);
2226 ret = verify_image_exists_and_is_single(template_image,
2227 template_image_name_or_num,
2230 goto out_free_template_wim;
2232 template_wim = NULL;
2235 ret = wimlib_add_image_multisource(wim,
2242 goto out_free_template_wim;
2244 if (desc || flags_element || template_image_name_or_num) {
2245 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2246 * on which the added one is to be based has been specified with
2247 * --update-of. Get the index of the image we just
2248 * added, then use it to call the appropriate functions. */
2249 struct wimlib_wim_info info;
2251 wimlib_get_wim_info(wim, &info);
2254 ret = wimlib_set_image_descripton(wim,
2258 goto out_free_template_wim;
2261 if (flags_element) {
2262 ret = wimlib_set_image_flags(wim, info.image_count,
2265 goto out_free_template_wim;
2268 /* Reference template image if the user provided one. */
2269 if (template_image_name_or_num) {
2270 imagex_printf(T("Using image %d "
2271 "from \"%"TS"\" as template\n"),
2272 template_image, template_wimfile);
2273 ret = wimlib_reference_template_image(wim,
2279 goto out_free_template_wim;
2283 /* Write the new WIM or overwrite the existing WIM with the new image
2285 if (cmd == CMD_APPEND) {
2286 ret = wimlib_overwrite(wim, write_flags, num_threads);
2287 } else if (wimfile) {
2288 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2289 write_flags, num_threads);
2291 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2292 write_flags, num_threads);
2294 out_free_template_wim:
2295 /* template_wim may alias base_wims[0] or wim. */
2296 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2297 template_wim != wim)
2298 wimlib_free(template_wim);
2300 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2301 wimlib_free(base_wims[i]);
2305 out_free_capture_sources:
2306 if (capture_sources_malloced)
2307 free(capture_sources);
2308 out_free_source_list_contents:
2309 free(source_list_contents);
2310 out_free_base_wimfiles:
2311 string_set_destroy(&base_wimfiles);
2318 goto out_free_base_wimfiles;
2321 /* Remove image(s) from a WIM. */
2323 imagex_delete(int argc, tchar **argv, int cmd)
2326 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2327 int write_flags = 0;
2328 const tchar *wimfile;
2329 const tchar *image_num_or_name;
2334 for_opt(c, delete_options) {
2336 case IMAGEX_CHECK_OPTION:
2337 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2338 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2340 case IMAGEX_SOFT_OPTION:
2341 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2352 imagex_error(T("Must specify a WIM file"));
2354 imagex_error(T("Must specify an image"));
2358 image_num_or_name = argv[1];
2360 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2361 imagex_progress_func, NULL);
2365 image = wimlib_resolve_image(wim, image_num_or_name);
2367 ret = verify_image_exists(image, image_num_or_name, wimfile);
2369 goto out_wimlib_free;
2371 ret = wimlib_delete_image(wim, image);
2373 imagex_error(T("Failed to delete image from \"%"TS"\""),
2375 goto out_wimlib_free;
2378 ret = wimlib_overwrite(wim, write_flags, 0);
2380 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2381 "deleted"), wimfile);
2389 usage(CMD_DELETE, stderr);
2394 struct print_dentry_options {
2399 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2401 tprintf(T("%"TS"\n"), dentry->full_path);
2404 static const struct {
2407 } file_attr_flags[] = {
2408 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2409 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2410 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2411 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2412 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2413 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2414 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2415 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2416 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2417 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2418 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2419 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2420 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2421 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2422 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2425 #define TIMESTR_MAX 100
2428 timespec_to_string(const struct timespec *spec, tchar *buf)
2430 time_t t = spec->tv_sec;
2433 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2434 buf[TIMESTR_MAX - 1] = '\0';
2438 print_time(const tchar *type, const struct timespec *spec)
2440 tchar timestr[TIMESTR_MAX];
2442 timespec_to_string(spec, timestr);
2444 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2447 static void print_byte_field(const uint8_t field[], size_t len)
2450 tprintf(T("%02hhx"), *field++);
2454 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2456 tchar attr_string[256];
2459 tputs(T("WIM Information:"));
2460 tputs(T("----------------"));
2461 tprintf(T("Path: %"TS"\n"), wimfile);
2462 tprintf(T("GUID: 0x"));
2463 print_byte_field(info->guid, sizeof(info->guid));
2465 tprintf(T("Version: %u\n"), info->wim_version);
2466 tprintf(T("Image Count: %d\n"), info->image_count);
2467 tprintf(T("Compression: %"TS"\n"),
2468 wimlib_get_compression_type_string(info->compression_type));
2469 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2471 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2472 tprintf(T("Boot Index: %d\n"), info->boot_index);
2473 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2475 attr_string[0] = T('\0');
2478 tstrcat(attr_string, T("Pipable, "));
2480 if (info->has_integrity_table)
2481 tstrcat(attr_string, T("Integrity info, "));
2483 if (info->has_rpfix)
2484 tstrcat(attr_string, T("Relative path junction, "));
2486 if (info->resource_only)
2487 tstrcat(attr_string, T("Resource only, "));
2489 if (info->metadata_only)
2490 tstrcat(attr_string, T("Metadata only, "));
2492 if (info->is_marked_readonly)
2493 tstrcat(attr_string, T("Readonly, "));
2495 p = tstrchr(attr_string, T('\0'));
2496 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2499 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2503 print_resource(const struct wimlib_resource_entry *resource,
2506 tprintf(T("Hash = 0x"));
2507 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2510 if (!resource->is_missing) {
2511 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2512 resource->uncompressed_size);
2513 if (resource->packed) {
2514 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2515 "bytes @ offset %"PRIu64"\n"),
2516 resource->raw_resource_uncompressed_size,
2517 resource->raw_resource_compressed_size,
2518 resource->raw_resource_offset_in_wim);
2520 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2523 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2524 resource->compressed_size);
2526 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2530 tprintf(T("Part Number = %u\n"), resource->part_number);
2531 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2533 tprintf(T("Flags = "));
2534 if (resource->is_compressed)
2535 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2536 if (resource->is_metadata)
2537 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2538 if (resource->is_free)
2539 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2540 if (resource->is_spanned)
2541 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2542 if (resource->packed)
2543 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2551 print_blobs(WIMStruct *wim)
2553 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2557 default_print_security_descriptor(const uint8_t *sd, size_t size)
2559 tprintf(T("Security Descriptor = "));
2560 print_byte_field(sd, size);
2565 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2569 "----------------------------------------------------------------------------\n"));
2570 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2571 if (dentry->dos_name)
2572 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2573 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2574 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2575 if (file_attr_flags[i].flag & dentry->attributes)
2576 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2577 file_attr_flags[i].name);
2579 if (dentry->security_descriptor) {
2580 print_security_descriptor(dentry->security_descriptor,
2581 dentry->security_descriptor_size);
2584 print_time(T("Creation Time"), &dentry->creation_time);
2585 print_time(T("Last Write Time"), &dentry->last_write_time);
2586 print_time(T("Last Access Time"), &dentry->last_access_time);
2589 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2590 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2592 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2593 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2595 if (dentry->unix_mode != 0) {
2596 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2597 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2598 dentry->unix_uid, dentry->unix_gid,
2599 dentry->unix_mode, dentry->unix_rdev);
2602 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2603 if (dentry->streams[i].stream_name) {
2604 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2605 dentry->streams[i].stream_name);
2606 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2607 tprintf(T("\tRaw encrypted data stream:\n"));
2608 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2609 tprintf(T("\tReparse point stream:\n"));
2611 tprintf(T("\tUnnamed data stream:\n"));
2613 print_resource(&dentry->streams[i].resource, NULL);
2618 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2620 const struct print_dentry_options *options = _options;
2621 if (!options->detailed)
2622 print_dentry_full_path(dentry);
2624 print_dentry_detailed(dentry);
2628 /* Print the files contained in an image(s) in a WIM file. */
2630 imagex_dir(int argc, tchar **argv, int cmd)
2632 const tchar *wimfile;
2633 WIMStruct *wim = NULL;
2636 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2638 struct print_dentry_options options = {
2641 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2643 STRING_SET(refglobs);
2645 for_opt(c, dir_options) {
2647 case IMAGEX_PATH_OPTION:
2650 case IMAGEX_DETAILED_OPTION:
2651 options.detailed = true;
2653 case IMAGEX_ONE_FILE_ONLY_OPTION:
2654 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2656 case IMAGEX_REF_OPTION:
2657 ret = string_set_append(&refglobs, optarg);
2659 goto out_free_refglobs;
2669 imagex_error(T("Must specify a WIM file"));
2673 imagex_error(T("Too many arguments"));
2678 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2679 imagex_progress_func, NULL);
2681 goto out_free_refglobs;
2684 image = wimlib_resolve_image(wim, argv[1]);
2685 ret = verify_image_exists(image, argv[1], wimfile);
2687 goto out_wimlib_free;
2689 /* No image specified; default to image 1, but only if the WIM
2690 * contains exactly one image. */
2692 struct wimlib_wim_info info;
2694 wimlib_get_wim_info(wim, &info);
2695 if (info.image_count != 1) {
2696 imagex_error(T("\"%"TS"\" contains %d images; Please "
2697 "select one (or all)."),
2698 wimfile, info.image_count);
2705 if (refglobs.num_strings) {
2706 ret = wim_reference_globs(wim, &refglobs, 0);
2708 goto out_wimlib_free;
2711 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2712 print_dentry, &options);
2716 string_set_destroy(&refglobs);
2720 usage(CMD_DIR, stderr);
2722 goto out_free_refglobs;
2725 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2728 imagex_export(int argc, tchar **argv, int cmd)
2732 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2733 int write_flags = 0;
2734 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2735 const tchar *src_wimfile;
2736 const tchar *src_image_num_or_name;
2737 const tchar *dest_wimfile;
2739 const tchar *dest_name;
2740 const tchar *dest_desc;
2742 struct wimlib_wim_info src_info;
2743 WIMStruct *dest_wim;
2748 STRING_SET(refglobs);
2749 unsigned num_threads = 0;
2750 uint32_t chunk_size = UINT32_MAX;
2751 uint32_t solid_chunk_size = UINT32_MAX;
2752 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2754 for_opt(c, export_options) {
2756 case IMAGEX_BOOT_OPTION:
2757 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2759 case IMAGEX_CHECK_OPTION:
2760 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2761 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2763 case IMAGEX_NOCHECK_OPTION:
2764 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2766 case IMAGEX_COMPRESS_OPTION:
2767 compression_type = get_compression_type(optarg);
2768 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2771 case IMAGEX_COMPRESS_SLOW_OPTION:
2772 set_compress_slow();
2773 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2775 case IMAGEX_RECOMPRESS_OPTION:
2776 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2778 case IMAGEX_SOLID_OPTION:
2779 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2781 case IMAGEX_NO_SOLID_SORT_OPTION:
2782 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2784 case IMAGEX_CHUNK_SIZE_OPTION:
2785 chunk_size = parse_chunk_size(optarg);
2786 if (chunk_size == UINT32_MAX)
2789 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2790 solid_chunk_size = parse_chunk_size(optarg);
2791 if (solid_chunk_size == UINT32_MAX)
2794 case IMAGEX_SOLID_COMPRESS_OPTION:
2795 solid_ctype = get_compression_type(optarg);
2796 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2799 case IMAGEX_REF_OPTION:
2800 ret = string_set_append(&refglobs, optarg);
2802 goto out_free_refglobs;
2804 case IMAGEX_THREADS_OPTION:
2805 num_threads = parse_num_threads(optarg);
2806 if (num_threads == UINT_MAX)
2809 case IMAGEX_REBUILD_OPTION:
2810 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2812 case IMAGEX_PIPABLE_OPTION:
2813 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2815 case IMAGEX_NOT_PIPABLE_OPTION:
2816 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2818 case IMAGEX_WIMBOOT_OPTION:
2819 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2827 if (argc < 3 || argc > 5)
2829 src_wimfile = argv[0];
2830 src_image_num_or_name = argv[1];
2831 dest_wimfile = argv[2];
2832 dest_name = (argc >= 4) ? argv[3] : NULL;
2833 dest_desc = (argc >= 5) ? argv[4] : NULL;
2834 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2835 imagex_progress_func, NULL);
2837 goto out_free_refglobs;
2839 wimlib_get_wim_info(src_wim, &src_info);
2841 /* Determine if the destination is an existing file or not. If so, we
2842 * try to append the exported image(s) to it; otherwise, we create a new
2843 * WIM containing the exported image(s). Furthermore, determine if we
2844 * need to write a pipable WIM directly to standard output. */
2846 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2848 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2849 imagex_error("Can't write a non-pipable WIM to "
2850 "standard output! Specify --pipable\n"
2851 " if you want to create a pipable WIM "
2852 "(but read the docs first).");
2854 goto out_free_src_wim;
2857 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2859 dest_wimfile = NULL;
2860 dest_wim_fd = STDOUT_FILENO;
2861 imagex_info_file = stderr;
2862 set_fd_to_binary_mode(dest_wim_fd);
2865 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2867 /* Destination file exists. */
2869 if (!S_ISREG(stbuf.st_mode)) {
2870 imagex_error(T("\"%"TS"\" is not a regular file"),
2873 goto out_free_src_wim;
2875 ret = wimlib_open_wim_with_progress(dest_wimfile,
2877 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2879 imagex_progress_func,
2882 goto out_free_src_wim;
2884 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2885 /* The user specified a compression type, but we're
2886 * exporting to an existing WIM. Make sure the
2887 * specified compression type is the same as the
2888 * compression type of the existing destination WIM. */
2889 struct wimlib_wim_info dest_info;
2891 wimlib_get_wim_info(dest_wim, &dest_info);
2892 if (compression_type != dest_info.compression_type) {
2893 imagex_error(T("Cannot specify a compression type that is "
2894 "not the same as that used in the "
2895 "destination WIM"));
2897 goto out_free_dest_wim;
2903 if (errno != ENOENT) {
2904 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2907 goto out_free_src_wim;
2910 /* dest_wimfile is not an existing file, so create a new WIM. */
2912 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2913 /* The user did not specify a compression type; default
2914 * to that of the source WIM, unless --solid or
2915 * --wimboot was specified. */
2917 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2918 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2919 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2920 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2922 compression_type = src_info.compression_type;
2924 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2926 goto out_free_src_wim;
2928 wimlib_register_progress_function(dest_wim,
2929 imagex_progress_func, NULL);
2931 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2932 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2934 /* For --wimboot export, use small XPRESS chunks. */
2935 wimlib_set_output_chunk_size(dest_wim, 4096);
2936 } else if (compression_type == src_info.compression_type &&
2937 chunk_size == UINT32_MAX)
2939 /* Use same chunk size if compression type is the same. */
2940 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2944 if (chunk_size != UINT32_MAX) {
2945 /* Set destination chunk size. */
2946 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2948 goto out_free_dest_wim;
2950 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2951 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2953 goto out_free_dest_wim;
2955 if (solid_chunk_size != UINT32_MAX) {
2956 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2958 goto out_free_dest_wim;
2961 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2962 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2964 goto out_free_dest_wim;
2966 if (refglobs.num_strings) {
2967 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2969 goto out_free_dest_wim;
2972 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2973 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2975 imagex_error(T("--boot specified for all-images export, but source WIM "
2976 "has no bootable image."));
2978 goto out_free_dest_wim;
2981 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2982 dest_desc, export_flags);
2984 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2985 do_resource_not_found_warning(src_wimfile,
2986 &src_info, &refglobs);
2988 goto out_free_dest_wim;
2992 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2993 else if (dest_wimfile)
2994 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2995 write_flags, num_threads);
2997 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2998 WIMLIB_ALL_IMAGES, write_flags,
3001 wimlib_free(dest_wim);
3003 wimlib_free(src_wim);
3005 string_set_destroy(&refglobs);
3009 usage(CMD_EXPORT, stderr);
3012 goto out_free_refglobs;
3015 /* Extract files or directories from a WIM image */
3017 imagex_extract(int argc, tchar **argv, int cmd)
3024 const tchar *wimfile;
3025 const tchar *image_num_or_name;
3026 tchar *dest_dir = T(".");
3027 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3028 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3029 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3030 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3032 STRING_SET(refglobs);
3034 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3036 for_opt(c, extract_options) {
3038 case IMAGEX_CHECK_OPTION:
3039 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3041 case IMAGEX_VERBOSE_OPTION:
3042 /* No longer does anything. */
3044 case IMAGEX_REF_OPTION:
3045 ret = string_set_append(&refglobs, optarg);
3047 goto out_free_refglobs;
3049 case IMAGEX_UNIX_DATA_OPTION:
3050 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3052 case IMAGEX_NO_ACLS_OPTION:
3053 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3055 case IMAGEX_STRICT_ACLS_OPTION:
3056 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3058 case IMAGEX_NO_ATTRIBUTES_OPTION:
3059 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3061 case IMAGEX_DEST_DIR_OPTION:
3064 case IMAGEX_TO_STDOUT_OPTION:
3065 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3066 imagex_info_file = stderr;
3067 imagex_be_quiet = true;
3068 set_fd_to_binary_mode(STDOUT_FILENO);
3070 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3071 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3072 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3074 case IMAGEX_NO_GLOBS_OPTION:
3075 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3077 case IMAGEX_NULLGLOB_OPTION:
3078 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3080 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3081 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3083 case IMAGEX_WIMBOOT_OPTION:
3084 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3086 case IMAGEX_COMPACT_OPTION:
3087 ret = set_compact_mode(optarg, &extract_flags);
3089 goto out_free_refglobs;
3101 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3102 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3104 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3109 image_num_or_name = argv[1];
3114 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3115 imagex_progress_func, NULL);
3117 goto out_free_refglobs;
3119 image = wimlib_resolve_image(wim, image_num_or_name);
3120 ret = verify_image_exists_and_is_single(image,
3124 goto out_wimlib_free;
3126 if (refglobs.num_strings) {
3127 ret = wim_reference_globs(wim, &refglobs, open_flags);
3129 goto out_wimlib_free;
3135 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3138 while (argc != 0 && ret == 0) {
3142 num_paths < argc && argv[num_paths][0] != T('@');
3147 ret = wimlib_extract_paths(wim, image, dest_dir,
3148 (const tchar **)argv,
3150 extract_flags | notlist_extract_flags);
3154 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3163 if (!imagex_be_quiet)
3164 imagex_printf(T("Done extracting files.\n"));
3165 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3166 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3167 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3168 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3169 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3172 T("Note: You can use the '--nullglob' "
3173 "option to ignore missing files.\n"));
3175 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3176 "files and directories\n"
3177 " are in the WIM image.\n"),
3178 get_cmd_string(CMD_DIR, false));
3179 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3180 struct wimlib_wim_info info;
3182 wimlib_get_wim_info(wim, &info);
3183 do_resource_not_found_warning(wimfile, &info, &refglobs);
3188 string_set_destroy(&refglobs);
3192 usage(CMD_EXTRACT, stderr);
3195 goto out_free_refglobs;
3198 /* Prints information about a WIM file; also can mark an image as bootable,
3199 * change the name of an image, or change the description of an image. */
3201 imagex_info(int argc, tchar **argv, int cmd)
3206 bool nocheck = false;
3207 bool header = false;
3210 bool short_header = true;
3211 const tchar *xml_out_file = NULL;
3212 const tchar *wimfile;
3213 const tchar *image_num_or_name;
3214 const tchar *new_name;
3215 const tchar *new_desc;
3220 struct wimlib_wim_info info;
3222 for_opt(c, info_options) {
3224 case IMAGEX_BOOT_OPTION:
3227 case IMAGEX_CHECK_OPTION:
3230 case IMAGEX_NOCHECK_OPTION:
3233 case IMAGEX_HEADER_OPTION:
3235 short_header = false;
3237 case IMAGEX_BLOBS_OPTION:
3239 short_header = false;
3241 case IMAGEX_XML_OPTION:
3243 short_header = false;
3245 case IMAGEX_EXTRACT_XML_OPTION:
3246 xml_out_file = optarg;
3247 short_header = false;
3249 case IMAGEX_METADATA_OPTION:
3250 imagex_error(T("The --metadata option has been removed. "
3251 "Use 'wimdir --detail' instead."));
3260 if (argc < 1 || argc > 4)
3264 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3265 new_name = (argc >= 3) ? argv[2] : NULL;
3266 new_desc = (argc >= 4) ? argv[3] : NULL;
3268 if (check && nocheck) {
3269 imagex_error(T("Can't specify both --check and --nocheck"));
3274 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3276 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3277 imagex_progress_func, NULL);
3281 wimlib_get_wim_info(wim, &info);
3283 image = wimlib_resolve_image(wim, image_num_or_name);
3284 ret = WIMLIB_ERR_INVALID_IMAGE;
3285 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3286 verify_image_exists(image, image_num_or_name, wimfile);
3288 imagex_error(T("If you would like to set the boot "
3289 "index to 0, specify image \"0\" with "
3290 "the --boot flag."));
3292 goto out_wimlib_free;
3295 if (boot && info.image_count == 0) {
3296 imagex_error(T("--boot is meaningless on a WIM with no images"));
3297 goto out_wimlib_free;
3300 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3302 imagex_error(T("Cannot specify the --boot flag "
3303 "without specifying a specific "
3304 "image in a multi-image WIM"));
3305 goto out_wimlib_free;
3308 imagex_error(T("Cannot specify the NEW_NAME "
3309 "without specifying a specific "
3310 "image in a multi-image WIM"));
3311 goto out_wimlib_free;
3315 /* Operations that print information are separated from operations that
3316 * recreate the WIM file. */
3317 if (!new_name && !boot) {
3319 /* Read-only operations */
3321 if (image == WIMLIB_NO_IMAGE) {
3322 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3323 image_num_or_name, wimfile);
3324 goto out_wimlib_free;
3327 if (image == WIMLIB_ALL_IMAGES && short_header)
3328 print_wim_information(wimfile, &info);
3331 wimlib_print_header(wim);
3334 if (info.total_parts != 1) {
3335 tfprintf(stderr, T("Warning: Only showing the blobs "
3336 "for part %d of a %d-part WIM.\n"),
3337 info.part_number, info.total_parts);
3343 ret = wimlib_extract_xml_data(wim, stdout);
3345 goto out_wimlib_free;
3351 fp = tfopen(xml_out_file, T("wb"));
3353 imagex_error_with_errno(T("Failed to open the "
3354 "file \"%"TS"\" for "
3358 goto out_wimlib_free;
3360 ret = wimlib_extract_xml_data(wim, fp);
3362 imagex_error(T("Failed to close the file "
3368 goto out_wimlib_free;
3372 wimlib_print_available_images(wim, image);
3377 /* Modification operations */
3379 if (image == WIMLIB_ALL_IMAGES)
3382 if (image == WIMLIB_NO_IMAGE && new_name) {
3383 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3384 "when using image 0"), new_name);
3386 goto out_wimlib_free;
3390 if (image == info.boot_index) {
3391 imagex_printf(T("Image %d is already marked as "
3392 "bootable.\n"), image);
3395 imagex_printf(T("Marking image %d as bootable.\n"),
3397 info.boot_index = image;
3398 ret = wimlib_set_wim_info(wim, &info,
3399 WIMLIB_CHANGE_BOOT_INDEX);
3401 goto out_wimlib_free;
3405 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3407 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3411 imagex_printf(T("Changing the name of image %d to "
3412 "\"%"TS"\".\n"), image, new_name);
3413 ret = wimlib_set_image_name(wim, image, new_name);
3415 goto out_wimlib_free;
3419 const tchar *old_desc;
3420 old_desc = wimlib_get_image_description(wim, image);
3421 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3422 imagex_printf(T("The description of image %d is already "
3423 "\"%"TS"\".\n"), image, new_desc);
3426 imagex_printf(T("Changing the description of image %d "
3427 "to \"%"TS"\".\n"), image, new_desc);
3428 ret = wimlib_set_image_descripton(wim, image,
3431 goto out_wimlib_free;
3435 /* Only call wimlib_overwrite() if something actually needs to
3437 if (boot || new_name || new_desc ||
3438 (check && !info.has_integrity_table) ||
3439 (nocheck && info.has_integrity_table))
3441 int write_flags = 0;
3444 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3446 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3447 ret = wimlib_overwrite(wim, write_flags, 1);
3449 imagex_printf(T("The file \"%"TS"\" was not modified "
3450 "because nothing needed to be done.\n"),
3461 usage(CMD_INFO, stderr);
3467 /* Join split WIMs into one part WIM */
3469 imagex_join(int argc, tchar **argv, int cmd)
3472 int swm_open_flags = 0;
3473 int wim_write_flags = 0;
3474 const tchar *output_path;
3477 for_opt(c, join_options) {
3479 case IMAGEX_CHECK_OPTION:
3480 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3481 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3491 imagex_error(T("Must specify one or more split WIM (.swm) "
3495 output_path = argv[0];
3496 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3501 imagex_progress_func,
3507 usage(CMD_JOIN, stderr);
3512 #if WIM_MOUNTING_SUPPORTED
3514 /* Mounts a WIM image. */
3516 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3519 int mount_flags = 0;
3521 const tchar *staging_dir = NULL;
3522 const tchar *wimfile;
3525 struct wimlib_wim_info info;
3529 STRING_SET(refglobs);
3531 if (cmd == CMD_MOUNTRW) {
3532 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3533 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3536 for_opt(c, mount_options) {
3538 case IMAGEX_ALLOW_OTHER_OPTION:
3539 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3541 case IMAGEX_CHECK_OPTION:
3542 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3544 case IMAGEX_DEBUG_OPTION:
3545 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3547 case IMAGEX_STREAMS_INTERFACE_OPTION:
3548 if (!tstrcasecmp(optarg, T("none")))
3549 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3550 else if (!tstrcasecmp(optarg, T("xattr")))
3551 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3552 else if (!tstrcasecmp(optarg, T("windows")))
3553 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3555 imagex_error(T("Unknown stream interface \"%"TS"\""),
3560 case IMAGEX_REF_OPTION:
3561 ret = string_set_append(&refglobs, optarg);
3563 goto out_free_refglobs;
3565 case IMAGEX_STAGING_DIR_OPTION:
3566 staging_dir = optarg;
3568 case IMAGEX_UNIX_DATA_OPTION:
3569 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3577 if (argc != 2 && argc != 3)
3582 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3583 imagex_progress_func, NULL);
3585 goto out_free_refglobs;
3587 wimlib_get_wim_info(wim, &info);
3590 /* Image explicitly specified. */
3591 image = wimlib_resolve_image(wim, argv[1]);
3593 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3597 /* No image specified; default to image 1, but only if the WIM
3598 * contains exactly one image. */
3600 if (info.image_count != 1) {
3601 imagex_error(T("\"%"TS"\" contains %d images; Please "
3602 "select one."), wimfile, info.image_count);
3610 if (refglobs.num_strings) {
3611 ret = wim_reference_globs(wim, &refglobs, open_flags);
3616 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3618 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3620 image, wimfile, dir);
3625 string_set_destroy(&refglobs);
3631 goto out_free_refglobs;
3633 #endif /* WIM_MOUNTING_SUPPORTED */
3635 /* Rebuild a WIM file */
3637 imagex_optimize(int argc, tchar **argv, int cmd)
3640 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3641 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3642 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3643 uint32_t chunk_size = UINT32_MAX;
3644 uint32_t solid_chunk_size = UINT32_MAX;
3645 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3648 const tchar *wimfile;
3651 unsigned num_threads = 0;
3653 for_opt(c, optimize_options) {
3655 case IMAGEX_CHECK_OPTION:
3656 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3657 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3659 case IMAGEX_NOCHECK_OPTION:
3660 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3662 case IMAGEX_COMPRESS_OPTION:
3663 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3664 compression_type = get_compression_type(optarg);
3665 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3668 case IMAGEX_COMPRESS_SLOW_OPTION:
3669 set_compress_slow();
3670 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3672 case IMAGEX_RECOMPRESS_OPTION:
3673 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3675 case IMAGEX_CHUNK_SIZE_OPTION:
3676 chunk_size = parse_chunk_size(optarg);
3677 if (chunk_size == UINT32_MAX)
3680 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3681 solid_chunk_size = parse_chunk_size(optarg);
3682 if (solid_chunk_size == UINT32_MAX)
3685 case IMAGEX_SOLID_COMPRESS_OPTION:
3686 solid_ctype = get_compression_type(optarg);
3687 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3690 case IMAGEX_SOLID_OPTION:
3691 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3692 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3694 case IMAGEX_NO_SOLID_SORT_OPTION:
3695 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3697 case IMAGEX_THREADS_OPTION:
3698 num_threads = parse_num_threads(optarg);
3699 if (num_threads == UINT_MAX)
3702 case IMAGEX_PIPABLE_OPTION:
3703 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3705 case IMAGEX_NOT_PIPABLE_OPTION:
3706 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3720 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3721 imagex_progress_func, NULL);
3725 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3726 /* Change compression type. */
3727 ret = wimlib_set_output_compression_type(wim, compression_type);
3729 goto out_wimlib_free;
3732 if (chunk_size != UINT32_MAX) {
3733 /* Change chunk size. */
3734 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3736 goto out_wimlib_free;
3738 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3739 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3741 goto out_wimlib_free;
3743 if (solid_chunk_size != UINT32_MAX) {
3744 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3746 goto out_wimlib_free;
3749 old_size = file_get_size(wimfile);
3750 tprintf(T("\"%"TS"\" original size: "), wimfile);
3752 tputs(T("Unknown"));
3754 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3756 ret = wimlib_overwrite(wim, write_flags, num_threads);
3758 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3759 goto out_wimlib_free;
3762 new_size = file_get_size(wimfile);
3763 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3765 tputs(T("Unknown"));
3767 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3769 tfputs(T("Space saved: "), stdout);
3770 if (new_size != -1 && old_size != -1) {
3771 tprintf(T("%lld KiB\n"),
3772 ((long long)old_size - (long long)new_size) >> 10);
3774 tputs(T("Unknown"));
3783 usage(CMD_OPTIMIZE, stderr);
3789 /* Split a WIM into a spanned set */
3791 imagex_split(int argc, tchar **argv, int cmd)
3795 int write_flags = 0;
3796 unsigned long part_size;
3801 for_opt(c, split_options) {
3803 case IMAGEX_CHECK_OPTION:
3804 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3805 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3817 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3818 if (tmp == argv[2] || *tmp) {
3819 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3820 imagex_error(T("The part size must be an integer or "
3821 "floating-point number of megabytes."));
3824 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3825 imagex_progress_func, NULL);
3829 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3835 usage(CMD_SPLIT, stderr);
3841 #if WIM_MOUNTING_SUPPORTED
3842 /* Unmounts a mounted WIM image. */
3844 imagex_unmount(int argc, tchar **argv, int cmd)
3847 int unmount_flags = 0;
3850 for_opt(c, unmount_options) {
3852 case IMAGEX_COMMIT_OPTION:
3853 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3855 case IMAGEX_CHECK_OPTION:
3856 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3858 case IMAGEX_REBUILD_OPTION:
3859 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3861 case IMAGEX_LAZY_OPTION:
3862 case IMAGEX_FORCE_OPTION:
3863 /* Now, unmount is lazy by default. However, committing
3864 * the image will fail with
3865 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3866 * file descriptors on the WIM image. The
3867 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3868 * descriptors to be closed. */
3869 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3871 case IMAGEX_NEW_IMAGE_OPTION:
3872 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3883 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3884 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3885 imagex_error(T("--new-image is meaningless "
3886 "without --commit also specified!"));
3891 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3892 imagex_progress_func, NULL);
3894 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3895 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3897 "\tNote: Use --commit --force to force changes "
3898 "to be committed, regardless\n"
3899 "\t of open files.\n"));
3906 usage(CMD_UNMOUNT, stderr);
3911 #endif /* WIM_MOUNTING_SUPPORTED */
3914 * Add, delete, or rename files in a WIM image.
3917 imagex_update(int argc, tchar **argv, int cmd)
3919 const tchar *wimfile;
3923 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3924 int write_flags = 0;
3925 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3926 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3927 WIMLIB_ADD_FLAG_VERBOSE |
3928 WIMLIB_ADD_FLAG_WINCONFIG;
3929 int default_delete_flags = 0;
3930 unsigned num_threads = 0;
3932 tchar *cmd_file_contents;
3933 size_t cmd_file_nchars;
3934 struct wimlib_update_command *cmds;
3936 tchar *command_str = NULL;
3937 tchar *config_file = NULL;
3938 tchar *wimboot_config = NULL;
3940 for_opt(c, update_options) {
3942 /* Generic or write options */
3943 case IMAGEX_THREADS_OPTION:
3944 num_threads = parse_num_threads(optarg);
3945 if (num_threads == UINT_MAX)
3948 case IMAGEX_CHECK_OPTION:
3949 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3950 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3952 case IMAGEX_REBUILD_OPTION:
3953 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3955 case IMAGEX_COMMAND_OPTION:
3957 imagex_error(T("--command may only be specified "
3958 "one time. Please provide\n"
3959 " the update commands "
3960 "on standard input instead."));
3963 command_str = tstrdup(optarg);
3965 imagex_error(T("Out of memory!"));
3969 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3970 wimboot_config = optarg;
3972 /* Default delete options */
3973 case IMAGEX_FORCE_OPTION:
3974 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3976 case IMAGEX_RECURSIVE_OPTION:
3977 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3980 /* Global add option */
3981 case IMAGEX_CONFIG_OPTION:
3982 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3983 config_file = optarg;
3986 /* Default add options */
3987 case IMAGEX_VERBOSE_OPTION:
3988 /* No longer does anything. */
3990 case IMAGEX_DEREFERENCE_OPTION:
3991 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3993 case IMAGEX_UNIX_DATA_OPTION:
3994 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3996 case IMAGEX_NO_ACLS_OPTION:
3997 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3999 case IMAGEX_STRICT_ACLS_OPTION:
4000 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4002 case IMAGEX_NO_REPLACE_OPTION:
4003 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4012 if (argc != 1 && argc != 2)
4016 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4017 imagex_progress_func, NULL);
4019 goto out_free_command_str;
4022 /* Image explicitly specified. */
4023 image = wimlib_resolve_image(wim, argv[1]);
4024 ret = verify_image_exists_and_is_single(image, argv[1],
4027 goto out_wimlib_free;
4029 /* No image specified; default to image 1, but only if the WIM
4030 * contains exactly one image. */
4031 struct wimlib_wim_info info;
4033 wimlib_get_wim_info(wim, &info);
4034 if (info.image_count != 1) {
4035 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4036 wimfile, info.image_count);
4043 /* Read update commands from standard input, or the command string if
4046 cmd_file_contents = NULL;
4047 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4051 goto out_free_cmd_file_contents;
4053 } else if (!wimboot_config) {
4054 if (isatty(STDIN_FILENO)) {
4055 tputs(T("Reading update commands from standard input..."));
4056 recommend_man_page(CMD_UPDATE, stdout);
4058 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4059 if (!cmd_file_contents) {
4061 goto out_wimlib_free;
4064 /* Parse the update commands */
4065 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4069 goto out_free_cmd_file_contents;
4072 cmd_file_contents = NULL;
4077 /* Set default flags and capture config on the update commands */
4078 for (size_t i = 0; i < num_cmds; i++) {
4079 switch (cmds[i].op) {
4080 case WIMLIB_UPDATE_OP_ADD:
4081 cmds[i].add.add_flags |= default_add_flags;
4082 cmds[i].add.config_file = config_file;
4084 case WIMLIB_UPDATE_OP_DELETE:
4085 cmds[i].delete_.delete_flags |= default_delete_flags;
4092 /* Execute the update commands */
4093 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4097 if (wimboot_config) {
4098 /* --wimboot-config=FILE is short for an
4099 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4101 struct wimlib_update_command cmd;
4103 cmd.op = WIMLIB_UPDATE_OP_ADD;
4104 cmd.add.fs_source_path = wimboot_config;
4105 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4106 cmd.add.config_file = NULL;
4107 cmd.add.add_flags = 0;
4109 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4114 /* Overwrite the updated WIM */
4115 ret = wimlib_overwrite(wim, write_flags, num_threads);
4118 out_free_cmd_file_contents:
4119 free(cmd_file_contents);
4122 out_free_command_str:
4127 usage(CMD_UPDATE, stderr);
4130 goto out_free_command_str;
4133 /* Verify a WIM file. */
4135 imagex_verify(int argc, tchar **argv, int cmd)
4138 const tchar *wimfile;
4140 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4141 int verify_flags = 0;
4142 STRING_SET(refglobs);
4145 for_opt(c, verify_options) {
4147 case IMAGEX_REF_OPTION:
4148 ret = string_set_append(&refglobs, optarg);
4150 goto out_free_refglobs;
4152 case IMAGEX_NOCHECK_OPTION:
4153 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4165 imagex_error(T("Must specify a WIM file!"));
4167 imagex_error(T("At most one WIM file can be specified!"));
4173 ret = wimlib_open_wim_with_progress(wimfile,
4176 imagex_progress_func,
4179 goto out_free_refglobs;
4181 ret = wim_reference_globs(wim, &refglobs, open_flags);
4183 goto out_wimlib_free;
4185 ret = wimlib_verify_wim(wim, verify_flags);
4187 tputc(T('\n'), stderr);
4188 imagex_error(T("\"%"TS"\" failed verification!"),
4190 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4191 refglobs.num_strings == 0)
4193 imagex_printf(T("Note: if this WIM file is not standalone, "
4194 "use the --ref option to specify the other parts.\n"));
4197 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4204 string_set_destroy(&refglobs);
4208 usage(CMD_VERIFY, stderr);
4210 goto out_free_refglobs;
4213 struct imagex_command {
4215 int (*func)(int argc, tchar **argv, int cmd);
4218 static const struct imagex_command imagex_commands[] = {
4219 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4220 [CMD_APPLY] = {T("apply"), imagex_apply},
4221 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4222 [CMD_DELETE] = {T("delete"), imagex_delete},
4223 [CMD_DIR ] = {T("dir"), imagex_dir},
4224 [CMD_EXPORT] = {T("export"), imagex_export},
4225 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4226 [CMD_INFO] = {T("info"), imagex_info},
4227 [CMD_JOIN] = {T("join"), imagex_join},
4228 #if WIM_MOUNTING_SUPPORTED
4229 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4230 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4232 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4233 [CMD_SPLIT] = {T("split"), imagex_split},
4234 #if WIM_MOUNTING_SUPPORTED
4235 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4237 [CMD_UPDATE] = {T("update"), imagex_update},
4238 [CMD_VERIFY] = {T("verify"), imagex_verify},
4243 /* Can be a directory or source list file. But source list file is probably
4244 * a rare use case, so just say directory. */
4245 # define SOURCE_STR T("DIRECTORY")
4247 /* Can only be a directory */
4248 # define TARGET_STR T("DIRECTORY")
4251 /* Can be a directory, NTFS volume, or source list file. */
4252 # define SOURCE_STR T("SOURCE")
4254 /* Can be a directory or NTFS volume. */
4255 # define TARGET_STR T("TARGET")
4259 static const tchar *usage_strings[] = {
4262 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4263 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4264 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4265 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4266 " [--wimboot] [--unix-data] [--dereference]\n"
4270 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4271 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4272 " [--no-attributes] [--rpfix] [--norpfix]\n"
4273 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4274 " [--compact=FORMAT]\n"
4278 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4279 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4280 " [--config=FILE] [--threads=NUM_THREADS]\n"
4281 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4282 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4283 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4287 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4291 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4295 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4296 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4297 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4298 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4299 " [--wimboot] [--solid]\n"
4303 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4304 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4305 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4306 " [--no-attributes] [--include-invalid-names]\n"
4307 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4311 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4312 " [--boot] [--check] [--nocheck] [--xml]\n"
4313 " [--extract-xml FILE] [--header] [--blobs]\n"
4317 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4319 #if WIM_MOUNTING_SUPPORTED
4322 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4323 " [--check] [--streams-interface=INTERFACE]\n"
4324 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4328 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4329 " [--check] [--streams-interface=INTERFACE]\n"
4330 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4336 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4337 " [--check] [--nocheck] [--solid]\n"
4342 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4344 #if WIM_MOUNTING_SUPPORTED
4347 " %"TS" DIRECTORY\n"
4348 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4353 " %"TS" WIMFILE [IMAGE]\n"
4354 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4355 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4356 " [--command=STRING] [--wimboot-config=FILE]\n"
4361 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4365 static const tchar *invocation_name;
4366 static int invocation_cmd = CMD_NONE;
4368 static const tchar *get_cmd_string(int cmd, bool nospace)
4370 static tchar buf[50];
4371 if (cmd == CMD_NONE) {
4372 return T("wimlib-imagex");
4373 } else if (invocation_cmd != CMD_NONE) {
4374 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4376 const tchar *format;
4379 format = T("%"TS"-%"TS"");
4381 format = T("%"TS" %"TS"");
4382 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4390 static const tchar *s =
4392 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4393 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4394 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4395 "This is free software: you are free to change and redistribute it.\n"
4396 "There is NO WARRANTY, to the extent permitted by law.\n"
4398 "Report bugs to "PACKAGE_BUGREPORT".\n"
4405 help_or_version(int argc, tchar **argv, int cmd)
4410 for (i = 1; i < argc; i++) {
4412 if (p[0] == T('-') && p[1] == T('-')) {
4414 if (!tstrcmp(p, T("help"))) {
4415 if (cmd == CMD_NONE)
4420 } else if (!tstrcmp(p, T("version"))) {
4429 print_usage_string(int cmd, FILE *fp)
4431 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4435 recommend_man_page(int cmd, FILE *fp)
4437 const tchar *format_str;
4439 format_str = T("Some uncommon options are not listed;\n"
4440 "See %"TS".pdf in the doc directory for more details.\n");
4442 format_str = T("Some uncommon options are not listed;\n"
4443 "Try `man %"TS"' for more details.\n");
4445 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4449 usage(int cmd, FILE *fp)
4451 tfprintf(fp, T("Usage:\n"));
4452 print_usage_string(cmd, fp);
4453 tfprintf(fp, T("\n"));
4454 recommend_man_page(cmd, fp);
4460 tfprintf(fp, T("Usage:\n"));
4461 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4462 print_usage_string(cmd, fp);
4463 tfprintf(fp, T("\n"));
4465 static const tchar *extra =
4468 " %"TS" --version\n"
4471 tfprintf(fp, extra, invocation_name, invocation_name);
4473 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4474 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4475 "For some commands IMAGE may be \"all\".\n"
4477 recommend_man_page(CMD_NONE, fp);
4480 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4481 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4482 * something else), while on Windows the command arguments will be UTF-16LE
4483 * encoded 'wchar_t' strings. */
4486 wmain(int argc, wchar_t **argv, wchar_t **envp)
4488 main(int argc, char **argv)
4495 imagex_info_file = stdout;
4496 invocation_name = tbasename(argv[0]);
4499 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4500 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4504 setlocale(LC_ALL, "");
4505 codeset = nl_langinfo(CODESET);
4506 if (!strstr(codeset, "UTF-8") &&
4507 !strstr(codeset, "UTF8") &&
4508 !strstr(codeset, "utf-8") &&
4509 !strstr(codeset, "utf8"))
4512 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4513 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4514 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4515 " to any value to force wimlib to use UTF-8.\n",
4521 #endif /* !__WIN32__ */
4524 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4525 if (igcase != NULL) {
4526 if (!tstrcmp(igcase, T("no")) ||
4527 !tstrcmp(igcase, T("0")))
4528 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4529 else if (!tstrcmp(igcase, T("yes")) ||
4530 !tstrcmp(igcase, T("1")))
4531 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4534 "WARNING: Ignoring unknown setting of "
4535 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4540 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4542 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4543 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4544 for (int i = 0; i < CMD_MAX; i++) {
4545 if (!tstrcmp(invocation_name + 3,
4546 imagex_commands[i].name))
4555 /* Unless already known from the invocation name, determine which
4556 * command was specified. */
4557 if (cmd == CMD_NONE) {
4559 imagex_error(T("No command specified!\n"));
4563 for (int i = 0; i < CMD_MAX; i++) {
4564 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4569 if (cmd != CMD_NONE) {
4575 /* Handle --help and --version. --help can be either for the program as
4576 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4577 * CMD_NONE). Note: help_or_version() will not return if a --help or
4578 * --version argument was found. */
4579 help_or_version(argc, argv, cmd);
4581 /* Bail if a valid command was not specified. */
4582 if (cmd == CMD_NONE) {
4583 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4588 /* Enable warning and error messages in wimlib to be more user-friendly.
4590 wimlib_set_print_errors(true);
4592 /* Initialize wimlib. */
4593 ret = wimlib_global_init(init_flags);
4595 goto out_check_status;
4597 /* Call the command handler function. */
4598 ret = imagex_commands[cmd].func(argc, argv, cmd);
4600 /* Check for error writing to standard output, especially since for some
4601 * commands, writing to standard output is part of the program's actual
4602 * behavior and not just for informational purposes. */
4603 if (ferror(stdout) || fclose(stdout)) {
4604 imagex_error_with_errno(T("error writing to standard output"));
4609 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4610 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4611 * error code from which an error message can be printed. */
4613 imagex_error(T("Exiting with error code %d:\n"
4615 wimlib_get_error_string(ret));
4616 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4617 imagex_error_with_errno(T("errno"));
4619 /* Make wimlib free any resources it's holding (although this is not
4620 * strictly necessary because the process is ending anyway). */
4621 wimlib_global_cleanup();