4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # include "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
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!"));
1307 parse_chunk_size(const tchar *optarg)
1310 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1311 if (chunk_size == 0) {
1312 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1313 " with optional K, M, or G suffix"));
1317 if (*tmp == T('k') || *tmp == T('K')) {
1320 } else if (*tmp == T('m') || *tmp == T('M')) {
1323 } else if (*tmp == T('g') || *tmp == T('G')) {
1327 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1328 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1332 if (chunk_size >= UINT32_MAX) {
1333 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1341 * Parse an option passed to an update command.
1343 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1346 * @option: Text string for the option (beginning with --)
1348 * @cmd: `struct wimlib_update_command' that is being constructed for
1351 * Returns true if the option was recognized; false if not.
1354 update_command_add_option(int op, const tchar *option,
1355 struct wimlib_update_command *cmd)
1357 bool recognized = true;
1359 case WIMLIB_UPDATE_OP_ADD:
1360 if (!tstrcmp(option, T("--verbose")))
1361 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1362 else if (!tstrcmp(option, T("--unix-data")))
1363 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1364 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1365 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1366 else if (!tstrcmp(option, T("--strict-acls")))
1367 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1368 else if (!tstrcmp(option, T("--dereference")))
1369 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1370 else if (!tstrcmp(option, T("--no-replace")))
1371 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1375 case WIMLIB_UPDATE_OP_DELETE:
1376 if (!tstrcmp(option, T("--force")))
1377 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1378 else if (!tstrcmp(option, T("--recursive")))
1379 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1390 /* How many nonoption arguments each `imagex update' command expects */
1391 static const unsigned update_command_num_nonoptions[] = {
1392 [WIMLIB_UPDATE_OP_ADD] = 2,
1393 [WIMLIB_UPDATE_OP_DELETE] = 1,
1394 [WIMLIB_UPDATE_OP_RENAME] = 2,
1398 update_command_add_nonoption(int op, const tchar *nonoption,
1399 struct wimlib_update_command *cmd,
1400 unsigned num_nonoptions)
1403 case WIMLIB_UPDATE_OP_ADD:
1404 if (num_nonoptions == 0)
1405 cmd->add.fs_source_path = (tchar*)nonoption;
1407 cmd->add.wim_target_path = (tchar*)nonoption;
1409 case WIMLIB_UPDATE_OP_DELETE:
1410 cmd->delete_.wim_path = (tchar*)nonoption;
1412 case WIMLIB_UPDATE_OP_RENAME:
1413 if (num_nonoptions == 0)
1414 cmd->rename.wim_source_path = (tchar*)nonoption;
1416 cmd->rename.wim_target_path = (tchar*)nonoption;
1422 * Parse a command passed on stdin to `imagex update'.
1424 * @line: Text of the command.
1425 * @len: Length of the line, including a null terminator
1428 * @command: A `struct wimlib_update_command' to fill in from the parsed
1431 * @line_number: Line number of the command, for diagnostics.
1433 * Returns true on success; returns false on parse error.
1436 parse_update_command(tchar *line, size_t len,
1437 struct wimlib_update_command *command,
1441 tchar *command_name;
1443 size_t num_nonoptions;
1445 /* Get the command name ("add", "delete", "rename") */
1446 ret = parse_string(&line, &len, &command_name);
1447 if (ret != PARSE_STRING_SUCCESS)
1450 if (!tstrcasecmp(command_name, T("add"))) {
1451 op = WIMLIB_UPDATE_OP_ADD;
1452 } else if (!tstrcasecmp(command_name, T("delete"))) {
1453 op = WIMLIB_UPDATE_OP_DELETE;
1454 } else if (!tstrcasecmp(command_name, T("rename"))) {
1455 op = WIMLIB_UPDATE_OP_RENAME;
1457 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1458 command_name, line_number);
1463 /* Parse additional options and non-options as needed */
1468 ret = parse_string(&line, &len, &next_string);
1469 if (ret == PARSE_STRING_NONE) /* End of line */
1471 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1473 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1475 if (!update_command_add_option(op, next_string, command))
1477 imagex_error(T("Unrecognized option \"%"TS"\" to "
1478 "update command \"%"TS"\" on line %zu"),
1479 next_string, command_name, line_number);
1485 if (num_nonoptions == update_command_num_nonoptions[op])
1487 imagex_error(T("Unexpected argument \"%"TS"\" in "
1488 "update command on line %zu\n"
1489 " (The \"%"TS"\" command only "
1490 "takes %zu nonoption arguments!)\n"),
1491 next_string, line_number,
1492 command_name, num_nonoptions);
1495 update_command_add_nonoption(op, next_string,
1496 command, num_nonoptions);
1501 if (num_nonoptions != update_command_num_nonoptions[op]) {
1502 imagex_error(T("Not enough arguments to update command "
1503 "\"%"TS"\" on line %zu"), command_name, line_number);
1509 static struct wimlib_update_command *
1510 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1511 size_t *num_cmds_ret)
1515 struct wimlib_update_command *cmds;
1518 nlines = text_file_count_lines(cmd_file_contents_p,
1523 /* Always allocate at least 1 slot, just in case the implementation of
1524 * calloc() returns NULL if 0 bytes are requested. */
1525 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1527 imagex_error(T("out of memory"));
1530 p = *cmd_file_contents_p;
1532 for (i = 0; i < nlines; i++) {
1533 /* XXX: Could use rawmemchr() here instead, but it may not be
1534 * available on all platforms. */
1535 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1536 size_t len = endp - p + 1;
1538 if (!is_comment_line(p, len)) {
1539 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1550 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1551 * one image from a WIM file to an NTFS volume. */
1553 imagex_apply(int argc, tchar **argv, int cmd)
1557 int image = WIMLIB_NO_IMAGE;
1559 struct wimlib_wim_info info;
1561 const tchar *wimfile;
1562 const tchar *target;
1563 const tchar *image_num_or_name = NULL;
1564 int extract_flags = 0;
1566 STRING_SET(refglobs);
1568 for_opt(c, apply_options) {
1570 case IMAGEX_CHECK_OPTION:
1571 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1573 case IMAGEX_VERBOSE_OPTION:
1574 /* No longer does anything. */
1576 case IMAGEX_REF_OPTION:
1577 ret = string_set_append(&refglobs, optarg);
1579 goto out_free_refglobs;
1581 case IMAGEX_UNIX_DATA_OPTION:
1582 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1584 case IMAGEX_NO_ACLS_OPTION:
1585 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1587 case IMAGEX_STRICT_ACLS_OPTION:
1588 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1590 case IMAGEX_NO_ATTRIBUTES_OPTION:
1591 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1593 case IMAGEX_NORPFIX_OPTION:
1594 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1596 case IMAGEX_RPFIX_OPTION:
1597 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1599 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1600 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1601 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1603 case IMAGEX_RESUME_OPTION:
1604 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1606 case IMAGEX_WIMBOOT_OPTION:
1607 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1615 if (argc != 2 && argc != 3)
1620 if (!tstrcmp(wimfile, T("-"))) {
1621 /* Attempt to apply pipable WIM from standard input. */
1623 image_num_or_name = NULL;
1626 image_num_or_name = argv[1];
1631 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1632 imagex_progress_func, NULL);
1634 goto out_free_refglobs;
1636 wimlib_get_wim_info(wim, &info);
1639 /* Image explicitly specified. */
1640 image_num_or_name = argv[1];
1641 image = wimlib_resolve_image(wim, image_num_or_name);
1642 ret = verify_image_exists(image, image_num_or_name, wimfile);
1644 goto out_wimlib_free;
1647 /* No image specified; default to image 1, but only if the WIM
1648 * contains exactly one image. */
1650 if (info.image_count != 1) {
1651 imagex_error(T("\"%"TS"\" contains %d images; "
1652 "Please select one (or all)."),
1653 wimfile, info.image_count);
1662 if (refglobs.num_strings) {
1664 imagex_error(T("Can't specify --ref when applying from stdin!"));
1666 goto out_wimlib_free;
1668 ret = wim_reference_globs(wim, &refglobs, open_flags);
1670 goto out_wimlib_free;
1675 /* Interpret a regular file or block device target as an NTFS
1679 if (tstat(target, &stbuf)) {
1680 if (errno != ENOENT) {
1681 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1684 goto out_wimlib_free;
1687 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1688 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1694 ret = wimlib_extract_image(wim, image, target, extract_flags);
1696 set_fd_to_binary_mode(STDIN_FILENO);
1697 ret = wimlib_extract_image_from_pipe_with_progress(
1702 imagex_progress_func,
1706 imagex_printf(T("Done applying WIM image.\n"));
1707 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1709 do_resource_not_found_warning(wimfile, &info, &refglobs);
1711 imagex_error(T( "If you are applying an image "
1712 "from a split pipable WIM,\n"
1713 " make sure you have "
1714 "concatenated together all parts."));
1720 string_set_destroy(&refglobs);
1724 usage(CMD_APPLY, stderr);
1726 goto out_free_refglobs;
1729 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1730 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1731 * the desired image. 'wimlib-imagex append': add a new image to an existing
1734 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1738 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1739 WIMLIB_ADD_FLAG_WINCONFIG |
1740 WIMLIB_ADD_FLAG_VERBOSE;
1741 int write_flags = 0;
1742 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1743 uint32_t chunk_size = UINT32_MAX;
1744 uint32_t solid_chunk_size = UINT32_MAX;
1745 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1746 const tchar *wimfile;
1750 const tchar *flags_element = NULL;
1753 STRING_SET(base_wimfiles);
1754 WIMStruct **base_wims;
1756 WIMStruct *template_wim;
1757 const tchar *template_wimfile = NULL;
1758 const tchar *template_image_name_or_num = NULL;
1759 int template_image = WIMLIB_NO_IMAGE;
1762 unsigned num_threads = 0;
1767 tchar *config_file = NULL;
1769 bool source_list = false;
1770 size_t source_list_nchars = 0;
1771 tchar *source_list_contents;
1772 bool capture_sources_malloced;
1773 struct wimlib_capture_source *capture_sources;
1775 bool name_defaulted;
1777 for_opt(c, capture_or_append_options) {
1779 case IMAGEX_BOOT_OPTION:
1780 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1782 case IMAGEX_CHECK_OPTION:
1783 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1784 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1786 case IMAGEX_NOCHECK_OPTION:
1787 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1789 case IMAGEX_CONFIG_OPTION:
1790 config_file = optarg;
1791 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1793 case IMAGEX_COMPRESS_OPTION:
1794 compression_type = get_compression_type(optarg);
1795 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1798 case IMAGEX_COMPRESS_SLOW_OPTION:
1799 set_compress_slow();
1801 case IMAGEX_CHUNK_SIZE_OPTION:
1802 chunk_size = parse_chunk_size(optarg);
1803 if (chunk_size == UINT32_MAX)
1806 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1807 solid_chunk_size = parse_chunk_size(optarg);
1808 if (solid_chunk_size == UINT32_MAX)
1811 case IMAGEX_SOLID_COMPRESS_OPTION:
1812 solid_ctype = get_compression_type(optarg);
1813 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1816 case IMAGEX_SOLID_OPTION:
1817 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1819 case IMAGEX_FLAGS_OPTION:
1820 flags_element = optarg;
1822 case IMAGEX_DEREFERENCE_OPTION:
1823 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1825 case IMAGEX_VERBOSE_OPTION:
1826 /* No longer does anything. */
1828 case IMAGEX_THREADS_OPTION:
1829 num_threads = parse_num_threads(optarg);
1830 if (num_threads == UINT_MAX)
1833 case IMAGEX_REBUILD_OPTION:
1834 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1836 case IMAGEX_UNIX_DATA_OPTION:
1837 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1839 case IMAGEX_SOURCE_LIST_OPTION:
1842 case IMAGEX_NO_ACLS_OPTION:
1843 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1845 case IMAGEX_STRICT_ACLS_OPTION:
1846 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1848 case IMAGEX_RPFIX_OPTION:
1849 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1851 case IMAGEX_NORPFIX_OPTION:
1852 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1854 case IMAGEX_PIPABLE_OPTION:
1855 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1857 case IMAGEX_NOT_PIPABLE_OPTION:
1858 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1860 case IMAGEX_UPDATE_OF_OPTION:
1861 if (template_image_name_or_num) {
1862 imagex_error(T("'--update-of' can only be "
1863 "specified one time!"));
1867 colon = tstrrchr(optarg, T(':'));
1870 template_wimfile = optarg;
1872 template_image_name_or_num = colon + 1;
1874 template_wimfile = NULL;
1875 template_image_name_or_num = optarg;
1879 case IMAGEX_DELTA_FROM_OPTION:
1880 if (cmd != CMD_CAPTURE) {
1881 imagex_error(T("'--delta-from' is only "
1882 "valid for capture!"));
1885 ret = string_set_append(&base_wimfiles, optarg);
1887 goto out_free_base_wimfiles;
1888 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1890 case IMAGEX_WIMBOOT_OPTION:
1891 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1900 if (argc < 2 || argc > 4)
1906 /* Set default compression type and parameters. */
1909 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1910 /* No compression type specified. Use the default. */
1912 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1913 /* With --wimboot, default to XPRESS compression. */
1914 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1915 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1916 /* With --solid, default to LZMS compression. (However,
1917 * this will not affect solid resources!) */
1918 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1920 /* Otherwise, default to LZX compression. */
1921 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1925 if (!tstrcmp(wimfile, T("-"))) {
1926 /* Writing captured WIM to standard output. */
1928 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1929 imagex_error("Can't write a non-pipable WIM to "
1930 "standard output! Specify --pipable\n"
1931 " if you want to create a pipable WIM "
1932 "(but read the docs first).");
1936 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1938 if (cmd == CMD_APPEND) {
1939 imagex_error(T("Using standard output for append does "
1940 "not make sense."));
1943 wim_fd = STDOUT_FILENO;
1945 imagex_info_file = stderr;
1946 set_fd_to_binary_mode(wim_fd);
1949 /* If template image was specified using --update-of=IMAGE rather
1950 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1951 if (template_image_name_or_num && !template_wimfile) {
1952 if (base_wimfiles.num_strings == 1) {
1953 /* Capturing delta WIM based on single WIM: default to
1955 template_wimfile = base_wimfiles.strings[0];
1956 } else if (cmd == CMD_APPEND) {
1957 /* Appending to WIM: default to WIM being appended to.
1959 template_wimfile = wimfile;
1961 /* Capturing a normal (non-delta) WIM, so the WIM file
1962 * *must* be explicitly specified. */
1963 if (base_wimfiles.num_strings > 1) {
1964 imagex_error(T("For capture of delta WIM "
1965 "based on multiple existing "
1967 " '--update-of' must "
1968 "specify WIMFILE:IMAGE!"));
1970 imagex_error(T("For capture of non-delta WIM, "
1971 "'--update-of' must specify "
1980 name_defaulted = false;
1982 /* Set default name to SOURCE argument, omitting any directory
1983 * prefixes and trailing slashes. This requires making a copy
1984 * of @source. Leave some free characters at the end in case we
1985 * append a number to keep the name unique. */
1986 size_t source_name_len;
1988 source_name_len = tstrlen(source);
1989 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1990 name = tbasename(tstrcpy(source_copy, source));
1991 name_defaulted = true;
1993 /* Image description defaults to NULL if not given. */
2000 /* Set up capture sources in source list mode */
2001 if (source[0] == T('-') && source[1] == T('\0')) {
2002 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2004 source_list_contents = file_get_text_contents(source,
2005 &source_list_nchars);
2007 if (!source_list_contents)
2010 capture_sources = parse_source_list(&source_list_contents,
2013 if (!capture_sources) {
2015 goto out_free_source_list_contents;
2017 capture_sources_malloced = true;
2019 /* Set up capture source in non-source-list mode. */
2020 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2021 capture_sources[0].fs_source_path = source;
2022 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2023 capture_sources[0].reserved = 0;
2025 capture_sources_malloced = false;
2026 source_list_contents = NULL;
2029 /* Open the existing WIM, or create a new one. */
2030 if (cmd == CMD_APPEND) {
2031 ret = wimlib_open_wim_with_progress(wimfile,
2032 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2034 imagex_progress_func,
2037 goto out_free_capture_sources;
2039 ret = wimlib_create_new_wim(compression_type, &wim);
2041 goto out_free_capture_sources;
2042 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2045 /* Set chunk size if non-default. */
2046 if (chunk_size != UINT32_MAX) {
2047 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2050 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2051 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2052 ret = wimlib_set_output_chunk_size(wim, 4096);
2056 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2057 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2061 if (solid_chunk_size != UINT32_MAX) {
2062 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2068 /* Detect if source is regular file or block device and set NTFS volume
2073 if (tstat(source, &stbuf) == 0) {
2074 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2075 imagex_printf(T("Capturing WIM image from NTFS "
2076 "filesystem on \"%"TS"\"\n"), source);
2077 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2080 if (errno != ENOENT) {
2081 imagex_error_with_errno(T("Failed to stat "
2082 "\"%"TS"\""), source);
2090 /* If the user did not specify an image name, and the basename of the
2091 * source already exists as an image name in the WIM file, append a
2092 * suffix to make it unique. */
2093 if (cmd == CMD_APPEND && name_defaulted) {
2094 unsigned long conflict_idx;
2095 tchar *name_end = tstrchr(name, T('\0'));
2096 for (conflict_idx = 1;
2097 wimlib_image_name_in_use(wim, name);
2100 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2104 /* If capturing a delta WIM, reference resources from the base WIMs
2105 * before adding the new image. */
2106 if (base_wimfiles.num_strings) {
2107 base_wims = calloc(base_wimfiles.num_strings,
2108 sizeof(base_wims[0]));
2109 if (base_wims == NULL) {
2110 imagex_error(T("Out of memory!"));
2115 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2116 ret = wimlib_open_wim_with_progress(
2117 base_wimfiles.strings[i], open_flags,
2118 &base_wims[i], imagex_progress_func, NULL);
2120 goto out_free_base_wims;
2124 ret = wimlib_reference_resources(wim, base_wims,
2125 base_wimfiles.num_strings, 0);
2127 goto out_free_base_wims;
2129 if (base_wimfiles.num_strings == 1) {
2130 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2131 base_wimfiles.strings[0]);
2133 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2134 base_wimfiles.num_strings);
2141 /* If capturing or appending as an update of an existing (template) image,
2142 * open the WIM if needed and parse the image index. */
2143 if (template_image_name_or_num) {
2146 if (base_wimfiles.num_strings == 1 &&
2147 template_wimfile == base_wimfiles.strings[0]) {
2148 template_wim = base_wims[0];
2149 } else if (template_wimfile == wimfile) {
2152 ret = wimlib_open_wim_with_progress(template_wimfile,
2155 imagex_progress_func,
2158 goto out_free_base_wims;
2161 template_image = wimlib_resolve_image(template_wim,
2162 template_image_name_or_num);
2164 if (template_image_name_or_num[0] == T('-')) {
2167 struct wimlib_wim_info info;
2169 wimlib_get_wim_info(template_wim, &info);
2170 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2171 if (n >= 1 && n <= info.image_count &&
2173 tmp != template_image_name_or_num + 1)
2175 template_image = info.image_count - (n - 1);
2178 ret = verify_image_exists_and_is_single(template_image,
2179 template_image_name_or_num,
2182 goto out_free_template_wim;
2184 template_wim = NULL;
2187 ret = wimlib_add_image_multisource(wim,
2194 goto out_free_template_wim;
2196 if (desc || flags_element || template_image_name_or_num) {
2197 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2198 * on which the added one is to be based has been specified with
2199 * --update-of. Get the index of the image we just
2200 * added, then use it to call the appropriate functions. */
2201 struct wimlib_wim_info info;
2203 wimlib_get_wim_info(wim, &info);
2206 ret = wimlib_set_image_descripton(wim,
2210 goto out_free_template_wim;
2213 if (flags_element) {
2214 ret = wimlib_set_image_flags(wim, info.image_count,
2217 goto out_free_template_wim;
2220 /* Reference template image if the user provided one. */
2221 if (template_image_name_or_num) {
2222 imagex_printf(T("Using image %d "
2223 "from \"%"TS"\" as template\n"),
2224 template_image, template_wimfile);
2225 ret = wimlib_reference_template_image(wim,
2231 goto out_free_template_wim;
2235 /* Write the new WIM or overwrite the existing WIM with the new image
2237 if (cmd == CMD_APPEND) {
2238 ret = wimlib_overwrite(wim, write_flags, num_threads);
2239 } else if (wimfile) {
2240 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2241 write_flags, num_threads);
2243 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2244 write_flags, num_threads);
2246 out_free_template_wim:
2247 /* template_wim may alias base_wims[0] or wim. */
2248 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2249 template_wim != wim)
2250 wimlib_free(template_wim);
2252 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2253 wimlib_free(base_wims[i]);
2257 out_free_capture_sources:
2258 if (capture_sources_malloced)
2259 free(capture_sources);
2260 out_free_source_list_contents:
2261 free(source_list_contents);
2262 out_free_base_wimfiles:
2263 string_set_destroy(&base_wimfiles);
2270 goto out_free_base_wimfiles;
2273 /* Remove image(s) from a WIM. */
2275 imagex_delete(int argc, tchar **argv, int cmd)
2278 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2279 int write_flags = 0;
2280 const tchar *wimfile;
2281 const tchar *image_num_or_name;
2286 for_opt(c, delete_options) {
2288 case IMAGEX_CHECK_OPTION:
2289 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2290 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2292 case IMAGEX_SOFT_OPTION:
2293 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2304 imagex_error(T("Must specify a WIM file"));
2306 imagex_error(T("Must specify an image"));
2310 image_num_or_name = argv[1];
2312 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2313 imagex_progress_func, NULL);
2317 image = wimlib_resolve_image(wim, image_num_or_name);
2319 ret = verify_image_exists(image, image_num_or_name, wimfile);
2321 goto out_wimlib_free;
2323 ret = wimlib_delete_image(wim, image);
2325 imagex_error(T("Failed to delete image from \"%"TS"\""),
2327 goto out_wimlib_free;
2330 ret = wimlib_overwrite(wim, write_flags, 0);
2332 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2333 "deleted"), wimfile);
2341 usage(CMD_DELETE, stderr);
2346 struct print_dentry_options {
2351 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2353 tprintf(T("%"TS"\n"), dentry->full_path);
2356 static const struct {
2359 } file_attr_flags[] = {
2360 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2361 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2362 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2363 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2364 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2365 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2366 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2367 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2368 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2369 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2370 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2371 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2372 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2373 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2374 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2377 #define TIMESTR_MAX 100
2380 timespec_to_string(const struct timespec *spec, tchar *buf)
2382 time_t t = spec->tv_sec;
2385 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2386 buf[TIMESTR_MAX - 1] = '\0';
2390 print_time(const tchar *type, const struct timespec *spec)
2392 tchar timestr[TIMESTR_MAX];
2394 timespec_to_string(spec, timestr);
2396 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2399 static void print_byte_field(const uint8_t field[], size_t len)
2402 tprintf(T("%02hhx"), *field++);
2406 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2408 tputs(T("WIM Information:"));
2409 tputs(T("----------------"));
2410 tprintf(T("Path: %"TS"\n"), wimfile);
2411 tprintf(T("GUID: 0x"));
2412 print_byte_field(info->guid, sizeof(info->guid));
2414 tprintf(T("Version: %u\n"), info->wim_version);
2415 tprintf(T("Image Count: %d\n"), info->image_count);
2416 tprintf(T("Compression: %"TS"\n"),
2417 wimlib_get_compression_type_string(info->compression_type));
2418 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2420 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2421 tprintf(T("Boot Index: %d\n"), info->boot_index);
2422 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2423 tprintf(T("Integrity Info: %"TS"\n"),
2424 info->has_integrity_table ? T("yes") : T("no"));
2425 tprintf(T("Relative path junction: %"TS"\n"),
2426 info->has_rpfix ? T("yes") : T("no"));
2427 tprintf(T("Pipable: %"TS"\n"),
2428 info->pipable ? T("yes") : T("no"));
2433 print_resource(const struct wimlib_resource_entry *resource,
2436 tprintf(T("Hash = 0x"));
2437 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2440 if (!resource->is_missing) {
2441 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2442 resource->uncompressed_size);
2443 if (resource->packed) {
2444 tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2445 resource->raw_resource_compressed_size);
2447 tprintf(T("Raw offset in WIM = %"PRIu64" bytes\n"),
2448 resource->raw_resource_offset_in_wim);
2450 tprintf(T("Offset in raw = %"PRIu64" bytes\n"),
2453 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2454 resource->compressed_size);
2456 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2460 tprintf(T("Part Number = %u\n"), resource->part_number);
2461 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2463 tprintf(T("Flags = "));
2464 if (resource->is_compressed)
2465 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2466 if (resource->is_metadata)
2467 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2468 if (resource->is_free)
2469 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2470 if (resource->is_spanned)
2471 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2472 if (resource->packed)
2473 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2481 print_lookup_table(WIMStruct *wim)
2483 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2487 default_print_security_descriptor(const uint8_t *sd, size_t size)
2489 tprintf(T("Security Descriptor = "));
2490 print_byte_field(sd, size);
2495 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2499 "----------------------------------------------------------------------------\n"));
2500 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2501 if (dentry->dos_name)
2502 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2503 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2504 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2505 if (file_attr_flags[i].flag & dentry->attributes)
2506 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2507 file_attr_flags[i].name);
2509 if (dentry->security_descriptor) {
2510 print_security_descriptor(dentry->security_descriptor,
2511 dentry->security_descriptor_size);
2514 print_time(T("Creation Time"), &dentry->creation_time);
2515 print_time(T("Last Write Time"), &dentry->last_write_time);
2516 print_time(T("Last Access Time"), &dentry->last_access_time);
2519 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2520 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2522 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2523 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2525 if (dentry->unix_mode != 0) {
2526 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2527 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2528 dentry->unix_uid, dentry->unix_gid,
2529 dentry->unix_mode, dentry->unix_rdev);
2532 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2533 if (dentry->streams[i].stream_name) {
2534 tprintf(T("\tData stream \"%"TS"\":\n"),
2535 dentry->streams[i].stream_name);
2537 tprintf(T("\tUnnamed data stream:\n"));
2539 print_resource(&dentry->streams[i].resource, NULL);
2544 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2546 const struct print_dentry_options *options = _options;
2547 if (!options->detailed)
2548 print_dentry_full_path(dentry);
2550 print_dentry_detailed(dentry);
2554 /* Print the files contained in an image(s) in a WIM file. */
2556 imagex_dir(int argc, tchar **argv, int cmd)
2558 const tchar *wimfile;
2559 WIMStruct *wim = NULL;
2562 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2564 struct print_dentry_options options = {
2567 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2569 for_opt(c, dir_options) {
2571 case IMAGEX_PATH_OPTION:
2574 case IMAGEX_DETAILED_OPTION:
2575 options.detailed = true;
2577 case IMAGEX_ONE_FILE_ONLY_OPTION:
2578 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2588 imagex_error(T("Must specify a WIM file"));
2592 imagex_error(T("Too many arguments"));
2597 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2598 imagex_progress_func, NULL);
2603 image = wimlib_resolve_image(wim, argv[1]);
2604 ret = verify_image_exists(image, argv[1], wimfile);
2606 goto out_wimlib_free;
2608 /* No image specified; default to image 1, but only if the WIM
2609 * contains exactly one image. */
2611 struct wimlib_wim_info info;
2613 wimlib_get_wim_info(wim, &info);
2614 if (info.image_count != 1) {
2615 imagex_error(T("\"%"TS"\" contains %d images; Please "
2616 "select one (or all)."),
2617 wimfile, info.image_count);
2624 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2625 print_dentry, &options);
2632 usage(CMD_DIR, stderr);
2637 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2640 imagex_export(int argc, tchar **argv, int cmd)
2644 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2645 int write_flags = 0;
2646 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2647 const tchar *src_wimfile;
2648 const tchar *src_image_num_or_name;
2649 const tchar *dest_wimfile;
2651 const tchar *dest_name;
2652 const tchar *dest_desc;
2654 struct wimlib_wim_info src_info;
2655 WIMStruct *dest_wim;
2660 STRING_SET(refglobs);
2661 unsigned num_threads = 0;
2662 uint32_t chunk_size = UINT32_MAX;
2663 uint32_t solid_chunk_size = UINT32_MAX;
2664 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2666 for_opt(c, export_options) {
2668 case IMAGEX_BOOT_OPTION:
2669 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2671 case IMAGEX_CHECK_OPTION:
2672 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2673 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2675 case IMAGEX_NOCHECK_OPTION:
2676 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2678 case IMAGEX_COMPRESS_OPTION:
2679 compression_type = get_compression_type(optarg);
2680 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2683 case IMAGEX_COMPRESS_SLOW_OPTION:
2684 set_compress_slow();
2685 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2687 case IMAGEX_RECOMPRESS_OPTION:
2688 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2690 case IMAGEX_SOLID_OPTION:
2691 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2693 case IMAGEX_CHUNK_SIZE_OPTION:
2694 chunk_size = parse_chunk_size(optarg);
2695 if (chunk_size == UINT32_MAX)
2698 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2699 solid_chunk_size = parse_chunk_size(optarg);
2700 if (solid_chunk_size == UINT32_MAX)
2703 case IMAGEX_SOLID_COMPRESS_OPTION:
2704 solid_ctype = get_compression_type(optarg);
2705 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2708 case IMAGEX_REF_OPTION:
2709 ret = string_set_append(&refglobs, optarg);
2711 goto out_free_refglobs;
2713 case IMAGEX_THREADS_OPTION:
2714 num_threads = parse_num_threads(optarg);
2715 if (num_threads == UINT_MAX)
2718 case IMAGEX_REBUILD_OPTION:
2719 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2721 case IMAGEX_PIPABLE_OPTION:
2722 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2724 case IMAGEX_NOT_PIPABLE_OPTION:
2725 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2727 case IMAGEX_WIMBOOT_OPTION:
2728 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2736 if (argc < 3 || argc > 5)
2738 src_wimfile = argv[0];
2739 src_image_num_or_name = argv[1];
2740 dest_wimfile = argv[2];
2741 dest_name = (argc >= 4) ? argv[3] : NULL;
2742 dest_desc = (argc >= 5) ? argv[4] : NULL;
2743 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2744 imagex_progress_func, NULL);
2746 goto out_free_refglobs;
2748 wimlib_get_wim_info(src_wim, &src_info);
2750 /* Determine if the destination is an existing file or not. If so, we
2751 * try to append the exported image(s) to it; otherwise, we create a new
2752 * WIM containing the exported image(s). Furthermore, determine if we
2753 * need to write a pipable WIM directly to standard output. */
2755 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2757 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2758 imagex_error("Can't write a non-pipable WIM to "
2759 "standard output! Specify --pipable\n"
2760 " if you want to create a pipable WIM "
2761 "(but read the docs first).");
2763 goto out_free_src_wim;
2766 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2768 dest_wimfile = NULL;
2769 dest_wim_fd = STDOUT_FILENO;
2770 imagex_info_file = stderr;
2771 set_fd_to_binary_mode(dest_wim_fd);
2774 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2776 /* Destination file exists. */
2778 if (!S_ISREG(stbuf.st_mode)) {
2779 imagex_error(T("\"%"TS"\" is not a regular file"),
2782 goto out_free_src_wim;
2784 ret = wimlib_open_wim_with_progress(dest_wimfile,
2786 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2788 imagex_progress_func,
2791 goto out_free_src_wim;
2793 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2794 /* The user specified a compression type, but we're
2795 * exporting to an existing WIM. Make sure the
2796 * specified compression type is the same as the
2797 * compression type of the existing destination WIM. */
2798 struct wimlib_wim_info dest_info;
2800 wimlib_get_wim_info(dest_wim, &dest_info);
2801 if (compression_type != dest_info.compression_type) {
2802 imagex_error(T("Cannot specify a compression type that is "
2803 "not the same as that used in the "
2804 "destination WIM"));
2806 goto out_free_dest_wim;
2812 if (errno != ENOENT) {
2813 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2816 goto out_free_src_wim;
2819 /* dest_wimfile is not an existing file, so create a new WIM. */
2821 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2822 /* The user did not specify a compression type; default
2823 * to that of the source WIM, unless --solid or
2824 * --wimboot was specified. */
2826 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2827 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2828 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2829 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2831 compression_type = src_info.compression_type;
2833 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2835 goto out_free_src_wim;
2837 wimlib_register_progress_function(dest_wim,
2838 imagex_progress_func, NULL);
2840 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2841 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2843 /* For --wimboot export, use small XPRESS chunks. */
2844 wimlib_set_output_chunk_size(dest_wim, 4096);
2845 } else if (compression_type == src_info.compression_type &&
2846 chunk_size == UINT32_MAX)
2848 /* Use same chunk size if compression type is the same. */
2849 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2853 if (chunk_size != UINT32_MAX) {
2854 /* Set destination chunk size. */
2855 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2857 goto out_free_dest_wim;
2859 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2860 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2862 goto out_free_dest_wim;
2864 if (solid_chunk_size != UINT32_MAX) {
2865 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2867 goto out_free_dest_wim;
2870 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2871 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2873 goto out_free_dest_wim;
2875 if (refglobs.num_strings) {
2876 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2878 goto out_free_dest_wim;
2881 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2882 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2884 imagex_error(T("--boot specified for all-images export, but source WIM "
2885 "has no bootable image."));
2887 goto out_free_dest_wim;
2890 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2891 dest_desc, export_flags);
2893 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2894 do_resource_not_found_warning(src_wimfile,
2895 &src_info, &refglobs);
2897 goto out_free_dest_wim;
2901 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2902 else if (dest_wimfile)
2903 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2904 write_flags, num_threads);
2906 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2907 WIMLIB_ALL_IMAGES, write_flags,
2910 wimlib_free(dest_wim);
2912 wimlib_free(src_wim);
2914 string_set_destroy(&refglobs);
2918 usage(CMD_EXPORT, stderr);
2921 goto out_free_refglobs;
2924 /* Extract files or directories from a WIM image */
2926 imagex_extract(int argc, tchar **argv, int cmd)
2933 const tchar *wimfile;
2934 const tchar *image_num_or_name;
2935 tchar *dest_dir = T(".");
2936 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2937 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2938 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2939 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2941 STRING_SET(refglobs);
2943 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2945 for_opt(c, extract_options) {
2947 case IMAGEX_CHECK_OPTION:
2948 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2950 case IMAGEX_VERBOSE_OPTION:
2951 /* No longer does anything. */
2953 case IMAGEX_REF_OPTION:
2954 ret = string_set_append(&refglobs, optarg);
2956 goto out_free_refglobs;
2958 case IMAGEX_UNIX_DATA_OPTION:
2959 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2961 case IMAGEX_NO_ACLS_OPTION:
2962 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2964 case IMAGEX_STRICT_ACLS_OPTION:
2965 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2967 case IMAGEX_NO_ATTRIBUTES_OPTION:
2968 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2970 case IMAGEX_DEST_DIR_OPTION:
2973 case IMAGEX_TO_STDOUT_OPTION:
2974 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2975 imagex_info_file = stderr;
2976 imagex_be_quiet = true;
2977 set_fd_to_binary_mode(STDOUT_FILENO);
2979 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2980 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2981 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2983 case IMAGEX_NO_GLOBS_OPTION:
2984 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2986 case IMAGEX_NULLGLOB_OPTION:
2987 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2989 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2990 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2992 case IMAGEX_WIMBOOT_OPTION:
2993 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3005 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3006 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3008 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3013 image_num_or_name = argv[1];
3018 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3019 imagex_progress_func, NULL);
3021 goto out_free_refglobs;
3023 image = wimlib_resolve_image(wim, image_num_or_name);
3024 ret = verify_image_exists_and_is_single(image,
3028 goto out_wimlib_free;
3030 if (refglobs.num_strings) {
3031 ret = wim_reference_globs(wim, &refglobs, open_flags);
3033 goto out_wimlib_free;
3039 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3042 while (argc != 0 && ret == 0) {
3046 num_paths < argc && argv[num_paths][0] != T('@');
3051 ret = wimlib_extract_paths(wim, image, dest_dir,
3052 (const tchar **)argv,
3054 extract_flags | notlist_extract_flags);
3058 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3067 if (!imagex_be_quiet)
3068 imagex_printf(T("Done extracting files.\n"));
3069 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3070 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3071 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3072 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3073 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3076 T("Note: You can use the '--nullglob' "
3077 "option to ignore missing files.\n"));
3079 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3080 "files and directories\n"
3081 " are in the WIM image.\n"),
3082 get_cmd_string(CMD_DIR, false));
3083 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3084 struct wimlib_wim_info info;
3086 wimlib_get_wim_info(wim, &info);
3087 do_resource_not_found_warning(wimfile, &info, &refglobs);
3092 string_set_destroy(&refglobs);
3096 usage(CMD_EXTRACT, stderr);
3099 goto out_free_refglobs;
3102 /* Prints information about a WIM file; also can mark an image as bootable,
3103 * change the name of an image, or change the description of an image. */
3105 imagex_info(int argc, tchar **argv, int cmd)
3110 bool nocheck = false;
3111 bool header = false;
3112 bool lookup_table = false;
3114 bool short_header = true;
3115 const tchar *xml_out_file = NULL;
3116 const tchar *wimfile;
3117 const tchar *image_num_or_name;
3118 const tchar *new_name;
3119 const tchar *new_desc;
3124 struct wimlib_wim_info info;
3126 for_opt(c, info_options) {
3128 case IMAGEX_BOOT_OPTION:
3131 case IMAGEX_CHECK_OPTION:
3134 case IMAGEX_NOCHECK_OPTION:
3137 case IMAGEX_HEADER_OPTION:
3139 short_header = false;
3141 case IMAGEX_LOOKUP_TABLE_OPTION:
3142 lookup_table = true;
3143 short_header = false;
3145 case IMAGEX_XML_OPTION:
3147 short_header = false;
3149 case IMAGEX_EXTRACT_XML_OPTION:
3150 xml_out_file = optarg;
3151 short_header = false;
3153 case IMAGEX_METADATA_OPTION:
3154 imagex_error(T("The --metadata option has been removed. "
3155 "Use 'wimdir --detail' instead."));
3164 if (argc < 1 || argc > 4)
3168 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3169 new_name = (argc >= 3) ? argv[2] : NULL;
3170 new_desc = (argc >= 4) ? argv[3] : NULL;
3172 if (check && nocheck) {
3173 imagex_error(T("Can't specify both --check and --nocheck"));
3178 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3180 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3181 imagex_progress_func, NULL);
3185 wimlib_get_wim_info(wim, &info);
3187 image = wimlib_resolve_image(wim, image_num_or_name);
3188 ret = WIMLIB_ERR_INVALID_IMAGE;
3189 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3190 verify_image_exists(image, image_num_or_name, wimfile);
3192 imagex_error(T("If you would like to set the boot "
3193 "index to 0, specify image \"0\" with "
3194 "the --boot flag."));
3196 goto out_wimlib_free;
3199 if (boot && info.image_count == 0) {
3200 imagex_error(T("--boot is meaningless on a WIM with no images"));
3201 goto out_wimlib_free;
3204 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3206 imagex_error(T("Cannot specify the --boot flag "
3207 "without specifying a specific "
3208 "image in a multi-image WIM"));
3209 goto out_wimlib_free;
3212 imagex_error(T("Cannot specify the NEW_NAME "
3213 "without specifying a specific "
3214 "image in a multi-image WIM"));
3215 goto out_wimlib_free;
3219 /* Operations that print information are separated from operations that
3220 * recreate the WIM file. */
3221 if (!new_name && !boot) {
3223 /* Read-only operations */
3225 if (image == WIMLIB_NO_IMAGE) {
3226 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3227 image_num_or_name, wimfile);
3228 goto out_wimlib_free;
3231 if (image == WIMLIB_ALL_IMAGES && short_header)
3232 print_wim_information(wimfile, &info);
3235 wimlib_print_header(wim);
3238 if (info.total_parts != 1) {
3239 tfprintf(stderr, T("Warning: Only showing the lookup table "
3240 "for part %d of a %d-part WIM.\n"),
3241 info.part_number, info.total_parts);
3243 print_lookup_table(wim);
3247 ret = wimlib_extract_xml_data(wim, stdout);
3249 goto out_wimlib_free;
3255 fp = tfopen(xml_out_file, T("wb"));
3257 imagex_error_with_errno(T("Failed to open the "
3258 "file \"%"TS"\" for "
3262 goto out_wimlib_free;
3264 ret = wimlib_extract_xml_data(wim, fp);
3266 imagex_error(T("Failed to close the file "
3272 goto out_wimlib_free;
3276 wimlib_print_available_images(wim, image);
3281 /* Modification operations */
3283 if (image == WIMLIB_ALL_IMAGES)
3286 if (image == WIMLIB_NO_IMAGE && new_name) {
3287 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3288 "when using image 0"), new_name);
3290 goto out_wimlib_free;
3294 if (image == info.boot_index) {
3295 imagex_printf(T("Image %d is already marked as "
3296 "bootable.\n"), image);
3299 imagex_printf(T("Marking image %d as bootable.\n"),
3301 info.boot_index = image;
3302 ret = wimlib_set_wim_info(wim, &info,
3303 WIMLIB_CHANGE_BOOT_INDEX);
3305 goto out_wimlib_free;
3309 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3311 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3315 imagex_printf(T("Changing the name of image %d to "
3316 "\"%"TS"\".\n"), image, new_name);
3317 ret = wimlib_set_image_name(wim, image, new_name);
3319 goto out_wimlib_free;
3323 const tchar *old_desc;
3324 old_desc = wimlib_get_image_description(wim, image);
3325 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3326 imagex_printf(T("The description of image %d is already "
3327 "\"%"TS"\".\n"), image, new_desc);
3330 imagex_printf(T("Changing the description of image %d "
3331 "to \"%"TS"\".\n"), image, new_desc);
3332 ret = wimlib_set_image_descripton(wim, image,
3335 goto out_wimlib_free;
3339 /* Only call wimlib_overwrite() if something actually needs to
3341 if (boot || new_name || new_desc ||
3342 (check && !info.has_integrity_table) ||
3343 (nocheck && info.has_integrity_table))
3345 int write_flags = 0;
3348 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3350 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3351 ret = wimlib_overwrite(wim, write_flags, 1);
3353 imagex_printf(T("The file \"%"TS"\" was not modified "
3354 "because nothing needed to be done.\n"),
3365 usage(CMD_INFO, stderr);
3371 /* Join split WIMs into one part WIM */
3373 imagex_join(int argc, tchar **argv, int cmd)
3376 int swm_open_flags = 0;
3377 int wim_write_flags = 0;
3378 const tchar *output_path;
3381 for_opt(c, join_options) {
3383 case IMAGEX_CHECK_OPTION:
3384 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3385 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3395 imagex_error(T("Must specify one or more split WIM (.swm) "
3399 output_path = argv[0];
3400 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3405 imagex_progress_func,
3411 usage(CMD_JOIN, stderr);
3416 #if WIM_MOUNTING_SUPPORTED
3418 /* Mounts a WIM image. */
3420 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3423 int mount_flags = 0;
3425 const tchar *staging_dir = NULL;
3426 const tchar *wimfile;
3429 struct wimlib_wim_info info;
3433 STRING_SET(refglobs);
3435 if (cmd == CMD_MOUNTRW) {
3436 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3437 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3440 for_opt(c, mount_options) {
3442 case IMAGEX_ALLOW_OTHER_OPTION:
3443 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3445 case IMAGEX_CHECK_OPTION:
3446 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3448 case IMAGEX_DEBUG_OPTION:
3449 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3451 case IMAGEX_STREAMS_INTERFACE_OPTION:
3452 if (!tstrcasecmp(optarg, T("none")))
3453 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3454 else if (!tstrcasecmp(optarg, T("xattr")))
3455 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3456 else if (!tstrcasecmp(optarg, T("windows")))
3457 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3459 imagex_error(T("Unknown stream interface \"%"TS"\""),
3464 case IMAGEX_REF_OPTION:
3465 ret = string_set_append(&refglobs, optarg);
3467 goto out_free_refglobs;
3469 case IMAGEX_STAGING_DIR_OPTION:
3470 staging_dir = optarg;
3472 case IMAGEX_UNIX_DATA_OPTION:
3473 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3481 if (argc != 2 && argc != 3)
3486 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3487 imagex_progress_func, NULL);
3489 goto out_free_refglobs;
3491 wimlib_get_wim_info(wim, &info);
3494 /* Image explicitly specified. */
3495 image = wimlib_resolve_image(wim, argv[1]);
3497 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3501 /* No image specified; default to image 1, but only if the WIM
3502 * contains exactly one image. */
3504 if (info.image_count != 1) {
3505 imagex_error(T("\"%"TS"\" contains %d images; Please "
3506 "select one."), wimfile, info.image_count);
3514 if (refglobs.num_strings) {
3515 ret = wim_reference_globs(wim, &refglobs, open_flags);
3520 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3522 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3524 image, wimfile, dir);
3529 string_set_destroy(&refglobs);
3535 goto out_free_refglobs;
3537 #endif /* WIM_MOUNTING_SUPPORTED */
3539 /* Rebuild a WIM file */
3541 imagex_optimize(int argc, tchar **argv, int cmd)
3544 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3545 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3546 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3547 uint32_t chunk_size = UINT32_MAX;
3548 uint32_t solid_chunk_size = UINT32_MAX;
3549 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3552 const tchar *wimfile;
3555 unsigned num_threads = 0;
3557 for_opt(c, optimize_options) {
3559 case IMAGEX_CHECK_OPTION:
3560 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3561 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3563 case IMAGEX_NOCHECK_OPTION:
3564 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3566 case IMAGEX_COMPRESS_OPTION:
3567 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3568 compression_type = get_compression_type(optarg);
3569 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3572 case IMAGEX_COMPRESS_SLOW_OPTION:
3573 set_compress_slow();
3574 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3576 case IMAGEX_RECOMPRESS_OPTION:
3577 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3579 case IMAGEX_CHUNK_SIZE_OPTION:
3580 chunk_size = parse_chunk_size(optarg);
3581 if (chunk_size == UINT32_MAX)
3584 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3585 solid_chunk_size = parse_chunk_size(optarg);
3586 if (solid_chunk_size == UINT32_MAX)
3589 case IMAGEX_SOLID_COMPRESS_OPTION:
3590 solid_ctype = get_compression_type(optarg);
3591 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3594 case IMAGEX_SOLID_OPTION:
3595 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3596 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3598 case IMAGEX_THREADS_OPTION:
3599 num_threads = parse_num_threads(optarg);
3600 if (num_threads == UINT_MAX)
3603 case IMAGEX_PIPABLE_OPTION:
3604 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3606 case IMAGEX_NOT_PIPABLE_OPTION:
3607 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3621 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3622 imagex_progress_func, NULL);
3626 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3627 /* Change compression type. */
3628 ret = wimlib_set_output_compression_type(wim, compression_type);
3630 goto out_wimlib_free;
3633 if (chunk_size != UINT32_MAX) {
3634 /* Change chunk size. */
3635 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3637 goto out_wimlib_free;
3639 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3640 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3642 goto out_wimlib_free;
3644 if (solid_chunk_size != UINT32_MAX) {
3645 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3647 goto out_wimlib_free;
3650 old_size = file_get_size(wimfile);
3651 tprintf(T("\"%"TS"\" original size: "), wimfile);
3653 tputs(T("Unknown"));
3655 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3657 ret = wimlib_overwrite(wim, write_flags, num_threads);
3659 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3660 goto out_wimlib_free;
3663 new_size = file_get_size(wimfile);
3664 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3666 tputs(T("Unknown"));
3668 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3670 tfputs(T("Space saved: "), stdout);
3671 if (new_size != -1 && old_size != -1) {
3672 tprintf(T("%lld KiB\n"),
3673 ((long long)old_size - (long long)new_size) >> 10);
3675 tputs(T("Unknown"));
3684 usage(CMD_OPTIMIZE, stderr);
3690 /* Split a WIM into a spanned set */
3692 imagex_split(int argc, tchar **argv, int cmd)
3696 int write_flags = 0;
3697 unsigned long part_size;
3702 for_opt(c, split_options) {
3704 case IMAGEX_CHECK_OPTION:
3705 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3706 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3718 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3719 if (tmp == argv[2] || *tmp) {
3720 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3721 imagex_error(T("The part size must be an integer or "
3722 "floating-point number of megabytes."));
3725 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3726 imagex_progress_func, NULL);
3730 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3736 usage(CMD_SPLIT, stderr);
3742 #if WIM_MOUNTING_SUPPORTED
3743 /* Unmounts a mounted WIM image. */
3745 imagex_unmount(int argc, tchar **argv, int cmd)
3748 int unmount_flags = 0;
3751 for_opt(c, unmount_options) {
3753 case IMAGEX_COMMIT_OPTION:
3754 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3756 case IMAGEX_CHECK_OPTION:
3757 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3759 case IMAGEX_REBUILD_OPTION:
3760 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3762 case IMAGEX_LAZY_OPTION:
3763 case IMAGEX_FORCE_OPTION:
3764 /* Now, unmount is lazy by default. However, committing
3765 * the image will fail with
3766 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3767 * file descriptors on the WIM image. The
3768 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3769 * descriptors to be closed. */
3770 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3772 case IMAGEX_NEW_IMAGE_OPTION:
3773 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3784 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3785 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3786 imagex_error(T("--new-image is meaningless "
3787 "without --commit also specified!"));
3792 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3793 imagex_progress_func, NULL);
3795 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3796 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3798 "\tNote: Use --commit --force to force changes "
3799 "to be committed, regardless\n"
3800 "\t of open files.\n"));
3807 usage(CMD_UNMOUNT, stderr);
3812 #endif /* WIM_MOUNTING_SUPPORTED */
3815 * Add, delete, or rename files in a WIM image.
3818 imagex_update(int argc, tchar **argv, int cmd)
3820 const tchar *wimfile;
3824 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3825 int write_flags = 0;
3826 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3827 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3828 WIMLIB_ADD_FLAG_VERBOSE |
3829 WIMLIB_ADD_FLAG_WINCONFIG;
3830 int default_delete_flags = 0;
3831 unsigned num_threads = 0;
3833 tchar *cmd_file_contents;
3834 size_t cmd_file_nchars;
3835 struct wimlib_update_command *cmds;
3837 tchar *command_str = NULL;
3838 tchar *config_file = NULL;
3839 tchar *wimboot_config = NULL;
3841 for_opt(c, update_options) {
3843 /* Generic or write options */
3844 case IMAGEX_THREADS_OPTION:
3845 num_threads = parse_num_threads(optarg);
3846 if (num_threads == UINT_MAX)
3849 case IMAGEX_CHECK_OPTION:
3850 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3851 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3853 case IMAGEX_REBUILD_OPTION:
3854 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3856 case IMAGEX_COMMAND_OPTION:
3858 imagex_error(T("--command may only be specified "
3859 "one time. Please provide\n"
3860 " the update commands "
3861 "on standard input instead."));
3864 command_str = tstrdup(optarg);
3866 imagex_error(T("Out of memory!"));
3870 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3871 wimboot_config = optarg;
3873 /* Default delete options */
3874 case IMAGEX_FORCE_OPTION:
3875 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3877 case IMAGEX_RECURSIVE_OPTION:
3878 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3881 /* Global add option */
3882 case IMAGEX_CONFIG_OPTION:
3883 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3884 config_file = optarg;
3887 /* Default add options */
3888 case IMAGEX_VERBOSE_OPTION:
3889 /* No longer does anything. */
3891 case IMAGEX_DEREFERENCE_OPTION:
3892 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3894 case IMAGEX_UNIX_DATA_OPTION:
3895 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3897 case IMAGEX_NO_ACLS_OPTION:
3898 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3900 case IMAGEX_STRICT_ACLS_OPTION:
3901 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3903 case IMAGEX_NO_REPLACE_OPTION:
3904 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3913 if (argc != 1 && argc != 2)
3917 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3918 imagex_progress_func, NULL);
3920 goto out_free_command_str;
3923 /* Image explicitly specified. */
3924 image = wimlib_resolve_image(wim, argv[1]);
3925 ret = verify_image_exists_and_is_single(image, argv[1],
3928 goto out_wimlib_free;
3930 /* No image specified; default to image 1, but only if the WIM
3931 * contains exactly one image. */
3932 struct wimlib_wim_info info;
3934 wimlib_get_wim_info(wim, &info);
3935 if (info.image_count != 1) {
3936 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3937 wimfile, info.image_count);
3944 /* Read update commands from standard input, or the command string if
3947 cmd_file_contents = NULL;
3948 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3952 goto out_free_cmd_file_contents;
3954 } else if (!wimboot_config) {
3955 if (isatty(STDIN_FILENO)) {
3956 tputs(T("Reading update commands from standard input..."));
3957 recommend_man_page(CMD_UPDATE, stdout);
3959 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3960 if (!cmd_file_contents) {
3962 goto out_wimlib_free;
3965 /* Parse the update commands */
3966 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3970 goto out_free_cmd_file_contents;
3973 cmd_file_contents = NULL;
3978 /* Set default flags and capture config on the update commands */
3979 for (size_t i = 0; i < num_cmds; i++) {
3980 switch (cmds[i].op) {
3981 case WIMLIB_UPDATE_OP_ADD:
3982 cmds[i].add.add_flags |= default_add_flags;
3983 cmds[i].add.config_file = config_file;
3985 case WIMLIB_UPDATE_OP_DELETE:
3986 cmds[i].delete_.delete_flags |= default_delete_flags;
3993 /* Execute the update commands */
3994 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3998 if (wimboot_config) {
3999 /* --wimboot-config=FILE is short for an
4000 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4002 struct wimlib_update_command cmd;
4004 cmd.op = WIMLIB_UPDATE_OP_ADD;
4005 cmd.add.fs_source_path = wimboot_config;
4006 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4007 cmd.add.config_file = NULL;
4008 cmd.add.add_flags = 0;
4010 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4015 /* Overwrite the updated WIM */
4016 ret = wimlib_overwrite(wim, write_flags, num_threads);
4019 out_free_cmd_file_contents:
4020 free(cmd_file_contents);
4023 out_free_command_str:
4028 usage(CMD_UPDATE, stderr);
4031 goto out_free_command_str;
4034 /* Verify a WIM file. */
4036 imagex_verify(int argc, tchar **argv, int cmd)
4039 const tchar *wimfile;
4041 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4042 int verify_flags = 0;
4043 STRING_SET(refglobs);
4046 for_opt(c, verify_options) {
4048 case IMAGEX_REF_OPTION:
4049 ret = string_set_append(&refglobs, optarg);
4051 goto out_free_refglobs;
4053 case IMAGEX_NOCHECK_OPTION:
4054 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4066 imagex_error(T("Must specify a WIM file!"));
4068 imagex_error(T("At most one WIM file can be specified!"));
4074 ret = wimlib_open_wim_with_progress(wimfile,
4077 imagex_progress_func,
4080 goto out_free_refglobs;
4082 ret = wim_reference_globs(wim, &refglobs, open_flags);
4084 goto out_wimlib_free;
4086 ret = wimlib_verify_wim(wim, verify_flags);
4088 tputc(T('\n'), stderr);
4089 imagex_error(T("\"%"TS"\" failed verification!"),
4091 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4092 refglobs.num_strings == 0)
4094 imagex_printf(T("Note: if this WIM file is not standalone, "
4095 "use the --ref option to specify the other parts.\n"));
4098 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4105 string_set_destroy(&refglobs);
4109 usage(CMD_VERIFY, stderr);
4111 goto out_free_refglobs;
4114 struct imagex_command {
4116 int (*func)(int argc, tchar **argv, int cmd);
4119 static const struct imagex_command imagex_commands[] = {
4120 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4121 [CMD_APPLY] = {T("apply"), imagex_apply},
4122 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4123 [CMD_DELETE] = {T("delete"), imagex_delete},
4124 [CMD_DIR ] = {T("dir"), imagex_dir},
4125 [CMD_EXPORT] = {T("export"), imagex_export},
4126 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4127 [CMD_INFO] = {T("info"), imagex_info},
4128 [CMD_JOIN] = {T("join"), imagex_join},
4129 #if WIM_MOUNTING_SUPPORTED
4130 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4131 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4133 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4134 [CMD_SPLIT] = {T("split"), imagex_split},
4135 #if WIM_MOUNTING_SUPPORTED
4136 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4138 [CMD_UPDATE] = {T("update"), imagex_update},
4139 [CMD_VERIFY] = {T("verify"), imagex_verify},
4144 /* Can be a directory or source list file. But source list file is probably
4145 * a rare use case, so just say directory. */
4146 # define SOURCE_STR T("DIRECTORY")
4148 /* Can only be a directory */
4149 # define TARGET_STR T("DIRECTORY")
4152 /* Can be a directory, NTFS volume, or source list file. */
4153 # define SOURCE_STR T("SOURCE")
4155 /* Can be a directory or NTFS volume. */
4156 # define TARGET_STR T("TARGET")
4160 static const tchar *usage_strings[] = {
4163 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4164 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4165 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4166 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4167 " [--wimboot] [--unix-data] [--dereference]\n"
4171 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4172 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4173 " [--no-attributes] [--rpfix] [--norpfix]\n"
4174 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4178 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4179 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4180 " [--config=FILE] [--threads=NUM_THREADS]\n"
4181 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4182 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4183 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4187 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4191 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4195 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4196 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4197 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4198 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4203 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4204 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4205 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4206 " [--no-attributes] [--include-invalid-names]\n"
4207 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4211 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4212 " [--boot] [--check] [--nocheck] [--xml]\n"
4213 " [--extract-xml FILE] [--header] [--lookup-table]\n"
4217 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4219 #if WIM_MOUNTING_SUPPORTED
4222 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4223 " [--check] [--streams-interface=INTERFACE]\n"
4224 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4228 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4229 " [--check] [--streams-interface=INTERFACE]\n"
4230 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4236 " [--recompress] [--compress=TYPE]\n"
4237 " [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4242 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4244 #if WIM_MOUNTING_SUPPORTED
4247 " %"TS" DIRECTORY\n"
4248 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4253 " %"TS" WIMFILE [IMAGE]\n"
4254 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4255 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4256 " [--command=STRING] [--wimboot-config=FILE]\n"
4261 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4265 static const tchar *invocation_name;
4266 static int invocation_cmd = CMD_NONE;
4268 static const tchar *get_cmd_string(int cmd, bool nospace)
4270 static tchar buf[50];
4271 if (cmd == CMD_NONE) {
4272 return T("wimlib-imagex");
4273 } else if (invocation_cmd != CMD_NONE) {
4274 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4276 const tchar *format;
4279 format = T("%"TS"-%"TS"");
4281 format = T("%"TS" %"TS"");
4282 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4290 static const tchar *s =
4292 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4293 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4294 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4295 "This is free software: you are free to change and redistribute it.\n"
4296 "There is NO WARRANTY, to the extent permitted by law.\n"
4298 "Report bugs to "PACKAGE_BUGREPORT".\n"
4305 help_or_version(int argc, tchar **argv, int cmd)
4310 for (i = 1; i < argc; i++) {
4312 if (p[0] == T('-') && p[1] == T('-')) {
4314 if (!tstrcmp(p, T("help"))) {
4315 if (cmd == CMD_NONE)
4320 } else if (!tstrcmp(p, T("version"))) {
4329 print_usage_string(int cmd, FILE *fp)
4331 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4335 recommend_man_page(int cmd, FILE *fp)
4337 const tchar *format_str;
4339 format_str = T("Some uncommon options are not listed;\n"
4340 "See %"TS".pdf in the doc directory for more details.\n");
4342 format_str = T("Some uncommon options are not listed;\n"
4343 "Try `man %"TS"' for more details.\n");
4345 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4349 usage(int cmd, FILE *fp)
4351 tfprintf(fp, T("Usage:\n"));
4352 print_usage_string(cmd, fp);
4353 tfprintf(fp, T("\n"));
4354 recommend_man_page(cmd, fp);
4360 tfprintf(fp, T("Usage:\n"));
4361 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4362 print_usage_string(cmd, fp);
4363 tfprintf(fp, T("\n"));
4365 static const tchar *extra =
4368 " %"TS" --version\n"
4371 tfprintf(fp, extra, invocation_name, invocation_name);
4373 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4374 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4375 "For some commands IMAGE may be \"all\".\n"
4377 recommend_man_page(CMD_NONE, fp);
4380 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4381 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4382 * something else), while on Windows the command arguments will be UTF-16LE
4383 * encoded 'wchar_t' strings. */
4386 wmain(int argc, wchar_t **argv, wchar_t **envp)
4388 main(int argc, char **argv)
4395 imagex_info_file = stdout;
4396 invocation_name = tbasename(argv[0]);
4399 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4400 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4404 setlocale(LC_ALL, "");
4405 codeset = nl_langinfo(CODESET);
4406 if (!strstr(codeset, "UTF-8") &&
4407 !strstr(codeset, "UTF8") &&
4408 !strstr(codeset, "utf-8") &&
4409 !strstr(codeset, "utf8"))
4412 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4413 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4414 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4415 " to any value to force wimlib to use UTF-8.\n",
4421 #endif /* !__WIN32__ */
4424 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4425 if (igcase != NULL) {
4426 if (!tstrcmp(igcase, T("no")) ||
4427 !tstrcmp(igcase, T("0")))
4428 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4429 else if (!tstrcmp(igcase, T("yes")) ||
4430 !tstrcmp(igcase, T("1")))
4431 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4434 "WARNING: Ignoring unknown setting of "
4435 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4440 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4442 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4443 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4444 for (int i = 0; i < CMD_MAX; i++) {
4445 if (!tstrcmp(invocation_name + 3,
4446 imagex_commands[i].name))
4455 /* Unless already known from the invocation name, determine which
4456 * command was specified. */
4457 if (cmd == CMD_NONE) {
4459 imagex_error(T("No command specified!\n"));
4463 for (int i = 0; i < CMD_MAX; i++) {
4464 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4469 if (cmd != CMD_NONE) {
4475 /* Handle --help and --version. --help can be either for the program as
4476 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4477 * CMD_NONE). Note: help_or_version() will not return if a --help or
4478 * --version argument was found. */
4479 help_or_version(argc, argv, cmd);
4481 /* Bail if a valid command was not specified. */
4482 if (cmd == CMD_NONE) {
4483 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4488 /* Enable warning and error messages in wimlib to be more user-friendly.
4490 wimlib_set_print_errors(true);
4492 /* Initialize wimlib. */
4493 ret = wimlib_global_init(init_flags);
4495 goto out_check_status;
4497 /* Call the command handler function. */
4498 ret = imagex_commands[cmd].func(argc, argv, cmd);
4500 /* Check for error writing to standard output, especially since for some
4501 * commands, writing to standard output is part of the program's actual
4502 * behavior and not just for informational purposes. */
4503 if (ferror(stdout) || fclose(stdout)) {
4504 imagex_error_with_errno(T("error writing to standard output"));
4509 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4510 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4511 * error code from which an error message can be printed. */
4513 imagex_error(T("Exiting with error code %d:\n"
4515 wimlib_get_error_string(ret));
4516 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4517 imagex_error_with_errno(T("errno"));
4519 /* Make wimlib free any resources it's holding (although this is not
4520 * strictly necessary because the process is ending anyway). */
4521 wimlib_global_cleanup();