4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014 Eric Biggers
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # include "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
146 IMAGEX_CHUNK_SIZE_OPTION,
147 IMAGEX_COMMAND_OPTION,
148 IMAGEX_COMMIT_OPTION,
149 IMAGEX_COMPRESS_OPTION,
150 IMAGEX_COMPRESS_SLOW_OPTION,
151 IMAGEX_CONFIG_OPTION,
153 IMAGEX_DELTA_FROM_OPTION,
154 IMAGEX_DEREFERENCE_OPTION,
155 IMAGEX_DEST_DIR_OPTION,
156 IMAGEX_DETAILED_OPTION,
157 IMAGEX_EXTRACT_XML_OPTION,
160 IMAGEX_HEADER_OPTION,
161 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
163 IMAGEX_LOOKUP_TABLE_OPTION,
164 IMAGEX_METADATA_OPTION,
165 IMAGEX_NEW_IMAGE_OPTION,
166 IMAGEX_NOCHECK_OPTION,
167 IMAGEX_NORPFIX_OPTION,
168 IMAGEX_NOT_PIPABLE_OPTION,
169 IMAGEX_NO_ACLS_OPTION,
170 IMAGEX_NO_ATTRIBUTES_OPTION,
171 IMAGEX_NO_REPLACE_OPTION,
172 IMAGEX_NO_GLOBS_OPTION,
173 IMAGEX_NULLGLOB_OPTION,
174 IMAGEX_ONE_FILE_ONLY_OPTION,
176 IMAGEX_PIPABLE_OPTION,
177 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
178 IMAGEX_REBUILD_OPTION,
179 IMAGEX_RECOMPRESS_OPTION,
180 IMAGEX_RECURSIVE_OPTION,
182 IMAGEX_RESUME_OPTION,
186 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
187 IMAGEX_SOLID_COMPRESS_OPTION,
188 IMAGEX_SOURCE_LIST_OPTION,
189 IMAGEX_STAGING_DIR_OPTION,
190 IMAGEX_STREAMS_INTERFACE_OPTION,
191 IMAGEX_STRICT_ACLS_OPTION,
192 IMAGEX_THREADS_OPTION,
193 IMAGEX_TO_STDOUT_OPTION,
194 IMAGEX_UNIX_DATA_OPTION,
195 IMAGEX_UPDATE_OF_OPTION,
196 IMAGEX_VERBOSE_OPTION,
197 IMAGEX_WIMBOOT_OPTION,
198 IMAGEX_WIMBOOT_CONFIG_OPTION,
202 static const struct option apply_options[] = {
203 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
204 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
205 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
206 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
207 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
208 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
209 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
210 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
211 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
212 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
213 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
215 /* --resume is undocumented for now as it needs improvement. */
216 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
217 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
221 static const struct option capture_or_append_options[] = {
222 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
223 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
224 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
225 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
226 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
227 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
228 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
229 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
230 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
231 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
232 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
233 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
234 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
235 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
236 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
237 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
238 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
239 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
240 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
241 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
242 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
243 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
244 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
245 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
246 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
247 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
248 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
249 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
250 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
251 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
252 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
256 static const struct option delete_options[] = {
257 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
258 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
262 static const struct option dir_options[] = {
263 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
264 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
265 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
269 static const struct option export_options[] = {
270 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
271 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
272 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
273 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
274 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
275 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
276 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
277 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
278 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
279 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
280 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
281 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
282 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
283 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
284 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
285 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
286 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
287 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
288 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
289 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
293 static const struct option extract_options[] = {
294 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
295 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
296 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
297 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
298 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
299 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
300 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
301 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
302 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
303 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
304 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
305 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
306 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
307 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
308 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
309 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
313 static const struct option info_options[] = {
314 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
315 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
316 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
317 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
318 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
319 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
320 {T("lookup-table"), no_argument, NULL, IMAGEX_LOOKUP_TABLE_OPTION},
321 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
322 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
326 static const struct option join_options[] = {
327 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
331 static const struct option mount_options[] = {
332 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
333 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
334 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
335 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
336 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
337 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
338 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
342 static const struct option optimize_options[] = {
343 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
344 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
345 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
346 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
347 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
348 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
349 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
350 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
351 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
352 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
353 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
354 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
355 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
356 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
357 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
358 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
359 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
363 static const struct option split_options[] = {
364 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
368 static const struct option unmount_options[] = {
369 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
370 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
371 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
372 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
373 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
374 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
378 static const struct option update_options[] = {
379 /* Careful: some of the options here set the defaults for update
380 * commands, but the flags given to an actual update command (and not to
381 * `imagex update' itself are also handled in
382 * update_command_add_option(). */
383 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
384 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
385 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
386 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
387 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
389 /* Default delete options */
390 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
391 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
393 /* Global add option */
394 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
396 /* Default add options */
397 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
398 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
399 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
400 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
401 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
402 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
403 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
408 static const struct option verify_options[] = {
409 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
410 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
416 # define _format_attribute(type, format_str, args_start) \
417 __attribute__((format(type, format_str, args_start)))
419 # define _format_attribute(type, format_str, args_start)
422 /* Print formatted error message to stderr. */
423 static void _format_attribute(printf, 1, 2)
424 imagex_error(const tchar *format, ...)
427 va_start(va, format);
428 tfputs(T("ERROR: "), stderr);
429 tvfprintf(stderr, format, va);
430 tputc(T('\n'), stderr);
434 /* Print formatted error message to stderr. */
435 static void _format_attribute(printf, 1, 2)
436 imagex_error_with_errno(const tchar *format, ...)
438 int errno_save = errno;
440 va_start(va, format);
441 tfputs(T("ERROR: "), stderr);
442 tvfprintf(stderr, format, va);
443 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
448 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
450 if (image == WIMLIB_NO_IMAGE) {
451 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
452 " Please specify a 1-based image index or "
453 "image name. To list the images\n"
454 " contained in the WIM archive, run\n"
456 " %"TS" \"%"TS"\"\n"),
457 image_name, wim_name,
458 get_cmd_string(CMD_INFO, false), wim_name);
459 return WIMLIB_ERR_INVALID_IMAGE;
465 verify_image_is_single(int image)
467 if (image == WIMLIB_ALL_IMAGES) {
468 imagex_error(T("Cannot specify all images for this action!"));
469 return WIMLIB_ERR_INVALID_IMAGE;
475 verify_image_exists_and_is_single(int image, const tchar *image_name,
476 const tchar *wim_name)
479 ret = verify_image_exists(image, image_name, wim_name);
481 ret = verify_image_is_single(image);
486 print_available_compression_types(FILE *fp)
488 static const tchar *s =
490 "Available compression types:\n"
493 " xpress (alias: \"fast\")\n"
494 " lzx (alias: \"maximum\") (default for capture)\n"
495 " lzms (alias: \"recovery\")\n"
501 /* Parse the argument to --compress */
503 get_compression_type(tchar *optarg)
506 unsigned int compression_level = 0;
509 plevel = tstrchr(optarg, T(':'));
515 ultmp = tstrtoul(plevel, &ptmp, 10);
516 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
517 imagex_error(T("Compression level must be a positive integer! "
518 "e.g. --compress=lzx:80"));
519 return WIMLIB_COMPRESSION_TYPE_INVALID;
521 compression_level = ultmp;
524 if (!tstrcasecmp(optarg, T("maximum")) ||
525 !tstrcasecmp(optarg, T("lzx")) ||
526 !tstrcasecmp(optarg, T("max")))
527 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
528 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
529 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
530 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
531 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
532 else if (!tstrcasecmp(optarg, T("none")))
533 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
535 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
536 print_available_compression_types(stderr);
537 return WIMLIB_COMPRESSION_TYPE_INVALID;
540 if (compression_level != 0)
541 wimlib_set_default_compression_level(ctype, compression_level);
546 set_compress_slow(void)
549 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
550 " Use the '--compress=TYPE:LEVEL' option instead.\n");
552 wimlib_set_default_compression_level(-1, 100);
556 const tchar **strings;
557 unsigned num_strings;
558 unsigned num_alloc_strings;
561 #define STRING_SET_INITIALIZER \
562 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
564 #define STRING_SET(_strings) \
565 struct string_set _strings = STRING_SET_INITIALIZER
568 string_set_append(struct string_set *set, const tchar *glob)
570 unsigned num_alloc_strings = set->num_alloc_strings;
572 if (set->num_strings == num_alloc_strings) {
573 const tchar **new_strings;
575 num_alloc_strings += 4;
576 new_strings = realloc(set->strings,
577 sizeof(set->strings[0]) * num_alloc_strings);
579 imagex_error(T("Out of memory!"));
582 set->strings = new_strings;
583 set->num_alloc_strings = num_alloc_strings;
585 set->strings[set->num_strings++] = glob;
590 string_set_destroy(struct string_set *set)
596 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
598 return wimlib_reference_resource_files(wim, set->strings,
600 WIMLIB_REF_FLAG_GLOB_ENABLE,
605 do_resource_not_found_warning(const tchar *wimfile,
606 const struct wimlib_wim_info *info,
607 const struct string_set *refglobs)
609 if (info->total_parts > 1) {
610 if (refglobs->num_strings == 0) {
611 imagex_error(T("\"%"TS"\" is part of a split WIM. "
612 "Use --ref to specify the other parts."),
615 imagex_error(T("Perhaps the '--ref' argument did not "
616 "specify all other parts of the split "
620 imagex_error(T("If this is a delta WIM, use the --ref argument "
621 "to specify the WIM(s) on which it is based."));
625 /* Returns the size of a file given its name, or -1 if the file does not exist
626 * or its size cannot be determined. */
628 file_get_size(const tchar *filename)
631 if (tstat(filename, &st) == 0)
638 PARSE_STRING_SUCCESS = 0,
639 PARSE_STRING_FAILURE = 1,
640 PARSE_STRING_NONE = 2,
644 * Parses a string token from an array of characters.
646 * Tokens are either whitespace-delimited, or double or single-quoted.
648 * @line_p: Pointer to the pointer to the line of data. Will be updated
649 * to point past the string token iff the return value is
650 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
653 * @len_p: @len_p initially stores the length of the line of data, which may
654 * be 0, and it will be updated to the number of bytes remaining in
655 * the line iff the return value is PARSE_STRING_SUCCESS.
657 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
658 * parsed string token will be returned here.
660 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
661 * PARSE_STRING_FAILURE if the data was invalid due to a missing
662 * closing quote; or PARSE_STRING_NONE if the line ended before the
663 * beginning of a string token was found.
666 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
669 tchar *line = *line_p;
673 /* Skip leading whitespace */
676 return PARSE_STRING_NONE;
677 if (!istspace(*line) && *line != T('\0'))
683 if (quote_char == T('"') || quote_char == T('\'')) {
688 line = tmemchr(line, quote_char, len);
690 imagex_error(T("Missing closing quote: %"TS), fn - 1);
691 return PARSE_STRING_FAILURE;
694 /* Unquoted string. Go until whitespace. Line is terminated
695 * by '\0', so no need to check 'len'. */
699 } while (!istspace(*line) && *line != T('\0'));
706 return PARSE_STRING_SUCCESS;
709 /* Parses a line of data (not an empty line or comment) in the source list file
710 * format. (See the man page for 'wimlib-imagex capture' for details on this
711 * format and the meaning.)
713 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
714 * len == 0. The data in @line will be modified by this function call.
716 * @len: Length of the line of data.
718 * @source: On success, the capture source and target described by the line is
719 * written into this destination. Note that it will contain pointers
720 * to data in the @line array.
722 * Returns true if the line was valid; false otherwise. */
724 parse_source_list_line(tchar *line, size_t len,
725 struct wimlib_capture_source *source)
729 ret = parse_string(&line, &len, &source->fs_source_path);
730 if (ret != PARSE_STRING_SUCCESS)
732 ret = parse_string(&line, &len, &source->wim_target_path);
733 if (ret == PARSE_STRING_NONE)
734 source->wim_target_path = source->fs_source_path;
735 return ret != PARSE_STRING_FAILURE;
738 /* Returns %true if the given line of length @len > 0 is a comment or empty line
739 * in the source list file format. */
741 is_comment_line(const tchar *line, size_t len)
744 if (*line == T('#') || *line == T(';'))
746 if (!istspace(*line) && *line != T('\0'))
756 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
759 tchar *contents = *contents_p;
760 size_t nchars = *nchars_p;
763 for (i = 0; i < nchars; i++)
764 if (contents[i] == T('\n'))
767 /* Handle last line not terminated by a newline */
768 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
769 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
771 imagex_error(T("Out of memory!"));
774 contents[nchars] = T('\n');
775 *contents_p = contents;
783 /* Parses a file in the source list format. (See the man page for
784 * 'wimlib-imagex capture' for details on this format and the meaning.)
786 * @source_list_contents: Contents of the source list file. Note that this
787 * buffer will be modified to save memory allocations,
788 * and cannot be freed until the returned array of
789 * wimlib_capture_source's has also been freed.
791 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
794 * @nsources_ret: On success, the length of the returned array is
797 * Returns: An array of `struct wimlib_capture_source's that can be passed to
798 * the wimlib_add_image_multisource() function to specify how a WIM image is to
800 static struct wimlib_capture_source *
801 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
802 size_t *nsources_ret)
806 struct wimlib_capture_source *sources;
809 nlines = text_file_count_lines(source_list_contents_p,
810 &source_list_nchars);
814 /* Always allocate at least 1 slot, just in case the implementation of
815 * calloc() returns NULL if 0 bytes are requested. */
816 sources = calloc(nlines ?: 1, sizeof(*sources));
818 imagex_error(T("out of memory"));
821 p = *source_list_contents_p;
823 for (i = 0; i < nlines; i++) {
824 /* XXX: Could use rawmemchr() here instead, but it may not be
825 * available on all platforms. */
826 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
827 size_t len = endp - p + 1;
829 if (!is_comment_line(p, len)) {
830 if (!parse_source_list_line(p, len, &sources[j++])) {
842 /* Reads the contents of a file into memory. */
844 file_get_contents(const tchar *filename, size_t *len_ret)
851 if (tstat(filename, &stbuf) != 0) {
852 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
857 fp = tfopen(filename, T("rb"));
859 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
863 buf = malloc(len ? len : 1);
865 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
866 "contents of file \"%"TS"\""), len, filename);
869 if (fread(buf, 1, len, fp) != len) {
870 imagex_error_with_errno(T("Failed to read %zu bytes from the "
871 "file \"%"TS"\""), len, filename);
885 /* Read standard input until EOF and return the full contents in a malloc()ed
886 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
889 stdin_get_contents(size_t *len_ret)
891 /* stdin can, of course, be a pipe or other non-seekable file, so the
892 * total length of the data cannot be pre-determined */
894 size_t newlen = 1024;
898 char *p = realloc(buf, newlen);
899 size_t bytes_read, bytes_to_read;
901 imagex_error(T("out of memory while reading stdin"));
905 bytes_to_read = newlen - pos;
906 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
908 if (bytes_read != bytes_to_read) {
913 imagex_error_with_errno(T("error reading stdin"));
927 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
930 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
932 *num_tchars_ret = num_bytes;
934 #else /* !__WIN32__ */
935 /* On Windows, translate the text to UTF-16LE */
939 if (num_bytes >= 2 &&
940 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
941 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
943 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
944 * with something that looks like an ASCII character encoded as
945 * a UTF-16LE code unit. Assume the file is encoded as
946 * UTF-16LE. This is not a 100% reliable check. */
947 num_wchars = num_bytes / 2;
948 text_wstr = (wchar_t*)text;
950 /* File does not look like UTF-16LE. Assume it is encoded in
951 * the current Windows code page. I think these are always
952 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
953 * should work as expected. */
954 text_wstr = win32_mbs_to_wcs(text,
959 *num_tchars_ret = num_wchars;
961 #endif /* __WIN32__ */
965 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
970 contents = file_get_contents(filename, &num_bytes);
973 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
977 stdin_get_text_contents(size_t *num_tchars_ret)
982 contents = stdin_get_contents(&num_bytes);
985 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
988 #define TO_PERCENT(numerator, denominator) \
989 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
991 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
992 #define MEBIBYTE_MIN_NBYTES 10000000ULL
993 #define KIBIBYTE_MIN_NBYTES 10000ULL
996 get_unit(uint64_t total_bytes, const tchar **name_ret)
998 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
999 *name_ret = T("GiB");
1001 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1002 *name_ret = T("MiB");
1004 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1005 *name_ret = T("KiB");
1008 *name_ret = T("bytes");
1013 static struct wimlib_progress_info_scan last_scan_progress;
1016 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1018 uint64_t prev_count, cur_count;
1020 prev_count = last_scan_progress.num_nondirs_scanned +
1021 last_scan_progress.num_dirs_scanned;
1022 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1024 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1025 cur_count % 128 == 0)
1027 unsigned unit_shift;
1028 const tchar *unit_name;
1030 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1031 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1032 "%"PRIu64" directories) "),
1033 scan->num_bytes_scanned >> unit_shift,
1035 scan->num_nondirs_scanned,
1036 scan->num_dirs_scanned);
1037 last_scan_progress = *scan;
1040 /* Progress callback function passed to various wimlib functions. */
1041 static enum wimlib_progress_status
1042 imagex_progress_func(enum wimlib_progress_msg msg,
1043 union wimlib_progress_info *info,
1044 void *_ignored_context)
1046 unsigned percent_done;
1047 unsigned unit_shift;
1048 const tchar *unit_name;
1050 if (imagex_be_quiet)
1051 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1053 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1055 static bool first = true;
1057 imagex_printf(T("Writing %"TS"-compressed data "
1058 "using %u thread%"TS"\n"),
1059 wimlib_get_compression_type_string(
1060 info->write_streams.compression_type),
1061 info->write_streams.num_threads,
1062 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1066 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1067 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1068 info->write_streams.total_bytes);
1070 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1071 "written (%u%% done)"),
1072 info->write_streams.completed_bytes >> unit_shift,
1074 info->write_streams.total_bytes >> unit_shift,
1077 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1078 imagex_printf(T("\n"));
1080 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1081 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1082 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1083 imagex_printf(T("\n"));
1085 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1086 info->scan.wim_target_path);
1088 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1090 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1091 switch (info->scan.status) {
1092 case WIMLIB_SCAN_DENTRY_OK:
1093 report_scan_progress(&info->scan, false);
1095 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1096 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1098 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1099 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1100 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1102 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1103 /* Symlink fixups are enabled by default. This is
1104 * mainly intended for Windows, which for some reason
1105 * uses absolute junctions (with drive letters!) in the
1106 * default installation. On UNIX-like systems, warn the
1107 * user when fixing the target of an absolute symbolic
1108 * link, so they know to disable this if they want. */
1110 imagex_printf(T("\nWARNING: Adjusted target of "
1111 "absolute symbolic link \"%"TS"\"\n"
1112 " (Use --norpfix to capture "
1113 "absolute symbolic links as-is)\n"),
1114 info->scan.cur_path);
1121 case WIMLIB_PROGRESS_MSG_SCAN_END:
1122 report_scan_progress(&info->scan, true);
1123 imagex_printf(T("\n"));
1125 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1126 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1127 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1128 info->integrity.total_bytes);
1129 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1130 "of %"PRIu64" %"TS" (%u%%) done"),
1131 info->integrity.filename,
1132 info->integrity.completed_bytes >> unit_shift,
1134 info->integrity.total_bytes >> unit_shift,
1137 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1138 imagex_printf(T("\n"));
1140 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1141 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1142 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1143 info->integrity.total_bytes);
1144 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1145 "of %"PRIu64" %"TS" (%u%%) done"),
1146 info->integrity.completed_bytes >> unit_shift,
1148 info->integrity.total_bytes >> unit_shift,
1151 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1152 imagex_printf(T("\n"));
1154 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1155 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1156 "to %"TS" \"%"TS"\"\n"),
1157 info->extract.image,
1158 info->extract.image_name,
1159 info->extract.wimfile_name,
1160 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1161 T("NTFS volume") : T("directory")),
1162 info->extract.target);
1164 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1165 if (info->extract.end_file_count >= 2000) {
1166 percent_done = TO_PERCENT(info->extract.current_file_count,
1167 info->extract.end_file_count);
1168 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1169 info->extract.current_file_count,
1170 info->extract.end_file_count, percent_done);
1171 if (info->extract.current_file_count == info->extract.end_file_count)
1172 imagex_printf(T("\n"));
1175 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1176 percent_done = TO_PERCENT(info->extract.completed_bytes,
1177 info->extract.total_bytes);
1178 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1179 imagex_printf(T("\rExtracting files: "
1180 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1181 info->extract.completed_bytes >> unit_shift,
1183 info->extract.total_bytes >> unit_shift,
1186 if (info->extract.completed_bytes >= info->extract.total_bytes)
1187 imagex_printf(T("\n"));
1189 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1190 if (info->extract.end_file_count >= 2000) {
1191 percent_done = TO_PERCENT(info->extract.current_file_count,
1192 info->extract.end_file_count);
1193 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1194 info->extract.current_file_count,
1195 info->extract.end_file_count, percent_done);
1196 if (info->extract.current_file_count == info->extract.end_file_count)
1197 imagex_printf(T("\n"));
1200 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1201 if (info->extract.total_parts != 1) {
1202 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1203 info->extract.part_number,
1204 info->extract.total_parts);
1207 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1208 percent_done = TO_PERCENT(info->split.completed_bytes,
1209 info->split.total_bytes);
1210 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1211 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1212 "%"PRIu64" %"TS" (%u%%) written\n"),
1213 info->split.part_name,
1214 info->split.cur_part_number,
1215 info->split.total_parts,
1216 info->split.completed_bytes >> unit_shift,
1218 info->split.total_bytes >> unit_shift,
1222 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1223 if (info->split.completed_bytes == info->split.total_bytes) {
1224 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1225 info->split.cur_part_number,
1226 info->split.total_parts);
1229 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1230 switch (info->update.command->op) {
1231 case WIMLIB_UPDATE_OP_DELETE:
1232 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1233 info->update.command->delete_.wim_path);
1235 case WIMLIB_UPDATE_OP_RENAME:
1236 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1237 info->update.command->rename.wim_source_path,
1238 info->update.command->rename.wim_target_path);
1240 case WIMLIB_UPDATE_OP_ADD:
1245 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1246 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1247 info->replace.path_in_wim);
1249 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1250 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1251 info->wimboot_exclude.path_in_wim);
1253 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1254 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1255 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1256 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1257 info->unmount.mounted_wim,
1258 info->unmount.mounted_image);
1260 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1261 info->unmount.mounted_wim,
1262 info->unmount.mounted_image);
1263 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1267 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1268 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1269 info->verify_image.current_image,
1270 info->verify_image.total_images);
1272 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1273 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1274 info->verify_streams.total_bytes);
1275 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1276 imagex_printf(T("\rVerifying streams: "
1277 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1278 info->verify_streams.completed_bytes >> unit_shift,
1280 info->verify_streams.total_bytes >> unit_shift,
1283 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1284 imagex_printf(T("\n"));
1289 fflush(imagex_info_file);
1290 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1294 parse_num_threads(const tchar *optarg)
1297 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1298 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1299 imagex_error(T("Number of threads must be a non-negative integer!"));
1306 static uint32_t parse_chunk_size(const tchar *optarg)
1309 unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
1310 if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
1311 imagex_error(T("Chunk size must be a non-negative integer!"));
1320 * Parse an option passed to an update command.
1322 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1325 * @option: Text string for the option (beginning with --)
1327 * @cmd: `struct wimlib_update_command' that is being constructed for
1330 * Returns true if the option was recognized; false if not.
1333 update_command_add_option(int op, const tchar *option,
1334 struct wimlib_update_command *cmd)
1336 bool recognized = true;
1338 case WIMLIB_UPDATE_OP_ADD:
1339 if (!tstrcmp(option, T("--verbose")))
1340 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1341 else if (!tstrcmp(option, T("--unix-data")))
1342 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1343 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1344 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1345 else if (!tstrcmp(option, T("--strict-acls")))
1346 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1347 else if (!tstrcmp(option, T("--dereference")))
1348 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1349 else if (!tstrcmp(option, T("--no-replace")))
1350 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1354 case WIMLIB_UPDATE_OP_DELETE:
1355 if (!tstrcmp(option, T("--force")))
1356 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1357 else if (!tstrcmp(option, T("--recursive")))
1358 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1369 /* How many nonoption arguments each `imagex update' command expects */
1370 static const unsigned update_command_num_nonoptions[] = {
1371 [WIMLIB_UPDATE_OP_ADD] = 2,
1372 [WIMLIB_UPDATE_OP_DELETE] = 1,
1373 [WIMLIB_UPDATE_OP_RENAME] = 2,
1377 update_command_add_nonoption(int op, const tchar *nonoption,
1378 struct wimlib_update_command *cmd,
1379 unsigned num_nonoptions)
1382 case WIMLIB_UPDATE_OP_ADD:
1383 if (num_nonoptions == 0)
1384 cmd->add.fs_source_path = (tchar*)nonoption;
1386 cmd->add.wim_target_path = (tchar*)nonoption;
1388 case WIMLIB_UPDATE_OP_DELETE:
1389 cmd->delete_.wim_path = (tchar*)nonoption;
1391 case WIMLIB_UPDATE_OP_RENAME:
1392 if (num_nonoptions == 0)
1393 cmd->rename.wim_source_path = (tchar*)nonoption;
1395 cmd->rename.wim_target_path = (tchar*)nonoption;
1401 * Parse a command passed on stdin to `imagex update'.
1403 * @line: Text of the command.
1404 * @len: Length of the line, including a null terminator
1407 * @command: A `struct wimlib_update_command' to fill in from the parsed
1410 * @line_number: Line number of the command, for diagnostics.
1412 * Returns true on success; returns false on parse error.
1415 parse_update_command(tchar *line, size_t len,
1416 struct wimlib_update_command *command,
1420 tchar *command_name;
1422 size_t num_nonoptions;
1424 /* Get the command name ("add", "delete", "rename") */
1425 ret = parse_string(&line, &len, &command_name);
1426 if (ret != PARSE_STRING_SUCCESS)
1429 if (!tstrcasecmp(command_name, T("add"))) {
1430 op = WIMLIB_UPDATE_OP_ADD;
1431 } else if (!tstrcasecmp(command_name, T("delete"))) {
1432 op = WIMLIB_UPDATE_OP_DELETE;
1433 } else if (!tstrcasecmp(command_name, T("rename"))) {
1434 op = WIMLIB_UPDATE_OP_RENAME;
1436 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1437 command_name, line_number);
1442 /* Parse additional options and non-options as needed */
1447 ret = parse_string(&line, &len, &next_string);
1448 if (ret == PARSE_STRING_NONE) /* End of line */
1450 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1452 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1454 if (!update_command_add_option(op, next_string, command))
1456 imagex_error(T("Unrecognized option \"%"TS"\" to "
1457 "update command \"%"TS"\" on line %zu"),
1458 next_string, command_name, line_number);
1464 if (num_nonoptions == update_command_num_nonoptions[op])
1466 imagex_error(T("Unexpected argument \"%"TS"\" in "
1467 "update command on line %zu\n"
1468 " (The \"%"TS"\" command only "
1469 "takes %zu nonoption arguments!)\n"),
1470 next_string, line_number,
1471 command_name, num_nonoptions);
1474 update_command_add_nonoption(op, next_string,
1475 command, num_nonoptions);
1480 if (num_nonoptions != update_command_num_nonoptions[op]) {
1481 imagex_error(T("Not enough arguments to update command "
1482 "\"%"TS"\" on line %zu"), command_name, line_number);
1488 static struct wimlib_update_command *
1489 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1490 size_t *num_cmds_ret)
1494 struct wimlib_update_command *cmds;
1497 nlines = text_file_count_lines(cmd_file_contents_p,
1502 /* Always allocate at least 1 slot, just in case the implementation of
1503 * calloc() returns NULL if 0 bytes are requested. */
1504 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1506 imagex_error(T("out of memory"));
1509 p = *cmd_file_contents_p;
1511 for (i = 0; i < nlines; i++) {
1512 /* XXX: Could use rawmemchr() here instead, but it may not be
1513 * available on all platforms. */
1514 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1515 size_t len = endp - p + 1;
1517 if (!is_comment_line(p, len)) {
1518 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1529 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1530 * one image from a WIM file to an NTFS volume. */
1532 imagex_apply(int argc, tchar **argv, int cmd)
1536 int image = WIMLIB_NO_IMAGE;
1538 struct wimlib_wim_info info;
1540 const tchar *wimfile;
1541 const tchar *target;
1542 const tchar *image_num_or_name = NULL;
1543 int extract_flags = 0;
1545 STRING_SET(refglobs);
1547 for_opt(c, apply_options) {
1549 case IMAGEX_CHECK_OPTION:
1550 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1552 case IMAGEX_VERBOSE_OPTION:
1553 /* No longer does anything. */
1555 case IMAGEX_REF_OPTION:
1556 ret = string_set_append(&refglobs, optarg);
1558 goto out_free_refglobs;
1560 case IMAGEX_UNIX_DATA_OPTION:
1561 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1563 case IMAGEX_NO_ACLS_OPTION:
1564 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1566 case IMAGEX_STRICT_ACLS_OPTION:
1567 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1569 case IMAGEX_NO_ATTRIBUTES_OPTION:
1570 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1572 case IMAGEX_NORPFIX_OPTION:
1573 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1575 case IMAGEX_RPFIX_OPTION:
1576 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1578 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1579 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1580 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1582 case IMAGEX_RESUME_OPTION:
1583 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1585 case IMAGEX_WIMBOOT_OPTION:
1586 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1594 if (argc != 2 && argc != 3)
1599 if (!tstrcmp(wimfile, T("-"))) {
1600 /* Attempt to apply pipable WIM from standard input. */
1602 image_num_or_name = NULL;
1605 image_num_or_name = argv[1];
1610 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1611 imagex_progress_func, NULL);
1613 goto out_free_refglobs;
1615 wimlib_get_wim_info(wim, &info);
1618 /* Image explicitly specified. */
1619 image_num_or_name = argv[1];
1620 image = wimlib_resolve_image(wim, image_num_or_name);
1621 ret = verify_image_exists(image, image_num_or_name, wimfile);
1623 goto out_wimlib_free;
1626 /* No image specified; default to image 1, but only if the WIM
1627 * contains exactly one image. */
1629 if (info.image_count != 1) {
1630 imagex_error(T("\"%"TS"\" contains %d images; "
1631 "Please select one (or all)."),
1632 wimfile, info.image_count);
1641 if (refglobs.num_strings) {
1643 imagex_error(T("Can't specify --ref when applying from stdin!"));
1645 goto out_wimlib_free;
1647 ret = wim_reference_globs(wim, &refglobs, open_flags);
1649 goto out_wimlib_free;
1654 /* Interpret a regular file or block device target as an NTFS
1658 if (tstat(target, &stbuf)) {
1659 if (errno != ENOENT) {
1660 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1663 goto out_wimlib_free;
1666 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1667 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1673 ret = wimlib_extract_image(wim, image, target, extract_flags);
1675 set_fd_to_binary_mode(STDIN_FILENO);
1676 ret = wimlib_extract_image_from_pipe_with_progress(
1681 imagex_progress_func,
1685 imagex_printf(T("Done applying WIM image.\n"));
1686 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1688 do_resource_not_found_warning(wimfile, &info, &refglobs);
1690 imagex_error(T( "If you are applying an image "
1691 "from a split pipable WIM,\n"
1692 " make sure you have "
1693 "concatenated together all parts."));
1699 string_set_destroy(&refglobs);
1703 usage(CMD_APPLY, stderr);
1705 goto out_free_refglobs;
1708 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1709 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1710 * the desired image. 'wimlib-imagex append': add a new image to an existing
1713 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1717 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1718 WIMLIB_ADD_FLAG_WINCONFIG |
1719 WIMLIB_ADD_FLAG_VERBOSE;
1720 int write_flags = 0;
1721 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1722 uint32_t chunk_size = UINT32_MAX;
1723 uint32_t solid_chunk_size = UINT32_MAX;
1724 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1725 const tchar *wimfile;
1729 const tchar *flags_element = NULL;
1732 STRING_SET(base_wimfiles);
1733 WIMStruct **base_wims;
1735 WIMStruct *template_wim;
1736 const tchar *template_wimfile = NULL;
1737 const tchar *template_image_name_or_num = NULL;
1738 int template_image = WIMLIB_NO_IMAGE;
1741 unsigned num_threads = 0;
1746 tchar *config_file = NULL;
1748 bool source_list = false;
1749 size_t source_list_nchars = 0;
1750 tchar *source_list_contents;
1751 bool capture_sources_malloced;
1752 struct wimlib_capture_source *capture_sources;
1754 bool name_defaulted;
1756 for_opt(c, capture_or_append_options) {
1758 case IMAGEX_BOOT_OPTION:
1759 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1761 case IMAGEX_CHECK_OPTION:
1762 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1763 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1765 case IMAGEX_NOCHECK_OPTION:
1766 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1768 case IMAGEX_CONFIG_OPTION:
1769 config_file = optarg;
1770 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1772 case IMAGEX_COMPRESS_OPTION:
1773 compression_type = get_compression_type(optarg);
1774 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1777 case IMAGEX_COMPRESS_SLOW_OPTION:
1778 set_compress_slow();
1780 case IMAGEX_CHUNK_SIZE_OPTION:
1781 chunk_size = parse_chunk_size(optarg);
1782 if (chunk_size == UINT32_MAX)
1785 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1786 solid_chunk_size = parse_chunk_size(optarg);
1787 if (solid_chunk_size == UINT32_MAX)
1790 case IMAGEX_SOLID_COMPRESS_OPTION:
1791 solid_ctype = get_compression_type(optarg);
1792 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1795 case IMAGEX_SOLID_OPTION:
1796 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
1798 case IMAGEX_FLAGS_OPTION:
1799 flags_element = optarg;
1801 case IMAGEX_DEREFERENCE_OPTION:
1802 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1804 case IMAGEX_VERBOSE_OPTION:
1805 /* No longer does anything. */
1807 case IMAGEX_THREADS_OPTION:
1808 num_threads = parse_num_threads(optarg);
1809 if (num_threads == UINT_MAX)
1812 case IMAGEX_REBUILD_OPTION:
1813 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1815 case IMAGEX_UNIX_DATA_OPTION:
1816 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1818 case IMAGEX_SOURCE_LIST_OPTION:
1821 case IMAGEX_NO_ACLS_OPTION:
1822 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1824 case IMAGEX_STRICT_ACLS_OPTION:
1825 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1827 case IMAGEX_RPFIX_OPTION:
1828 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1830 case IMAGEX_NORPFIX_OPTION:
1831 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1833 case IMAGEX_PIPABLE_OPTION:
1834 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1836 case IMAGEX_NOT_PIPABLE_OPTION:
1837 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1839 case IMAGEX_UPDATE_OF_OPTION:
1840 if (template_image_name_or_num) {
1841 imagex_error(T("'--update-of' can only be "
1842 "specified one time!"));
1846 colon = tstrrchr(optarg, T(':'));
1849 template_wimfile = optarg;
1851 template_image_name_or_num = colon + 1;
1853 template_wimfile = NULL;
1854 template_image_name_or_num = optarg;
1858 case IMAGEX_DELTA_FROM_OPTION:
1859 if (cmd != CMD_CAPTURE) {
1860 imagex_error(T("'--delta-from' is only "
1861 "valid for capture!"));
1864 ret = string_set_append(&base_wimfiles, optarg);
1866 goto out_free_base_wimfiles;
1867 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1869 case IMAGEX_WIMBOOT_OPTION:
1870 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1879 if (argc < 2 || argc > 4)
1885 /* Set default compression type and parameters. */
1888 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1889 /* No compression type specified. Use the default. */
1891 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1892 /* With --wimboot, default to XPRESS compression. */
1893 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1894 } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
1895 /* With --solid, default to LZMS compression. (However,
1896 * this will not affect solid blocks!) */
1897 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1899 /* Otherwise, default to LZX compression. */
1900 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1904 if (!tstrcmp(wimfile, T("-"))) {
1905 /* Writing captured WIM to standard output. */
1907 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1908 imagex_error("Can't write a non-pipable WIM to "
1909 "standard output! Specify --pipable\n"
1910 " if you want to create a pipable WIM "
1911 "(but read the docs first).");
1915 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1917 if (cmd == CMD_APPEND) {
1918 imagex_error(T("Using standard output for append does "
1919 "not make sense."));
1922 wim_fd = STDOUT_FILENO;
1924 imagex_info_file = stderr;
1925 set_fd_to_binary_mode(wim_fd);
1928 /* If template image was specified using --update-of=IMAGE rather
1929 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1930 if (template_image_name_or_num && !template_wimfile) {
1931 if (base_wimfiles.num_strings == 1) {
1932 /* Capturing delta WIM based on single WIM: default to
1934 template_wimfile = base_wimfiles.strings[0];
1935 } else if (cmd == CMD_APPEND) {
1936 /* Appending to WIM: default to WIM being appended to.
1938 template_wimfile = wimfile;
1940 /* Capturing a normal (non-delta) WIM, so the WIM file
1941 * *must* be explicitly specified. */
1942 if (base_wimfiles.num_strings > 1) {
1943 imagex_error(T("For capture of delta WIM "
1944 "based on multiple existing "
1946 " '--update-of' must "
1947 "specify WIMFILE:IMAGE!"));
1949 imagex_error(T("For capture of non-delta WIM, "
1950 "'--update-of' must specify "
1959 name_defaulted = false;
1961 /* Set default name to SOURCE argument, omitting any directory
1962 * prefixes and trailing slashes. This requires making a copy
1963 * of @source. Leave some free characters at the end in case we
1964 * append a number to keep the name unique. */
1965 size_t source_name_len;
1967 source_name_len = tstrlen(source);
1968 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1969 name = tbasename(tstrcpy(source_copy, source));
1970 name_defaulted = true;
1972 /* Image description defaults to NULL if not given. */
1979 /* Set up capture sources in source list mode */
1980 if (source[0] == T('-') && source[1] == T('\0')) {
1981 source_list_contents = stdin_get_text_contents(&source_list_nchars);
1983 source_list_contents = file_get_text_contents(source,
1984 &source_list_nchars);
1986 if (!source_list_contents)
1989 capture_sources = parse_source_list(&source_list_contents,
1992 if (!capture_sources) {
1994 goto out_free_source_list_contents;
1996 capture_sources_malloced = true;
1998 /* Set up capture source in non-source-list mode. */
1999 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2000 capture_sources[0].fs_source_path = source;
2001 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2002 capture_sources[0].reserved = 0;
2004 capture_sources_malloced = false;
2005 source_list_contents = NULL;
2008 /* Open the existing WIM, or create a new one. */
2009 if (cmd == CMD_APPEND) {
2010 ret = wimlib_open_wim_with_progress(wimfile,
2011 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2013 imagex_progress_func,
2016 goto out_free_capture_sources;
2018 ret = wimlib_create_new_wim(compression_type, &wim);
2020 goto out_free_capture_sources;
2021 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2024 /* Set chunk size if non-default. */
2025 if (chunk_size != UINT32_MAX) {
2026 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2029 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2030 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2031 ret = wimlib_set_output_chunk_size(wim, 4096);
2035 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2036 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2040 if (solid_chunk_size != UINT32_MAX) {
2041 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2047 /* Detect if source is regular file or block device and set NTFS volume
2052 if (tstat(source, &stbuf) == 0) {
2053 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2054 imagex_printf(T("Capturing WIM image from NTFS "
2055 "filesystem on \"%"TS"\"\n"), source);
2056 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2059 if (errno != ENOENT) {
2060 imagex_error_with_errno(T("Failed to stat "
2061 "\"%"TS"\""), source);
2069 /* If the user did not specify an image name, and the basename of the
2070 * source already exists as an image name in the WIM file, append a
2071 * suffix to make it unique. */
2072 if (cmd == CMD_APPEND && name_defaulted) {
2073 unsigned long conflict_idx;
2074 tchar *name_end = tstrchr(name, T('\0'));
2075 for (conflict_idx = 1;
2076 wimlib_image_name_in_use(wim, name);
2079 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2083 /* If capturing a delta WIM, reference resources from the base WIMs
2084 * before adding the new image. */
2085 if (base_wimfiles.num_strings) {
2086 base_wims = calloc(base_wimfiles.num_strings,
2087 sizeof(base_wims[0]));
2088 if (base_wims == NULL) {
2089 imagex_error(T("Out of memory!"));
2094 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2095 ret = wimlib_open_wim_with_progress(
2096 base_wimfiles.strings[i], open_flags,
2097 &base_wims[i], imagex_progress_func, NULL);
2099 goto out_free_base_wims;
2103 ret = wimlib_reference_resources(wim, base_wims,
2104 base_wimfiles.num_strings, 0);
2106 goto out_free_base_wims;
2108 if (base_wimfiles.num_strings == 1) {
2109 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2110 base_wimfiles.strings[0]);
2112 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2113 base_wimfiles.num_strings);
2120 /* If capturing or appending as an update of an existing (template) image,
2121 * open the WIM if needed and parse the image index. */
2122 if (template_image_name_or_num) {
2125 if (base_wimfiles.num_strings == 1 &&
2126 template_wimfile == base_wimfiles.strings[0]) {
2127 template_wim = base_wims[0];
2128 } else if (template_wimfile == wimfile) {
2131 ret = wimlib_open_wim_with_progress(template_wimfile,
2134 imagex_progress_func,
2137 goto out_free_base_wims;
2140 template_image = wimlib_resolve_image(template_wim,
2141 template_image_name_or_num);
2143 if (template_image_name_or_num[0] == T('-')) {
2146 struct wimlib_wim_info info;
2148 wimlib_get_wim_info(template_wim, &info);
2149 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2150 if (n >= 1 && n <= info.image_count &&
2152 tmp != template_image_name_or_num + 1)
2154 template_image = info.image_count - (n - 1);
2157 ret = verify_image_exists_and_is_single(template_image,
2158 template_image_name_or_num,
2161 goto out_free_template_wim;
2163 template_wim = NULL;
2166 ret = wimlib_add_image_multisource(wim,
2173 goto out_free_template_wim;
2175 if (desc || flags_element || template_image_name_or_num) {
2176 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2177 * on which the added one is to be based has been specified with
2178 * --update-of. Get the index of the image we just
2179 * added, then use it to call the appropriate functions. */
2180 struct wimlib_wim_info info;
2182 wimlib_get_wim_info(wim, &info);
2185 ret = wimlib_set_image_descripton(wim,
2189 goto out_free_template_wim;
2192 if (flags_element) {
2193 ret = wimlib_set_image_flags(wim, info.image_count,
2196 goto out_free_template_wim;
2199 /* Reference template image if the user provided one. */
2200 if (template_image_name_or_num) {
2201 imagex_printf(T("Using image %d "
2202 "from \"%"TS"\" as template\n"),
2203 template_image, template_wimfile);
2204 ret = wimlib_reference_template_image(wim,
2210 goto out_free_template_wim;
2214 /* Write the new WIM or overwrite the existing WIM with the new image
2216 if (cmd == CMD_APPEND) {
2217 ret = wimlib_overwrite(wim, write_flags, num_threads);
2218 } else if (wimfile) {
2219 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2220 write_flags, num_threads);
2222 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2223 write_flags, num_threads);
2225 out_free_template_wim:
2226 /* template_wim may alias base_wims[0] or wim. */
2227 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2228 template_wim != wim)
2229 wimlib_free(template_wim);
2231 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2232 wimlib_free(base_wims[i]);
2236 out_free_capture_sources:
2237 if (capture_sources_malloced)
2238 free(capture_sources);
2239 out_free_source_list_contents:
2240 free(source_list_contents);
2241 out_free_base_wimfiles:
2242 string_set_destroy(&base_wimfiles);
2249 goto out_free_base_wimfiles;
2252 /* Remove image(s) from a WIM. */
2254 imagex_delete(int argc, tchar **argv, int cmd)
2257 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2258 int write_flags = 0;
2259 const tchar *wimfile;
2260 const tchar *image_num_or_name;
2265 for_opt(c, delete_options) {
2267 case IMAGEX_CHECK_OPTION:
2268 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2269 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2271 case IMAGEX_SOFT_OPTION:
2272 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2283 imagex_error(T("Must specify a WIM file"));
2285 imagex_error(T("Must specify an image"));
2289 image_num_or_name = argv[1];
2291 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2292 imagex_progress_func, NULL);
2296 image = wimlib_resolve_image(wim, image_num_or_name);
2298 ret = verify_image_exists(image, image_num_or_name, wimfile);
2300 goto out_wimlib_free;
2302 ret = wimlib_delete_image(wim, image);
2304 imagex_error(T("Failed to delete image from \"%"TS"\""),
2306 goto out_wimlib_free;
2309 ret = wimlib_overwrite(wim, write_flags, 0);
2311 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2312 "deleted"), wimfile);
2320 usage(CMD_DELETE, stderr);
2325 struct print_dentry_options {
2330 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2332 tprintf(T("%"TS"\n"), dentry->full_path);
2335 static const struct {
2338 } file_attr_flags[] = {
2339 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2340 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2341 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2342 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2343 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2344 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2345 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2346 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2347 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2348 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2349 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2350 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2351 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2352 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2353 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2356 #define TIMESTR_MAX 100
2359 timespec_to_string(const struct timespec *spec, tchar *buf)
2361 time_t t = spec->tv_sec;
2364 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2365 buf[TIMESTR_MAX - 1] = '\0';
2369 print_time(const tchar *type, const struct timespec *spec)
2371 tchar timestr[TIMESTR_MAX];
2373 timespec_to_string(spec, timestr);
2375 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2378 static void print_byte_field(const uint8_t field[], size_t len)
2381 tprintf(T("%02hhx"), *field++);
2385 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2387 tputs(T("WIM Information:"));
2388 tputs(T("----------------"));
2389 tprintf(T("Path: %"TS"\n"), wimfile);
2390 tprintf(T("GUID: 0x"));
2391 print_byte_field(info->guid, sizeof(info->guid));
2393 tprintf(T("Version: %u\n"), info->wim_version);
2394 tprintf(T("Image Count: %d\n"), info->image_count);
2395 tprintf(T("Compression: %"TS"\n"),
2396 wimlib_get_compression_type_string(info->compression_type));
2397 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2399 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2400 tprintf(T("Boot Index: %d\n"), info->boot_index);
2401 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2402 tprintf(T("Integrity Info: %"TS"\n"),
2403 info->has_integrity_table ? T("yes") : T("no"));
2404 tprintf(T("Relative path junction: %"TS"\n"),
2405 info->has_rpfix ? T("yes") : T("no"));
2406 tprintf(T("Pipable: %"TS"\n"),
2407 info->pipable ? T("yes") : T("no"));
2412 print_resource(const struct wimlib_resource_entry *resource,
2415 tprintf(T("Hash = 0x"));
2416 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2419 if (!resource->is_missing) {
2420 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2421 resource->uncompressed_size);
2422 if (resource->packed) {
2423 tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2424 resource->raw_resource_compressed_size);
2426 tprintf(T("Raw offset in WIM = %"PRIu64" bytes\n"),
2427 resource->raw_resource_offset_in_wim);
2429 tprintf(T("Offset in raw = %"PRIu64" bytes\n"),
2432 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2433 resource->compressed_size);
2435 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2439 tprintf(T("Part Number = %u\n"), resource->part_number);
2440 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2442 tprintf(T("Flags = "));
2443 if (resource->is_compressed)
2444 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2445 if (resource->is_metadata)
2446 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2447 if (resource->is_free)
2448 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2449 if (resource->is_spanned)
2450 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2451 if (resource->packed)
2452 tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS "));
2460 print_lookup_table(WIMStruct *wim)
2462 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2466 default_print_security_descriptor(const uint8_t *sd, size_t size)
2468 tprintf(T("Security Descriptor = "));
2469 print_byte_field(sd, size);
2474 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2478 "----------------------------------------------------------------------------\n"));
2479 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2480 if (dentry->dos_name)
2481 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2482 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2483 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2484 if (file_attr_flags[i].flag & dentry->attributes)
2485 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2486 file_attr_flags[i].name);
2488 if (dentry->security_descriptor) {
2489 print_security_descriptor(dentry->security_descriptor,
2490 dentry->security_descriptor_size);
2493 print_time(T("Creation Time"), &dentry->creation_time);
2494 print_time(T("Last Write Time"), &dentry->last_write_time);
2495 print_time(T("Last Access Time"), &dentry->last_access_time);
2498 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2499 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2501 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2502 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2504 if (dentry->unix_mode != 0) {
2505 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2506 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2507 dentry->unix_uid, dentry->unix_gid,
2508 dentry->unix_mode, dentry->unix_rdev);
2511 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2512 if (dentry->streams[i].stream_name) {
2513 tprintf(T("\tData stream \"%"TS"\":\n"),
2514 dentry->streams[i].stream_name);
2516 tprintf(T("\tUnnamed data stream:\n"));
2518 print_resource(&dentry->streams[i].resource, NULL);
2523 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2525 const struct print_dentry_options *options = _options;
2526 if (!options->detailed)
2527 print_dentry_full_path(dentry);
2529 print_dentry_detailed(dentry);
2533 /* Print the files contained in an image(s) in a WIM file. */
2535 imagex_dir(int argc, tchar **argv, int cmd)
2537 const tchar *wimfile;
2538 WIMStruct *wim = NULL;
2541 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2543 struct print_dentry_options options = {
2546 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2548 for_opt(c, dir_options) {
2550 case IMAGEX_PATH_OPTION:
2553 case IMAGEX_DETAILED_OPTION:
2554 options.detailed = true;
2556 case IMAGEX_ONE_FILE_ONLY_OPTION:
2557 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2567 imagex_error(T("Must specify a WIM file"));
2571 imagex_error(T("Too many arguments"));
2576 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2577 imagex_progress_func, NULL);
2582 image = wimlib_resolve_image(wim, argv[1]);
2583 ret = verify_image_exists(image, argv[1], wimfile);
2585 goto out_wimlib_free;
2587 /* No image specified; default to image 1, but only if the WIM
2588 * contains exactly one image. */
2590 struct wimlib_wim_info info;
2592 wimlib_get_wim_info(wim, &info);
2593 if (info.image_count != 1) {
2594 imagex_error(T("\"%"TS"\" contains %d images; Please "
2595 "select one (or all)."),
2596 wimfile, info.image_count);
2603 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2604 print_dentry, &options);
2611 usage(CMD_DIR, stderr);
2616 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2619 imagex_export(int argc, tchar **argv, int cmd)
2623 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2624 int write_flags = 0;
2625 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2626 const tchar *src_wimfile;
2627 const tchar *src_image_num_or_name;
2628 const tchar *dest_wimfile;
2630 const tchar *dest_name;
2631 const tchar *dest_desc;
2633 struct wimlib_wim_info src_info;
2634 WIMStruct *dest_wim;
2639 STRING_SET(refglobs);
2640 unsigned num_threads = 0;
2641 uint32_t chunk_size = UINT32_MAX;
2642 uint32_t solid_chunk_size = UINT32_MAX;
2643 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2645 for_opt(c, export_options) {
2647 case IMAGEX_BOOT_OPTION:
2648 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2650 case IMAGEX_CHECK_OPTION:
2651 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2652 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2654 case IMAGEX_NOCHECK_OPTION:
2655 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2657 case IMAGEX_COMPRESS_OPTION:
2658 compression_type = get_compression_type(optarg);
2659 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2662 case IMAGEX_COMPRESS_SLOW_OPTION:
2663 set_compress_slow();
2664 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2666 case IMAGEX_RECOMPRESS_OPTION:
2667 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2669 case IMAGEX_SOLID_OPTION:
2670 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
2672 case IMAGEX_CHUNK_SIZE_OPTION:
2673 chunk_size = parse_chunk_size(optarg);
2674 if (chunk_size == UINT32_MAX)
2677 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2678 solid_chunk_size = parse_chunk_size(optarg);
2679 if (solid_chunk_size == UINT32_MAX)
2682 case IMAGEX_SOLID_COMPRESS_OPTION:
2683 solid_ctype = get_compression_type(optarg);
2684 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2687 case IMAGEX_REF_OPTION:
2688 ret = string_set_append(&refglobs, optarg);
2690 goto out_free_refglobs;
2692 case IMAGEX_THREADS_OPTION:
2693 num_threads = parse_num_threads(optarg);
2694 if (num_threads == UINT_MAX)
2697 case IMAGEX_REBUILD_OPTION:
2698 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2700 case IMAGEX_PIPABLE_OPTION:
2701 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2703 case IMAGEX_NOT_PIPABLE_OPTION:
2704 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2706 case IMAGEX_WIMBOOT_OPTION:
2707 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2715 if (argc < 3 || argc > 5)
2717 src_wimfile = argv[0];
2718 src_image_num_or_name = argv[1];
2719 dest_wimfile = argv[2];
2720 dest_name = (argc >= 4) ? argv[3] : NULL;
2721 dest_desc = (argc >= 5) ? argv[4] : NULL;
2722 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2723 imagex_progress_func, NULL);
2725 goto out_free_refglobs;
2727 wimlib_get_wim_info(src_wim, &src_info);
2729 /* Determine if the destination is an existing file or not. If so, we
2730 * try to append the exported image(s) to it; otherwise, we create a new
2731 * WIM containing the exported image(s). Furthermore, determine if we
2732 * need to write a pipable WIM directly to standard output. */
2734 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2736 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2737 imagex_error("Can't write a non-pipable WIM to "
2738 "standard output! Specify --pipable\n"
2739 " if you want to create a pipable WIM "
2740 "(but read the docs first).");
2742 goto out_free_src_wim;
2745 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2747 dest_wimfile = NULL;
2748 dest_wim_fd = STDOUT_FILENO;
2749 imagex_info_file = stderr;
2750 set_fd_to_binary_mode(dest_wim_fd);
2753 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2755 /* Destination file exists. */
2757 if (!S_ISREG(stbuf.st_mode)) {
2758 imagex_error(T("\"%"TS"\" is not a regular file"),
2761 goto out_free_src_wim;
2763 ret = wimlib_open_wim_with_progress(dest_wimfile,
2765 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2767 imagex_progress_func,
2770 goto out_free_src_wim;
2772 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2773 /* The user specified a compression type, but we're
2774 * exporting to an existing WIM. Make sure the
2775 * specified compression type is the same as the
2776 * compression type of the existing destination WIM. */
2777 struct wimlib_wim_info dest_info;
2779 wimlib_get_wim_info(dest_wim, &dest_info);
2780 if (compression_type != dest_info.compression_type) {
2781 imagex_error(T("Cannot specify a compression type that is "
2782 "not the same as that used in the "
2783 "destination WIM"));
2785 goto out_free_dest_wim;
2791 if (errno != ENOENT) {
2792 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2795 goto out_free_src_wim;
2798 /* dest_wimfile is not an existing file, so create a new WIM. */
2800 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2801 /* The user did not specify a compression type; default
2802 * to that of the source WIM, unless --solid or
2803 * --wimboot was specified. */
2805 if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
2806 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2807 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2808 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2810 compression_type = src_info.compression_type;
2812 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2814 goto out_free_src_wim;
2816 wimlib_register_progress_function(dest_wim,
2817 imagex_progress_func, NULL);
2819 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2820 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2822 /* For --wimboot export, use small XPRESS chunks. */
2823 wimlib_set_output_chunk_size(dest_wim, 4096);
2824 } else if (compression_type == src_info.compression_type &&
2825 chunk_size == UINT32_MAX)
2827 /* Use same chunk size if compression type is the same. */
2828 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2832 if (chunk_size != UINT32_MAX) {
2833 /* Set destination chunk size. */
2834 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2836 goto out_free_dest_wim;
2838 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2839 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2841 goto out_free_dest_wim;
2843 if (solid_chunk_size != UINT32_MAX) {
2844 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2846 goto out_free_dest_wim;
2849 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2850 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2852 goto out_free_dest_wim;
2854 if (refglobs.num_strings) {
2855 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2857 goto out_free_dest_wim;
2860 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2861 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2863 imagex_error(T("--boot specified for all-images export, but source WIM "
2864 "has no bootable image."));
2866 goto out_free_dest_wim;
2869 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2870 dest_desc, export_flags);
2872 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2873 do_resource_not_found_warning(src_wimfile,
2874 &src_info, &refglobs);
2876 goto out_free_dest_wim;
2880 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2881 else if (dest_wimfile)
2882 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2883 write_flags, num_threads);
2885 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2886 WIMLIB_ALL_IMAGES, write_flags,
2889 wimlib_free(dest_wim);
2891 wimlib_free(src_wim);
2893 string_set_destroy(&refglobs);
2897 usage(CMD_EXPORT, stderr);
2900 goto out_free_refglobs;
2903 /* Extract files or directories from a WIM image */
2905 imagex_extract(int argc, tchar **argv, int cmd)
2912 const tchar *wimfile;
2913 const tchar *image_num_or_name;
2914 tchar *dest_dir = T(".");
2915 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2916 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2917 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2918 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2920 STRING_SET(refglobs);
2922 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2924 for_opt(c, extract_options) {
2926 case IMAGEX_CHECK_OPTION:
2927 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2929 case IMAGEX_VERBOSE_OPTION:
2930 /* No longer does anything. */
2932 case IMAGEX_REF_OPTION:
2933 ret = string_set_append(&refglobs, optarg);
2935 goto out_free_refglobs;
2937 case IMAGEX_UNIX_DATA_OPTION:
2938 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2940 case IMAGEX_NO_ACLS_OPTION:
2941 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2943 case IMAGEX_STRICT_ACLS_OPTION:
2944 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2946 case IMAGEX_NO_ATTRIBUTES_OPTION:
2947 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2949 case IMAGEX_DEST_DIR_OPTION:
2952 case IMAGEX_TO_STDOUT_OPTION:
2953 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2954 imagex_info_file = stderr;
2955 imagex_be_quiet = true;
2956 set_fd_to_binary_mode(STDOUT_FILENO);
2958 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2959 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2960 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2962 case IMAGEX_NO_GLOBS_OPTION:
2963 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2965 case IMAGEX_NULLGLOB_OPTION:
2966 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2968 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2969 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2971 case IMAGEX_WIMBOOT_OPTION:
2972 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
2984 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2985 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
2987 imagex_error(T("Can't combine --no-globs and --nullglob!"));
2992 image_num_or_name = argv[1];
2997 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2998 imagex_progress_func, NULL);
3000 goto out_free_refglobs;
3002 image = wimlib_resolve_image(wim, image_num_or_name);
3003 ret = verify_image_exists_and_is_single(image,
3007 goto out_wimlib_free;
3009 if (refglobs.num_strings) {
3010 ret = wim_reference_globs(wim, &refglobs, open_flags);
3012 goto out_wimlib_free;
3018 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3021 while (argc != 0 && ret == 0) {
3025 num_paths < argc && argv[num_paths][0] != T('@');
3030 ret = wimlib_extract_paths(wim, image, dest_dir,
3031 (const tchar **)argv,
3033 extract_flags | notlist_extract_flags);
3037 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3046 if (!imagex_be_quiet)
3047 imagex_printf(T("Done extracting files.\n"));
3048 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3049 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3050 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3051 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3052 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3055 T("Note: You can use the '--nullglob' "
3056 "option to ignore missing files.\n"));
3058 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3059 "files and directories\n"
3060 " are in the WIM image.\n"),
3061 get_cmd_string(CMD_DIR, false));
3062 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3063 struct wimlib_wim_info info;
3065 wimlib_get_wim_info(wim, &info);
3066 do_resource_not_found_warning(wimfile, &info, &refglobs);
3071 string_set_destroy(&refglobs);
3075 usage(CMD_EXTRACT, stderr);
3078 goto out_free_refglobs;
3081 /* Prints information about a WIM file; also can mark an image as bootable,
3082 * change the name of an image, or change the description of an image. */
3084 imagex_info(int argc, tchar **argv, int cmd)
3089 bool nocheck = false;
3090 bool header = false;
3091 bool lookup_table = false;
3093 bool short_header = true;
3094 const tchar *xml_out_file = NULL;
3095 const tchar *wimfile;
3096 const tchar *image_num_or_name;
3097 const tchar *new_name;
3098 const tchar *new_desc;
3103 struct wimlib_wim_info info;
3105 for_opt(c, info_options) {
3107 case IMAGEX_BOOT_OPTION:
3110 case IMAGEX_CHECK_OPTION:
3113 case IMAGEX_NOCHECK_OPTION:
3116 case IMAGEX_HEADER_OPTION:
3118 short_header = false;
3120 case IMAGEX_LOOKUP_TABLE_OPTION:
3121 lookup_table = true;
3122 short_header = false;
3124 case IMAGEX_XML_OPTION:
3126 short_header = false;
3128 case IMAGEX_EXTRACT_XML_OPTION:
3129 xml_out_file = optarg;
3130 short_header = false;
3132 case IMAGEX_METADATA_OPTION:
3133 imagex_error(T("The --metadata option has been removed. "
3134 "Use 'wimdir --detail' instead."));
3143 if (argc < 1 || argc > 4)
3147 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3148 new_name = (argc >= 3) ? argv[2] : NULL;
3149 new_desc = (argc >= 4) ? argv[3] : NULL;
3151 if (check && nocheck) {
3152 imagex_error(T("Can't specify both --check and --nocheck"));
3157 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3159 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3160 imagex_progress_func, NULL);
3164 wimlib_get_wim_info(wim, &info);
3166 image = wimlib_resolve_image(wim, image_num_or_name);
3167 ret = WIMLIB_ERR_INVALID_IMAGE;
3168 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3169 verify_image_exists(image, image_num_or_name, wimfile);
3171 imagex_error(T("If you would like to set the boot "
3172 "index to 0, specify image \"0\" with "
3173 "the --boot flag."));
3175 goto out_wimlib_free;
3178 if (boot && info.image_count == 0) {
3179 imagex_error(T("--boot is meaningless on a WIM with no images"));
3180 goto out_wimlib_free;
3183 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3185 imagex_error(T("Cannot specify the --boot flag "
3186 "without specifying a specific "
3187 "image in a multi-image WIM"));
3188 goto out_wimlib_free;
3191 imagex_error(T("Cannot specify the NEW_NAME "
3192 "without specifying a specific "
3193 "image in a multi-image WIM"));
3194 goto out_wimlib_free;
3198 /* Operations that print information are separated from operations that
3199 * recreate the WIM file. */
3200 if (!new_name && !boot) {
3202 /* Read-only operations */
3204 if (image == WIMLIB_NO_IMAGE) {
3205 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3206 image_num_or_name, wimfile);
3207 goto out_wimlib_free;
3210 if (image == WIMLIB_ALL_IMAGES && short_header)
3211 print_wim_information(wimfile, &info);
3214 wimlib_print_header(wim);
3217 if (info.total_parts != 1) {
3218 tfprintf(stderr, T("Warning: Only showing the lookup table "
3219 "for part %d of a %d-part WIM.\n"),
3220 info.part_number, info.total_parts);
3222 print_lookup_table(wim);
3226 ret = wimlib_extract_xml_data(wim, stdout);
3228 goto out_wimlib_free;
3234 fp = tfopen(xml_out_file, T("wb"));
3236 imagex_error_with_errno(T("Failed to open the "
3237 "file \"%"TS"\" for "
3241 goto out_wimlib_free;
3243 ret = wimlib_extract_xml_data(wim, fp);
3245 imagex_error(T("Failed to close the file "
3251 goto out_wimlib_free;
3255 wimlib_print_available_images(wim, image);
3260 /* Modification operations */
3262 if (image == WIMLIB_ALL_IMAGES)
3265 if (image == WIMLIB_NO_IMAGE && new_name) {
3266 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3267 "when using image 0"), new_name);
3269 goto out_wimlib_free;
3273 if (image == info.boot_index) {
3274 imagex_printf(T("Image %d is already marked as "
3275 "bootable.\n"), image);
3278 imagex_printf(T("Marking image %d as bootable.\n"),
3280 info.boot_index = image;
3281 ret = wimlib_set_wim_info(wim, &info,
3282 WIMLIB_CHANGE_BOOT_INDEX);
3284 goto out_wimlib_free;
3288 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3290 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3294 imagex_printf(T("Changing the name of image %d to "
3295 "\"%"TS"\".\n"), image, new_name);
3296 ret = wimlib_set_image_name(wim, image, new_name);
3298 goto out_wimlib_free;
3302 const tchar *old_desc;
3303 old_desc = wimlib_get_image_description(wim, image);
3304 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3305 imagex_printf(T("The description of image %d is already "
3306 "\"%"TS"\".\n"), image, new_desc);
3309 imagex_printf(T("Changing the description of image %d "
3310 "to \"%"TS"\".\n"), image, new_desc);
3311 ret = wimlib_set_image_descripton(wim, image,
3314 goto out_wimlib_free;
3318 /* Only call wimlib_overwrite() if something actually needs to
3320 if (boot || new_name || new_desc ||
3321 (check && !info.has_integrity_table) ||
3322 (nocheck && info.has_integrity_table))
3324 int write_flags = 0;
3327 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3329 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3330 ret = wimlib_overwrite(wim, write_flags, 1);
3332 imagex_printf(T("The file \"%"TS"\" was not modified "
3333 "because nothing needed to be done.\n"),
3344 usage(CMD_INFO, stderr);
3350 /* Join split WIMs into one part WIM */
3352 imagex_join(int argc, tchar **argv, int cmd)
3355 int swm_open_flags = 0;
3356 int wim_write_flags = 0;
3357 const tchar *output_path;
3360 for_opt(c, join_options) {
3362 case IMAGEX_CHECK_OPTION:
3363 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3364 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3374 imagex_error(T("Must specify one or more split WIM (.swm) "
3378 output_path = argv[0];
3379 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3384 imagex_progress_func,
3390 usage(CMD_JOIN, stderr);
3395 #if WIM_MOUNTING_SUPPORTED
3397 /* Mounts a WIM image. */
3399 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3402 int mount_flags = 0;
3404 const tchar *staging_dir = NULL;
3405 const tchar *wimfile;
3408 struct wimlib_wim_info info;
3412 STRING_SET(refglobs);
3414 if (cmd == CMD_MOUNTRW) {
3415 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3416 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3419 for_opt(c, mount_options) {
3421 case IMAGEX_ALLOW_OTHER_OPTION:
3422 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3424 case IMAGEX_CHECK_OPTION:
3425 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3427 case IMAGEX_DEBUG_OPTION:
3428 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3430 case IMAGEX_STREAMS_INTERFACE_OPTION:
3431 if (!tstrcasecmp(optarg, T("none")))
3432 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3433 else if (!tstrcasecmp(optarg, T("xattr")))
3434 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3435 else if (!tstrcasecmp(optarg, T("windows")))
3436 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3438 imagex_error(T("Unknown stream interface \"%"TS"\""),
3443 case IMAGEX_REF_OPTION:
3444 ret = string_set_append(&refglobs, optarg);
3446 goto out_free_refglobs;
3448 case IMAGEX_STAGING_DIR_OPTION:
3449 staging_dir = optarg;
3451 case IMAGEX_UNIX_DATA_OPTION:
3452 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3460 if (argc != 2 && argc != 3)
3465 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3466 imagex_progress_func, NULL);
3468 goto out_free_refglobs;
3470 wimlib_get_wim_info(wim, &info);
3473 /* Image explicitly specified. */
3474 image = wimlib_resolve_image(wim, argv[1]);
3476 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3480 /* No image specified; default to image 1, but only if the WIM
3481 * contains exactly one image. */
3483 if (info.image_count != 1) {
3484 imagex_error(T("\"%"TS"\" contains %d images; Please "
3485 "select one."), wimfile, info.image_count);
3493 if (refglobs.num_strings) {
3494 ret = wim_reference_globs(wim, &refglobs, open_flags);
3499 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3501 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3503 image, wimfile, dir);
3508 string_set_destroy(&refglobs);
3514 goto out_free_refglobs;
3516 #endif /* WIM_MOUNTING_SUPPORTED */
3518 /* Rebuild a WIM file */
3520 imagex_optimize(int argc, tchar **argv, int cmd)
3523 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3524 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3525 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3526 uint32_t chunk_size = UINT32_MAX;
3527 uint32_t solid_chunk_size = UINT32_MAX;
3528 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3531 const tchar *wimfile;
3534 unsigned num_threads = 0;
3536 for_opt(c, optimize_options) {
3538 case IMAGEX_CHECK_OPTION:
3539 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3540 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3542 case IMAGEX_NOCHECK_OPTION:
3543 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3545 case IMAGEX_COMPRESS_OPTION:
3546 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3547 compression_type = get_compression_type(optarg);
3548 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3551 case IMAGEX_COMPRESS_SLOW_OPTION:
3552 set_compress_slow();
3553 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3555 case IMAGEX_RECOMPRESS_OPTION:
3556 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3558 case IMAGEX_CHUNK_SIZE_OPTION:
3559 chunk_size = parse_chunk_size(optarg);
3560 if (chunk_size == UINT32_MAX)
3563 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3564 solid_chunk_size = parse_chunk_size(optarg);
3565 if (solid_chunk_size == UINT32_MAX)
3568 case IMAGEX_SOLID_COMPRESS_OPTION:
3569 solid_ctype = get_compression_type(optarg);
3570 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3573 case IMAGEX_SOLID_OPTION:
3574 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
3575 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3577 case IMAGEX_THREADS_OPTION:
3578 num_threads = parse_num_threads(optarg);
3579 if (num_threads == UINT_MAX)
3582 case IMAGEX_PIPABLE_OPTION:
3583 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3585 case IMAGEX_NOT_PIPABLE_OPTION:
3586 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3600 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3601 imagex_progress_func, NULL);
3605 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3606 /* Change compression type. */
3607 ret = wimlib_set_output_compression_type(wim, compression_type);
3609 goto out_wimlib_free;
3612 if (chunk_size != UINT32_MAX) {
3613 /* Change chunk size. */
3614 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3616 goto out_wimlib_free;
3618 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3619 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3621 goto out_wimlib_free;
3623 if (solid_chunk_size != UINT32_MAX) {
3624 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3626 goto out_wimlib_free;
3629 old_size = file_get_size(wimfile);
3630 tprintf(T("\"%"TS"\" original size: "), wimfile);
3632 tputs(T("Unknown"));
3634 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3636 ret = wimlib_overwrite(wim, write_flags, num_threads);
3638 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3639 goto out_wimlib_free;
3642 new_size = file_get_size(wimfile);
3643 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3645 tputs(T("Unknown"));
3647 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3649 tfputs(T("Space saved: "), stdout);
3650 if (new_size != -1 && old_size != -1) {
3651 tprintf(T("%lld KiB\n"),
3652 ((long long)old_size - (long long)new_size) >> 10);
3654 tputs(T("Unknown"));
3663 usage(CMD_OPTIMIZE, stderr);
3669 /* Split a WIM into a spanned set */
3671 imagex_split(int argc, tchar **argv, int cmd)
3675 int write_flags = 0;
3676 unsigned long part_size;
3681 for_opt(c, split_options) {
3683 case IMAGEX_CHECK_OPTION:
3684 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3685 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3697 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3698 if (tmp == argv[2] || *tmp) {
3699 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3700 imagex_error(T("The part size must be an integer or "
3701 "floating-point number of megabytes."));
3704 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3705 imagex_progress_func, NULL);
3709 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3715 usage(CMD_SPLIT, stderr);
3721 #if WIM_MOUNTING_SUPPORTED
3722 /* Unmounts a mounted WIM image. */
3724 imagex_unmount(int argc, tchar **argv, int cmd)
3727 int unmount_flags = 0;
3730 for_opt(c, unmount_options) {
3732 case IMAGEX_COMMIT_OPTION:
3733 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3735 case IMAGEX_CHECK_OPTION:
3736 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3738 case IMAGEX_REBUILD_OPTION:
3739 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3741 case IMAGEX_LAZY_OPTION:
3742 case IMAGEX_FORCE_OPTION:
3743 /* Now, unmount is lazy by default. However, committing
3744 * the image will fail with
3745 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3746 * file descriptors on the WIM image. The
3747 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3748 * descriptors to be closed. */
3749 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3751 case IMAGEX_NEW_IMAGE_OPTION:
3752 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3763 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3764 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3765 imagex_error(T("--new-image is meaningless "
3766 "without --commit also specified!"));
3771 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3772 imagex_progress_func, NULL);
3774 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3775 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3777 "\tNote: Use --commit --force to force changes "
3778 "to be committed, regardless\n"
3779 "\t of open files.\n"));
3786 usage(CMD_UNMOUNT, stderr);
3791 #endif /* WIM_MOUNTING_SUPPORTED */
3794 * Add, delete, or rename files in a WIM image.
3797 imagex_update(int argc, tchar **argv, int cmd)
3799 const tchar *wimfile;
3803 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3804 int write_flags = 0;
3805 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3806 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3807 WIMLIB_ADD_FLAG_VERBOSE |
3808 WIMLIB_ADD_FLAG_WINCONFIG;
3809 int default_delete_flags = 0;
3810 unsigned num_threads = 0;
3812 tchar *cmd_file_contents;
3813 size_t cmd_file_nchars;
3814 struct wimlib_update_command *cmds;
3816 tchar *command_str = NULL;
3817 tchar *config_file = NULL;
3818 tchar *wimboot_config = NULL;
3820 for_opt(c, update_options) {
3822 /* Generic or write options */
3823 case IMAGEX_THREADS_OPTION:
3824 num_threads = parse_num_threads(optarg);
3825 if (num_threads == UINT_MAX)
3828 case IMAGEX_CHECK_OPTION:
3829 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3830 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3832 case IMAGEX_REBUILD_OPTION:
3833 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3835 case IMAGEX_COMMAND_OPTION:
3837 imagex_error(T("--command may only be specified "
3838 "one time. Please provide\n"
3839 " the update commands "
3840 "on standard input instead."));
3843 command_str = tstrdup(optarg);
3845 imagex_error(T("Out of memory!"));
3849 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3850 wimboot_config = optarg;
3852 /* Default delete options */
3853 case IMAGEX_FORCE_OPTION:
3854 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3856 case IMAGEX_RECURSIVE_OPTION:
3857 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3860 /* Global add option */
3861 case IMAGEX_CONFIG_OPTION:
3862 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3863 config_file = optarg;
3866 /* Default add options */
3867 case IMAGEX_VERBOSE_OPTION:
3868 /* No longer does anything. */
3870 case IMAGEX_DEREFERENCE_OPTION:
3871 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3873 case IMAGEX_UNIX_DATA_OPTION:
3874 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3876 case IMAGEX_NO_ACLS_OPTION:
3877 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3879 case IMAGEX_STRICT_ACLS_OPTION:
3880 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3882 case IMAGEX_NO_REPLACE_OPTION:
3883 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3892 if (argc != 1 && argc != 2)
3896 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3897 imagex_progress_func, NULL);
3899 goto out_free_command_str;
3902 /* Image explicitly specified. */
3903 image = wimlib_resolve_image(wim, argv[1]);
3904 ret = verify_image_exists_and_is_single(image, argv[1],
3907 goto out_wimlib_free;
3909 /* No image specified; default to image 1, but only if the WIM
3910 * contains exactly one image. */
3911 struct wimlib_wim_info info;
3913 wimlib_get_wim_info(wim, &info);
3914 if (info.image_count != 1) {
3915 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3916 wimfile, info.image_count);
3923 /* Read update commands from standard input, or the command string if
3926 cmd_file_contents = NULL;
3927 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3931 goto out_free_cmd_file_contents;
3933 } else if (!wimboot_config) {
3934 if (isatty(STDIN_FILENO)) {
3935 tputs(T("Reading update commands from standard input..."));
3936 recommend_man_page(CMD_UPDATE, stdout);
3938 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3939 if (!cmd_file_contents) {
3941 goto out_wimlib_free;
3944 /* Parse the update commands */
3945 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3949 goto out_free_cmd_file_contents;
3952 cmd_file_contents = NULL;
3957 /* Set default flags and capture config on the update commands */
3958 for (size_t i = 0; i < num_cmds; i++) {
3959 switch (cmds[i].op) {
3960 case WIMLIB_UPDATE_OP_ADD:
3961 cmds[i].add.add_flags |= default_add_flags;
3962 cmds[i].add.config_file = config_file;
3964 case WIMLIB_UPDATE_OP_DELETE:
3965 cmds[i].delete_.delete_flags |= default_delete_flags;
3972 /* Execute the update commands */
3973 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3977 if (wimboot_config) {
3978 /* --wimboot-config=FILE is short for an
3979 * "add FILE /Windows/System32/WimBootCompress.ini" command.
3981 struct wimlib_update_command cmd;
3983 cmd.op = WIMLIB_UPDATE_OP_ADD;
3984 cmd.add.fs_source_path = wimboot_config;
3985 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
3986 cmd.add.config_file = NULL;
3987 cmd.add.add_flags = 0;
3989 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
3994 /* Overwrite the updated WIM */
3995 ret = wimlib_overwrite(wim, write_flags, num_threads);
3998 out_free_cmd_file_contents:
3999 free(cmd_file_contents);
4002 out_free_command_str:
4007 usage(CMD_UPDATE, stderr);
4010 goto out_free_command_str;
4013 /* Verify a WIM file. */
4015 imagex_verify(int argc, tchar **argv, int cmd)
4018 const tchar *wimfile;
4020 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4021 int verify_flags = 0;
4022 STRING_SET(refglobs);
4025 for_opt(c, verify_options) {
4027 case IMAGEX_REF_OPTION:
4028 ret = string_set_append(&refglobs, optarg);
4030 goto out_free_refglobs;
4032 case IMAGEX_NOCHECK_OPTION:
4033 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4045 imagex_error(T("Must specify a WIM file!"));
4047 imagex_error(T("At most one WIM file can be specified!"));
4053 ret = wimlib_open_wim_with_progress(wimfile,
4056 imagex_progress_func,
4059 goto out_free_refglobs;
4061 ret = wim_reference_globs(wim, &refglobs, open_flags);
4063 goto out_wimlib_free;
4065 ret = wimlib_verify_wim(wim, verify_flags);
4067 tputc(T('\n'), stderr);
4068 imagex_error(T("\"%"TS"\" failed verification!"),
4070 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4071 refglobs.num_strings == 0)
4073 imagex_printf(T("Note: if this WIM file is not standalone, "
4074 "use the --ref option to specify the other parts.\n"));
4077 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4084 string_set_destroy(&refglobs);
4088 usage(CMD_VERIFY, stderr);
4090 goto out_free_refglobs;
4093 struct imagex_command {
4095 int (*func)(int argc, tchar **argv, int cmd);
4098 static const struct imagex_command imagex_commands[] = {
4099 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4100 [CMD_APPLY] = {T("apply"), imagex_apply},
4101 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4102 [CMD_DELETE] = {T("delete"), imagex_delete},
4103 [CMD_DIR ] = {T("dir"), imagex_dir},
4104 [CMD_EXPORT] = {T("export"), imagex_export},
4105 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4106 [CMD_INFO] = {T("info"), imagex_info},
4107 [CMD_JOIN] = {T("join"), imagex_join},
4108 #if WIM_MOUNTING_SUPPORTED
4109 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4110 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4112 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4113 [CMD_SPLIT] = {T("split"), imagex_split},
4114 #if WIM_MOUNTING_SUPPORTED
4115 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4117 [CMD_UPDATE] = {T("update"), imagex_update},
4118 [CMD_VERIFY] = {T("verify"), imagex_verify},
4123 /* Can be a directory or source list file. But source list file is probably
4124 * a rare use case, so just say directory. */
4125 # define SOURCE_STR T("DIRECTORY")
4127 /* Can only be a directory */
4128 # define TARGET_STR T("DIRECTORY")
4131 /* Can be a directory, NTFS volume, or source list file. */
4132 # define SOURCE_STR T("SOURCE")
4134 /* Can be a directory or NTFS volume. */
4135 # define TARGET_STR T("TARGET")
4139 static const tchar *usage_strings[] = {
4142 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4143 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4144 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4145 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4146 " [--wimboot] [--unix-data] [--dereference]\n"
4150 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4151 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4152 " [--no-attributes] [--rpfix] [--norpfix]\n"
4153 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4157 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4158 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4159 " [--config=FILE] [--threads=NUM_THREADS]\n"
4160 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4161 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4162 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4166 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4170 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4174 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4175 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4176 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4177 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4182 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4183 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4184 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4185 " [--no-attributes] [--include-invalid-names]\n"
4186 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4190 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4191 " [--boot] [--check] [--nocheck] [--xml]\n"
4192 " [--extract-xml FILE] [--header] [--lookup-table]\n"
4196 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4198 #if WIM_MOUNTING_SUPPORTED
4201 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4202 " [--check] [--streams-interface=INTERFACE]\n"
4203 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4207 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4208 " [--check] [--streams-interface=INTERFACE]\n"
4209 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4215 " [--recompress] [--compress=TYPE]\n"
4216 " [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4221 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4223 #if WIM_MOUNTING_SUPPORTED
4226 " %"TS" DIRECTORY\n"
4227 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4232 " %"TS" WIMFILE [IMAGE]\n"
4233 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4234 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4235 " [--command=STRING] [--wimboot-config=FILE]\n"
4240 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4244 static const tchar *invocation_name;
4245 static int invocation_cmd = CMD_NONE;
4247 static const tchar *get_cmd_string(int cmd, bool nospace)
4249 static tchar buf[50];
4250 if (cmd == CMD_NONE) {
4251 tsprintf(buf, T("%"TS), T(IMAGEX_PROGNAME));
4252 } else if (invocation_cmd != CMD_NONE) {
4253 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4255 const tchar *format;
4258 format = T("%"TS"-%"TS"");
4260 format = T("%"TS" %"TS"");
4261 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4269 static const tchar *s =
4271 IMAGEX_PROGNAME " (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4272 "Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
4273 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4274 "This is free software: you are free to change and redistribute it.\n"
4275 "There is NO WARRANTY, to the extent permitted by law.\n"
4277 "Report bugs to "PACKAGE_BUGREPORT".\n"
4284 help_or_version(int argc, tchar **argv, int cmd)
4289 for (i = 1; i < argc; i++) {
4291 if (p[0] == T('-') && p[1] == T('-')) {
4293 if (!tstrcmp(p, T("help"))) {
4294 if (cmd == CMD_NONE)
4299 } else if (!tstrcmp(p, T("version"))) {
4308 print_usage_string(int cmd, FILE *fp)
4310 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4314 recommend_man_page(int cmd, FILE *fp)
4316 const tchar *format_str;
4318 format_str = T("Some uncommon options are not listed;\n"
4319 "See %"TS".pdf in the doc directory for more details.\n");
4321 format_str = T("Some uncommon options are not listed;\n"
4322 "Try `man %"TS"' for more details.\n");
4324 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4328 usage(int cmd, FILE *fp)
4330 tfprintf(fp, T("Usage:\n"));
4331 print_usage_string(cmd, fp);
4332 tfprintf(fp, T("\n"));
4333 recommend_man_page(cmd, fp);
4339 tfprintf(fp, T("Usage:\n"));
4340 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4341 print_usage_string(cmd, fp);
4342 tfprintf(fp, T("\n"));
4344 static const tchar *extra =
4347 " %"TS" --version\n"
4350 tfprintf(fp, extra, invocation_name, invocation_name);
4352 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4353 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4354 "For some commands IMAGE may be \"all\".\n"
4356 recommend_man_page(CMD_NONE, fp);
4359 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4360 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4361 * something else), while on Windows the command arguments will be UTF-16LE
4362 * encoded 'wchar_t' strings. */
4365 wmain(int argc, wchar_t **argv, wchar_t **envp)
4367 main(int argc, char **argv)
4374 imagex_info_file = stdout;
4375 invocation_name = tbasename(argv[0]);
4378 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4379 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4383 setlocale(LC_ALL, "");
4384 codeset = nl_langinfo(CODESET);
4385 if (!strstr(codeset, "UTF-8") &&
4386 !strstr(codeset, "UTF8") &&
4387 !strstr(codeset, "utf-8") &&
4388 !strstr(codeset, "utf8"))
4391 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4392 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4393 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4394 " to any value to force wimlib to use UTF-8.\n",
4400 #endif /* !__WIN32__ */
4403 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4404 if (igcase != NULL) {
4405 if (!tstrcmp(igcase, T("no")) ||
4406 !tstrcmp(igcase, T("0")))
4407 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4408 else if (!tstrcmp(igcase, T("yes")) ||
4409 !tstrcmp(igcase, T("1")))
4410 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4413 "WARNING: Ignoring unknown setting of "
4414 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4419 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4421 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4422 tstrcmp(invocation_name, T(IMAGEX_PROGNAME))) {
4423 for (int i = 0; i < CMD_MAX; i++) {
4424 if (!tstrcmp(invocation_name + 3,
4425 imagex_commands[i].name))
4434 /* Unless already known from the invocation name, determine which
4435 * command was specified. */
4436 if (cmd == CMD_NONE) {
4438 imagex_error(T("No command specified!\n"));
4442 for (int i = 0; i < CMD_MAX; i++) {
4443 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4448 if (cmd != CMD_NONE) {
4454 /* Handle --help and --version. --help can be either for the program as
4455 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4456 * CMD_NONE). Note: help_or_version() will not return if a --help or
4457 * --version argument was found. */
4458 help_or_version(argc, argv, cmd);
4460 /* Bail if a valid command was not specified. */
4461 if (cmd == CMD_NONE) {
4462 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4467 /* Enable warning and error messages in wimlib to be more user-friendly.
4469 wimlib_set_print_errors(true);
4471 /* Initialize wimlib. */
4472 ret = wimlib_global_init(init_flags);
4474 goto out_check_status;
4476 /* Call the command handler function. */
4477 ret = imagex_commands[cmd].func(argc, argv, cmd);
4479 /* Check for error writing to standard output, especially since for some
4480 * commands, writing to standard output is part of the program's actual
4481 * behavior and not just for informational purposes. */
4482 if (ferror(stdout) || fclose(stdout)) {
4483 imagex_error_with_errno(T("error writing to standard output"));
4488 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4489 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4490 * error code from which an error message can be printed. */
4492 imagex_error(T("Exiting with error code %d:\n"
4494 wimlib_get_error_string(ret));
4495 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4496 imagex_error_with_errno(T("errno"));
4498 /* Make wimlib free any resources it's holding (although this is not
4499 * strictly necessary because the process is ending anyway). */
4500 wimlib_global_cleanup();