4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014 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,
146 IMAGEX_CHUNK_SIZE_OPTION,
147 IMAGEX_COMMAND_OPTION,
148 IMAGEX_COMMIT_OPTION,
149 IMAGEX_COMPRESS_OPTION,
150 IMAGEX_COMPRESS_SLOW_OPTION,
151 IMAGEX_CONFIG_OPTION,
153 IMAGEX_DELTA_FROM_OPTION,
154 IMAGEX_DEREFERENCE_OPTION,
155 IMAGEX_DEST_DIR_OPTION,
156 IMAGEX_DETAILED_OPTION,
157 IMAGEX_EXTRACT_XML_OPTION,
160 IMAGEX_HEADER_OPTION,
161 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
163 IMAGEX_LOOKUP_TABLE_OPTION,
164 IMAGEX_METADATA_OPTION,
165 IMAGEX_NEW_IMAGE_OPTION,
166 IMAGEX_NOCHECK_OPTION,
167 IMAGEX_NORPFIX_OPTION,
168 IMAGEX_NOT_PIPABLE_OPTION,
169 IMAGEX_NO_ACLS_OPTION,
170 IMAGEX_NO_ATTRIBUTES_OPTION,
171 IMAGEX_NO_REPLACE_OPTION,
172 IMAGEX_NO_GLOBS_OPTION,
173 IMAGEX_NULLGLOB_OPTION,
174 IMAGEX_ONE_FILE_ONLY_OPTION,
176 IMAGEX_PIPABLE_OPTION,
177 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
178 IMAGEX_REBUILD_OPTION,
179 IMAGEX_RECOMPRESS_OPTION,
180 IMAGEX_RECURSIVE_OPTION,
182 IMAGEX_RESUME_OPTION,
186 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
187 IMAGEX_SOLID_COMPRESS_OPTION,
188 IMAGEX_SOURCE_LIST_OPTION,
189 IMAGEX_STAGING_DIR_OPTION,
190 IMAGEX_STREAMS_INTERFACE_OPTION,
191 IMAGEX_STRICT_ACLS_OPTION,
192 IMAGEX_THREADS_OPTION,
193 IMAGEX_TO_STDOUT_OPTION,
194 IMAGEX_UNIX_DATA_OPTION,
195 IMAGEX_UPDATE_OF_OPTION,
196 IMAGEX_VERBOSE_OPTION,
197 IMAGEX_WIMBOOT_OPTION,
198 IMAGEX_WIMBOOT_CONFIG_OPTION,
202 static const struct option apply_options[] = {
203 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
204 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
205 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
206 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
207 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
208 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
209 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
210 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
211 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
212 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
213 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
215 /* --resume is undocumented for now as it needs improvement. */
216 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
217 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
221 static const struct option capture_or_append_options[] = {
222 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
223 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
224 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
225 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
226 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
227 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
228 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
229 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
230 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
231 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
232 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
233 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
234 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
235 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
236 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
237 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
238 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
239 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
240 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
241 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
242 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
243 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
244 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
245 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
246 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
247 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
248 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
249 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
250 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
251 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
252 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
256 static const struct option delete_options[] = {
257 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
258 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
262 static const struct option dir_options[] = {
263 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
264 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
265 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
269 static const struct option export_options[] = {
270 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
271 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
272 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
273 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
274 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
275 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
276 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
277 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
278 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
279 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
280 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
281 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
282 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
283 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
284 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
285 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
286 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
287 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
288 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
289 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
293 static const struct option extract_options[] = {
294 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
295 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
296 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
297 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
298 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
299 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
300 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
301 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
302 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
303 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
304 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
305 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
306 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
307 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
308 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
309 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
313 static const struct option info_options[] = {
314 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
315 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
316 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
317 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
318 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
319 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
320 {T("lookup-table"), no_argument, NULL, IMAGEX_LOOKUP_TABLE_OPTION},
321 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
322 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
326 static const struct option join_options[] = {
327 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
331 static const struct option mount_options[] = {
332 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
333 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
334 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
335 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
336 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
337 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
338 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
342 static const struct option optimize_options[] = {
343 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
344 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
345 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
346 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
347 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
348 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
349 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
350 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
351 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
352 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
353 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
354 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
355 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
356 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
357 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
358 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
359 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
363 static const struct option split_options[] = {
364 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
368 static const struct option unmount_options[] = {
369 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
370 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
371 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
372 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
373 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
374 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
378 static const struct option update_options[] = {
379 /* Careful: some of the options here set the defaults for update
380 * commands, but the flags given to an actual update command (and not to
381 * `imagex update' itself are also handled in
382 * update_command_add_option(). */
383 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
384 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
385 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
386 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
387 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
389 /* Default delete options */
390 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
391 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
393 /* Global add option */
394 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
396 /* Default add options */
397 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
398 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
399 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
400 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
401 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
402 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
403 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
408 static const struct option verify_options[] = {
409 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
410 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
416 # define _format_attribute(type, format_str, args_start) \
417 __attribute__((format(type, format_str, args_start)))
419 # define _format_attribute(type, format_str, args_start)
422 /* Print formatted error message to stderr. */
423 static void _format_attribute(printf, 1, 2)
424 imagex_error(const tchar *format, ...)
427 va_start(va, format);
428 tfputs(T("ERROR: "), stderr);
429 tvfprintf(stderr, format, va);
430 tputc(T('\n'), stderr);
434 /* Print formatted error message to stderr. */
435 static void _format_attribute(printf, 1, 2)
436 imagex_error_with_errno(const tchar *format, ...)
438 int errno_save = errno;
440 va_start(va, format);
441 tfputs(T("ERROR: "), stderr);
442 tvfprintf(stderr, format, va);
443 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
448 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
450 if (image == WIMLIB_NO_IMAGE) {
451 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
452 " Please specify a 1-based image index or "
453 "image name. To list the images\n"
454 " contained in the WIM archive, run\n"
456 " %"TS" \"%"TS"\"\n"),
457 image_name, wim_name,
458 get_cmd_string(CMD_INFO, false), wim_name);
459 return WIMLIB_ERR_INVALID_IMAGE;
465 verify_image_is_single(int image)
467 if (image == WIMLIB_ALL_IMAGES) {
468 imagex_error(T("Cannot specify all images for this action!"));
469 return WIMLIB_ERR_INVALID_IMAGE;
475 verify_image_exists_and_is_single(int image, const tchar *image_name,
476 const tchar *wim_name)
479 ret = verify_image_exists(image, image_name, wim_name);
481 ret = verify_image_is_single(image);
486 print_available_compression_types(FILE *fp)
488 static const tchar *s =
490 "Available compression types:\n"
493 " xpress (alias: \"fast\")\n"
494 " lzx (alias: \"maximum\") (default for capture)\n"
495 " lzms (alias: \"recovery\")\n"
501 /* Parse the argument to --compress */
503 get_compression_type(tchar *optarg)
506 unsigned int compression_level = 0;
509 plevel = tstrchr(optarg, T(':'));
515 ultmp = tstrtoul(plevel, &ptmp, 10);
516 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
517 imagex_error(T("Compression level must be a positive integer! "
518 "e.g. --compress=lzx:80"));
519 return WIMLIB_COMPRESSION_TYPE_INVALID;
521 compression_level = ultmp;
524 if (!tstrcasecmp(optarg, T("maximum")) ||
525 !tstrcasecmp(optarg, T("lzx")) ||
526 !tstrcasecmp(optarg, T("max")))
527 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
528 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
529 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
530 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
531 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
532 else if (!tstrcasecmp(optarg, T("none")))
533 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
535 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
536 print_available_compression_types(stderr);
537 return WIMLIB_COMPRESSION_TYPE_INVALID;
540 if (compression_level != 0)
541 wimlib_set_default_compression_level(ctype, compression_level);
546 set_compress_slow(void)
549 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
550 " Use the '--compress=TYPE:LEVEL' option instead.\n");
552 wimlib_set_default_compression_level(-1, 100);
556 const tchar **strings;
557 unsigned num_strings;
558 unsigned num_alloc_strings;
561 #define STRING_SET_INITIALIZER \
562 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
564 #define STRING_SET(_strings) \
565 struct string_set _strings = STRING_SET_INITIALIZER
568 string_set_append(struct string_set *set, const tchar *glob)
570 unsigned num_alloc_strings = set->num_alloc_strings;
572 if (set->num_strings == num_alloc_strings) {
573 const tchar **new_strings;
575 num_alloc_strings += 4;
576 new_strings = realloc(set->strings,
577 sizeof(set->strings[0]) * num_alloc_strings);
579 imagex_error(T("Out of memory!"));
582 set->strings = new_strings;
583 set->num_alloc_strings = num_alloc_strings;
585 set->strings[set->num_strings++] = glob;
590 string_set_destroy(struct string_set *set)
596 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
598 return wimlib_reference_resource_files(wim, set->strings,
600 WIMLIB_REF_FLAG_GLOB_ENABLE,
605 do_resource_not_found_warning(const tchar *wimfile,
606 const struct wimlib_wim_info *info,
607 const struct string_set *refglobs)
609 if (info->total_parts > 1) {
610 if (refglobs->num_strings == 0) {
611 imagex_error(T("\"%"TS"\" is part of a split WIM. "
612 "Use --ref to specify the other parts."),
615 imagex_error(T("Perhaps the '--ref' argument did not "
616 "specify all other parts of the split "
620 imagex_error(T("If this is a delta WIM, use the --ref argument "
621 "to specify the WIM(s) on which it is based."));
625 /* Returns the size of a file given its name, or -1 if the file does not exist
626 * or its size cannot be determined. */
628 file_get_size(const tchar *filename)
631 if (tstat(filename, &st) == 0)
638 PARSE_STRING_SUCCESS = 0,
639 PARSE_STRING_FAILURE = 1,
640 PARSE_STRING_NONE = 2,
644 * Parses a string token from an array of characters.
646 * Tokens are either whitespace-delimited, or double or single-quoted.
648 * @line_p: Pointer to the pointer to the line of data. Will be updated
649 * to point past the string token iff the return value is
650 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
653 * @len_p: @len_p initially stores the length of the line of data, which may
654 * be 0, and it will be updated to the number of bytes remaining in
655 * the line iff the return value is PARSE_STRING_SUCCESS.
657 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
658 * parsed string token will be returned here.
660 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
661 * PARSE_STRING_FAILURE if the data was invalid due to a missing
662 * closing quote; or PARSE_STRING_NONE if the line ended before the
663 * beginning of a string token was found.
666 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
669 tchar *line = *line_p;
673 /* Skip leading whitespace */
676 return PARSE_STRING_NONE;
677 if (!istspace(*line) && *line != T('\0'))
683 if (quote_char == T('"') || quote_char == T('\'')) {
688 line = tmemchr(line, quote_char, len);
690 imagex_error(T("Missing closing quote: %"TS), fn - 1);
691 return PARSE_STRING_FAILURE;
694 /* Unquoted string. Go until whitespace. Line is terminated
695 * by '\0', so no need to check 'len'. */
699 } while (!istspace(*line) && *line != T('\0'));
706 return PARSE_STRING_SUCCESS;
709 /* Parses a line of data (not an empty line or comment) in the source list file
710 * format. (See the man page for 'wimlib-imagex capture' for details on this
711 * format and the meaning.)
713 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
714 * len == 0. The data in @line will be modified by this function call.
716 * @len: Length of the line of data.
718 * @source: On success, the capture source and target described by the line is
719 * written into this destination. Note that it will contain pointers
720 * to data in the @line array.
722 * Returns true if the line was valid; false otherwise. */
724 parse_source_list_line(tchar *line, size_t len,
725 struct wimlib_capture_source *source)
729 ret = parse_string(&line, &len, &source->fs_source_path);
730 if (ret != PARSE_STRING_SUCCESS)
732 ret = parse_string(&line, &len, &source->wim_target_path);
733 if (ret == PARSE_STRING_NONE)
734 source->wim_target_path = source->fs_source_path;
735 return ret != PARSE_STRING_FAILURE;
738 /* Returns %true if the given line of length @len > 0 is a comment or empty line
739 * in the source list file format. */
741 is_comment_line(const tchar *line, size_t len)
744 if (*line == T('#') || *line == T(';'))
746 if (!istspace(*line) && *line != T('\0'))
756 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
759 tchar *contents = *contents_p;
760 size_t nchars = *nchars_p;
763 for (i = 0; i < nchars; i++)
764 if (contents[i] == T('\n'))
767 /* Handle last line not terminated by a newline */
768 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
769 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
771 imagex_error(T("Out of memory!"));
774 contents[nchars] = T('\n');
775 *contents_p = contents;
783 /* Parses a file in the source list format. (See the man page for
784 * 'wimlib-imagex capture' for details on this format and the meaning.)
786 * @source_list_contents: Contents of the source list file. Note that this
787 * buffer will be modified to save memory allocations,
788 * and cannot be freed until the returned array of
789 * wimlib_capture_source's has also been freed.
791 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
794 * @nsources_ret: On success, the length of the returned array is
797 * Returns: An array of `struct wimlib_capture_source's that can be passed to
798 * the wimlib_add_image_multisource() function to specify how a WIM image is to
800 static struct wimlib_capture_source *
801 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
802 size_t *nsources_ret)
806 struct wimlib_capture_source *sources;
809 nlines = text_file_count_lines(source_list_contents_p,
810 &source_list_nchars);
814 /* Always allocate at least 1 slot, just in case the implementation of
815 * calloc() returns NULL if 0 bytes are requested. */
816 sources = calloc(nlines ?: 1, sizeof(*sources));
818 imagex_error(T("out of memory"));
821 p = *source_list_contents_p;
823 for (i = 0; i < nlines; i++) {
824 /* XXX: Could use rawmemchr() here instead, but it may not be
825 * available on all platforms. */
826 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
827 size_t len = endp - p + 1;
829 if (!is_comment_line(p, len)) {
830 if (!parse_source_list_line(p, len, &sources[j++])) {
842 /* Reads the contents of a file into memory. */
844 file_get_contents(const tchar *filename, size_t *len_ret)
851 if (tstat(filename, &stbuf) != 0) {
852 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
857 fp = tfopen(filename, T("rb"));
859 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
863 buf = malloc(len ? len : 1);
865 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
866 "contents of file \"%"TS"\""), len, filename);
869 if (fread(buf, 1, len, fp) != len) {
870 imagex_error_with_errno(T("Failed to read %zu bytes from the "
871 "file \"%"TS"\""), len, filename);
885 /* Read standard input until EOF and return the full contents in a malloc()ed
886 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
889 stdin_get_contents(size_t *len_ret)
891 /* stdin can, of course, be a pipe or other non-seekable file, so the
892 * total length of the data cannot be pre-determined */
894 size_t newlen = 1024;
898 char *p = realloc(buf, newlen);
899 size_t bytes_read, bytes_to_read;
901 imagex_error(T("out of memory while reading stdin"));
905 bytes_to_read = newlen - pos;
906 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
908 if (bytes_read != bytes_to_read) {
913 imagex_error_with_errno(T("error reading stdin"));
927 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
930 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
932 *num_tchars_ret = num_bytes;
934 #else /* !__WIN32__ */
935 /* On Windows, translate the text to UTF-16LE */
939 if (num_bytes >= 2 &&
940 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
941 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
943 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
944 * with something that looks like an ASCII character encoded as
945 * a UTF-16LE code unit. Assume the file is encoded as
946 * UTF-16LE. This is not a 100% reliable check. */
947 num_wchars = num_bytes / 2;
948 text_wstr = (wchar_t*)text;
950 /* File does not look like UTF-16LE. Assume it is encoded in
951 * the current Windows code page. I think these are always
952 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
953 * should work as expected. */
954 text_wstr = win32_mbs_to_wcs(text,
959 *num_tchars_ret = num_wchars;
961 #endif /* __WIN32__ */
965 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
970 contents = file_get_contents(filename, &num_bytes);
973 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
977 stdin_get_text_contents(size_t *num_tchars_ret)
982 contents = stdin_get_contents(&num_bytes);
985 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
988 #define TO_PERCENT(numerator, denominator) \
989 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
991 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
992 #define MEBIBYTE_MIN_NBYTES 10000000ULL
993 #define KIBIBYTE_MIN_NBYTES 10000ULL
996 get_unit(uint64_t total_bytes, const tchar **name_ret)
998 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
999 *name_ret = T("GiB");
1001 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1002 *name_ret = T("MiB");
1004 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1005 *name_ret = T("KiB");
1008 *name_ret = T("bytes");
1013 static struct wimlib_progress_info_scan last_scan_progress;
1016 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1018 uint64_t prev_count, cur_count;
1020 prev_count = last_scan_progress.num_nondirs_scanned +
1021 last_scan_progress.num_dirs_scanned;
1022 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1024 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1025 cur_count % 128 == 0)
1027 unsigned unit_shift;
1028 const tchar *unit_name;
1030 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1031 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1032 "%"PRIu64" directories) "),
1033 scan->num_bytes_scanned >> unit_shift,
1035 scan->num_nondirs_scanned,
1036 scan->num_dirs_scanned);
1037 last_scan_progress = *scan;
1040 /* Progress callback function passed to various wimlib functions. */
1041 static enum wimlib_progress_status
1042 imagex_progress_func(enum wimlib_progress_msg msg,
1043 union wimlib_progress_info *info,
1044 void *_ignored_context)
1046 unsigned percent_done;
1047 unsigned unit_shift;
1048 const tchar *unit_name;
1050 if (imagex_be_quiet)
1051 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1053 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1055 static bool first = true;
1057 imagex_printf(T("Writing %"TS"-compressed data "
1058 "using %u thread%"TS"\n"),
1059 wimlib_get_compression_type_string(
1060 info->write_streams.compression_type),
1061 info->write_streams.num_threads,
1062 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1066 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1067 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1068 info->write_streams.total_bytes);
1070 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1071 "written (%u%% done)"),
1072 info->write_streams.completed_bytes >> unit_shift,
1074 info->write_streams.total_bytes >> unit_shift,
1077 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1078 imagex_printf(T("\n"));
1080 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1081 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1082 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1083 imagex_printf(T("\n"));
1085 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1086 info->scan.wim_target_path);
1088 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1090 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1091 switch (info->scan.status) {
1092 case WIMLIB_SCAN_DENTRY_OK:
1093 report_scan_progress(&info->scan, false);
1095 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1096 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1098 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1099 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1100 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1102 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1103 /* Symlink fixups are enabled by default. This is
1104 * mainly intended for Windows, which for some reason
1105 * uses absolute junctions (with drive letters!) in the
1106 * default installation. On UNIX-like systems, warn the
1107 * user when fixing the target of an absolute symbolic
1108 * link, so they know to disable this if they want. */
1110 imagex_printf(T("\nWARNING: Adjusted target of "
1111 "absolute symbolic link \"%"TS"\"\n"
1112 " (Use --norpfix to capture "
1113 "absolute symbolic links as-is)\n"),
1114 info->scan.cur_path);
1121 case WIMLIB_PROGRESS_MSG_SCAN_END:
1122 report_scan_progress(&info->scan, true);
1123 imagex_printf(T("\n"));
1125 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1126 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1127 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1128 info->integrity.total_bytes);
1129 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1130 "of %"PRIu64" %"TS" (%u%%) done"),
1131 info->integrity.filename,
1132 info->integrity.completed_bytes >> unit_shift,
1134 info->integrity.total_bytes >> unit_shift,
1137 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1138 imagex_printf(T("\n"));
1140 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1141 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1142 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1143 info->integrity.total_bytes);
1144 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1145 "of %"PRIu64" %"TS" (%u%%) done"),
1146 info->integrity.completed_bytes >> unit_shift,
1148 info->integrity.total_bytes >> unit_shift,
1151 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1152 imagex_printf(T("\n"));
1154 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1155 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1156 "to %"TS" \"%"TS"\"\n"),
1157 info->extract.image,
1158 info->extract.image_name,
1159 info->extract.wimfile_name,
1160 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1161 T("NTFS volume") : T("directory")),
1162 info->extract.target);
1164 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1165 if (info->extract.end_file_count >= 2000) {
1166 percent_done = TO_PERCENT(info->extract.current_file_count,
1167 info->extract.end_file_count);
1168 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1169 info->extract.current_file_count,
1170 info->extract.end_file_count, percent_done);
1171 if (info->extract.current_file_count == info->extract.end_file_count)
1172 imagex_printf(T("\n"));
1175 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1176 percent_done = TO_PERCENT(info->extract.completed_bytes,
1177 info->extract.total_bytes);
1178 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1179 imagex_printf(T("\rExtracting files: "
1180 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1181 info->extract.completed_bytes >> unit_shift,
1183 info->extract.total_bytes >> unit_shift,
1186 if (info->extract.completed_bytes >= info->extract.total_bytes)
1187 imagex_printf(T("\n"));
1189 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1190 if (info->extract.end_file_count >= 2000) {
1191 percent_done = TO_PERCENT(info->extract.current_file_count,
1192 info->extract.end_file_count);
1193 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1194 info->extract.current_file_count,
1195 info->extract.end_file_count, percent_done);
1196 if (info->extract.current_file_count == info->extract.end_file_count)
1197 imagex_printf(T("\n"));
1200 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1201 if (info->extract.total_parts != 1) {
1202 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1203 info->extract.part_number,
1204 info->extract.total_parts);
1207 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1208 percent_done = TO_PERCENT(info->split.completed_bytes,
1209 info->split.total_bytes);
1210 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1211 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1212 "%"PRIu64" %"TS" (%u%%) written\n"),
1213 info->split.part_name,
1214 info->split.cur_part_number,
1215 info->split.total_parts,
1216 info->split.completed_bytes >> unit_shift,
1218 info->split.total_bytes >> unit_shift,
1222 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1223 if (info->split.completed_bytes == info->split.total_bytes) {
1224 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1225 info->split.cur_part_number,
1226 info->split.total_parts);
1229 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1230 switch (info->update.command->op) {
1231 case WIMLIB_UPDATE_OP_DELETE:
1232 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1233 info->update.command->delete_.wim_path);
1235 case WIMLIB_UPDATE_OP_RENAME:
1236 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1237 info->update.command->rename.wim_source_path,
1238 info->update.command->rename.wim_target_path);
1240 case WIMLIB_UPDATE_OP_ADD:
1245 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1246 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1247 info->replace.path_in_wim);
1249 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1250 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1251 info->wimboot_exclude.path_in_wim);
1253 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1254 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1255 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1256 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1257 info->unmount.mounted_wim,
1258 info->unmount.mounted_image);
1260 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1261 info->unmount.mounted_wim,
1262 info->unmount.mounted_image);
1263 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1267 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1268 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1269 info->verify_image.current_image,
1270 info->verify_image.total_images);
1272 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1273 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1274 info->verify_streams.total_bytes);
1275 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1276 imagex_printf(T("\rVerifying streams: "
1277 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1278 info->verify_streams.completed_bytes >> unit_shift,
1280 info->verify_streams.total_bytes >> unit_shift,
1283 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1284 imagex_printf(T("\n"));
1289 fflush(imagex_info_file);
1290 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1294 parse_num_threads(const tchar *optarg)
1297 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1298 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1299 imagex_error(T("Number of threads must be a non-negative integer!"));
1306 static uint32_t parse_chunk_size(const tchar *optarg)
1309 unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
1310 if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
1311 imagex_error(T("Chunk size must be a non-negative integer!"));
1320 * Parse an option passed to an update command.
1322 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1325 * @option: Text string for the option (beginning with --)
1327 * @cmd: `struct wimlib_update_command' that is being constructed for
1330 * Returns true if the option was recognized; false if not.
1333 update_command_add_option(int op, const tchar *option,
1334 struct wimlib_update_command *cmd)
1336 bool recognized = true;
1338 case WIMLIB_UPDATE_OP_ADD:
1339 if (!tstrcmp(option, T("--verbose")))
1340 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1341 else if (!tstrcmp(option, T("--unix-data")))
1342 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1343 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1344 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1345 else if (!tstrcmp(option, T("--strict-acls")))
1346 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1347 else if (!tstrcmp(option, T("--dereference")))
1348 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1349 else if (!tstrcmp(option, T("--no-replace")))
1350 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1354 case WIMLIB_UPDATE_OP_DELETE:
1355 if (!tstrcmp(option, T("--force")))
1356 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1357 else if (!tstrcmp(option, T("--recursive")))
1358 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1369 /* How many nonoption arguments each `imagex update' command expects */
1370 static const unsigned update_command_num_nonoptions[] = {
1371 [WIMLIB_UPDATE_OP_ADD] = 2,
1372 [WIMLIB_UPDATE_OP_DELETE] = 1,
1373 [WIMLIB_UPDATE_OP_RENAME] = 2,
1377 update_command_add_nonoption(int op, const tchar *nonoption,
1378 struct wimlib_update_command *cmd,
1379 unsigned num_nonoptions)
1382 case WIMLIB_UPDATE_OP_ADD:
1383 if (num_nonoptions == 0)
1384 cmd->add.fs_source_path = (tchar*)nonoption;
1386 cmd->add.wim_target_path = (tchar*)nonoption;
1388 case WIMLIB_UPDATE_OP_DELETE:
1389 cmd->delete_.wim_path = (tchar*)nonoption;
1391 case WIMLIB_UPDATE_OP_RENAME:
1392 if (num_nonoptions == 0)
1393 cmd->rename.wim_source_path = (tchar*)nonoption;
1395 cmd->rename.wim_target_path = (tchar*)nonoption;
1401 * Parse a command passed on stdin to `imagex update'.
1403 * @line: Text of the command.
1404 * @len: Length of the line, including a null terminator
1407 * @command: A `struct wimlib_update_command' to fill in from the parsed
1410 * @line_number: Line number of the command, for diagnostics.
1412 * Returns true on success; returns false on parse error.
1415 parse_update_command(tchar *line, size_t len,
1416 struct wimlib_update_command *command,
1420 tchar *command_name;
1422 size_t num_nonoptions;
1424 /* Get the command name ("add", "delete", "rename") */
1425 ret = parse_string(&line, &len, &command_name);
1426 if (ret != PARSE_STRING_SUCCESS)
1429 if (!tstrcasecmp(command_name, T("add"))) {
1430 op = WIMLIB_UPDATE_OP_ADD;
1431 } else if (!tstrcasecmp(command_name, T("delete"))) {
1432 op = WIMLIB_UPDATE_OP_DELETE;
1433 } else if (!tstrcasecmp(command_name, T("rename"))) {
1434 op = WIMLIB_UPDATE_OP_RENAME;
1436 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1437 command_name, line_number);
1442 /* Parse additional options and non-options as needed */
1447 ret = parse_string(&line, &len, &next_string);
1448 if (ret == PARSE_STRING_NONE) /* End of line */
1450 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1452 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1454 if (!update_command_add_option(op, next_string, command))
1456 imagex_error(T("Unrecognized option \"%"TS"\" to "
1457 "update command \"%"TS"\" on line %zu"),
1458 next_string, command_name, line_number);
1464 if (num_nonoptions == update_command_num_nonoptions[op])
1466 imagex_error(T("Unexpected argument \"%"TS"\" in "
1467 "update command on line %zu\n"
1468 " (The \"%"TS"\" command only "
1469 "takes %zu nonoption arguments!)\n"),
1470 next_string, line_number,
1471 command_name, num_nonoptions);
1474 update_command_add_nonoption(op, next_string,
1475 command, num_nonoptions);
1480 if (num_nonoptions != update_command_num_nonoptions[op]) {
1481 imagex_error(T("Not enough arguments to update command "
1482 "\"%"TS"\" on line %zu"), command_name, line_number);
1488 static struct wimlib_update_command *
1489 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1490 size_t *num_cmds_ret)
1494 struct wimlib_update_command *cmds;
1497 nlines = text_file_count_lines(cmd_file_contents_p,
1502 /* Always allocate at least 1 slot, just in case the implementation of
1503 * calloc() returns NULL if 0 bytes are requested. */
1504 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1506 imagex_error(T("out of memory"));
1509 p = *cmd_file_contents_p;
1511 for (i = 0; i < nlines; i++) {
1512 /* XXX: Could use rawmemchr() here instead, but it may not be
1513 * available on all platforms. */
1514 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1515 size_t len = endp - p + 1;
1517 if (!is_comment_line(p, len)) {
1518 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1529 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1530 * one image from a WIM file to an NTFS volume. */
1532 imagex_apply(int argc, tchar **argv, int cmd)
1536 int image = WIMLIB_NO_IMAGE;
1538 struct wimlib_wim_info info;
1540 const tchar *wimfile;
1541 const tchar *target;
1542 const tchar *image_num_or_name = NULL;
1543 int extract_flags = 0;
1545 STRING_SET(refglobs);
1547 for_opt(c, apply_options) {
1549 case IMAGEX_CHECK_OPTION:
1550 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1552 case IMAGEX_VERBOSE_OPTION:
1553 /* No longer does anything. */
1555 case IMAGEX_REF_OPTION:
1556 ret = string_set_append(&refglobs, optarg);
1558 goto out_free_refglobs;
1560 case IMAGEX_UNIX_DATA_OPTION:
1561 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1563 case IMAGEX_NO_ACLS_OPTION:
1564 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1566 case IMAGEX_STRICT_ACLS_OPTION:
1567 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1569 case IMAGEX_NO_ATTRIBUTES_OPTION:
1570 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1572 case IMAGEX_NORPFIX_OPTION:
1573 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1575 case IMAGEX_RPFIX_OPTION:
1576 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1578 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1579 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1580 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1582 case IMAGEX_RESUME_OPTION:
1583 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1585 case IMAGEX_WIMBOOT_OPTION:
1586 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1594 if (argc != 2 && argc != 3)
1599 if (!tstrcmp(wimfile, T("-"))) {
1600 /* Attempt to apply pipable WIM from standard input. */
1602 image_num_or_name = NULL;
1605 image_num_or_name = argv[1];
1610 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1611 imagex_progress_func, NULL);
1613 goto out_free_refglobs;
1615 wimlib_get_wim_info(wim, &info);
1618 /* Image explicitly specified. */
1619 image_num_or_name = argv[1];
1620 image = wimlib_resolve_image(wim, image_num_or_name);
1621 ret = verify_image_exists(image, image_num_or_name, wimfile);
1623 goto out_wimlib_free;
1626 /* No image specified; default to image 1, but only if the WIM
1627 * contains exactly one image. */
1629 if (info.image_count != 1) {
1630 imagex_error(T("\"%"TS"\" contains %d images; "
1631 "Please select one (or all)."),
1632 wimfile, info.image_count);
1641 if (refglobs.num_strings) {
1643 imagex_error(T("Can't specify --ref when applying from stdin!"));
1645 goto out_wimlib_free;
1647 ret = wim_reference_globs(wim, &refglobs, open_flags);
1649 goto out_wimlib_free;
1654 /* Interpret a regular file or block device target as an NTFS
1658 if (tstat(target, &stbuf)) {
1659 if (errno != ENOENT) {
1660 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1663 goto out_wimlib_free;
1666 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1667 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1673 ret = wimlib_extract_image(wim, image, target, extract_flags);
1675 set_fd_to_binary_mode(STDIN_FILENO);
1676 ret = wimlib_extract_image_from_pipe_with_progress(
1681 imagex_progress_func,
1685 imagex_printf(T("Done applying WIM image.\n"));
1686 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1688 do_resource_not_found_warning(wimfile, &info, &refglobs);
1690 imagex_error(T( "If you are applying an image "
1691 "from a split pipable WIM,\n"
1692 " make sure you have "
1693 "concatenated together all parts."));
1699 string_set_destroy(&refglobs);
1703 usage(CMD_APPLY, stderr);
1705 goto out_free_refglobs;
1708 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1709 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1710 * the desired image. 'wimlib-imagex append': add a new image to an existing
1713 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1716 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
1717 int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE |
1718 WIMLIB_ADD_IMAGE_FLAG_WINCONFIG |
1719 WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1720 int write_flags = 0;
1721 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1722 uint32_t chunk_size = UINT32_MAX;
1723 uint32_t solid_chunk_size = UINT32_MAX;
1724 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1725 const tchar *wimfile;
1729 const tchar *flags_element = NULL;
1732 STRING_SET(base_wimfiles);
1733 WIMStruct **base_wims;
1735 WIMStruct *template_wim;
1736 const tchar *template_wimfile = NULL;
1737 const tchar *template_image_name_or_num = NULL;
1738 int template_image = WIMLIB_NO_IMAGE;
1741 unsigned num_threads = 0;
1746 tchar *config_file = NULL;
1748 bool source_list = false;
1749 size_t source_list_nchars = 0;
1750 tchar *source_list_contents;
1751 bool capture_sources_malloced;
1752 struct wimlib_capture_source *capture_sources;
1754 bool name_defaulted;
1756 for_opt(c, capture_or_append_options) {
1758 case IMAGEX_BOOT_OPTION:
1759 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
1761 case IMAGEX_CHECK_OPTION:
1762 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1763 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1765 case IMAGEX_NOCHECK_OPTION:
1766 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1768 case IMAGEX_CONFIG_OPTION:
1769 config_file = optarg;
1770 add_image_flags &= ~WIMLIB_ADD_IMAGE_FLAG_WINCONFIG;
1772 case IMAGEX_COMPRESS_OPTION:
1773 compression_type = get_compression_type(optarg);
1774 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1777 case IMAGEX_COMPRESS_SLOW_OPTION:
1778 set_compress_slow();
1780 case IMAGEX_CHUNK_SIZE_OPTION:
1781 chunk_size = parse_chunk_size(optarg);
1782 if (chunk_size == UINT32_MAX)
1785 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1786 solid_chunk_size = parse_chunk_size(optarg);
1787 if (solid_chunk_size == UINT32_MAX)
1790 case IMAGEX_SOLID_COMPRESS_OPTION:
1791 solid_ctype = get_compression_type(optarg);
1792 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1795 case IMAGEX_SOLID_OPTION:
1796 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
1798 case IMAGEX_FLAGS_OPTION:
1799 flags_element = optarg;
1801 case IMAGEX_DEREFERENCE_OPTION:
1802 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1804 case IMAGEX_VERBOSE_OPTION:
1805 /* No longer does anything. */
1807 case IMAGEX_THREADS_OPTION:
1808 num_threads = parse_num_threads(optarg);
1809 if (num_threads == UINT_MAX)
1812 case IMAGEX_REBUILD_OPTION:
1813 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1815 case IMAGEX_UNIX_DATA_OPTION:
1816 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1818 case IMAGEX_SOURCE_LIST_OPTION:
1821 case IMAGEX_NO_ACLS_OPTION:
1822 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1824 case IMAGEX_STRICT_ACLS_OPTION:
1825 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1827 case IMAGEX_RPFIX_OPTION:
1828 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_RPFIX;
1830 case IMAGEX_NORPFIX_OPTION:
1831 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NORPFIX;
1833 case IMAGEX_PIPABLE_OPTION:
1834 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1836 case IMAGEX_NOT_PIPABLE_OPTION:
1837 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1839 case IMAGEX_UPDATE_OF_OPTION:
1840 if (template_image_name_or_num) {
1841 imagex_error(T("'--update-of' can only be "
1842 "specified one time!"));
1846 colon = tstrrchr(optarg, T(':'));
1849 template_wimfile = optarg;
1851 template_image_name_or_num = colon + 1;
1853 template_wimfile = NULL;
1854 template_image_name_or_num = optarg;
1858 case IMAGEX_DELTA_FROM_OPTION:
1859 if (cmd != CMD_CAPTURE) {
1860 imagex_error(T("'--delta-from' is only "
1861 "valid for capture!"));
1864 ret = string_set_append(&base_wimfiles, optarg);
1866 goto out_free_base_wimfiles;
1867 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1869 case IMAGEX_WIMBOOT_OPTION:
1870 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_WIMBOOT;
1879 if (argc < 2 || argc > 4)
1885 /* Set default compression type and parameters. */
1888 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1889 /* No compression type specified. Use the default. */
1891 if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) {
1892 /* With --wimboot, default to XPRESS compression. */
1893 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1894 } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
1895 /* With --solid, default to LZMS compression. (However,
1896 * this will not affect solid blocks!) */
1897 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1899 /* Otherwise, default to LZX compression. */
1900 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1904 if (!tstrcmp(wimfile, T("-"))) {
1905 /* Writing captured WIM to standard output. */
1907 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1908 imagex_error("Can't write a non-pipable WIM to "
1909 "standard output! Specify --pipable\n"
1910 " if you want to create a pipable WIM "
1911 "(but read the docs first).");
1915 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1917 if (cmd == CMD_APPEND) {
1918 imagex_error(T("Using standard output for append does "
1919 "not make sense."));
1922 wim_fd = STDOUT_FILENO;
1924 imagex_info_file = stderr;
1925 set_fd_to_binary_mode(wim_fd);
1928 /* If template image was specified using --update-of=IMAGE rather
1929 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1930 if (template_image_name_or_num && !template_wimfile) {
1931 if (base_wimfiles.num_strings == 1) {
1932 /* Capturing delta WIM based on single WIM: default to
1934 template_wimfile = base_wimfiles.strings[0];
1935 } else if (cmd == CMD_APPEND) {
1936 /* Appending to WIM: default to WIM being appended to.
1938 template_wimfile = wimfile;
1940 /* Capturing a normal (non-delta) WIM, so the WIM file
1941 * *must* be explicitly specified. */
1942 if (base_wimfiles.num_strings > 1) {
1943 imagex_error(T("For capture of delta WIM "
1944 "based on multiple existing "
1946 " '--update-of' must "
1947 "specify WIMFILE:IMAGE!"));
1949 imagex_error(T("For capture of non-delta WIM, "
1950 "'--update-of' must specify "
1959 name_defaulted = false;
1961 /* Set default name to SOURCE argument, omitting any directory
1962 * prefixes and trailing slashes. This requires making a copy
1963 * of @source. Leave some free characters at the end in case we
1964 * append a number to keep the name unique. */
1965 size_t source_name_len;
1967 source_name_len = tstrlen(source);
1968 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1969 name = tbasename(tstrcpy(source_copy, source));
1970 name_defaulted = true;
1972 /* Image description defaults to NULL if not given. */
1979 /* Set up capture sources in source list mode */
1980 if (source[0] == T('-') && source[1] == T('\0')) {
1981 source_list_contents = stdin_get_text_contents(&source_list_nchars);
1983 source_list_contents = file_get_text_contents(source,
1984 &source_list_nchars);
1986 if (!source_list_contents)
1989 capture_sources = parse_source_list(&source_list_contents,
1992 if (!capture_sources) {
1994 goto out_free_source_list_contents;
1996 capture_sources_malloced = true;
1998 /* Set up capture source in non-source-list mode. */
1999 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2000 capture_sources[0].fs_source_path = source;
2001 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2002 capture_sources[0].reserved = 0;
2004 capture_sources_malloced = false;
2005 source_list_contents = NULL;
2008 /* Open the existing WIM, or create a new one. */
2009 if (cmd == CMD_APPEND) {
2010 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2011 imagex_progress_func, NULL);
2013 goto out_free_capture_sources;
2015 ret = wimlib_create_new_wim(compression_type, &wim);
2017 goto out_free_capture_sources;
2018 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2021 /* Set chunk size if non-default. */
2022 if (chunk_size != UINT32_MAX) {
2023 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2026 } else if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) &&
2027 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2028 ret = wimlib_set_output_chunk_size(wim, 4096);
2032 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2033 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2037 if (solid_chunk_size != UINT32_MAX) {
2038 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2044 /* Detect if source is regular file or block device and set NTFS volume
2049 if (tstat(source, &stbuf) == 0) {
2050 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2051 imagex_printf(T("Capturing WIM image from NTFS "
2052 "filesystem on \"%"TS"\"\n"), source);
2053 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
2056 if (errno != ENOENT) {
2057 imagex_error_with_errno(T("Failed to stat "
2058 "\"%"TS"\""), source);
2066 /* If the user did not specify an image name, and the basename of the
2067 * source already exists as an image name in the WIM file, append a
2068 * suffix to make it unique. */
2069 if (cmd == CMD_APPEND && name_defaulted) {
2070 unsigned long conflict_idx;
2071 tchar *name_end = tstrchr(name, T('\0'));
2072 for (conflict_idx = 1;
2073 wimlib_image_name_in_use(wim, name);
2076 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2080 /* If capturing a delta WIM, reference resources from the base WIMs
2081 * before adding the new image. */
2082 if (base_wimfiles.num_strings) {
2083 base_wims = calloc(base_wimfiles.num_strings,
2084 sizeof(base_wims[0]));
2085 if (base_wims == NULL) {
2086 imagex_error(T("Out of memory!"));
2091 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2092 ret = wimlib_open_wim_with_progress(
2093 base_wimfiles.strings[i],
2094 open_flags, &base_wims[i],
2095 imagex_progress_func, NULL);
2097 goto out_free_base_wims;
2101 ret = wimlib_reference_resources(wim, base_wims,
2102 base_wimfiles.num_strings, 0);
2104 goto out_free_base_wims;
2106 if (base_wimfiles.num_strings == 1) {
2107 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2108 base_wimfiles.strings[0]);
2110 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2111 base_wimfiles.num_strings);
2118 /* If capturing or appending as an update of an existing (template) image,
2119 * open the WIM if needed and parse the image index. */
2120 if (template_image_name_or_num) {
2123 if (base_wimfiles.num_strings == 1 &&
2124 template_wimfile == base_wimfiles.strings[0]) {
2125 template_wim = base_wims[0];
2126 } else if (template_wimfile == wimfile) {
2129 ret = wimlib_open_wim_with_progress(template_wimfile,
2132 imagex_progress_func,
2135 goto out_free_base_wims;
2138 template_image = wimlib_resolve_image(template_wim,
2139 template_image_name_or_num);
2141 if (template_image_name_or_num[0] == T('-')) {
2144 struct wimlib_wim_info info;
2146 wimlib_get_wim_info(template_wim, &info);
2147 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2148 if (n >= 1 && n <= info.image_count &&
2150 tmp != template_image_name_or_num + 1)
2152 template_image = info.image_count - (n - 1);
2155 ret = verify_image_exists_and_is_single(template_image,
2156 template_image_name_or_num,
2159 goto out_free_template_wim;
2161 template_wim = NULL;
2164 ret = wimlib_add_image_multisource(wim,
2171 goto out_free_template_wim;
2173 if (desc || flags_element || template_image_name_or_num) {
2174 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2175 * on which the added one is to be based has been specified with
2176 * --update-of. Get the index of the image we just
2177 * added, then use it to call the appropriate functions. */
2178 struct wimlib_wim_info info;
2180 wimlib_get_wim_info(wim, &info);
2183 ret = wimlib_set_image_descripton(wim,
2187 goto out_free_template_wim;
2190 if (flags_element) {
2191 ret = wimlib_set_image_flags(wim, info.image_count,
2194 goto out_free_template_wim;
2197 /* Reference template image if the user provided one. */
2198 if (template_image_name_or_num) {
2199 imagex_printf(T("Using image %d "
2200 "from \"%"TS"\" as template\n"),
2201 template_image, template_wimfile);
2202 ret = wimlib_reference_template_image(wim,
2208 goto out_free_template_wim;
2212 /* Write the new WIM or overwrite the existing WIM with the new image
2214 if (cmd == CMD_APPEND) {
2215 ret = wimlib_overwrite(wim, write_flags, num_threads);
2216 } else if (wimfile) {
2217 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2218 write_flags, num_threads);
2220 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2221 write_flags, num_threads);
2223 out_free_template_wim:
2224 /* template_wim may alias base_wims[0] or wim. */
2225 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2226 template_wim != wim)
2227 wimlib_free(template_wim);
2229 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2230 wimlib_free(base_wims[i]);
2234 out_free_capture_sources:
2235 if (capture_sources_malloced)
2236 free(capture_sources);
2237 out_free_source_list_contents:
2238 free(source_list_contents);
2239 out_free_base_wimfiles:
2240 string_set_destroy(&base_wimfiles);
2247 goto out_free_base_wimfiles;
2250 /* Remove image(s) from a WIM. */
2252 imagex_delete(int argc, tchar **argv, int cmd)
2255 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2256 int write_flags = 0;
2257 const tchar *wimfile;
2258 const tchar *image_num_or_name;
2263 for_opt(c, delete_options) {
2265 case IMAGEX_CHECK_OPTION:
2266 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2267 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2269 case IMAGEX_SOFT_OPTION:
2270 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2281 imagex_error(T("Must specify a WIM file"));
2283 imagex_error(T("Must specify an image"));
2287 image_num_or_name = argv[1];
2289 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2290 imagex_progress_func, NULL);
2294 image = wimlib_resolve_image(wim, image_num_or_name);
2296 ret = verify_image_exists(image, image_num_or_name, wimfile);
2298 goto out_wimlib_free;
2300 ret = wimlib_delete_image(wim, image);
2302 imagex_error(T("Failed to delete image from \"%"TS"\""),
2304 goto out_wimlib_free;
2307 ret = wimlib_overwrite(wim, write_flags, 0);
2309 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2310 "deleted"), wimfile);
2318 usage(CMD_DELETE, stderr);
2323 struct print_dentry_options {
2328 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2330 tprintf(T("%"TS"\n"), dentry->full_path);
2333 static const struct {
2336 } file_attr_flags[] = {
2337 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2338 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2339 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2340 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2341 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2342 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2343 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2344 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2345 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2346 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2347 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2348 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2349 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2350 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2351 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2354 #define TIMESTR_MAX 100
2357 timespec_to_string(const struct timespec *spec, tchar *buf)
2359 time_t t = spec->tv_sec;
2362 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2363 buf[TIMESTR_MAX - 1] = '\0';
2367 print_time(const tchar *type, const struct timespec *spec)
2369 tchar timestr[TIMESTR_MAX];
2371 timespec_to_string(spec, timestr);
2373 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2376 static void print_byte_field(const uint8_t field[], size_t len)
2379 tprintf(T("%02hhx"), *field++);
2383 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2385 tputs(T("WIM Information:"));
2386 tputs(T("----------------"));
2387 tprintf(T("Path: %"TS"\n"), wimfile);
2388 tprintf(T("GUID: 0x"));
2389 print_byte_field(info->guid, sizeof(info->guid));
2391 tprintf(T("Version: %u\n"), info->wim_version);
2392 tprintf(T("Image Count: %d\n"), info->image_count);
2393 tprintf(T("Compression: %"TS"\n"),
2394 wimlib_get_compression_type_string(info->compression_type));
2395 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2397 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2398 tprintf(T("Boot Index: %d\n"), info->boot_index);
2399 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2400 tprintf(T("Integrity Info: %"TS"\n"),
2401 info->has_integrity_table ? T("yes") : T("no"));
2402 tprintf(T("Relative path junction: %"TS"\n"),
2403 info->has_rpfix ? T("yes") : T("no"));
2404 tprintf(T("Pipable: %"TS"\n"),
2405 info->pipable ? T("yes") : T("no"));
2410 print_resource(const struct wimlib_resource_entry *resource,
2413 tprintf(T("Hash = 0x"));
2414 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2417 if (!resource->is_missing) {
2418 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2419 resource->uncompressed_size);
2420 if (resource->packed) {
2421 tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2422 resource->raw_resource_compressed_size);
2424 tprintf(T("Raw offset in WIM = %"PRIu64" bytes\n"),
2425 resource->raw_resource_offset_in_wim);
2427 tprintf(T("Offset in raw = %"PRIu64" bytes\n"),
2430 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2431 resource->compressed_size);
2433 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2437 tprintf(T("Part Number = %u\n"), resource->part_number);
2438 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2440 tprintf(T("Flags = "));
2441 if (resource->is_compressed)
2442 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2443 if (resource->is_metadata)
2444 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2445 if (resource->is_free)
2446 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2447 if (resource->is_spanned)
2448 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2449 if (resource->packed)
2450 tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS "));
2458 print_lookup_table(WIMStruct *wim)
2460 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2464 default_print_security_descriptor(const uint8_t *sd, size_t size)
2466 tprintf(T("Security Descriptor = "));
2467 print_byte_field(sd, size);
2472 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2476 "----------------------------------------------------------------------------\n"));
2477 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2478 if (dentry->dos_name)
2479 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2480 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2481 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2482 if (file_attr_flags[i].flag & dentry->attributes)
2483 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2484 file_attr_flags[i].name);
2486 if (dentry->security_descriptor) {
2487 print_security_descriptor(dentry->security_descriptor,
2488 dentry->security_descriptor_size);
2491 print_time(T("Creation Time"), &dentry->creation_time);
2492 print_time(T("Last Write Time"), &dentry->last_write_time);
2493 print_time(T("Last Access Time"), &dentry->last_access_time);
2496 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2497 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2499 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2500 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2502 if (dentry->unix_mode != 0) {
2503 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2504 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2505 dentry->unix_uid, dentry->unix_gid,
2506 dentry->unix_mode, dentry->unix_rdev);
2509 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2510 if (dentry->streams[i].stream_name) {
2511 tprintf(T("\tData stream \"%"TS"\":\n"),
2512 dentry->streams[i].stream_name);
2514 tprintf(T("\tUnnamed data stream:\n"));
2516 print_resource(&dentry->streams[i].resource, NULL);
2521 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2523 const struct print_dentry_options *options = _options;
2524 if (!options->detailed)
2525 print_dentry_full_path(dentry);
2527 print_dentry_detailed(dentry);
2531 /* Print the files contained in an image(s) in a WIM file. */
2533 imagex_dir(int argc, tchar **argv, int cmd)
2535 const tchar *wimfile;
2536 WIMStruct *wim = NULL;
2539 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2541 struct print_dentry_options options = {
2544 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2546 for_opt(c, dir_options) {
2548 case IMAGEX_PATH_OPTION:
2551 case IMAGEX_DETAILED_OPTION:
2552 options.detailed = true;
2554 case IMAGEX_ONE_FILE_ONLY_OPTION:
2555 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2565 imagex_error(T("Must specify a WIM file"));
2569 imagex_error(T("Too many arguments"));
2574 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2575 imagex_progress_func, NULL);
2580 image = wimlib_resolve_image(wim, argv[1]);
2581 ret = verify_image_exists(image, argv[1], wimfile);
2583 goto out_wimlib_free;
2585 /* No image specified; default to image 1, but only if the WIM
2586 * contains exactly one image. */
2588 struct wimlib_wim_info info;
2590 wimlib_get_wim_info(wim, &info);
2591 if (info.image_count != 1) {
2592 imagex_error(T("\"%"TS"\" contains %d images; Please "
2593 "select one (or all)."),
2594 wimfile, info.image_count);
2601 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2602 print_dentry, &options);
2609 usage(CMD_DIR, stderr);
2614 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2617 imagex_export(int argc, tchar **argv, int cmd)
2621 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2622 int write_flags = 0;
2623 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2624 const tchar *src_wimfile;
2625 const tchar *src_image_num_or_name;
2626 const tchar *dest_wimfile;
2628 const tchar *dest_name;
2629 const tchar *dest_desc;
2631 struct wimlib_wim_info src_info;
2632 WIMStruct *dest_wim;
2637 STRING_SET(refglobs);
2638 unsigned num_threads = 0;
2639 uint32_t chunk_size = UINT32_MAX;
2640 uint32_t solid_chunk_size = UINT32_MAX;
2641 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2643 for_opt(c, export_options) {
2645 case IMAGEX_BOOT_OPTION:
2646 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2648 case IMAGEX_CHECK_OPTION:
2649 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2650 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2652 case IMAGEX_NOCHECK_OPTION:
2653 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2655 case IMAGEX_COMPRESS_OPTION:
2656 compression_type = get_compression_type(optarg);
2657 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2660 case IMAGEX_COMPRESS_SLOW_OPTION:
2661 set_compress_slow();
2662 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2664 case IMAGEX_RECOMPRESS_OPTION:
2665 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2667 case IMAGEX_SOLID_OPTION:
2668 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
2670 case IMAGEX_CHUNK_SIZE_OPTION:
2671 chunk_size = parse_chunk_size(optarg);
2672 if (chunk_size == UINT32_MAX)
2675 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2676 solid_chunk_size = parse_chunk_size(optarg);
2677 if (solid_chunk_size == UINT32_MAX)
2680 case IMAGEX_SOLID_COMPRESS_OPTION:
2681 solid_ctype = get_compression_type(optarg);
2682 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2685 case IMAGEX_REF_OPTION:
2686 ret = string_set_append(&refglobs, optarg);
2688 goto out_free_refglobs;
2690 case IMAGEX_THREADS_OPTION:
2691 num_threads = parse_num_threads(optarg);
2692 if (num_threads == UINT_MAX)
2695 case IMAGEX_REBUILD_OPTION:
2696 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2698 case IMAGEX_PIPABLE_OPTION:
2699 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2701 case IMAGEX_NOT_PIPABLE_OPTION:
2702 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2704 case IMAGEX_WIMBOOT_OPTION:
2705 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2713 if (argc < 3 || argc > 5)
2715 src_wimfile = argv[0];
2716 src_image_num_or_name = argv[1];
2717 dest_wimfile = argv[2];
2718 dest_name = (argc >= 4) ? argv[3] : NULL;
2719 dest_desc = (argc >= 5) ? argv[4] : NULL;
2720 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2721 imagex_progress_func, NULL);
2723 goto out_free_refglobs;
2725 wimlib_get_wim_info(src_wim, &src_info);
2727 /* Determine if the destination is an existing file or not. If so, we
2728 * try to append the exported image(s) to it; otherwise, we create a new
2729 * WIM containing the exported image(s). Furthermore, determine if we
2730 * need to write a pipable WIM directly to standard output. */
2732 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2734 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2735 imagex_error("Can't write a non-pipable WIM to "
2736 "standard output! Specify --pipable\n"
2737 " if you want to create a pipable WIM "
2738 "(but read the docs first).");
2740 goto out_free_src_wim;
2743 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2745 dest_wimfile = NULL;
2746 dest_wim_fd = STDOUT_FILENO;
2747 imagex_info_file = stderr;
2748 set_fd_to_binary_mode(dest_wim_fd);
2751 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2753 /* Destination file exists. */
2755 if (!S_ISREG(stbuf.st_mode)) {
2756 imagex_error(T("\"%"TS"\" is not a regular file"),
2759 goto out_free_src_wim;
2761 ret = wimlib_open_wim_with_progress(dest_wimfile,
2763 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2765 imagex_progress_func,
2768 goto out_free_src_wim;
2770 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2771 /* The user specified a compression type, but we're
2772 * exporting to an existing WIM. Make sure the
2773 * specified compression type is the same as the
2774 * compression type of the existing destination WIM. */
2775 struct wimlib_wim_info dest_info;
2777 wimlib_get_wim_info(dest_wim, &dest_info);
2778 if (compression_type != dest_info.compression_type) {
2779 imagex_error(T("Cannot specify a compression type that is "
2780 "not the same as that used in the "
2781 "destination WIM"));
2783 goto out_free_dest_wim;
2789 if (errno != ENOENT) {
2790 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2793 goto out_free_src_wim;
2796 /* dest_wimfile is not an existing file, so create a new WIM. */
2798 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2799 /* The user did not specify a compression type; default
2800 * to that of the source WIM, unless --solid or
2801 * --wimboot was specified. */
2803 if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
2804 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2805 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2806 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2808 compression_type = src_info.compression_type;
2810 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2812 goto out_free_src_wim;
2814 wimlib_register_progress_function(dest_wim,
2815 imagex_progress_func, NULL);
2817 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2818 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2820 /* For --wimboot export, use small XPRESS chunks. */
2821 wimlib_set_output_chunk_size(dest_wim, 4096);
2822 } else if (compression_type == src_info.compression_type &&
2823 chunk_size == UINT32_MAX)
2825 /* Use same chunk size if compression type is the same. */
2826 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2830 if (chunk_size != UINT32_MAX) {
2831 /* Set destination chunk size. */
2832 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2834 goto out_free_dest_wim;
2836 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2837 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2839 goto out_free_dest_wim;
2841 if (solid_chunk_size != UINT32_MAX) {
2842 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2844 goto out_free_dest_wim;
2847 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2848 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2850 goto out_free_dest_wim;
2852 if (refglobs.num_strings) {
2853 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2855 goto out_free_dest_wim;
2858 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2859 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2861 imagex_error(T("--boot specified for all-images export, but source WIM "
2862 "has no bootable image."));
2864 goto out_free_dest_wim;
2867 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2868 dest_desc, export_flags);
2870 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2871 do_resource_not_found_warning(src_wimfile,
2872 &src_info, &refglobs);
2874 goto out_free_dest_wim;
2878 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2879 else if (dest_wimfile)
2880 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2881 write_flags, num_threads);
2883 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2884 WIMLIB_ALL_IMAGES, write_flags,
2887 wimlib_free(dest_wim);
2889 wimlib_free(src_wim);
2891 string_set_destroy(&refglobs);
2895 usage(CMD_EXPORT, stderr);
2898 goto out_free_refglobs;
2901 /* Extract files or directories from a WIM image */
2903 imagex_extract(int argc, tchar **argv, int cmd)
2910 const tchar *wimfile;
2911 const tchar *image_num_or_name;
2912 tchar *dest_dir = T(".");
2913 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2914 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2915 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2916 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2918 STRING_SET(refglobs);
2920 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2922 for_opt(c, extract_options) {
2924 case IMAGEX_CHECK_OPTION:
2925 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2927 case IMAGEX_VERBOSE_OPTION:
2928 /* No longer does anything. */
2930 case IMAGEX_REF_OPTION:
2931 ret = string_set_append(&refglobs, optarg);
2933 goto out_free_refglobs;
2935 case IMAGEX_UNIX_DATA_OPTION:
2936 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2938 case IMAGEX_NO_ACLS_OPTION:
2939 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2941 case IMAGEX_STRICT_ACLS_OPTION:
2942 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2944 case IMAGEX_NO_ATTRIBUTES_OPTION:
2945 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2947 case IMAGEX_DEST_DIR_OPTION:
2950 case IMAGEX_TO_STDOUT_OPTION:
2951 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2952 imagex_info_file = stderr;
2953 imagex_be_quiet = true;
2954 set_fd_to_binary_mode(STDOUT_FILENO);
2956 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2957 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2958 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2960 case IMAGEX_NO_GLOBS_OPTION:
2961 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2963 case IMAGEX_NULLGLOB_OPTION:
2964 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2966 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2967 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2969 case IMAGEX_WIMBOOT_OPTION:
2970 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
2982 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2983 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
2985 imagex_error(T("Can't combine --no-globs and --nullglob!"));
2990 image_num_or_name = argv[1];
2995 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2996 imagex_progress_func, NULL);
2998 goto out_free_refglobs;
3000 image = wimlib_resolve_image(wim, image_num_or_name);
3001 ret = verify_image_exists_and_is_single(image,
3005 goto out_wimlib_free;
3007 if (refglobs.num_strings) {
3008 ret = wim_reference_globs(wim, &refglobs, open_flags);
3010 goto out_wimlib_free;
3016 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3019 while (argc != 0 && ret == 0) {
3023 num_paths < argc && argv[num_paths][0] != T('@');
3028 ret = wimlib_extract_paths(wim, image, dest_dir,
3029 (const tchar **)argv,
3031 extract_flags | notlist_extract_flags);
3035 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3044 if (!imagex_be_quiet)
3045 imagex_printf(T("Done extracting files.\n"));
3046 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3047 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3048 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3049 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3050 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3053 T("Note: You can use the '--nullglob' "
3054 "option to ignore missing files.\n"));
3056 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3057 "files and directories\n"
3058 " are in the WIM image.\n"),
3059 get_cmd_string(CMD_DIR, false));
3060 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3061 struct wimlib_wim_info info;
3063 wimlib_get_wim_info(wim, &info);
3064 do_resource_not_found_warning(wimfile, &info, &refglobs);
3069 string_set_destroy(&refglobs);
3073 usage(CMD_EXTRACT, stderr);
3076 goto out_free_refglobs;
3079 /* Prints information about a WIM file; also can mark an image as bootable,
3080 * change the name of an image, or change the description of an image. */
3082 imagex_info(int argc, tchar **argv, int cmd)
3087 bool nocheck = false;
3088 bool header = false;
3089 bool lookup_table = false;
3091 bool short_header = true;
3092 const tchar *xml_out_file = NULL;
3093 const tchar *wimfile;
3094 const tchar *image_num_or_name;
3095 const tchar *new_name;
3096 const tchar *new_desc;
3101 struct wimlib_wim_info info;
3103 for_opt(c, info_options) {
3105 case IMAGEX_BOOT_OPTION:
3108 case IMAGEX_CHECK_OPTION:
3111 case IMAGEX_NOCHECK_OPTION:
3114 case IMAGEX_HEADER_OPTION:
3116 short_header = false;
3118 case IMAGEX_LOOKUP_TABLE_OPTION:
3119 lookup_table = true;
3120 short_header = false;
3122 case IMAGEX_XML_OPTION:
3124 short_header = false;
3126 case IMAGEX_EXTRACT_XML_OPTION:
3127 xml_out_file = optarg;
3128 short_header = false;
3130 case IMAGEX_METADATA_OPTION:
3131 imagex_error(T("The --metadata option has been removed. "
3132 "Use 'wimdir --detail' instead."));
3141 if (argc < 1 || argc > 4)
3145 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3146 new_name = (argc >= 3) ? argv[2] : NULL;
3147 new_desc = (argc >= 4) ? argv[3] : NULL;
3149 if (check && nocheck) {
3150 imagex_error(T("Can't specify both --check and --nocheck"));
3155 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3157 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3158 imagex_progress_func, NULL);
3162 wimlib_get_wim_info(wim, &info);
3164 image = wimlib_resolve_image(wim, image_num_or_name);
3165 ret = WIMLIB_ERR_INVALID_IMAGE;
3166 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3167 verify_image_exists(image, image_num_or_name, wimfile);
3169 imagex_error(T("If you would like to set the boot "
3170 "index to 0, specify image \"0\" with "
3171 "the --boot flag."));
3173 goto out_wimlib_free;
3176 if (boot && info.image_count == 0) {
3177 imagex_error(T("--boot is meaningless on a WIM with no images"));
3178 goto out_wimlib_free;
3181 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3183 imagex_error(T("Cannot specify the --boot flag "
3184 "without specifying a specific "
3185 "image in a multi-image WIM"));
3186 goto out_wimlib_free;
3189 imagex_error(T("Cannot specify the NEW_NAME "
3190 "without specifying a specific "
3191 "image in a multi-image WIM"));
3192 goto out_wimlib_free;
3196 /* Operations that print information are separated from operations that
3197 * recreate the WIM file. */
3198 if (!new_name && !boot) {
3200 /* Read-only operations */
3202 if (image == WIMLIB_NO_IMAGE) {
3203 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3204 image_num_or_name, wimfile);
3205 goto out_wimlib_free;
3208 if (image == WIMLIB_ALL_IMAGES && short_header)
3209 print_wim_information(wimfile, &info);
3212 wimlib_print_header(wim);
3215 if (info.total_parts != 1) {
3216 tfprintf(stderr, T("Warning: Only showing the lookup table "
3217 "for part %d of a %d-part WIM.\n"),
3218 info.part_number, info.total_parts);
3220 print_lookup_table(wim);
3224 ret = wimlib_extract_xml_data(wim, stdout);
3226 goto out_wimlib_free;
3232 fp = tfopen(xml_out_file, T("wb"));
3234 imagex_error_with_errno(T("Failed to open the "
3235 "file \"%"TS"\" for "
3239 goto out_wimlib_free;
3241 ret = wimlib_extract_xml_data(wim, fp);
3243 imagex_error(T("Failed to close the file "
3249 goto out_wimlib_free;
3253 wimlib_print_available_images(wim, image);
3258 /* Modification operations */
3260 if (image == WIMLIB_ALL_IMAGES)
3263 if (image == WIMLIB_NO_IMAGE && new_name) {
3264 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3265 "when using image 0"), new_name);
3267 goto out_wimlib_free;
3271 if (image == info.boot_index) {
3272 imagex_printf(T("Image %d is already marked as "
3273 "bootable.\n"), image);
3276 imagex_printf(T("Marking image %d as bootable.\n"),
3278 info.boot_index = image;
3279 ret = wimlib_set_wim_info(wim, &info,
3280 WIMLIB_CHANGE_BOOT_INDEX);
3282 goto out_wimlib_free;
3286 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3288 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3292 imagex_printf(T("Changing the name of image %d to "
3293 "\"%"TS"\".\n"), image, new_name);
3294 ret = wimlib_set_image_name(wim, image, new_name);
3296 goto out_wimlib_free;
3300 const tchar *old_desc;
3301 old_desc = wimlib_get_image_description(wim, image);
3302 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3303 imagex_printf(T("The description of image %d is already "
3304 "\"%"TS"\".\n"), image, new_desc);
3307 imagex_printf(T("Changing the description of image %d "
3308 "to \"%"TS"\".\n"), image, new_desc);
3309 ret = wimlib_set_image_descripton(wim, image,
3312 goto out_wimlib_free;
3316 /* Only call wimlib_overwrite() if something actually needs to
3318 if (boot || new_name || new_desc ||
3319 (check && !info.has_integrity_table) ||
3320 (nocheck && info.has_integrity_table))
3322 int write_flags = 0;
3325 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3327 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3328 ret = wimlib_overwrite(wim, write_flags, 1);
3330 imagex_printf(T("The file \"%"TS"\" was not modified "
3331 "because nothing needed to be done.\n"),
3342 usage(CMD_INFO, stderr);
3348 /* Join split WIMs into one part WIM */
3350 imagex_join(int argc, tchar **argv, int cmd)
3353 int swm_open_flags = 0;
3354 int wim_write_flags = 0;
3355 const tchar *output_path;
3358 for_opt(c, join_options) {
3360 case IMAGEX_CHECK_OPTION:
3361 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3362 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3372 imagex_error(T("Must specify one or more split WIM (.swm) "
3376 output_path = argv[0];
3377 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3382 imagex_progress_func,
3388 usage(CMD_JOIN, stderr);
3393 #if WIM_MOUNTING_SUPPORTED
3395 /* Mounts a WIM image. */
3397 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3400 int mount_flags = 0;
3402 const tchar *staging_dir = NULL;
3403 const tchar *wimfile;
3406 struct wimlib_wim_info info;
3410 STRING_SET(refglobs);
3412 if (cmd == CMD_MOUNTRW) {
3413 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3414 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3417 for_opt(c, mount_options) {
3419 case IMAGEX_ALLOW_OTHER_OPTION:
3420 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3422 case IMAGEX_CHECK_OPTION:
3423 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3425 case IMAGEX_DEBUG_OPTION:
3426 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3428 case IMAGEX_STREAMS_INTERFACE_OPTION:
3429 if (!tstrcasecmp(optarg, T("none")))
3430 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3431 else if (!tstrcasecmp(optarg, T("xattr")))
3432 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3433 else if (!tstrcasecmp(optarg, T("windows")))
3434 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3436 imagex_error(T("Unknown stream interface \"%"TS"\""),
3441 case IMAGEX_REF_OPTION:
3442 ret = string_set_append(&refglobs, optarg);
3444 goto out_free_refglobs;
3446 case IMAGEX_STAGING_DIR_OPTION:
3447 staging_dir = optarg;
3449 case IMAGEX_UNIX_DATA_OPTION:
3450 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3458 if (argc != 2 && argc != 3)
3463 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3464 imagex_progress_func, NULL);
3466 goto out_free_refglobs;
3468 wimlib_get_wim_info(wim, &info);
3471 /* Image explicitly specified. */
3472 image = wimlib_resolve_image(wim, argv[1]);
3474 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3478 /* No image specified; default to image 1, but only if the WIM
3479 * contains exactly one image. */
3481 if (info.image_count != 1) {
3482 imagex_error(T("\"%"TS"\" contains %d images; Please "
3483 "select one."), wimfile, info.image_count);
3491 if (refglobs.num_strings) {
3492 ret = wim_reference_globs(wim, &refglobs, open_flags);
3497 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3499 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3501 image, wimfile, dir);
3506 string_set_destroy(&refglobs);
3512 goto out_free_refglobs;
3514 #endif /* WIM_MOUNTING_SUPPORTED */
3516 /* Rebuild a WIM file */
3518 imagex_optimize(int argc, tchar **argv, int cmd)
3521 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3522 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3523 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3524 uint32_t chunk_size = UINT32_MAX;
3525 uint32_t solid_chunk_size = UINT32_MAX;
3526 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3529 const tchar *wimfile;
3532 unsigned num_threads = 0;
3534 for_opt(c, optimize_options) {
3536 case IMAGEX_CHECK_OPTION:
3537 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3538 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3540 case IMAGEX_NOCHECK_OPTION:
3541 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3543 case IMAGEX_COMPRESS_OPTION:
3544 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3545 compression_type = get_compression_type(optarg);
3546 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3549 case IMAGEX_COMPRESS_SLOW_OPTION:
3550 set_compress_slow();
3551 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3553 case IMAGEX_RECOMPRESS_OPTION:
3554 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3556 case IMAGEX_CHUNK_SIZE_OPTION:
3557 chunk_size = parse_chunk_size(optarg);
3558 if (chunk_size == UINT32_MAX)
3561 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3562 solid_chunk_size = parse_chunk_size(optarg);
3563 if (solid_chunk_size == UINT32_MAX)
3566 case IMAGEX_SOLID_COMPRESS_OPTION:
3567 solid_ctype = get_compression_type(optarg);
3568 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3571 case IMAGEX_SOLID_OPTION:
3572 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
3573 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3575 case IMAGEX_THREADS_OPTION:
3576 num_threads = parse_num_threads(optarg);
3577 if (num_threads == UINT_MAX)
3580 case IMAGEX_PIPABLE_OPTION:
3581 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3583 case IMAGEX_NOT_PIPABLE_OPTION:
3584 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3598 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3599 imagex_progress_func, NULL);
3603 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3604 /* Change compression type. */
3605 ret = wimlib_set_output_compression_type(wim, compression_type);
3607 goto out_wimlib_free;
3610 if (chunk_size != UINT32_MAX) {
3611 /* Change chunk size. */
3612 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3614 goto out_wimlib_free;
3616 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3617 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3619 goto out_wimlib_free;
3621 if (solid_chunk_size != UINT32_MAX) {
3622 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3624 goto out_wimlib_free;
3627 old_size = file_get_size(wimfile);
3628 tprintf(T("\"%"TS"\" original size: "), wimfile);
3630 tputs(T("Unknown"));
3632 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3634 ret = wimlib_overwrite(wim, write_flags, num_threads);
3636 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3637 goto out_wimlib_free;
3640 new_size = file_get_size(wimfile);
3641 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3643 tputs(T("Unknown"));
3645 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3647 tfputs(T("Space saved: "), stdout);
3648 if (new_size != -1 && old_size != -1) {
3649 tprintf(T("%lld KiB\n"),
3650 ((long long)old_size - (long long)new_size) >> 10);
3652 tputs(T("Unknown"));
3661 usage(CMD_OPTIMIZE, stderr);
3667 /* Split a WIM into a spanned set */
3669 imagex_split(int argc, tchar **argv, int cmd)
3673 int write_flags = 0;
3674 unsigned long part_size;
3679 for_opt(c, split_options) {
3681 case IMAGEX_CHECK_OPTION:
3682 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3683 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3695 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3696 if (tmp == argv[2] || *tmp) {
3697 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3698 imagex_error(T("The part size must be an integer or "
3699 "floating-point number of megabytes."));
3702 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3703 imagex_progress_func, NULL);
3707 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3713 usage(CMD_SPLIT, stderr);
3719 #if WIM_MOUNTING_SUPPORTED
3720 /* Unmounts a mounted WIM image. */
3722 imagex_unmount(int argc, tchar **argv, int cmd)
3725 int unmount_flags = 0;
3728 for_opt(c, unmount_options) {
3730 case IMAGEX_COMMIT_OPTION:
3731 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3733 case IMAGEX_CHECK_OPTION:
3734 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3736 case IMAGEX_REBUILD_OPTION:
3737 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3739 case IMAGEX_LAZY_OPTION:
3740 case IMAGEX_FORCE_OPTION:
3741 /* Now, unmount is lazy by default. However, committing
3742 * the image will fail with
3743 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3744 * file descriptors on the WIM image. The
3745 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3746 * descriptors to be closed. */
3747 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3749 case IMAGEX_NEW_IMAGE_OPTION:
3750 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3761 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3762 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3763 imagex_error(T("--new-image is meaningless "
3764 "without --commit also specified!"));
3769 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3770 imagex_progress_func, NULL);
3772 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3773 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3775 "\tNote: Use --commit --force to force changes "
3776 "to be committed, regardless\n"
3777 "\t of open files.\n"));
3784 usage(CMD_UNMOUNT, stderr);
3789 #endif /* WIM_MOUNTING_SUPPORTED */
3792 * Add, delete, or rename files in a WIM image.
3795 imagex_update(int argc, tchar **argv, int cmd)
3797 const tchar *wimfile;
3801 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3802 int write_flags = 0;
3803 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3804 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3805 WIMLIB_ADD_FLAG_VERBOSE |
3806 WIMLIB_ADD_FLAG_WINCONFIG;
3807 int default_delete_flags = 0;
3808 unsigned num_threads = 0;
3810 tchar *cmd_file_contents;
3811 size_t cmd_file_nchars;
3812 struct wimlib_update_command *cmds;
3814 tchar *command_str = NULL;
3815 tchar *config_file = NULL;
3816 tchar *wimboot_config = NULL;
3818 for_opt(c, update_options) {
3820 /* Generic or write options */
3821 case IMAGEX_THREADS_OPTION:
3822 num_threads = parse_num_threads(optarg);
3823 if (num_threads == UINT_MAX)
3826 case IMAGEX_CHECK_OPTION:
3827 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3828 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3830 case IMAGEX_REBUILD_OPTION:
3831 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3833 case IMAGEX_COMMAND_OPTION:
3835 imagex_error(T("--command may only be specified "
3836 "one time. Please provide\n"
3837 " the update commands "
3838 "on standard input instead."));
3841 command_str = tstrdup(optarg);
3843 imagex_error(T("Out of memory!"));
3847 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3848 wimboot_config = optarg;
3850 /* Default delete options */
3851 case IMAGEX_FORCE_OPTION:
3852 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3854 case IMAGEX_RECURSIVE_OPTION:
3855 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3858 /* Global add option */
3859 case IMAGEX_CONFIG_OPTION:
3860 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3861 config_file = optarg;
3864 /* Default add options */
3865 case IMAGEX_VERBOSE_OPTION:
3866 /* No longer does anything. */
3868 case IMAGEX_DEREFERENCE_OPTION:
3869 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3871 case IMAGEX_UNIX_DATA_OPTION:
3872 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3874 case IMAGEX_NO_ACLS_OPTION:
3875 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3877 case IMAGEX_STRICT_ACLS_OPTION:
3878 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3880 case IMAGEX_NO_REPLACE_OPTION:
3881 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3890 if (argc != 1 && argc != 2)
3894 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3895 imagex_progress_func, NULL);
3897 goto out_free_command_str;
3900 /* Image explicitly specified. */
3901 image = wimlib_resolve_image(wim, argv[1]);
3902 ret = verify_image_exists_and_is_single(image, argv[1],
3905 goto out_wimlib_free;
3907 /* No image specified; default to image 1, but only if the WIM
3908 * contains exactly one image. */
3909 struct wimlib_wim_info info;
3911 wimlib_get_wim_info(wim, &info);
3912 if (info.image_count != 1) {
3913 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3914 wimfile, info.image_count);
3921 /* Read update commands from standard input, or the command string if
3924 cmd_file_contents = NULL;
3925 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3929 goto out_free_cmd_file_contents;
3931 } else if (!wimboot_config) {
3932 if (isatty(STDIN_FILENO)) {
3933 tputs(T("Reading update commands from standard input..."));
3934 recommend_man_page(CMD_UPDATE, stdout);
3936 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3937 if (!cmd_file_contents) {
3939 goto out_wimlib_free;
3942 /* Parse the update commands */
3943 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3947 goto out_free_cmd_file_contents;
3950 cmd_file_contents = NULL;
3955 /* Set default flags and capture config on the update commands */
3956 for (size_t i = 0; i < num_cmds; i++) {
3957 switch (cmds[i].op) {
3958 case WIMLIB_UPDATE_OP_ADD:
3959 cmds[i].add.add_flags |= default_add_flags;
3960 cmds[i].add.config_file = config_file;
3962 case WIMLIB_UPDATE_OP_DELETE:
3963 cmds[i].delete_.delete_flags |= default_delete_flags;
3970 /* Execute the update commands */
3971 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3975 if (wimboot_config) {
3976 /* --wimboot-config=FILE is short for an
3977 * "add FILE /Windows/System32/WimBootCompress.ini" command.
3979 struct wimlib_update_command cmd;
3981 cmd.op = WIMLIB_UPDATE_OP_ADD;
3982 cmd.add.fs_source_path = wimboot_config;
3983 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
3984 cmd.add.config_file = NULL;
3985 cmd.add.add_flags = 0;
3987 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
3992 /* Overwrite the updated WIM */
3993 ret = wimlib_overwrite(wim, write_flags, num_threads);
3996 out_free_cmd_file_contents:
3997 free(cmd_file_contents);
4000 out_free_command_str:
4005 usage(CMD_UPDATE, stderr);
4008 goto out_free_command_str;
4011 /* Verify a WIM file. */
4013 imagex_verify(int argc, tchar **argv, int cmd)
4016 const tchar *wimfile;
4018 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4019 int verify_flags = 0;
4020 STRING_SET(refglobs);
4023 for_opt(c, verify_options) {
4025 case IMAGEX_REF_OPTION:
4026 ret = string_set_append(&refglobs, optarg);
4028 goto out_free_refglobs;
4030 case IMAGEX_NOCHECK_OPTION:
4031 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4043 imagex_error(T("Must specify a WIM file!"));
4045 imagex_error(T("At most one WIM file can be specified!"));
4051 ret = wimlib_open_wim_with_progress(wimfile,
4054 imagex_progress_func,
4057 goto out_free_refglobs;
4059 ret = wim_reference_globs(wim, &refglobs, open_flags);
4061 goto out_wimlib_free;
4063 ret = wimlib_verify_wim(wim, verify_flags);
4065 tputc(T('\n'), stderr);
4066 imagex_error(T("\"%"TS"\" failed verification!"),
4068 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4069 refglobs.num_strings == 0)
4071 imagex_printf(T("Note: if this WIM file is not standalone, "
4072 "use the --ref option to specify the other parts.\n"));
4075 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4082 string_set_destroy(&refglobs);
4086 usage(CMD_VERIFY, stderr);
4088 goto out_free_refglobs;
4091 struct imagex_command {
4093 int (*func)(int argc, tchar **argv, int cmd);
4096 static const struct imagex_command imagex_commands[] = {
4097 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4098 [CMD_APPLY] = {T("apply"), imagex_apply},
4099 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4100 [CMD_DELETE] = {T("delete"), imagex_delete},
4101 [CMD_DIR ] = {T("dir"), imagex_dir},
4102 [CMD_EXPORT] = {T("export"), imagex_export},
4103 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4104 [CMD_INFO] = {T("info"), imagex_info},
4105 [CMD_JOIN] = {T("join"), imagex_join},
4106 #if WIM_MOUNTING_SUPPORTED
4107 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4108 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4110 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4111 [CMD_SPLIT] = {T("split"), imagex_split},
4112 #if WIM_MOUNTING_SUPPORTED
4113 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4115 [CMD_UPDATE] = {T("update"), imagex_update},
4116 [CMD_VERIFY] = {T("verify"), imagex_verify},
4121 /* Can be a directory or source list file. But source list file is probably
4122 * a rare use case, so just say directory. */
4123 # define SOURCE_STR T("DIRECTORY")
4125 /* Can only be a directory */
4126 # define TARGET_STR T("DIRECTORY")
4129 /* Can be a directory, NTFS volume, or source list file. */
4130 # define SOURCE_STR T("SOURCE")
4132 /* Can be a directory or NTFS volume. */
4133 # define TARGET_STR T("TARGET")
4137 static const tchar *usage_strings[] = {
4140 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4141 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4142 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4143 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4144 " [--wimboot] [--unix-data] [--dereference]\n"
4148 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4149 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4150 " [--no-attributes] [--rpfix] [--norpfix]\n"
4151 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4155 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4156 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4157 " [--config=FILE] [--threads=NUM_THREADS]\n"
4158 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4159 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4160 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4164 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4168 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4172 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4173 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4174 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4175 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4180 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4181 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4182 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4183 " [--no-attributes] [--include-invalid-names]\n"
4184 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4188 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4189 " [--boot] [--check] [--nocheck] [--xml]\n"
4190 " [--extract-xml FILE] [--header] [--lookup-table]\n"
4194 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4196 #if WIM_MOUNTING_SUPPORTED
4199 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4200 " [--check] [--streams-interface=INTERFACE]\n"
4201 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4205 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4206 " [--check] [--streams-interface=INTERFACE]\n"
4207 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4213 " [--recompress] [--compress=TYPE]\n"
4214 " [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4219 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4221 #if WIM_MOUNTING_SUPPORTED
4224 " %"TS" DIRECTORY\n"
4225 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4230 " %"TS" WIMFILE [IMAGE]\n"
4231 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4232 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4233 " [--command=STRING] [--wimboot-config=FILE]\n"
4238 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4242 static const tchar *invocation_name;
4243 static int invocation_cmd = CMD_NONE;
4245 static const tchar *get_cmd_string(int cmd, bool nospace)
4247 static tchar buf[50];
4248 if (cmd == CMD_NONE) {
4249 tsprintf(buf, T("%"TS), T(IMAGEX_PROGNAME));
4250 } else if (invocation_cmd != CMD_NONE) {
4251 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4253 const tchar *format;
4256 format = T("%"TS"-%"TS"");
4258 format = T("%"TS" %"TS"");
4259 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4267 static const tchar *s =
4269 IMAGEX_PROGNAME " (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4270 "Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
4271 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4272 "This is free software: you are free to change and redistribute it.\n"
4273 "There is NO WARRANTY, to the extent permitted by law.\n"
4275 "Report bugs to "PACKAGE_BUGREPORT".\n"
4282 help_or_version(int argc, tchar **argv, int cmd)
4287 for (i = 1; i < argc; i++) {
4289 if (p[0] == T('-') && p[1] == T('-')) {
4291 if (!tstrcmp(p, T("help"))) {
4292 if (cmd == CMD_NONE)
4297 } else if (!tstrcmp(p, T("version"))) {
4306 print_usage_string(int cmd, FILE *fp)
4308 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4312 recommend_man_page(int cmd, FILE *fp)
4314 const tchar *format_str;
4316 format_str = T("Some uncommon options are not listed;\n"
4317 "See %"TS".pdf in the doc directory for more details.\n");
4319 format_str = T("Some uncommon options are not listed;\n"
4320 "Try `man %"TS"' for more details.\n");
4322 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4326 usage(int cmd, FILE *fp)
4328 tfprintf(fp, T("Usage:\n"));
4329 print_usage_string(cmd, fp);
4330 tfprintf(fp, T("\n"));
4331 recommend_man_page(cmd, fp);
4337 tfprintf(fp, T("Usage:\n"));
4338 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4339 print_usage_string(cmd, fp);
4340 tfprintf(fp, T("\n"));
4342 static const tchar *extra =
4345 " %"TS" --version\n"
4348 tfprintf(fp, extra, invocation_name, invocation_name);
4350 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4351 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4352 "For some commands IMAGE may be \"all\".\n"
4354 recommend_man_page(CMD_NONE, fp);
4357 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4358 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4359 * something else), while on Windows the command arguments will be UTF-16LE
4360 * encoded 'wchar_t' strings. */
4363 wmain(int argc, wchar_t **argv, wchar_t **envp)
4365 main(int argc, char **argv)
4372 imagex_info_file = stdout;
4373 invocation_name = tbasename(argv[0]);
4376 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4377 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4381 setlocale(LC_ALL, "");
4382 codeset = nl_langinfo(CODESET);
4383 if (!strstr(codeset, "UTF-8") &&
4384 !strstr(codeset, "UTF8") &&
4385 !strstr(codeset, "utf-8") &&
4386 !strstr(codeset, "utf8"))
4389 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4390 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4391 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4392 " to any value to force wimlib to use UTF-8.\n",
4398 #endif /* !__WIN32__ */
4401 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4402 if (igcase != NULL) {
4403 if (!tstrcmp(igcase, T("no")) ||
4404 !tstrcmp(igcase, T("0")))
4405 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4406 else if (!tstrcmp(igcase, T("yes")) ||
4407 !tstrcmp(igcase, T("1")))
4408 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4411 "WARNING: Ignoring unknown setting of "
4412 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4417 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4419 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4420 tstrcmp(invocation_name, T(IMAGEX_PROGNAME))) {
4421 for (int i = 0; i < CMD_MAX; i++) {
4422 if (!tstrcmp(invocation_name + 3,
4423 imagex_commands[i].name))
4432 /* Unless already known from the invocation name, determine which
4433 * command was specified. */
4434 if (cmd == CMD_NONE) {
4436 imagex_error(T("No command specified!\n"));
4440 for (int i = 0; i < CMD_MAX; i++) {
4441 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4446 if (cmd != CMD_NONE) {
4452 /* Handle --help and --version. --help can be either for the program as
4453 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4454 * CMD_NONE). Note: help_or_version() will not return if a --help or
4455 * --version argument was found. */
4456 help_or_version(argc, argv, cmd);
4458 /* Bail if a valid command was not specified. */
4459 if (cmd == CMD_NONE) {
4460 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4465 /* Enable warning and error messages in wimlib to be more user-friendly.
4467 wimlib_set_print_errors(true);
4469 /* Initialize wimlib. */
4470 ret = wimlib_global_init(init_flags);
4472 goto out_check_status;
4474 /* Call the command handler function. */
4475 ret = imagex_commands[cmd].func(argc, argv, cmd);
4477 /* Check for error writing to standard output, especially since for some
4478 * commands, writing to standard output is part of the program's actual
4479 * behavior and not just for informational purposes. */
4480 if (ferror(stdout) || fclose(stdout)) {
4481 imagex_error_with_errno(T("error writing to standard output"));
4486 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4487 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4488 * error code from which an error message can be printed. */
4490 imagex_error(T("Exiting with error code %d:\n"
4492 wimlib_get_error_string(ret));
4493 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4494 imagex_error_with_errno(T("errno"));
4496 /* Make wimlib free any resources it's holding (although this is not
4497 * strictly necessary because the process is ending anyway). */
4498 wimlib_global_cleanup();