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},
273 static const struct option export_options[] = {
274 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
275 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
276 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
277 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
278 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
279 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
280 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
281 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
282 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
283 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
284 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
285 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
286 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
287 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
288 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
289 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
290 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
291 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
292 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
293 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
294 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
298 static const struct option extract_options[] = {
299 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
300 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
301 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
302 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
303 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
304 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
305 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
306 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
307 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
308 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
309 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
310 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
311 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
312 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
313 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
314 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
315 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
319 static const struct option info_options[] = {
320 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
321 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
322 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
323 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
324 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
325 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
326 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
327 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
328 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
329 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
333 static const struct option join_options[] = {
334 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
338 static const struct option mount_options[] = {
339 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
340 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
341 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
342 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
343 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
344 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
345 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
349 static const struct option optimize_options[] = {
350 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
351 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
352 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
353 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
354 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
355 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
356 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
357 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
358 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
359 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
360 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
361 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
362 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
363 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
364 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
365 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
366 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
367 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
371 static const struct option split_options[] = {
372 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
376 static const struct option unmount_options[] = {
377 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
378 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
379 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
380 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
381 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
382 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
386 static const struct option update_options[] = {
387 /* Careful: some of the options here set the defaults for update
388 * commands, but the flags given to an actual update command (and not to
389 * `imagex update' itself are also handled in
390 * update_command_add_option(). */
391 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
392 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
393 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
394 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
395 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
397 /* Default delete options */
398 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
399 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
401 /* Global add option */
402 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
404 /* Default add options */
405 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
406 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
407 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
408 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
409 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
410 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
411 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
416 static const struct option verify_options[] = {
417 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
418 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
424 # define _format_attribute(type, format_str, args_start) \
425 __attribute__((format(type, format_str, args_start)))
427 # define _format_attribute(type, format_str, args_start)
430 /* Print formatted error message to stderr. */
431 static void _format_attribute(printf, 1, 2)
432 imagex_error(const tchar *format, ...)
435 va_start(va, format);
436 tfputs(T("ERROR: "), stderr);
437 tvfprintf(stderr, format, va);
438 tputc(T('\n'), stderr);
442 /* Print formatted error message to stderr. */
443 static void _format_attribute(printf, 1, 2)
444 imagex_error_with_errno(const tchar *format, ...)
446 int errno_save = errno;
448 va_start(va, format);
449 tfputs(T("ERROR: "), stderr);
450 tvfprintf(stderr, format, va);
451 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
456 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
458 if (image == WIMLIB_NO_IMAGE) {
459 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
460 " Please specify a 1-based image index or "
461 "image name. To list the images\n"
462 " contained in the WIM archive, run\n"
464 " %"TS" \"%"TS"\"\n"),
465 image_name, wim_name,
466 get_cmd_string(CMD_INFO, false), wim_name);
467 return WIMLIB_ERR_INVALID_IMAGE;
473 verify_image_is_single(int image)
475 if (image == WIMLIB_ALL_IMAGES) {
476 imagex_error(T("Cannot specify all images for this action!"));
477 return WIMLIB_ERR_INVALID_IMAGE;
483 verify_image_exists_and_is_single(int image, const tchar *image_name,
484 const tchar *wim_name)
487 ret = verify_image_exists(image, image_name, wim_name);
489 ret = verify_image_is_single(image);
494 print_available_compression_types(FILE *fp)
496 static const tchar *s =
498 "Available compression types:\n"
501 " xpress (alias: \"fast\")\n"
502 " lzx (alias: \"maximum\") (default for capture)\n"
503 " lzms (alias: \"recovery\")\n"
509 /* Parse the argument to --compress */
511 get_compression_type(tchar *optarg)
514 unsigned int compression_level = 0;
517 plevel = tstrchr(optarg, T(':'));
523 ultmp = tstrtoul(plevel, &ptmp, 10);
524 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
525 imagex_error(T("Compression level must be a positive integer! "
526 "e.g. --compress=lzx:80"));
527 return WIMLIB_COMPRESSION_TYPE_INVALID;
529 compression_level = ultmp;
532 if (!tstrcasecmp(optarg, T("maximum")) ||
533 !tstrcasecmp(optarg, T("lzx")) ||
534 !tstrcasecmp(optarg, T("max")))
535 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
536 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
537 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
538 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
539 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
540 else if (!tstrcasecmp(optarg, T("none")))
541 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
543 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
544 print_available_compression_types(stderr);
545 return WIMLIB_COMPRESSION_TYPE_INVALID;
548 if (compression_level != 0)
549 wimlib_set_default_compression_level(ctype, compression_level);
553 /* Parse the argument to --compact */
555 set_compact_mode(const tchar *arg, int *extract_flags)
558 if (!tstrcasecmp(arg, T("xpress4k")))
559 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
560 else if (!tstrcasecmp(arg, T("xpress8k")))
561 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
562 else if (!tstrcasecmp(arg, T("xpress16k")))
563 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
564 else if (!tstrcasecmp(arg, T("lzx")))
565 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
568 *extract_flags |= flag;
573 "\"%"TS"\" is not a recognized System Compression format. The options are:"
575 " --compact=xpress4k\n"
576 " --compact=xpress8k\n"
577 " --compact=xpress16k\n"
585 set_compress_slow(void)
588 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
589 " Use the '--compress=TYPE:LEVEL' option instead.\n");
591 wimlib_set_default_compression_level(-1, 100);
595 const tchar **strings;
596 unsigned num_strings;
597 unsigned num_alloc_strings;
600 #define STRING_SET_INITIALIZER \
601 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
603 #define STRING_SET(_strings) \
604 struct string_set _strings = STRING_SET_INITIALIZER
607 string_set_append(struct string_set *set, const tchar *glob)
609 unsigned num_alloc_strings = set->num_alloc_strings;
611 if (set->num_strings == num_alloc_strings) {
612 const tchar **new_strings;
614 num_alloc_strings += 4;
615 new_strings = realloc(set->strings,
616 sizeof(set->strings[0]) * num_alloc_strings);
618 imagex_error(T("Out of memory!"));
621 set->strings = new_strings;
622 set->num_alloc_strings = num_alloc_strings;
624 set->strings[set->num_strings++] = glob;
629 string_set_destroy(struct string_set *set)
635 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
637 return wimlib_reference_resource_files(wim, set->strings,
639 WIMLIB_REF_FLAG_GLOB_ENABLE,
644 do_resource_not_found_warning(const tchar *wimfile,
645 const struct wimlib_wim_info *info,
646 const struct string_set *refglobs)
648 if (info->total_parts > 1) {
649 if (refglobs->num_strings == 0) {
650 imagex_error(T("\"%"TS"\" is part of a split WIM. "
651 "Use --ref to specify the other parts."),
654 imagex_error(T("Perhaps the '--ref' argument did not "
655 "specify all other parts of the split "
659 imagex_error(T("If this is a delta WIM, use the --ref argument "
660 "to specify the WIM(s) on which it is based."));
664 /* Returns the size of a file given its name, or -1 if the file does not exist
665 * or its size cannot be determined. */
667 file_get_size(const tchar *filename)
670 if (tstat(filename, &st) == 0)
677 PARSE_STRING_SUCCESS = 0,
678 PARSE_STRING_FAILURE = 1,
679 PARSE_STRING_NONE = 2,
683 * Parses a string token from an array of characters.
685 * Tokens are either whitespace-delimited, or double or single-quoted.
687 * @line_p: Pointer to the pointer to the line of data. Will be updated
688 * to point past the string token iff the return value is
689 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
692 * @len_p: @len_p initially stores the length of the line of data, which may
693 * be 0, and it will be updated to the number of bytes remaining in
694 * the line iff the return value is PARSE_STRING_SUCCESS.
696 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
697 * parsed string token will be returned here.
699 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
700 * PARSE_STRING_FAILURE if the data was invalid due to a missing
701 * closing quote; or PARSE_STRING_NONE if the line ended before the
702 * beginning of a string token was found.
705 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
708 tchar *line = *line_p;
712 /* Skip leading whitespace */
715 return PARSE_STRING_NONE;
716 if (!istspace(*line) && *line != T('\0'))
722 if (quote_char == T('"') || quote_char == T('\'')) {
727 line = tmemchr(line, quote_char, len);
729 imagex_error(T("Missing closing quote: %"TS), fn - 1);
730 return PARSE_STRING_FAILURE;
733 /* Unquoted string. Go until whitespace. Line is terminated
734 * by '\0', so no need to check 'len'. */
738 } while (!istspace(*line) && *line != T('\0'));
745 return PARSE_STRING_SUCCESS;
748 /* Parses a line of data (not an empty line or comment) in the source list file
749 * format. (See the man page for 'wimlib-imagex capture' for details on this
750 * format and the meaning.)
752 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
753 * len == 0. The data in @line will be modified by this function call.
755 * @len: Length of the line of data.
757 * @source: On success, the capture source and target described by the line is
758 * written into this destination. Note that it will contain pointers
759 * to data in the @line array.
761 * Returns true if the line was valid; false otherwise. */
763 parse_source_list_line(tchar *line, size_t len,
764 struct wimlib_capture_source *source)
768 ret = parse_string(&line, &len, &source->fs_source_path);
769 if (ret != PARSE_STRING_SUCCESS)
771 ret = parse_string(&line, &len, &source->wim_target_path);
772 if (ret == PARSE_STRING_NONE)
773 source->wim_target_path = source->fs_source_path;
774 return ret != PARSE_STRING_FAILURE;
777 /* Returns %true if the given line of length @len > 0 is a comment or empty line
778 * in the source list file format. */
780 is_comment_line(const tchar *line, size_t len)
783 if (*line == T('#') || *line == T(';'))
785 if (!istspace(*line) && *line != T('\0'))
795 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
798 tchar *contents = *contents_p;
799 size_t nchars = *nchars_p;
802 for (i = 0; i < nchars; i++)
803 if (contents[i] == T('\n'))
806 /* Handle last line not terminated by a newline */
807 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
808 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
810 imagex_error(T("Out of memory!"));
813 contents[nchars] = T('\n');
814 *contents_p = contents;
822 /* Parses a file in the source list format. (See the man page for
823 * 'wimlib-imagex capture' for details on this format and the meaning.)
825 * @source_list_contents: Contents of the source list file. Note that this
826 * buffer will be modified to save memory allocations,
827 * and cannot be freed until the returned array of
828 * wimlib_capture_source's has also been freed.
830 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
833 * @nsources_ret: On success, the length of the returned array is
836 * Returns: An array of `struct wimlib_capture_source's that can be passed to
837 * the wimlib_add_image_multisource() function to specify how a WIM image is to
839 static struct wimlib_capture_source *
840 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
841 size_t *nsources_ret)
845 struct wimlib_capture_source *sources;
848 nlines = text_file_count_lines(source_list_contents_p,
849 &source_list_nchars);
853 /* Always allocate at least 1 slot, just in case the implementation of
854 * calloc() returns NULL if 0 bytes are requested. */
855 sources = calloc(nlines ?: 1, sizeof(*sources));
857 imagex_error(T("out of memory"));
860 p = *source_list_contents_p;
862 for (i = 0; i < nlines; i++) {
863 /* XXX: Could use rawmemchr() here instead, but it may not be
864 * available on all platforms. */
865 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
866 size_t len = endp - p + 1;
868 if (!is_comment_line(p, len)) {
869 if (!parse_source_list_line(p, len, &sources[j++])) {
881 /* Reads the contents of a file into memory. */
883 file_get_contents(const tchar *filename, size_t *len_ret)
890 if (tstat(filename, &stbuf) != 0) {
891 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
896 fp = tfopen(filename, T("rb"));
898 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
902 buf = malloc(len ? len : 1);
904 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
905 "contents of file \"%"TS"\""), len, filename);
908 if (fread(buf, 1, len, fp) != len) {
909 imagex_error_with_errno(T("Failed to read %zu bytes from the "
910 "file \"%"TS"\""), len, filename);
924 /* Read standard input until EOF and return the full contents in a malloc()ed
925 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
928 stdin_get_contents(size_t *len_ret)
930 /* stdin can, of course, be a pipe or other non-seekable file, so the
931 * total length of the data cannot be pre-determined */
933 size_t newlen = 1024;
937 char *p = realloc(buf, newlen);
938 size_t bytes_read, bytes_to_read;
940 imagex_error(T("out of memory while reading stdin"));
944 bytes_to_read = newlen - pos;
945 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
947 if (bytes_read != bytes_to_read) {
952 imagex_error_with_errno(T("error reading stdin"));
966 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
969 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
971 *num_tchars_ret = num_bytes;
973 #else /* !__WIN32__ */
974 /* On Windows, translate the text to UTF-16LE */
978 if (num_bytes >= 2 &&
979 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
980 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
982 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
983 * with something that looks like an ASCII character encoded as
984 * a UTF-16LE code unit. Assume the file is encoded as
985 * UTF-16LE. This is not a 100% reliable check. */
986 num_wchars = num_bytes / 2;
987 text_wstr = (wchar_t*)text;
989 /* File does not look like UTF-16LE. Assume it is encoded in
990 * the current Windows code page. I think these are always
991 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
992 * should work as expected. */
993 text_wstr = win32_mbs_to_wcs(text,
998 *num_tchars_ret = num_wchars;
1000 #endif /* __WIN32__ */
1004 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1009 contents = file_get_contents(filename, &num_bytes);
1012 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1016 stdin_get_text_contents(size_t *num_tchars_ret)
1021 contents = stdin_get_contents(&num_bytes);
1024 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1027 #define TO_PERCENT(numerator, denominator) \
1028 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1030 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1031 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1032 #define KIBIBYTE_MIN_NBYTES 10000ULL
1035 get_unit(uint64_t total_bytes, const tchar **name_ret)
1037 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1038 *name_ret = T("GiB");
1040 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1041 *name_ret = T("MiB");
1043 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1044 *name_ret = T("KiB");
1047 *name_ret = T("bytes");
1052 static struct wimlib_progress_info_scan last_scan_progress;
1055 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1057 uint64_t prev_count, cur_count;
1059 prev_count = last_scan_progress.num_nondirs_scanned +
1060 last_scan_progress.num_dirs_scanned;
1061 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1063 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1064 cur_count % 128 == 0)
1066 unsigned unit_shift;
1067 const tchar *unit_name;
1069 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1070 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1071 "%"PRIu64" directories) "),
1072 scan->num_bytes_scanned >> unit_shift,
1074 scan->num_nondirs_scanned,
1075 scan->num_dirs_scanned);
1076 last_scan_progress = *scan;
1079 /* Progress callback function passed to various wimlib functions. */
1080 static enum wimlib_progress_status
1081 imagex_progress_func(enum wimlib_progress_msg msg,
1082 union wimlib_progress_info *info,
1083 void *_ignored_context)
1085 unsigned percent_done;
1086 unsigned unit_shift;
1087 const tchar *unit_name;
1089 if (imagex_be_quiet)
1090 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1092 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1094 static bool first = true;
1096 imagex_printf(T("Writing %"TS"-compressed data "
1097 "using %u thread%"TS"\n"),
1098 wimlib_get_compression_type_string(
1099 info->write_streams.compression_type),
1100 info->write_streams.num_threads,
1101 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1105 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1106 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1107 info->write_streams.total_bytes);
1109 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1110 "written (%u%% done)"),
1111 info->write_streams.completed_bytes >> unit_shift,
1113 info->write_streams.total_bytes >> unit_shift,
1116 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1117 imagex_printf(T("\n"));
1119 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1120 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1121 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1122 imagex_printf(T("\n"));
1124 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1125 info->scan.wim_target_path);
1127 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1129 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1130 switch (info->scan.status) {
1131 case WIMLIB_SCAN_DENTRY_OK:
1132 report_scan_progress(&info->scan, false);
1134 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1135 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1137 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1138 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1139 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1141 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1142 /* Symlink fixups are enabled by default. This is
1143 * mainly intended for Windows, which for some reason
1144 * uses absolute junctions (with drive letters!) in the
1145 * default installation. On UNIX-like systems, warn the
1146 * user when fixing the target of an absolute symbolic
1147 * link, so they know to disable this if they want. */
1149 imagex_printf(T("\nWARNING: Adjusted target of "
1150 "absolute symbolic link \"%"TS"\"\n"
1151 " (Use --norpfix to capture "
1152 "absolute symbolic links as-is)\n"),
1153 info->scan.cur_path);
1160 case WIMLIB_PROGRESS_MSG_SCAN_END:
1161 report_scan_progress(&info->scan, true);
1162 imagex_printf(T("\n"));
1164 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1165 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1166 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1167 info->integrity.total_bytes);
1168 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1169 "of %"PRIu64" %"TS" (%u%%) done"),
1170 info->integrity.filename,
1171 info->integrity.completed_bytes >> unit_shift,
1173 info->integrity.total_bytes >> unit_shift,
1176 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1177 imagex_printf(T("\n"));
1179 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1180 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1181 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1182 info->integrity.total_bytes);
1183 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1184 "of %"PRIu64" %"TS" (%u%%) done"),
1185 info->integrity.completed_bytes >> unit_shift,
1187 info->integrity.total_bytes >> unit_shift,
1190 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1191 imagex_printf(T("\n"));
1193 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1194 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1195 "to %"TS" \"%"TS"\"\n"),
1196 info->extract.image,
1197 info->extract.image_name,
1198 info->extract.wimfile_name,
1199 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1200 T("NTFS volume") : T("directory")),
1201 info->extract.target);
1203 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1204 if (info->extract.end_file_count >= 2000) {
1205 percent_done = TO_PERCENT(info->extract.current_file_count,
1206 info->extract.end_file_count);
1207 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1208 info->extract.current_file_count,
1209 info->extract.end_file_count, percent_done);
1210 if (info->extract.current_file_count == info->extract.end_file_count)
1211 imagex_printf(T("\n"));
1214 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1215 percent_done = TO_PERCENT(info->extract.completed_bytes,
1216 info->extract.total_bytes);
1217 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1218 imagex_printf(T("\rExtracting file data: "
1219 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1220 info->extract.completed_bytes >> unit_shift,
1222 info->extract.total_bytes >> unit_shift,
1225 if (info->extract.completed_bytes >= info->extract.total_bytes)
1226 imagex_printf(T("\n"));
1228 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1229 if (info->extract.end_file_count >= 2000) {
1230 percent_done = TO_PERCENT(info->extract.current_file_count,
1231 info->extract.end_file_count);
1232 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1233 info->extract.current_file_count,
1234 info->extract.end_file_count, percent_done);
1235 if (info->extract.current_file_count == info->extract.end_file_count)
1236 imagex_printf(T("\n"));
1239 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1240 if (info->extract.total_parts != 1) {
1241 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1242 info->extract.part_number,
1243 info->extract.total_parts);
1246 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1247 percent_done = TO_PERCENT(info->split.completed_bytes,
1248 info->split.total_bytes);
1249 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1250 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1251 "%"PRIu64" %"TS" (%u%%) written\n"),
1252 info->split.part_name,
1253 info->split.cur_part_number,
1254 info->split.total_parts,
1255 info->split.completed_bytes >> unit_shift,
1257 info->split.total_bytes >> unit_shift,
1261 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1262 if (info->split.completed_bytes == info->split.total_bytes) {
1263 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1264 info->split.cur_part_number,
1265 info->split.total_parts);
1268 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1269 switch (info->update.command->op) {
1270 case WIMLIB_UPDATE_OP_DELETE:
1271 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1272 info->update.command->delete_.wim_path);
1274 case WIMLIB_UPDATE_OP_RENAME:
1275 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1276 info->update.command->rename.wim_source_path,
1277 info->update.command->rename.wim_target_path);
1279 case WIMLIB_UPDATE_OP_ADD:
1284 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1285 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1286 info->replace.path_in_wim);
1288 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1289 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1290 info->wimboot_exclude.path_in_wim);
1292 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1293 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1294 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1295 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1296 info->unmount.mounted_wim,
1297 info->unmount.mounted_image);
1299 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1300 info->unmount.mounted_wim,
1301 info->unmount.mounted_image);
1302 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1306 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1307 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1308 info->verify_image.current_image,
1309 info->verify_image.total_images);
1311 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1312 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1313 info->verify_streams.total_bytes);
1314 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1315 imagex_printf(T("\rVerifying file data: "
1316 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1317 info->verify_streams.completed_bytes >> unit_shift,
1319 info->verify_streams.total_bytes >> unit_shift,
1322 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1323 imagex_printf(T("\n"));
1328 fflush(imagex_info_file);
1329 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1333 parse_num_threads(const tchar *optarg)
1336 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1337 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1338 imagex_error(T("Number of threads must be a non-negative integer!"));
1346 parse_chunk_size(const tchar *optarg)
1349 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1350 if (chunk_size == 0) {
1351 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1352 " with optional K, M, or G suffix"));
1356 if (*tmp == T('k') || *tmp == T('K')) {
1359 } else if (*tmp == T('m') || *tmp == T('M')) {
1362 } else if (*tmp == T('g') || *tmp == T('G')) {
1366 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1367 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1371 if (chunk_size >= UINT32_MAX) {
1372 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1380 * Parse an option passed to an update command.
1382 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1385 * @option: Text string for the option (beginning with --)
1387 * @cmd: `struct wimlib_update_command' that is being constructed for
1390 * Returns true if the option was recognized; false if not.
1393 update_command_add_option(int op, const tchar *option,
1394 struct wimlib_update_command *cmd)
1396 bool recognized = true;
1398 case WIMLIB_UPDATE_OP_ADD:
1399 if (!tstrcmp(option, T("--verbose")))
1400 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1401 else if (!tstrcmp(option, T("--unix-data")))
1402 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1403 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1404 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1405 else if (!tstrcmp(option, T("--strict-acls")))
1406 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1407 else if (!tstrcmp(option, T("--dereference")))
1408 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1409 else if (!tstrcmp(option, T("--no-replace")))
1410 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1414 case WIMLIB_UPDATE_OP_DELETE:
1415 if (!tstrcmp(option, T("--force")))
1416 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1417 else if (!tstrcmp(option, T("--recursive")))
1418 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1429 /* How many nonoption arguments each `imagex update' command expects */
1430 static const unsigned update_command_num_nonoptions[] = {
1431 [WIMLIB_UPDATE_OP_ADD] = 2,
1432 [WIMLIB_UPDATE_OP_DELETE] = 1,
1433 [WIMLIB_UPDATE_OP_RENAME] = 2,
1437 update_command_add_nonoption(int op, const tchar *nonoption,
1438 struct wimlib_update_command *cmd,
1439 unsigned num_nonoptions)
1442 case WIMLIB_UPDATE_OP_ADD:
1443 if (num_nonoptions == 0)
1444 cmd->add.fs_source_path = (tchar*)nonoption;
1446 cmd->add.wim_target_path = (tchar*)nonoption;
1448 case WIMLIB_UPDATE_OP_DELETE:
1449 cmd->delete_.wim_path = (tchar*)nonoption;
1451 case WIMLIB_UPDATE_OP_RENAME:
1452 if (num_nonoptions == 0)
1453 cmd->rename.wim_source_path = (tchar*)nonoption;
1455 cmd->rename.wim_target_path = (tchar*)nonoption;
1461 * Parse a command passed on stdin to `imagex update'.
1463 * @line: Text of the command.
1464 * @len: Length of the line, including a null terminator
1467 * @command: A `struct wimlib_update_command' to fill in from the parsed
1470 * @line_number: Line number of the command, for diagnostics.
1472 * Returns true on success; returns false on parse error.
1475 parse_update_command(tchar *line, size_t len,
1476 struct wimlib_update_command *command,
1480 tchar *command_name;
1482 size_t num_nonoptions;
1484 /* Get the command name ("add", "delete", "rename") */
1485 ret = parse_string(&line, &len, &command_name);
1486 if (ret != PARSE_STRING_SUCCESS)
1489 if (!tstrcasecmp(command_name, T("add"))) {
1490 op = WIMLIB_UPDATE_OP_ADD;
1491 } else if (!tstrcasecmp(command_name, T("delete"))) {
1492 op = WIMLIB_UPDATE_OP_DELETE;
1493 } else if (!tstrcasecmp(command_name, T("rename"))) {
1494 op = WIMLIB_UPDATE_OP_RENAME;
1496 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1497 command_name, line_number);
1502 /* Parse additional options and non-options as needed */
1507 ret = parse_string(&line, &len, &next_string);
1508 if (ret == PARSE_STRING_NONE) /* End of line */
1510 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1512 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1514 if (!update_command_add_option(op, next_string, command))
1516 imagex_error(T("Unrecognized option \"%"TS"\" to "
1517 "update command \"%"TS"\" on line %zu"),
1518 next_string, command_name, line_number);
1524 if (num_nonoptions == update_command_num_nonoptions[op])
1526 imagex_error(T("Unexpected argument \"%"TS"\" in "
1527 "update command on line %zu\n"
1528 " (The \"%"TS"\" command only "
1529 "takes %zu nonoption arguments!)\n"),
1530 next_string, line_number,
1531 command_name, num_nonoptions);
1534 update_command_add_nonoption(op, next_string,
1535 command, num_nonoptions);
1540 if (num_nonoptions != update_command_num_nonoptions[op]) {
1541 imagex_error(T("Not enough arguments to update command "
1542 "\"%"TS"\" on line %zu"), command_name, line_number);
1548 static struct wimlib_update_command *
1549 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1550 size_t *num_cmds_ret)
1554 struct wimlib_update_command *cmds;
1557 nlines = text_file_count_lines(cmd_file_contents_p,
1562 /* Always allocate at least 1 slot, just in case the implementation of
1563 * calloc() returns NULL if 0 bytes are requested. */
1564 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1566 imagex_error(T("out of memory"));
1569 p = *cmd_file_contents_p;
1571 for (i = 0; i < nlines; i++) {
1572 /* XXX: Could use rawmemchr() here instead, but it may not be
1573 * available on all platforms. */
1574 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1575 size_t len = endp - p + 1;
1577 if (!is_comment_line(p, len)) {
1578 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1589 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1590 * one image from a WIM file to an NTFS volume. */
1592 imagex_apply(int argc, tchar **argv, int cmd)
1596 int image = WIMLIB_NO_IMAGE;
1598 struct wimlib_wim_info info;
1600 const tchar *wimfile;
1601 const tchar *target;
1602 const tchar *image_num_or_name = NULL;
1603 int extract_flags = 0;
1605 STRING_SET(refglobs);
1607 for_opt(c, apply_options) {
1609 case IMAGEX_CHECK_OPTION:
1610 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1612 case IMAGEX_VERBOSE_OPTION:
1613 /* No longer does anything. */
1615 case IMAGEX_REF_OPTION:
1616 ret = string_set_append(&refglobs, optarg);
1618 goto out_free_refglobs;
1620 case IMAGEX_UNIX_DATA_OPTION:
1621 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1623 case IMAGEX_NO_ACLS_OPTION:
1624 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1626 case IMAGEX_STRICT_ACLS_OPTION:
1627 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1629 case IMAGEX_NO_ATTRIBUTES_OPTION:
1630 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1632 case IMAGEX_NORPFIX_OPTION:
1633 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1635 case IMAGEX_RPFIX_OPTION:
1636 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1638 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1639 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1640 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1642 case IMAGEX_RESUME_OPTION:
1643 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1645 case IMAGEX_WIMBOOT_OPTION:
1646 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1648 case IMAGEX_COMPACT_OPTION:
1649 ret = set_compact_mode(optarg, &extract_flags);
1651 goto out_free_refglobs;
1659 if (argc != 2 && argc != 3)
1664 if (!tstrcmp(wimfile, T("-"))) {
1665 /* Attempt to apply pipable WIM from standard input. */
1667 image_num_or_name = NULL;
1670 image_num_or_name = argv[1];
1675 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1676 imagex_progress_func, NULL);
1678 goto out_free_refglobs;
1680 wimlib_get_wim_info(wim, &info);
1683 /* Image explicitly specified. */
1684 image_num_or_name = argv[1];
1685 image = wimlib_resolve_image(wim, image_num_or_name);
1686 ret = verify_image_exists(image, image_num_or_name, wimfile);
1688 goto out_wimlib_free;
1691 /* No image specified; default to image 1, but only if the WIM
1692 * contains exactly one image. */
1694 if (info.image_count != 1) {
1695 imagex_error(T("\"%"TS"\" contains %d images; "
1696 "Please select one (or all)."),
1697 wimfile, info.image_count);
1706 if (refglobs.num_strings) {
1708 imagex_error(T("Can't specify --ref when applying from stdin!"));
1710 goto out_wimlib_free;
1712 ret = wim_reference_globs(wim, &refglobs, open_flags);
1714 goto out_wimlib_free;
1719 /* Interpret a regular file or block device target as an NTFS
1723 if (tstat(target, &stbuf)) {
1724 if (errno != ENOENT) {
1725 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1728 goto out_wimlib_free;
1731 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1732 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1738 ret = wimlib_extract_image(wim, image, target, extract_flags);
1740 set_fd_to_binary_mode(STDIN_FILENO);
1741 ret = wimlib_extract_image_from_pipe_with_progress(
1746 imagex_progress_func,
1750 imagex_printf(T("Done applying WIM image.\n"));
1751 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1753 do_resource_not_found_warning(wimfile, &info, &refglobs);
1755 imagex_error(T( "If you are applying an image "
1756 "from a split pipable WIM,\n"
1757 " make sure you have "
1758 "concatenated together all parts."));
1764 string_set_destroy(&refglobs);
1768 usage(CMD_APPLY, stderr);
1770 goto out_free_refglobs;
1773 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1774 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1775 * the desired image. 'wimlib-imagex append': add a new image to an existing
1778 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1782 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1783 WIMLIB_ADD_FLAG_WINCONFIG |
1784 WIMLIB_ADD_FLAG_VERBOSE;
1785 int write_flags = 0;
1786 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1787 uint32_t chunk_size = UINT32_MAX;
1788 uint32_t solid_chunk_size = UINT32_MAX;
1789 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1790 const tchar *wimfile;
1794 const tchar *flags_element = NULL;
1797 STRING_SET(base_wimfiles);
1798 WIMStruct **base_wims;
1800 WIMStruct *template_wim;
1801 const tchar *template_wimfile = NULL;
1802 const tchar *template_image_name_or_num = NULL;
1803 int template_image = WIMLIB_NO_IMAGE;
1806 unsigned num_threads = 0;
1811 tchar *config_file = NULL;
1813 bool source_list = false;
1814 size_t source_list_nchars = 0;
1815 tchar *source_list_contents;
1816 bool capture_sources_malloced;
1817 struct wimlib_capture_source *capture_sources;
1819 bool name_defaulted;
1821 for_opt(c, capture_or_append_options) {
1823 case IMAGEX_BOOT_OPTION:
1824 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1826 case IMAGEX_CHECK_OPTION:
1827 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1828 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1830 case IMAGEX_NOCHECK_OPTION:
1831 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1833 case IMAGEX_CONFIG_OPTION:
1834 config_file = optarg;
1835 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1837 case IMAGEX_COMPRESS_OPTION:
1838 compression_type = get_compression_type(optarg);
1839 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1842 case IMAGEX_COMPRESS_SLOW_OPTION:
1843 set_compress_slow();
1845 case IMAGEX_CHUNK_SIZE_OPTION:
1846 chunk_size = parse_chunk_size(optarg);
1847 if (chunk_size == UINT32_MAX)
1850 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1851 solid_chunk_size = parse_chunk_size(optarg);
1852 if (solid_chunk_size == UINT32_MAX)
1855 case IMAGEX_SOLID_COMPRESS_OPTION:
1856 solid_ctype = get_compression_type(optarg);
1857 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1860 case IMAGEX_SOLID_OPTION:
1861 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1863 case IMAGEX_NO_SOLID_SORT_OPTION:
1864 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1866 case IMAGEX_FLAGS_OPTION:
1867 flags_element = optarg;
1869 case IMAGEX_DEREFERENCE_OPTION:
1870 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1872 case IMAGEX_VERBOSE_OPTION:
1873 /* No longer does anything. */
1875 case IMAGEX_THREADS_OPTION:
1876 num_threads = parse_num_threads(optarg);
1877 if (num_threads == UINT_MAX)
1880 case IMAGEX_REBUILD_OPTION:
1881 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1883 case IMAGEX_UNIX_DATA_OPTION:
1884 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1886 case IMAGEX_SOURCE_LIST_OPTION:
1889 case IMAGEX_NO_ACLS_OPTION:
1890 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1892 case IMAGEX_STRICT_ACLS_OPTION:
1893 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1895 case IMAGEX_RPFIX_OPTION:
1896 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1898 case IMAGEX_NORPFIX_OPTION:
1899 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1901 case IMAGEX_PIPABLE_OPTION:
1902 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1904 case IMAGEX_NOT_PIPABLE_OPTION:
1905 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1907 case IMAGEX_UPDATE_OF_OPTION:
1908 if (template_image_name_or_num) {
1909 imagex_error(T("'--update-of' can only be "
1910 "specified one time!"));
1914 colon = tstrrchr(optarg, T(':'));
1917 template_wimfile = optarg;
1919 template_image_name_or_num = colon + 1;
1921 template_wimfile = NULL;
1922 template_image_name_or_num = optarg;
1926 case IMAGEX_DELTA_FROM_OPTION:
1927 if (cmd != CMD_CAPTURE) {
1928 imagex_error(T("'--delta-from' is only "
1929 "valid for capture!"));
1932 ret = string_set_append(&base_wimfiles, optarg);
1934 goto out_free_base_wimfiles;
1935 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1937 case IMAGEX_WIMBOOT_OPTION:
1938 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1947 if (argc < 2 || argc > 4)
1953 /* Set default compression type and parameters. */
1956 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1957 /* No compression type specified. Use the default. */
1959 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1960 /* With --wimboot, default to XPRESS compression. */
1961 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1962 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1963 /* With --solid, default to LZMS compression. (However,
1964 * this will not affect solid resources!) */
1965 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1967 /* Otherwise, default to LZX compression. */
1968 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1972 if (!tstrcmp(wimfile, T("-"))) {
1973 /* Writing captured WIM to standard output. */
1975 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1976 imagex_error("Can't write a non-pipable WIM to "
1977 "standard output! Specify --pipable\n"
1978 " if you want to create a pipable WIM "
1979 "(but read the docs first).");
1983 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1985 if (cmd == CMD_APPEND) {
1986 imagex_error(T("Using standard output for append does "
1987 "not make sense."));
1990 wim_fd = STDOUT_FILENO;
1992 imagex_info_file = stderr;
1993 set_fd_to_binary_mode(wim_fd);
1996 /* If template image was specified using --update-of=IMAGE rather
1997 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1998 if (template_image_name_or_num && !template_wimfile) {
1999 if (base_wimfiles.num_strings == 1) {
2000 /* Capturing delta WIM based on single WIM: default to
2002 template_wimfile = base_wimfiles.strings[0];
2003 } else if (cmd == CMD_APPEND) {
2004 /* Appending to WIM: default to WIM being appended to.
2006 template_wimfile = wimfile;
2008 /* Capturing a normal (non-delta) WIM, so the WIM file
2009 * *must* be explicitly specified. */
2010 if (base_wimfiles.num_strings > 1) {
2011 imagex_error(T("For capture of delta WIM "
2012 "based on multiple existing "
2014 " '--update-of' must "
2015 "specify WIMFILE:IMAGE!"));
2017 imagex_error(T("For capture of non-delta WIM, "
2018 "'--update-of' must specify "
2027 name_defaulted = false;
2029 /* Set default name to SOURCE argument, omitting any directory
2030 * prefixes and trailing slashes. This requires making a copy
2031 * of @source. Leave some free characters at the end in case we
2032 * append a number to keep the name unique. */
2033 size_t source_name_len;
2035 source_name_len = tstrlen(source);
2036 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2037 name = tbasename(tstrcpy(source_copy, source));
2038 name_defaulted = true;
2040 /* Image description defaults to NULL if not given. */
2047 /* Set up capture sources in source list mode */
2048 if (source[0] == T('-') && source[1] == T('\0')) {
2049 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2051 source_list_contents = file_get_text_contents(source,
2052 &source_list_nchars);
2054 if (!source_list_contents)
2057 capture_sources = parse_source_list(&source_list_contents,
2060 if (!capture_sources) {
2062 goto out_free_source_list_contents;
2064 capture_sources_malloced = true;
2066 /* Set up capture source in non-source-list mode. */
2067 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2068 capture_sources[0].fs_source_path = source;
2069 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2070 capture_sources[0].reserved = 0;
2072 capture_sources_malloced = false;
2073 source_list_contents = NULL;
2076 /* Open the existing WIM, or create a new one. */
2077 if (cmd == CMD_APPEND) {
2078 ret = wimlib_open_wim_with_progress(wimfile,
2079 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2081 imagex_progress_func,
2084 goto out_free_capture_sources;
2086 ret = wimlib_create_new_wim(compression_type, &wim);
2088 goto out_free_capture_sources;
2089 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2092 /* Set chunk size if non-default. */
2093 if (chunk_size != UINT32_MAX) {
2094 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2097 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2098 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2099 ret = wimlib_set_output_chunk_size(wim, 4096);
2103 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2104 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2108 if (solid_chunk_size != UINT32_MAX) {
2109 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2115 /* Detect if source is regular file or block device and set NTFS volume
2120 if (tstat(source, &stbuf) == 0) {
2121 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2122 imagex_printf(T("Capturing WIM image from NTFS "
2123 "filesystem on \"%"TS"\"\n"), source);
2124 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2127 if (errno != ENOENT) {
2128 imagex_error_with_errno(T("Failed to stat "
2129 "\"%"TS"\""), source);
2137 /* If the user did not specify an image name, and the basename of the
2138 * source already exists as an image name in the WIM file, append a
2139 * suffix to make it unique. */
2140 if (cmd == CMD_APPEND && name_defaulted) {
2141 unsigned long conflict_idx;
2142 tchar *name_end = tstrchr(name, T('\0'));
2143 for (conflict_idx = 1;
2144 wimlib_image_name_in_use(wim, name);
2147 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2151 /* If capturing a delta WIM, reference resources from the base WIMs
2152 * before adding the new image. */
2153 if (base_wimfiles.num_strings) {
2154 base_wims = calloc(base_wimfiles.num_strings,
2155 sizeof(base_wims[0]));
2156 if (base_wims == NULL) {
2157 imagex_error(T("Out of memory!"));
2162 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2163 ret = wimlib_open_wim_with_progress(
2164 base_wimfiles.strings[i], open_flags,
2165 &base_wims[i], imagex_progress_func, NULL);
2167 goto out_free_base_wims;
2171 ret = wimlib_reference_resources(wim, base_wims,
2172 base_wimfiles.num_strings, 0);
2174 goto out_free_base_wims;
2176 if (base_wimfiles.num_strings == 1) {
2177 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2178 base_wimfiles.strings[0]);
2180 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2181 base_wimfiles.num_strings);
2188 /* If capturing or appending as an update of an existing (template) image,
2189 * open the WIM if needed and parse the image index. */
2190 if (template_image_name_or_num) {
2193 if (base_wimfiles.num_strings == 1 &&
2194 template_wimfile == base_wimfiles.strings[0]) {
2195 template_wim = base_wims[0];
2196 } else if (template_wimfile == wimfile) {
2199 ret = wimlib_open_wim_with_progress(template_wimfile,
2202 imagex_progress_func,
2205 goto out_free_base_wims;
2208 template_image = wimlib_resolve_image(template_wim,
2209 template_image_name_or_num);
2211 if (template_image_name_or_num[0] == T('-')) {
2214 struct wimlib_wim_info info;
2216 wimlib_get_wim_info(template_wim, &info);
2217 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2218 if (n >= 1 && n <= info.image_count &&
2220 tmp != template_image_name_or_num + 1)
2222 template_image = info.image_count - (n - 1);
2225 ret = verify_image_exists_and_is_single(template_image,
2226 template_image_name_or_num,
2229 goto out_free_template_wim;
2231 template_wim = NULL;
2234 ret = wimlib_add_image_multisource(wim,
2241 goto out_free_template_wim;
2243 if (desc || flags_element || template_image_name_or_num) {
2244 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2245 * on which the added one is to be based has been specified with
2246 * --update-of. Get the index of the image we just
2247 * added, then use it to call the appropriate functions. */
2248 struct wimlib_wim_info info;
2250 wimlib_get_wim_info(wim, &info);
2253 ret = wimlib_set_image_descripton(wim,
2257 goto out_free_template_wim;
2260 if (flags_element) {
2261 ret = wimlib_set_image_flags(wim, info.image_count,
2264 goto out_free_template_wim;
2267 /* Reference template image if the user provided one. */
2268 if (template_image_name_or_num) {
2269 imagex_printf(T("Using image %d "
2270 "from \"%"TS"\" as template\n"),
2271 template_image, template_wimfile);
2272 ret = wimlib_reference_template_image(wim,
2278 goto out_free_template_wim;
2282 /* Write the new WIM or overwrite the existing WIM with the new image
2284 if (cmd == CMD_APPEND) {
2285 ret = wimlib_overwrite(wim, write_flags, num_threads);
2286 } else if (wimfile) {
2287 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2288 write_flags, num_threads);
2290 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2291 write_flags, num_threads);
2293 out_free_template_wim:
2294 /* template_wim may alias base_wims[0] or wim. */
2295 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2296 template_wim != wim)
2297 wimlib_free(template_wim);
2299 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2300 wimlib_free(base_wims[i]);
2304 out_free_capture_sources:
2305 if (capture_sources_malloced)
2306 free(capture_sources);
2307 out_free_source_list_contents:
2308 free(source_list_contents);
2309 out_free_base_wimfiles:
2310 string_set_destroy(&base_wimfiles);
2317 goto out_free_base_wimfiles;
2320 /* Remove image(s) from a WIM. */
2322 imagex_delete(int argc, tchar **argv, int cmd)
2325 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2326 int write_flags = 0;
2327 const tchar *wimfile;
2328 const tchar *image_num_or_name;
2333 for_opt(c, delete_options) {
2335 case IMAGEX_CHECK_OPTION:
2336 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2337 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2339 case IMAGEX_SOFT_OPTION:
2340 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2351 imagex_error(T("Must specify a WIM file"));
2353 imagex_error(T("Must specify an image"));
2357 image_num_or_name = argv[1];
2359 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2360 imagex_progress_func, NULL);
2364 image = wimlib_resolve_image(wim, image_num_or_name);
2366 ret = verify_image_exists(image, image_num_or_name, wimfile);
2368 goto out_wimlib_free;
2370 ret = wimlib_delete_image(wim, image);
2372 imagex_error(T("Failed to delete image from \"%"TS"\""),
2374 goto out_wimlib_free;
2377 ret = wimlib_overwrite(wim, write_flags, 0);
2379 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2380 "deleted"), wimfile);
2388 usage(CMD_DELETE, stderr);
2393 struct print_dentry_options {
2398 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2400 tprintf(T("%"TS"\n"), dentry->full_path);
2403 static const struct {
2406 } file_attr_flags[] = {
2407 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2408 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2409 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2410 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2411 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2412 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2413 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2414 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2415 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2416 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2417 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2418 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2419 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2420 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2421 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2424 #define TIMESTR_MAX 100
2427 timespec_to_string(const struct timespec *spec, tchar *buf)
2429 time_t t = spec->tv_sec;
2432 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2433 buf[TIMESTR_MAX - 1] = '\0';
2437 print_time(const tchar *type, const struct timespec *spec)
2439 tchar timestr[TIMESTR_MAX];
2441 timespec_to_string(spec, timestr);
2443 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2446 static void print_byte_field(const uint8_t field[], size_t len)
2449 tprintf(T("%02hhx"), *field++);
2453 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2455 tchar attr_string[256];
2458 tputs(T("WIM Information:"));
2459 tputs(T("----------------"));
2460 tprintf(T("Path: %"TS"\n"), wimfile);
2461 tprintf(T("GUID: 0x"));
2462 print_byte_field(info->guid, sizeof(info->guid));
2464 tprintf(T("Version: %u\n"), info->wim_version);
2465 tprintf(T("Image Count: %d\n"), info->image_count);
2466 tprintf(T("Compression: %"TS"\n"),
2467 wimlib_get_compression_type_string(info->compression_type));
2468 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2470 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2471 tprintf(T("Boot Index: %d\n"), info->boot_index);
2472 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2474 attr_string[0] = T('\0');
2477 tstrcat(attr_string, T("Pipable, "));
2479 if (info->has_integrity_table)
2480 tstrcat(attr_string, T("Integrity info, "));
2482 if (info->has_rpfix)
2483 tstrcat(attr_string, T("Relative path junction, "));
2485 if (info->resource_only)
2486 tstrcat(attr_string, T("Resource only, "));
2488 if (info->metadata_only)
2489 tstrcat(attr_string, T("Metadata only, "));
2491 if (info->is_marked_readonly)
2492 tstrcat(attr_string, T("Readonly, "));
2494 p = tstrchr(attr_string, T('\0'));
2495 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2498 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2502 print_resource(const struct wimlib_resource_entry *resource,
2505 tprintf(T("Hash = 0x"));
2506 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2509 if (!resource->is_missing) {
2510 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2511 resource->uncompressed_size);
2512 if (resource->packed) {
2513 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2514 "bytes @ offset %"PRIu64"\n"),
2515 resource->raw_resource_uncompressed_size,
2516 resource->raw_resource_compressed_size,
2517 resource->raw_resource_offset_in_wim);
2519 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2522 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2523 resource->compressed_size);
2525 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2529 tprintf(T("Part Number = %u\n"), resource->part_number);
2530 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2532 tprintf(T("Flags = "));
2533 if (resource->is_compressed)
2534 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2535 if (resource->is_metadata)
2536 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2537 if (resource->is_free)
2538 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2539 if (resource->is_spanned)
2540 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2541 if (resource->packed)
2542 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2550 print_blobs(WIMStruct *wim)
2552 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2556 default_print_security_descriptor(const uint8_t *sd, size_t size)
2558 tprintf(T("Security Descriptor = "));
2559 print_byte_field(sd, size);
2564 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2568 "----------------------------------------------------------------------------\n"));
2569 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2570 if (dentry->dos_name)
2571 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2572 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2573 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2574 if (file_attr_flags[i].flag & dentry->attributes)
2575 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2576 file_attr_flags[i].name);
2578 if (dentry->security_descriptor) {
2579 print_security_descriptor(dentry->security_descriptor,
2580 dentry->security_descriptor_size);
2583 print_time(T("Creation Time"), &dentry->creation_time);
2584 print_time(T("Last Write Time"), &dentry->last_write_time);
2585 print_time(T("Last Access Time"), &dentry->last_access_time);
2588 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2589 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2591 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2592 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2594 if (dentry->unix_mode != 0) {
2595 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2596 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2597 dentry->unix_uid, dentry->unix_gid,
2598 dentry->unix_mode, dentry->unix_rdev);
2601 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2602 if (dentry->streams[i].stream_name) {
2603 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2604 dentry->streams[i].stream_name);
2605 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2606 tprintf(T("\tRaw encrypted data stream:\n"));
2607 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2608 tprintf(T("\tReparse point stream:\n"));
2610 tprintf(T("\tUnnamed data stream:\n"));
2612 print_resource(&dentry->streams[i].resource, NULL);
2617 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2619 const struct print_dentry_options *options = _options;
2620 if (!options->detailed)
2621 print_dentry_full_path(dentry);
2623 print_dentry_detailed(dentry);
2627 /* Print the files contained in an image(s) in a WIM file. */
2629 imagex_dir(int argc, tchar **argv, int cmd)
2631 const tchar *wimfile;
2632 WIMStruct *wim = NULL;
2635 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2637 struct print_dentry_options options = {
2640 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2642 for_opt(c, dir_options) {
2644 case IMAGEX_PATH_OPTION:
2647 case IMAGEX_DETAILED_OPTION:
2648 options.detailed = true;
2650 case IMAGEX_ONE_FILE_ONLY_OPTION:
2651 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2661 imagex_error(T("Must specify a WIM file"));
2665 imagex_error(T("Too many arguments"));
2670 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2671 imagex_progress_func, NULL);
2676 image = wimlib_resolve_image(wim, argv[1]);
2677 ret = verify_image_exists(image, argv[1], wimfile);
2679 goto out_wimlib_free;
2681 /* No image specified; default to image 1, but only if the WIM
2682 * contains exactly one image. */
2684 struct wimlib_wim_info info;
2686 wimlib_get_wim_info(wim, &info);
2687 if (info.image_count != 1) {
2688 imagex_error(T("\"%"TS"\" contains %d images; Please "
2689 "select one (or all)."),
2690 wimfile, info.image_count);
2697 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2698 print_dentry, &options);
2705 usage(CMD_DIR, stderr);
2710 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2713 imagex_export(int argc, tchar **argv, int cmd)
2717 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2718 int write_flags = 0;
2719 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2720 const tchar *src_wimfile;
2721 const tchar *src_image_num_or_name;
2722 const tchar *dest_wimfile;
2724 const tchar *dest_name;
2725 const tchar *dest_desc;
2727 struct wimlib_wim_info src_info;
2728 WIMStruct *dest_wim;
2733 STRING_SET(refglobs);
2734 unsigned num_threads = 0;
2735 uint32_t chunk_size = UINT32_MAX;
2736 uint32_t solid_chunk_size = UINT32_MAX;
2737 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2739 for_opt(c, export_options) {
2741 case IMAGEX_BOOT_OPTION:
2742 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2744 case IMAGEX_CHECK_OPTION:
2745 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2746 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2748 case IMAGEX_NOCHECK_OPTION:
2749 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2751 case IMAGEX_COMPRESS_OPTION:
2752 compression_type = get_compression_type(optarg);
2753 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2756 case IMAGEX_COMPRESS_SLOW_OPTION:
2757 set_compress_slow();
2758 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2760 case IMAGEX_RECOMPRESS_OPTION:
2761 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2763 case IMAGEX_SOLID_OPTION:
2764 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2766 case IMAGEX_NO_SOLID_SORT_OPTION:
2767 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2769 case IMAGEX_CHUNK_SIZE_OPTION:
2770 chunk_size = parse_chunk_size(optarg);
2771 if (chunk_size == UINT32_MAX)
2774 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2775 solid_chunk_size = parse_chunk_size(optarg);
2776 if (solid_chunk_size == UINT32_MAX)
2779 case IMAGEX_SOLID_COMPRESS_OPTION:
2780 solid_ctype = get_compression_type(optarg);
2781 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2784 case IMAGEX_REF_OPTION:
2785 ret = string_set_append(&refglobs, optarg);
2787 goto out_free_refglobs;
2789 case IMAGEX_THREADS_OPTION:
2790 num_threads = parse_num_threads(optarg);
2791 if (num_threads == UINT_MAX)
2794 case IMAGEX_REBUILD_OPTION:
2795 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2797 case IMAGEX_PIPABLE_OPTION:
2798 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2800 case IMAGEX_NOT_PIPABLE_OPTION:
2801 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2803 case IMAGEX_WIMBOOT_OPTION:
2804 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2812 if (argc < 3 || argc > 5)
2814 src_wimfile = argv[0];
2815 src_image_num_or_name = argv[1];
2816 dest_wimfile = argv[2];
2817 dest_name = (argc >= 4) ? argv[3] : NULL;
2818 dest_desc = (argc >= 5) ? argv[4] : NULL;
2819 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2820 imagex_progress_func, NULL);
2822 goto out_free_refglobs;
2824 wimlib_get_wim_info(src_wim, &src_info);
2826 /* Determine if the destination is an existing file or not. If so, we
2827 * try to append the exported image(s) to it; otherwise, we create a new
2828 * WIM containing the exported image(s). Furthermore, determine if we
2829 * need to write a pipable WIM directly to standard output. */
2831 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2833 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2834 imagex_error("Can't write a non-pipable WIM to "
2835 "standard output! Specify --pipable\n"
2836 " if you want to create a pipable WIM "
2837 "(but read the docs first).");
2839 goto out_free_src_wim;
2842 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2844 dest_wimfile = NULL;
2845 dest_wim_fd = STDOUT_FILENO;
2846 imagex_info_file = stderr;
2847 set_fd_to_binary_mode(dest_wim_fd);
2850 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2852 /* Destination file exists. */
2854 if (!S_ISREG(stbuf.st_mode)) {
2855 imagex_error(T("\"%"TS"\" is not a regular file"),
2858 goto out_free_src_wim;
2860 ret = wimlib_open_wim_with_progress(dest_wimfile,
2862 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2864 imagex_progress_func,
2867 goto out_free_src_wim;
2869 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2870 /* The user specified a compression type, but we're
2871 * exporting to an existing WIM. Make sure the
2872 * specified compression type is the same as the
2873 * compression type of the existing destination WIM. */
2874 struct wimlib_wim_info dest_info;
2876 wimlib_get_wim_info(dest_wim, &dest_info);
2877 if (compression_type != dest_info.compression_type) {
2878 imagex_error(T("Cannot specify a compression type that is "
2879 "not the same as that used in the "
2880 "destination WIM"));
2882 goto out_free_dest_wim;
2888 if (errno != ENOENT) {
2889 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2892 goto out_free_src_wim;
2895 /* dest_wimfile is not an existing file, so create a new WIM. */
2897 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2898 /* The user did not specify a compression type; default
2899 * to that of the source WIM, unless --solid or
2900 * --wimboot was specified. */
2902 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2903 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2904 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2905 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2907 compression_type = src_info.compression_type;
2909 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2911 goto out_free_src_wim;
2913 wimlib_register_progress_function(dest_wim,
2914 imagex_progress_func, NULL);
2916 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2917 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2919 /* For --wimboot export, use small XPRESS chunks. */
2920 wimlib_set_output_chunk_size(dest_wim, 4096);
2921 } else if (compression_type == src_info.compression_type &&
2922 chunk_size == UINT32_MAX)
2924 /* Use same chunk size if compression type is the same. */
2925 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2929 if (chunk_size != UINT32_MAX) {
2930 /* Set destination chunk size. */
2931 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2933 goto out_free_dest_wim;
2935 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2936 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2938 goto out_free_dest_wim;
2940 if (solid_chunk_size != UINT32_MAX) {
2941 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2943 goto out_free_dest_wim;
2946 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2947 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2949 goto out_free_dest_wim;
2951 if (refglobs.num_strings) {
2952 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2954 goto out_free_dest_wim;
2957 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2958 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2960 imagex_error(T("--boot specified for all-images export, but source WIM "
2961 "has no bootable image."));
2963 goto out_free_dest_wim;
2966 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2967 dest_desc, export_flags);
2969 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2970 do_resource_not_found_warning(src_wimfile,
2971 &src_info, &refglobs);
2973 goto out_free_dest_wim;
2977 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2978 else if (dest_wimfile)
2979 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2980 write_flags, num_threads);
2982 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2983 WIMLIB_ALL_IMAGES, write_flags,
2986 wimlib_free(dest_wim);
2988 wimlib_free(src_wim);
2990 string_set_destroy(&refglobs);
2994 usage(CMD_EXPORT, stderr);
2997 goto out_free_refglobs;
3000 /* Extract files or directories from a WIM image */
3002 imagex_extract(int argc, tchar **argv, int cmd)
3009 const tchar *wimfile;
3010 const tchar *image_num_or_name;
3011 tchar *dest_dir = T(".");
3012 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3013 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3014 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3015 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3017 STRING_SET(refglobs);
3019 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3021 for_opt(c, extract_options) {
3023 case IMAGEX_CHECK_OPTION:
3024 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3026 case IMAGEX_VERBOSE_OPTION:
3027 /* No longer does anything. */
3029 case IMAGEX_REF_OPTION:
3030 ret = string_set_append(&refglobs, optarg);
3032 goto out_free_refglobs;
3034 case IMAGEX_UNIX_DATA_OPTION:
3035 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3037 case IMAGEX_NO_ACLS_OPTION:
3038 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3040 case IMAGEX_STRICT_ACLS_OPTION:
3041 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3043 case IMAGEX_NO_ATTRIBUTES_OPTION:
3044 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3046 case IMAGEX_DEST_DIR_OPTION:
3049 case IMAGEX_TO_STDOUT_OPTION:
3050 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3051 imagex_info_file = stderr;
3052 imagex_be_quiet = true;
3053 set_fd_to_binary_mode(STDOUT_FILENO);
3055 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3056 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3057 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3059 case IMAGEX_NO_GLOBS_OPTION:
3060 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3062 case IMAGEX_NULLGLOB_OPTION:
3063 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3065 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3066 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3068 case IMAGEX_WIMBOOT_OPTION:
3069 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3071 case IMAGEX_COMPACT_OPTION:
3072 ret = set_compact_mode(optarg, &extract_flags);
3074 goto out_free_refglobs;
3086 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3087 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3089 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3094 image_num_or_name = argv[1];
3099 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3100 imagex_progress_func, NULL);
3102 goto out_free_refglobs;
3104 image = wimlib_resolve_image(wim, image_num_or_name);
3105 ret = verify_image_exists_and_is_single(image,
3109 goto out_wimlib_free;
3111 if (refglobs.num_strings) {
3112 ret = wim_reference_globs(wim, &refglobs, open_flags);
3114 goto out_wimlib_free;
3120 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3123 while (argc != 0 && ret == 0) {
3127 num_paths < argc && argv[num_paths][0] != T('@');
3132 ret = wimlib_extract_paths(wim, image, dest_dir,
3133 (const tchar **)argv,
3135 extract_flags | notlist_extract_flags);
3139 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3148 if (!imagex_be_quiet)
3149 imagex_printf(T("Done extracting files.\n"));
3150 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3151 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3152 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3153 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3154 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3157 T("Note: You can use the '--nullglob' "
3158 "option to ignore missing files.\n"));
3160 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3161 "files and directories\n"
3162 " are in the WIM image.\n"),
3163 get_cmd_string(CMD_DIR, false));
3164 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3165 struct wimlib_wim_info info;
3167 wimlib_get_wim_info(wim, &info);
3168 do_resource_not_found_warning(wimfile, &info, &refglobs);
3173 string_set_destroy(&refglobs);
3177 usage(CMD_EXTRACT, stderr);
3180 goto out_free_refglobs;
3183 /* Prints information about a WIM file; also can mark an image as bootable,
3184 * change the name of an image, or change the description of an image. */
3186 imagex_info(int argc, tchar **argv, int cmd)
3191 bool nocheck = false;
3192 bool header = false;
3195 bool short_header = true;
3196 const tchar *xml_out_file = NULL;
3197 const tchar *wimfile;
3198 const tchar *image_num_or_name;
3199 const tchar *new_name;
3200 const tchar *new_desc;
3205 struct wimlib_wim_info info;
3207 for_opt(c, info_options) {
3209 case IMAGEX_BOOT_OPTION:
3212 case IMAGEX_CHECK_OPTION:
3215 case IMAGEX_NOCHECK_OPTION:
3218 case IMAGEX_HEADER_OPTION:
3220 short_header = false;
3222 case IMAGEX_BLOBS_OPTION:
3224 short_header = false;
3226 case IMAGEX_XML_OPTION:
3228 short_header = false;
3230 case IMAGEX_EXTRACT_XML_OPTION:
3231 xml_out_file = optarg;
3232 short_header = false;
3234 case IMAGEX_METADATA_OPTION:
3235 imagex_error(T("The --metadata option has been removed. "
3236 "Use 'wimdir --detail' instead."));
3245 if (argc < 1 || argc > 4)
3249 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3250 new_name = (argc >= 3) ? argv[2] : NULL;
3251 new_desc = (argc >= 4) ? argv[3] : NULL;
3253 if (check && nocheck) {
3254 imagex_error(T("Can't specify both --check and --nocheck"));
3259 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3261 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3262 imagex_progress_func, NULL);
3266 wimlib_get_wim_info(wim, &info);
3268 image = wimlib_resolve_image(wim, image_num_or_name);
3269 ret = WIMLIB_ERR_INVALID_IMAGE;
3270 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3271 verify_image_exists(image, image_num_or_name, wimfile);
3273 imagex_error(T("If you would like to set the boot "
3274 "index to 0, specify image \"0\" with "
3275 "the --boot flag."));
3277 goto out_wimlib_free;
3280 if (boot && info.image_count == 0) {
3281 imagex_error(T("--boot is meaningless on a WIM with no images"));
3282 goto out_wimlib_free;
3285 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3287 imagex_error(T("Cannot specify the --boot flag "
3288 "without specifying a specific "
3289 "image in a multi-image WIM"));
3290 goto out_wimlib_free;
3293 imagex_error(T("Cannot specify the NEW_NAME "
3294 "without specifying a specific "
3295 "image in a multi-image WIM"));
3296 goto out_wimlib_free;
3300 /* Operations that print information are separated from operations that
3301 * recreate the WIM file. */
3302 if (!new_name && !boot) {
3304 /* Read-only operations */
3306 if (image == WIMLIB_NO_IMAGE) {
3307 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3308 image_num_or_name, wimfile);
3309 goto out_wimlib_free;
3312 if (image == WIMLIB_ALL_IMAGES && short_header)
3313 print_wim_information(wimfile, &info);
3316 wimlib_print_header(wim);
3319 if (info.total_parts != 1) {
3320 tfprintf(stderr, T("Warning: Only showing the blobs "
3321 "for part %d of a %d-part WIM.\n"),
3322 info.part_number, info.total_parts);
3328 ret = wimlib_extract_xml_data(wim, stdout);
3330 goto out_wimlib_free;
3336 fp = tfopen(xml_out_file, T("wb"));
3338 imagex_error_with_errno(T("Failed to open the "
3339 "file \"%"TS"\" for "
3343 goto out_wimlib_free;
3345 ret = wimlib_extract_xml_data(wim, fp);
3347 imagex_error(T("Failed to close the file "
3353 goto out_wimlib_free;
3357 wimlib_print_available_images(wim, image);
3362 /* Modification operations */
3364 if (image == WIMLIB_ALL_IMAGES)
3367 if (image == WIMLIB_NO_IMAGE && new_name) {
3368 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3369 "when using image 0"), new_name);
3371 goto out_wimlib_free;
3375 if (image == info.boot_index) {
3376 imagex_printf(T("Image %d is already marked as "
3377 "bootable.\n"), image);
3380 imagex_printf(T("Marking image %d as bootable.\n"),
3382 info.boot_index = image;
3383 ret = wimlib_set_wim_info(wim, &info,
3384 WIMLIB_CHANGE_BOOT_INDEX);
3386 goto out_wimlib_free;
3390 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3392 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3396 imagex_printf(T("Changing the name of image %d to "
3397 "\"%"TS"\".\n"), image, new_name);
3398 ret = wimlib_set_image_name(wim, image, new_name);
3400 goto out_wimlib_free;
3404 const tchar *old_desc;
3405 old_desc = wimlib_get_image_description(wim, image);
3406 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3407 imagex_printf(T("The description of image %d is already "
3408 "\"%"TS"\".\n"), image, new_desc);
3411 imagex_printf(T("Changing the description of image %d "
3412 "to \"%"TS"\".\n"), image, new_desc);
3413 ret = wimlib_set_image_descripton(wim, image,
3416 goto out_wimlib_free;
3420 /* Only call wimlib_overwrite() if something actually needs to
3422 if (boot || new_name || new_desc ||
3423 (check && !info.has_integrity_table) ||
3424 (nocheck && info.has_integrity_table))
3426 int write_flags = 0;
3429 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3431 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3432 ret = wimlib_overwrite(wim, write_flags, 1);
3434 imagex_printf(T("The file \"%"TS"\" was not modified "
3435 "because nothing needed to be done.\n"),
3446 usage(CMD_INFO, stderr);
3452 /* Join split WIMs into one part WIM */
3454 imagex_join(int argc, tchar **argv, int cmd)
3457 int swm_open_flags = 0;
3458 int wim_write_flags = 0;
3459 const tchar *output_path;
3462 for_opt(c, join_options) {
3464 case IMAGEX_CHECK_OPTION:
3465 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3466 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3476 imagex_error(T("Must specify one or more split WIM (.swm) "
3480 output_path = argv[0];
3481 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3486 imagex_progress_func,
3492 usage(CMD_JOIN, stderr);
3497 #if WIM_MOUNTING_SUPPORTED
3499 /* Mounts a WIM image. */
3501 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3504 int mount_flags = 0;
3506 const tchar *staging_dir = NULL;
3507 const tchar *wimfile;
3510 struct wimlib_wim_info info;
3514 STRING_SET(refglobs);
3516 if (cmd == CMD_MOUNTRW) {
3517 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3518 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3521 for_opt(c, mount_options) {
3523 case IMAGEX_ALLOW_OTHER_OPTION:
3524 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3526 case IMAGEX_CHECK_OPTION:
3527 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3529 case IMAGEX_DEBUG_OPTION:
3530 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3532 case IMAGEX_STREAMS_INTERFACE_OPTION:
3533 if (!tstrcasecmp(optarg, T("none")))
3534 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3535 else if (!tstrcasecmp(optarg, T("xattr")))
3536 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3537 else if (!tstrcasecmp(optarg, T("windows")))
3538 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3540 imagex_error(T("Unknown stream interface \"%"TS"\""),
3545 case IMAGEX_REF_OPTION:
3546 ret = string_set_append(&refglobs, optarg);
3548 goto out_free_refglobs;
3550 case IMAGEX_STAGING_DIR_OPTION:
3551 staging_dir = optarg;
3553 case IMAGEX_UNIX_DATA_OPTION:
3554 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3562 if (argc != 2 && argc != 3)
3567 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3568 imagex_progress_func, NULL);
3570 goto out_free_refglobs;
3572 wimlib_get_wim_info(wim, &info);
3575 /* Image explicitly specified. */
3576 image = wimlib_resolve_image(wim, argv[1]);
3578 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3582 /* No image specified; default to image 1, but only if the WIM
3583 * contains exactly one image. */
3585 if (info.image_count != 1) {
3586 imagex_error(T("\"%"TS"\" contains %d images; Please "
3587 "select one."), wimfile, info.image_count);
3595 if (refglobs.num_strings) {
3596 ret = wim_reference_globs(wim, &refglobs, open_flags);
3601 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3603 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3605 image, wimfile, dir);
3610 string_set_destroy(&refglobs);
3616 goto out_free_refglobs;
3618 #endif /* WIM_MOUNTING_SUPPORTED */
3620 /* Rebuild a WIM file */
3622 imagex_optimize(int argc, tchar **argv, int cmd)
3625 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3626 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3627 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3628 uint32_t chunk_size = UINT32_MAX;
3629 uint32_t solid_chunk_size = UINT32_MAX;
3630 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3633 const tchar *wimfile;
3636 unsigned num_threads = 0;
3638 for_opt(c, optimize_options) {
3640 case IMAGEX_CHECK_OPTION:
3641 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3642 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3644 case IMAGEX_NOCHECK_OPTION:
3645 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3647 case IMAGEX_COMPRESS_OPTION:
3648 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3649 compression_type = get_compression_type(optarg);
3650 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3653 case IMAGEX_COMPRESS_SLOW_OPTION:
3654 set_compress_slow();
3655 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3657 case IMAGEX_RECOMPRESS_OPTION:
3658 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3660 case IMAGEX_CHUNK_SIZE_OPTION:
3661 chunk_size = parse_chunk_size(optarg);
3662 if (chunk_size == UINT32_MAX)
3665 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3666 solid_chunk_size = parse_chunk_size(optarg);
3667 if (solid_chunk_size == UINT32_MAX)
3670 case IMAGEX_SOLID_COMPRESS_OPTION:
3671 solid_ctype = get_compression_type(optarg);
3672 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3675 case IMAGEX_SOLID_OPTION:
3676 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3677 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3679 case IMAGEX_NO_SOLID_SORT_OPTION:
3680 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3682 case IMAGEX_THREADS_OPTION:
3683 num_threads = parse_num_threads(optarg);
3684 if (num_threads == UINT_MAX)
3687 case IMAGEX_PIPABLE_OPTION:
3688 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3690 case IMAGEX_NOT_PIPABLE_OPTION:
3691 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3705 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3706 imagex_progress_func, NULL);
3710 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3711 /* Change compression type. */
3712 ret = wimlib_set_output_compression_type(wim, compression_type);
3714 goto out_wimlib_free;
3717 if (chunk_size != UINT32_MAX) {
3718 /* Change chunk size. */
3719 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3721 goto out_wimlib_free;
3723 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3724 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3726 goto out_wimlib_free;
3728 if (solid_chunk_size != UINT32_MAX) {
3729 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3731 goto out_wimlib_free;
3734 old_size = file_get_size(wimfile);
3735 tprintf(T("\"%"TS"\" original size: "), wimfile);
3737 tputs(T("Unknown"));
3739 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3741 ret = wimlib_overwrite(wim, write_flags, num_threads);
3743 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3744 goto out_wimlib_free;
3747 new_size = file_get_size(wimfile);
3748 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3750 tputs(T("Unknown"));
3752 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3754 tfputs(T("Space saved: "), stdout);
3755 if (new_size != -1 && old_size != -1) {
3756 tprintf(T("%lld KiB\n"),
3757 ((long long)old_size - (long long)new_size) >> 10);
3759 tputs(T("Unknown"));
3768 usage(CMD_OPTIMIZE, stderr);
3774 /* Split a WIM into a spanned set */
3776 imagex_split(int argc, tchar **argv, int cmd)
3780 int write_flags = 0;
3781 unsigned long part_size;
3786 for_opt(c, split_options) {
3788 case IMAGEX_CHECK_OPTION:
3789 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3790 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3802 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3803 if (tmp == argv[2] || *tmp) {
3804 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3805 imagex_error(T("The part size must be an integer or "
3806 "floating-point number of megabytes."));
3809 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3810 imagex_progress_func, NULL);
3814 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3820 usage(CMD_SPLIT, stderr);
3826 #if WIM_MOUNTING_SUPPORTED
3827 /* Unmounts a mounted WIM image. */
3829 imagex_unmount(int argc, tchar **argv, int cmd)
3832 int unmount_flags = 0;
3835 for_opt(c, unmount_options) {
3837 case IMAGEX_COMMIT_OPTION:
3838 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3840 case IMAGEX_CHECK_OPTION:
3841 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3843 case IMAGEX_REBUILD_OPTION:
3844 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3846 case IMAGEX_LAZY_OPTION:
3847 case IMAGEX_FORCE_OPTION:
3848 /* Now, unmount is lazy by default. However, committing
3849 * the image will fail with
3850 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3851 * file descriptors on the WIM image. The
3852 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3853 * descriptors to be closed. */
3854 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3856 case IMAGEX_NEW_IMAGE_OPTION:
3857 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3868 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3869 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3870 imagex_error(T("--new-image is meaningless "
3871 "without --commit also specified!"));
3876 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3877 imagex_progress_func, NULL);
3879 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3880 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3882 "\tNote: Use --commit --force to force changes "
3883 "to be committed, regardless\n"
3884 "\t of open files.\n"));
3891 usage(CMD_UNMOUNT, stderr);
3896 #endif /* WIM_MOUNTING_SUPPORTED */
3899 * Add, delete, or rename files in a WIM image.
3902 imagex_update(int argc, tchar **argv, int cmd)
3904 const tchar *wimfile;
3908 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3909 int write_flags = 0;
3910 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3911 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3912 WIMLIB_ADD_FLAG_VERBOSE |
3913 WIMLIB_ADD_FLAG_WINCONFIG;
3914 int default_delete_flags = 0;
3915 unsigned num_threads = 0;
3917 tchar *cmd_file_contents;
3918 size_t cmd_file_nchars;
3919 struct wimlib_update_command *cmds;
3921 tchar *command_str = NULL;
3922 tchar *config_file = NULL;
3923 tchar *wimboot_config = NULL;
3925 for_opt(c, update_options) {
3927 /* Generic or write options */
3928 case IMAGEX_THREADS_OPTION:
3929 num_threads = parse_num_threads(optarg);
3930 if (num_threads == UINT_MAX)
3933 case IMAGEX_CHECK_OPTION:
3934 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3935 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3937 case IMAGEX_REBUILD_OPTION:
3938 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3940 case IMAGEX_COMMAND_OPTION:
3942 imagex_error(T("--command may only be specified "
3943 "one time. Please provide\n"
3944 " the update commands "
3945 "on standard input instead."));
3948 command_str = tstrdup(optarg);
3950 imagex_error(T("Out of memory!"));
3954 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3955 wimboot_config = optarg;
3957 /* Default delete options */
3958 case IMAGEX_FORCE_OPTION:
3959 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3961 case IMAGEX_RECURSIVE_OPTION:
3962 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3965 /* Global add option */
3966 case IMAGEX_CONFIG_OPTION:
3967 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3968 config_file = optarg;
3971 /* Default add options */
3972 case IMAGEX_VERBOSE_OPTION:
3973 /* No longer does anything. */
3975 case IMAGEX_DEREFERENCE_OPTION:
3976 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3978 case IMAGEX_UNIX_DATA_OPTION:
3979 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3981 case IMAGEX_NO_ACLS_OPTION:
3982 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3984 case IMAGEX_STRICT_ACLS_OPTION:
3985 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3987 case IMAGEX_NO_REPLACE_OPTION:
3988 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3997 if (argc != 1 && argc != 2)
4001 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4002 imagex_progress_func, NULL);
4004 goto out_free_command_str;
4007 /* Image explicitly specified. */
4008 image = wimlib_resolve_image(wim, argv[1]);
4009 ret = verify_image_exists_and_is_single(image, argv[1],
4012 goto out_wimlib_free;
4014 /* No image specified; default to image 1, but only if the WIM
4015 * contains exactly one image. */
4016 struct wimlib_wim_info info;
4018 wimlib_get_wim_info(wim, &info);
4019 if (info.image_count != 1) {
4020 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4021 wimfile, info.image_count);
4028 /* Read update commands from standard input, or the command string if
4031 cmd_file_contents = NULL;
4032 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4036 goto out_free_cmd_file_contents;
4038 } else if (!wimboot_config) {
4039 if (isatty(STDIN_FILENO)) {
4040 tputs(T("Reading update commands from standard input..."));
4041 recommend_man_page(CMD_UPDATE, stdout);
4043 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4044 if (!cmd_file_contents) {
4046 goto out_wimlib_free;
4049 /* Parse the update commands */
4050 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4054 goto out_free_cmd_file_contents;
4057 cmd_file_contents = NULL;
4062 /* Set default flags and capture config on the update commands */
4063 for (size_t i = 0; i < num_cmds; i++) {
4064 switch (cmds[i].op) {
4065 case WIMLIB_UPDATE_OP_ADD:
4066 cmds[i].add.add_flags |= default_add_flags;
4067 cmds[i].add.config_file = config_file;
4069 case WIMLIB_UPDATE_OP_DELETE:
4070 cmds[i].delete_.delete_flags |= default_delete_flags;
4077 /* Execute the update commands */
4078 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4082 if (wimboot_config) {
4083 /* --wimboot-config=FILE is short for an
4084 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4086 struct wimlib_update_command cmd;
4088 cmd.op = WIMLIB_UPDATE_OP_ADD;
4089 cmd.add.fs_source_path = wimboot_config;
4090 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4091 cmd.add.config_file = NULL;
4092 cmd.add.add_flags = 0;
4094 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4099 /* Overwrite the updated WIM */
4100 ret = wimlib_overwrite(wim, write_flags, num_threads);
4103 out_free_cmd_file_contents:
4104 free(cmd_file_contents);
4107 out_free_command_str:
4112 usage(CMD_UPDATE, stderr);
4115 goto out_free_command_str;
4118 /* Verify a WIM file. */
4120 imagex_verify(int argc, tchar **argv, int cmd)
4123 const tchar *wimfile;
4125 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4126 int verify_flags = 0;
4127 STRING_SET(refglobs);
4130 for_opt(c, verify_options) {
4132 case IMAGEX_REF_OPTION:
4133 ret = string_set_append(&refglobs, optarg);
4135 goto out_free_refglobs;
4137 case IMAGEX_NOCHECK_OPTION:
4138 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4150 imagex_error(T("Must specify a WIM file!"));
4152 imagex_error(T("At most one WIM file can be specified!"));
4158 ret = wimlib_open_wim_with_progress(wimfile,
4161 imagex_progress_func,
4164 goto out_free_refglobs;
4166 ret = wim_reference_globs(wim, &refglobs, open_flags);
4168 goto out_wimlib_free;
4170 ret = wimlib_verify_wim(wim, verify_flags);
4172 tputc(T('\n'), stderr);
4173 imagex_error(T("\"%"TS"\" failed verification!"),
4175 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4176 refglobs.num_strings == 0)
4178 imagex_printf(T("Note: if this WIM file is not standalone, "
4179 "use the --ref option to specify the other parts.\n"));
4182 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4189 string_set_destroy(&refglobs);
4193 usage(CMD_VERIFY, stderr);
4195 goto out_free_refglobs;
4198 struct imagex_command {
4200 int (*func)(int argc, tchar **argv, int cmd);
4203 static const struct imagex_command imagex_commands[] = {
4204 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4205 [CMD_APPLY] = {T("apply"), imagex_apply},
4206 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4207 [CMD_DELETE] = {T("delete"), imagex_delete},
4208 [CMD_DIR ] = {T("dir"), imagex_dir},
4209 [CMD_EXPORT] = {T("export"), imagex_export},
4210 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4211 [CMD_INFO] = {T("info"), imagex_info},
4212 [CMD_JOIN] = {T("join"), imagex_join},
4213 #if WIM_MOUNTING_SUPPORTED
4214 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4215 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4217 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4218 [CMD_SPLIT] = {T("split"), imagex_split},
4219 #if WIM_MOUNTING_SUPPORTED
4220 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4222 [CMD_UPDATE] = {T("update"), imagex_update},
4223 [CMD_VERIFY] = {T("verify"), imagex_verify},
4228 /* Can be a directory or source list file. But source list file is probably
4229 * a rare use case, so just say directory. */
4230 # define SOURCE_STR T("DIRECTORY")
4232 /* Can only be a directory */
4233 # define TARGET_STR T("DIRECTORY")
4236 /* Can be a directory, NTFS volume, or source list file. */
4237 # define SOURCE_STR T("SOURCE")
4239 /* Can be a directory or NTFS volume. */
4240 # define TARGET_STR T("TARGET")
4244 static const tchar *usage_strings[] = {
4247 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4248 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4249 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4250 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4251 " [--wimboot] [--unix-data] [--dereference]\n"
4255 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4256 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4257 " [--no-attributes] [--rpfix] [--norpfix]\n"
4258 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4259 " [--compact=FORMAT]\n"
4263 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4264 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4265 " [--config=FILE] [--threads=NUM_THREADS]\n"
4266 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4267 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4268 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4272 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4276 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4280 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4281 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4282 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4283 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4284 " [--wimboot] [--solid]\n"
4288 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4289 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4290 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4291 " [--no-attributes] [--include-invalid-names]\n"
4292 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4296 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4297 " [--boot] [--check] [--nocheck] [--xml]\n"
4298 " [--extract-xml FILE] [--header] [--blobs]\n"
4302 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4304 #if WIM_MOUNTING_SUPPORTED
4307 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4308 " [--check] [--streams-interface=INTERFACE]\n"
4309 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4313 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4314 " [--check] [--streams-interface=INTERFACE]\n"
4315 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4321 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4322 " [--check] [--nocheck] [--solid]\n"
4327 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4329 #if WIM_MOUNTING_SUPPORTED
4332 " %"TS" DIRECTORY\n"
4333 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4338 " %"TS" WIMFILE [IMAGE]\n"
4339 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4340 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4341 " [--command=STRING] [--wimboot-config=FILE]\n"
4346 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4350 static const tchar *invocation_name;
4351 static int invocation_cmd = CMD_NONE;
4353 static const tchar *get_cmd_string(int cmd, bool nospace)
4355 static tchar buf[50];
4356 if (cmd == CMD_NONE) {
4357 return T("wimlib-imagex");
4358 } else if (invocation_cmd != CMD_NONE) {
4359 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4361 const tchar *format;
4364 format = T("%"TS"-%"TS"");
4366 format = T("%"TS" %"TS"");
4367 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4375 static const tchar *s =
4377 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4378 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4379 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4380 "This is free software: you are free to change and redistribute it.\n"
4381 "There is NO WARRANTY, to the extent permitted by law.\n"
4383 "Report bugs to "PACKAGE_BUGREPORT".\n"
4390 help_or_version(int argc, tchar **argv, int cmd)
4395 for (i = 1; i < argc; i++) {
4397 if (p[0] == T('-') && p[1] == T('-')) {
4399 if (!tstrcmp(p, T("help"))) {
4400 if (cmd == CMD_NONE)
4405 } else if (!tstrcmp(p, T("version"))) {
4414 print_usage_string(int cmd, FILE *fp)
4416 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4420 recommend_man_page(int cmd, FILE *fp)
4422 const tchar *format_str;
4424 format_str = T("Some uncommon options are not listed;\n"
4425 "See %"TS".pdf in the doc directory for more details.\n");
4427 format_str = T("Some uncommon options are not listed;\n"
4428 "Try `man %"TS"' for more details.\n");
4430 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4434 usage(int cmd, FILE *fp)
4436 tfprintf(fp, T("Usage:\n"));
4437 print_usage_string(cmd, fp);
4438 tfprintf(fp, T("\n"));
4439 recommend_man_page(cmd, fp);
4445 tfprintf(fp, T("Usage:\n"));
4446 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4447 print_usage_string(cmd, fp);
4448 tfprintf(fp, T("\n"));
4450 static const tchar *extra =
4453 " %"TS" --version\n"
4456 tfprintf(fp, extra, invocation_name, invocation_name);
4458 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4459 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4460 "For some commands IMAGE may be \"all\".\n"
4462 recommend_man_page(CMD_NONE, fp);
4465 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4466 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4467 * something else), while on Windows the command arguments will be UTF-16LE
4468 * encoded 'wchar_t' strings. */
4471 wmain(int argc, wchar_t **argv, wchar_t **envp)
4473 main(int argc, char **argv)
4480 imagex_info_file = stdout;
4481 invocation_name = tbasename(argv[0]);
4484 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4485 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4489 setlocale(LC_ALL, "");
4490 codeset = nl_langinfo(CODESET);
4491 if (!strstr(codeset, "UTF-8") &&
4492 !strstr(codeset, "UTF8") &&
4493 !strstr(codeset, "utf-8") &&
4494 !strstr(codeset, "utf8"))
4497 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4498 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4499 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4500 " to any value to force wimlib to use UTF-8.\n",
4506 #endif /* !__WIN32__ */
4509 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4510 if (igcase != NULL) {
4511 if (!tstrcmp(igcase, T("no")) ||
4512 !tstrcmp(igcase, T("0")))
4513 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4514 else if (!tstrcmp(igcase, T("yes")) ||
4515 !tstrcmp(igcase, T("1")))
4516 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4519 "WARNING: Ignoring unknown setting of "
4520 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4525 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4527 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4528 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4529 for (int i = 0; i < CMD_MAX; i++) {
4530 if (!tstrcmp(invocation_name + 3,
4531 imagex_commands[i].name))
4540 /* Unless already known from the invocation name, determine which
4541 * command was specified. */
4542 if (cmd == CMD_NONE) {
4544 imagex_error(T("No command specified!\n"));
4548 for (int i = 0; i < CMD_MAX; i++) {
4549 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4554 if (cmd != CMD_NONE) {
4560 /* Handle --help and --version. --help can be either for the program as
4561 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4562 * CMD_NONE). Note: help_or_version() will not return if a --help or
4563 * --version argument was found. */
4564 help_or_version(argc, argv, cmd);
4566 /* Bail if a valid command was not specified. */
4567 if (cmd == CMD_NONE) {
4568 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4573 /* Enable warning and error messages in wimlib to be more user-friendly.
4575 wimlib_set_print_errors(true);
4577 /* Initialize wimlib. */
4578 ret = wimlib_global_init(init_flags);
4580 goto out_check_status;
4582 /* Call the command handler function. */
4583 ret = imagex_commands[cmd].func(argc, argv, cmd);
4585 /* Check for error writing to standard output, especially since for some
4586 * commands, writing to standard output is part of the program's actual
4587 * behavior and not just for informational purposes. */
4588 if (ferror(stdout) || fclose(stdout)) {
4589 imagex_error_with_errno(T("error writing to standard output"));
4594 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4595 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4596 * error code from which an error message can be printed. */
4598 imagex_error(T("Exiting with error code %d:\n"
4600 wimlib_get_error_string(ret));
4601 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4602 imagex_error_with_errno(T("errno"));
4604 /* Make wimlib free any resources it's holding (although this is not
4605 * strictly necessary because the process is ending anyway). */
4606 wimlib_global_cleanup();