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},
415 # define _format_attribute(type, format_str, args_start) \
416 __attribute__((format(type, format_str, args_start)))
418 # define _format_attribute(type, format_str, args_start)
421 /* Print formatted error message to stderr. */
422 static void _format_attribute(printf, 1, 2)
423 imagex_error(const tchar *format, ...)
426 va_start(va, format);
427 tfputs(T("ERROR: "), stderr);
428 tvfprintf(stderr, format, va);
429 tputc(T('\n'), stderr);
433 /* Print formatted error message to stderr. */
434 static void _format_attribute(printf, 1, 2)
435 imagex_error_with_errno(const tchar *format, ...)
437 int errno_save = errno;
439 va_start(va, format);
440 tfputs(T("ERROR: "), stderr);
441 tvfprintf(stderr, format, va);
442 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
447 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
449 if (image == WIMLIB_NO_IMAGE) {
450 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
451 " Please specify a 1-based image index or "
452 "image name. To list the images\n"
453 " contained in the WIM archive, run\n"
455 " %"TS" \"%"TS"\"\n"),
456 image_name, wim_name,
457 get_cmd_string(CMD_INFO, false), wim_name);
458 return WIMLIB_ERR_INVALID_IMAGE;
464 verify_image_is_single(int image)
466 if (image == WIMLIB_ALL_IMAGES) {
467 imagex_error(T("Cannot specify all images for this action!"));
468 return WIMLIB_ERR_INVALID_IMAGE;
474 verify_image_exists_and_is_single(int image, const tchar *image_name,
475 const tchar *wim_name)
478 ret = verify_image_exists(image, image_name, wim_name);
480 ret = verify_image_is_single(image);
485 print_available_compression_types(FILE *fp)
487 static const tchar *s =
489 "Available compression types:\n"
492 " xpress (alias: \"fast\")\n"
493 " lzx (alias: \"maximum\") (default for capture)\n"
494 " lzms (alias: \"recovery\")\n"
500 /* Parse the argument to --compress */
502 get_compression_type(tchar *optarg)
505 unsigned int compression_level = 0;
508 plevel = tstrchr(optarg, T(':'));
514 ultmp = tstrtoul(plevel, &ptmp, 10);
515 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
516 imagex_error(T("Compression level must be a positive integer! "
517 "e.g. --compress=lzx:80"));
518 return WIMLIB_COMPRESSION_TYPE_INVALID;
520 compression_level = ultmp;
523 if (!tstrcasecmp(optarg, T("maximum")) ||
524 !tstrcasecmp(optarg, T("lzx")) ||
525 !tstrcasecmp(optarg, T("max")))
526 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
527 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
528 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
529 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
530 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
531 else if (!tstrcasecmp(optarg, T("none")))
532 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
534 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
535 print_available_compression_types(stderr);
536 return WIMLIB_COMPRESSION_TYPE_INVALID;
539 if (compression_level != 0)
540 wimlib_set_default_compression_level(ctype, compression_level);
545 set_compress_slow(void)
548 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
549 " Use the '--compress=TYPE:LEVEL' option instead.\n");
551 wimlib_set_default_compression_level(-1, 100);
555 const tchar **strings;
556 unsigned num_strings;
557 unsigned num_alloc_strings;
560 #define STRING_SET_INITIALIZER \
561 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
563 #define STRING_SET(_strings) \
564 struct string_set _strings = STRING_SET_INITIALIZER
567 string_set_append(struct string_set *set, const tchar *glob)
569 unsigned num_alloc_strings = set->num_alloc_strings;
571 if (set->num_strings == num_alloc_strings) {
572 const tchar **new_strings;
574 num_alloc_strings += 4;
575 new_strings = realloc(set->strings,
576 sizeof(set->strings[0]) * num_alloc_strings);
578 imagex_error(T("Out of memory!"));
581 set->strings = new_strings;
582 set->num_alloc_strings = num_alloc_strings;
584 set->strings[set->num_strings++] = glob;
589 string_set_destroy(struct string_set *set)
595 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
597 return wimlib_reference_resource_files(wim, set->strings,
599 WIMLIB_REF_FLAG_GLOB_ENABLE,
604 do_resource_not_found_warning(const tchar *wimfile,
605 const struct wimlib_wim_info *info,
606 const struct string_set *refglobs)
608 if (info->total_parts > 1) {
609 if (refglobs->num_strings == 0) {
610 imagex_error(T("\"%"TS"\" is part of a split WIM. "
611 "Use --ref to specify the other parts."),
614 imagex_error(T("Perhaps the '--ref' argument did not "
615 "specify all other parts of the split "
619 imagex_error(T("If this is a delta WIM, use the --ref argument "
620 "to specify the WIM(s) on which it is based."));
624 /* Returns the size of a file given its name, or -1 if the file does not exist
625 * or its size cannot be determined. */
627 file_get_size(const tchar *filename)
630 if (tstat(filename, &st) == 0)
637 PARSE_STRING_SUCCESS = 0,
638 PARSE_STRING_FAILURE = 1,
639 PARSE_STRING_NONE = 2,
643 * Parses a string token from an array of characters.
645 * Tokens are either whitespace-delimited, or double or single-quoted.
647 * @line_p: Pointer to the pointer to the line of data. Will be updated
648 * to point past the string token iff the return value is
649 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
652 * @len_p: @len_p initially stores the length of the line of data, which may
653 * be 0, and it will be updated to the number of bytes remaining in
654 * the line iff the return value is PARSE_STRING_SUCCESS.
656 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
657 * parsed string token will be returned here.
659 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
660 * PARSE_STRING_FAILURE if the data was invalid due to a missing
661 * closing quote; or PARSE_STRING_NONE if the line ended before the
662 * beginning of a string token was found.
665 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
668 tchar *line = *line_p;
672 /* Skip leading whitespace */
675 return PARSE_STRING_NONE;
676 if (!istspace(*line) && *line != T('\0'))
682 if (quote_char == T('"') || quote_char == T('\'')) {
687 line = tmemchr(line, quote_char, len);
689 imagex_error(T("Missing closing quote: %"TS), fn - 1);
690 return PARSE_STRING_FAILURE;
693 /* Unquoted string. Go until whitespace. Line is terminated
694 * by '\0', so no need to check 'len'. */
698 } while (!istspace(*line) && *line != T('\0'));
705 return PARSE_STRING_SUCCESS;
708 /* Parses a line of data (not an empty line or comment) in the source list file
709 * format. (See the man page for 'wimlib-imagex capture' for details on this
710 * format and the meaning.)
712 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
713 * len == 0. The data in @line will be modified by this function call.
715 * @len: Length of the line of data.
717 * @source: On success, the capture source and target described by the line is
718 * written into this destination. Note that it will contain pointers
719 * to data in the @line array.
721 * Returns true if the line was valid; false otherwise. */
723 parse_source_list_line(tchar *line, size_t len,
724 struct wimlib_capture_source *source)
728 ret = parse_string(&line, &len, &source->fs_source_path);
729 if (ret != PARSE_STRING_SUCCESS)
731 ret = parse_string(&line, &len, &source->wim_target_path);
732 if (ret == PARSE_STRING_NONE)
733 source->wim_target_path = source->fs_source_path;
734 return ret != PARSE_STRING_FAILURE;
737 /* Returns %true if the given line of length @len > 0 is a comment or empty line
738 * in the source list file format. */
740 is_comment_line(const tchar *line, size_t len)
743 if (*line == T('#') || *line == T(';'))
745 if (!istspace(*line) && *line != T('\0'))
755 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
758 tchar *contents = *contents_p;
759 size_t nchars = *nchars_p;
762 for (i = 0; i < nchars; i++)
763 if (contents[i] == T('\n'))
766 /* Handle last line not terminated by a newline */
767 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
768 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
770 imagex_error(T("Out of memory!"));
773 contents[nchars] = T('\n');
774 *contents_p = contents;
782 /* Parses a file in the source list format. (See the man page for
783 * 'wimlib-imagex capture' for details on this format and the meaning.)
785 * @source_list_contents: Contents of the source list file. Note that this
786 * buffer will be modified to save memory allocations,
787 * and cannot be freed until the returned array of
788 * wimlib_capture_source's has also been freed.
790 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
793 * @nsources_ret: On success, the length of the returned array is
796 * Returns: An array of `struct wimlib_capture_source's that can be passed to
797 * the wimlib_add_image_multisource() function to specify how a WIM image is to
799 static struct wimlib_capture_source *
800 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
801 size_t *nsources_ret)
805 struct wimlib_capture_source *sources;
808 nlines = text_file_count_lines(source_list_contents_p,
809 &source_list_nchars);
813 /* Always allocate at least 1 slot, just in case the implementation of
814 * calloc() returns NULL if 0 bytes are requested. */
815 sources = calloc(nlines ?: 1, sizeof(*sources));
817 imagex_error(T("out of memory"));
820 p = *source_list_contents_p;
822 for (i = 0; i < nlines; i++) {
823 /* XXX: Could use rawmemchr() here instead, but it may not be
824 * available on all platforms. */
825 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
826 size_t len = endp - p + 1;
828 if (!is_comment_line(p, len)) {
829 if (!parse_source_list_line(p, len, &sources[j++])) {
841 /* Reads the contents of a file into memory. */
843 file_get_contents(const tchar *filename, size_t *len_ret)
850 if (tstat(filename, &stbuf) != 0) {
851 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
856 fp = tfopen(filename, T("rb"));
858 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
862 buf = malloc(len ? len : 1);
864 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
865 "contents of file \"%"TS"\""), len, filename);
868 if (fread(buf, 1, len, fp) != len) {
869 imagex_error_with_errno(T("Failed to read %zu bytes from the "
870 "file \"%"TS"\""), len, filename);
884 /* Read standard input until EOF and return the full contents in a malloc()ed
885 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
888 stdin_get_contents(size_t *len_ret)
890 /* stdin can, of course, be a pipe or other non-seekable file, so the
891 * total length of the data cannot be pre-determined */
893 size_t newlen = 1024;
897 char *p = realloc(buf, newlen);
898 size_t bytes_read, bytes_to_read;
900 imagex_error(T("out of memory while reading stdin"));
904 bytes_to_read = newlen - pos;
905 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
907 if (bytes_read != bytes_to_read) {
912 imagex_error_with_errno(T("error reading stdin"));
926 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
929 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
931 *num_tchars_ret = num_bytes;
933 #else /* !__WIN32__ */
934 /* On Windows, translate the text to UTF-16LE */
938 if (num_bytes >= 2 &&
939 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
940 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
942 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
943 * with something that looks like an ASCII character encoded as
944 * a UTF-16LE code unit. Assume the file is encoded as
945 * UTF-16LE. This is not a 100% reliable check. */
946 num_wchars = num_bytes / 2;
947 text_wstr = (wchar_t*)text;
949 /* File does not look like UTF-16LE. Assume it is encoded in
950 * the current Windows code page. I think these are always
951 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
952 * should work as expected. */
953 text_wstr = win32_mbs_to_wcs(text,
958 *num_tchars_ret = num_wchars;
960 #endif /* __WIN32__ */
964 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
969 contents = file_get_contents(filename, &num_bytes);
972 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
976 stdin_get_text_contents(size_t *num_tchars_ret)
981 contents = stdin_get_contents(&num_bytes);
984 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
987 #define TO_PERCENT(numerator, denominator) \
988 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
990 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
991 #define MEBIBYTE_MIN_NBYTES 10000000ULL
992 #define KIBIBYTE_MIN_NBYTES 10000ULL
995 get_unit(uint64_t total_bytes, const tchar **name_ret)
997 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
998 *name_ret = T("GiB");
1000 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1001 *name_ret = T("MiB");
1003 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1004 *name_ret = T("KiB");
1007 *name_ret = T("bytes");
1012 static struct wimlib_progress_info_scan last_scan_progress;
1015 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1017 uint64_t prev_count, cur_count;
1019 prev_count = last_scan_progress.num_nondirs_scanned +
1020 last_scan_progress.num_dirs_scanned;
1021 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1023 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1024 cur_count % 128 == 0)
1026 unsigned unit_shift;
1027 const tchar *unit_name;
1029 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1030 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1031 "%"PRIu64" directories) "),
1032 scan->num_bytes_scanned >> unit_shift,
1034 scan->num_nondirs_scanned,
1035 scan->num_dirs_scanned);
1036 last_scan_progress = *scan;
1039 /* Progress callback function passed to various wimlib functions. */
1040 static enum wimlib_progress_status
1041 imagex_progress_func(enum wimlib_progress_msg msg,
1042 union wimlib_progress_info *info,
1043 void *_ignored_context)
1045 unsigned percent_done;
1046 unsigned unit_shift;
1047 const tchar *unit_name;
1049 if (imagex_be_quiet)
1050 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1052 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1054 static bool first = true;
1056 imagex_printf(T("Writing %"TS"-compressed data "
1057 "using %u thread%"TS"\n"),
1058 wimlib_get_compression_type_string(
1059 info->write_streams.compression_type),
1060 info->write_streams.num_threads,
1061 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1065 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1066 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1067 info->write_streams.total_bytes);
1069 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1070 "written (%u%% done)"),
1071 info->write_streams.completed_bytes >> unit_shift,
1073 info->write_streams.total_bytes >> unit_shift,
1076 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1077 imagex_printf(T("\n"));
1079 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1080 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1081 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1082 imagex_printf(T("\n"));
1084 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1085 info->scan.wim_target_path);
1087 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1089 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1090 switch (info->scan.status) {
1091 case WIMLIB_SCAN_DENTRY_OK:
1092 report_scan_progress(&info->scan, false);
1094 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1095 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1097 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1098 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1099 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1101 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1102 /* Symlink fixups are enabled by default. This is
1103 * mainly intended for Windows, which for some reason
1104 * uses absolute junctions (with drive letters!) in the
1105 * default installation. On UNIX-like systems, warn the
1106 * user when fixing the target of an absolute symbolic
1107 * link, so they know to disable this if they want. */
1109 imagex_printf(T("\nWARNING: Adjusted target of "
1110 "absolute symbolic link \"%"TS"\"\n"
1111 " (Use --norpfix to capture "
1112 "absolute symbolic links as-is)\n"),
1113 info->scan.cur_path);
1120 case WIMLIB_PROGRESS_MSG_SCAN_END:
1121 report_scan_progress(&info->scan, true);
1122 imagex_printf(T("\n"));
1124 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1125 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1126 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1127 info->integrity.total_bytes);
1128 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1129 "of %"PRIu64" %"TS" (%u%%) done"),
1130 info->integrity.filename,
1131 info->integrity.completed_bytes >> unit_shift,
1133 info->integrity.total_bytes >> unit_shift,
1136 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1137 imagex_printf(T("\n"));
1139 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1140 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1141 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1142 info->integrity.total_bytes);
1143 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1144 "of %"PRIu64" %"TS" (%u%%) done"),
1145 info->integrity.completed_bytes >> unit_shift,
1147 info->integrity.total_bytes >> unit_shift,
1150 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1151 imagex_printf(T("\n"));
1153 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1154 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1155 "to %"TS" \"%"TS"\"\n"),
1156 info->extract.image,
1157 info->extract.image_name,
1158 info->extract.wimfile_name,
1159 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1160 T("NTFS volume") : T("directory")),
1161 info->extract.target);
1163 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1164 if (info->extract.end_file_count >= 2000) {
1165 percent_done = TO_PERCENT(info->extract.current_file_count,
1166 info->extract.end_file_count);
1167 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1168 info->extract.current_file_count,
1169 info->extract.end_file_count, percent_done);
1170 if (info->extract.current_file_count == info->extract.end_file_count)
1171 imagex_printf(T("\n"));
1174 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1175 percent_done = TO_PERCENT(info->extract.completed_bytes,
1176 info->extract.total_bytes);
1177 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1178 imagex_printf(T("\rExtracting files: "
1179 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1180 info->extract.completed_bytes >> unit_shift,
1182 info->extract.total_bytes >> unit_shift,
1185 if (info->extract.completed_bytes >= info->extract.total_bytes)
1186 imagex_printf(T("\n"));
1188 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1189 if (info->extract.end_file_count >= 2000) {
1190 percent_done = TO_PERCENT(info->extract.current_file_count,
1191 info->extract.end_file_count);
1192 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1193 info->extract.current_file_count,
1194 info->extract.end_file_count, percent_done);
1195 if (info->extract.current_file_count == info->extract.end_file_count)
1196 imagex_printf(T("\n"));
1199 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1200 if (info->extract.total_parts != 1) {
1201 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1202 info->extract.part_number,
1203 info->extract.total_parts);
1206 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1207 percent_done = TO_PERCENT(info->split.completed_bytes,
1208 info->split.total_bytes);
1209 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1210 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1211 "%"PRIu64" %"TS" (%u%%) written\n"),
1212 info->split.part_name,
1213 info->split.cur_part_number,
1214 info->split.total_parts,
1215 info->split.completed_bytes >> unit_shift,
1217 info->split.total_bytes >> unit_shift,
1221 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1222 if (info->split.completed_bytes == info->split.total_bytes) {
1223 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1224 info->split.cur_part_number,
1225 info->split.total_parts);
1228 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1229 switch (info->update.command->op) {
1230 case WIMLIB_UPDATE_OP_DELETE:
1231 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1232 info->update.command->delete_.wim_path);
1234 case WIMLIB_UPDATE_OP_RENAME:
1235 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1236 info->update.command->rename.wim_source_path,
1237 info->update.command->rename.wim_target_path);
1239 case WIMLIB_UPDATE_OP_ADD:
1244 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1245 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1246 info->replace.path_in_wim);
1248 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1249 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1250 info->wimboot_exclude.path_in_wim);
1252 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1253 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1254 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1255 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1256 info->unmount.mounted_wim,
1257 info->unmount.mounted_image);
1259 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1260 info->unmount.mounted_wim,
1261 info->unmount.mounted_image);
1262 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1266 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1267 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1268 info->verify_image.current_image,
1269 info->verify_image.total_images);
1271 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1272 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1273 info->verify_streams.total_bytes);
1274 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1275 imagex_printf(T("\rVerifying streams: "
1276 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1277 info->verify_streams.completed_bytes >> unit_shift,
1279 info->verify_streams.total_bytes >> unit_shift,
1282 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1283 imagex_printf(T("\n"));
1288 fflush(imagex_info_file);
1289 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1293 parse_num_threads(const tchar *optarg)
1296 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1297 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1298 imagex_error(T("Number of threads must be a non-negative integer!"));
1305 static uint32_t parse_chunk_size(const tchar *optarg)
1308 unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
1309 if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
1310 imagex_error(T("Chunk size must be a non-negative integer!"));
1319 * Parse an option passed to an update command.
1321 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1324 * @option: Text string for the option (beginning with --)
1326 * @cmd: `struct wimlib_update_command' that is being constructed for
1329 * Returns true if the option was recognized; false if not.
1332 update_command_add_option(int op, const tchar *option,
1333 struct wimlib_update_command *cmd)
1335 bool recognized = true;
1337 case WIMLIB_UPDATE_OP_ADD:
1338 if (!tstrcmp(option, T("--verbose")))
1339 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1340 else if (!tstrcmp(option, T("--unix-data")))
1341 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1342 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1343 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1344 else if (!tstrcmp(option, T("--strict-acls")))
1345 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1346 else if (!tstrcmp(option, T("--dereference")))
1347 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1348 else if (!tstrcmp(option, T("--no-replace")))
1349 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1353 case WIMLIB_UPDATE_OP_DELETE:
1354 if (!tstrcmp(option, T("--force")))
1355 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1356 else if (!tstrcmp(option, T("--recursive")))
1357 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1368 /* How many nonoption arguments each `imagex update' command expects */
1369 static const unsigned update_command_num_nonoptions[] = {
1370 [WIMLIB_UPDATE_OP_ADD] = 2,
1371 [WIMLIB_UPDATE_OP_DELETE] = 1,
1372 [WIMLIB_UPDATE_OP_RENAME] = 2,
1376 update_command_add_nonoption(int op, const tchar *nonoption,
1377 struct wimlib_update_command *cmd,
1378 unsigned num_nonoptions)
1381 case WIMLIB_UPDATE_OP_ADD:
1382 if (num_nonoptions == 0)
1383 cmd->add.fs_source_path = (tchar*)nonoption;
1385 cmd->add.wim_target_path = (tchar*)nonoption;
1387 case WIMLIB_UPDATE_OP_DELETE:
1388 cmd->delete_.wim_path = (tchar*)nonoption;
1390 case WIMLIB_UPDATE_OP_RENAME:
1391 if (num_nonoptions == 0)
1392 cmd->rename.wim_source_path = (tchar*)nonoption;
1394 cmd->rename.wim_target_path = (tchar*)nonoption;
1400 * Parse a command passed on stdin to `imagex update'.
1402 * @line: Text of the command.
1403 * @len: Length of the line, including a null terminator
1406 * @command: A `struct wimlib_update_command' to fill in from the parsed
1409 * @line_number: Line number of the command, for diagnostics.
1411 * Returns true on success; returns false on parse error.
1414 parse_update_command(tchar *line, size_t len,
1415 struct wimlib_update_command *command,
1419 tchar *command_name;
1421 size_t num_nonoptions;
1423 /* Get the command name ("add", "delete", "rename") */
1424 ret = parse_string(&line, &len, &command_name);
1425 if (ret != PARSE_STRING_SUCCESS)
1428 if (!tstrcasecmp(command_name, T("add"))) {
1429 op = WIMLIB_UPDATE_OP_ADD;
1430 } else if (!tstrcasecmp(command_name, T("delete"))) {
1431 op = WIMLIB_UPDATE_OP_DELETE;
1432 } else if (!tstrcasecmp(command_name, T("rename"))) {
1433 op = WIMLIB_UPDATE_OP_RENAME;
1435 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1436 command_name, line_number);
1441 /* Parse additional options and non-options as needed */
1446 ret = parse_string(&line, &len, &next_string);
1447 if (ret == PARSE_STRING_NONE) /* End of line */
1449 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1451 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1453 if (!update_command_add_option(op, next_string, command))
1455 imagex_error(T("Unrecognized option \"%"TS"\" to "
1456 "update command \"%"TS"\" on line %zu"),
1457 next_string, command_name, line_number);
1463 if (num_nonoptions == update_command_num_nonoptions[op])
1465 imagex_error(T("Unexpected argument \"%"TS"\" in "
1466 "update command on line %zu\n"
1467 " (The \"%"TS"\" command only "
1468 "takes %zu nonoption arguments!)\n"),
1469 next_string, line_number,
1470 command_name, num_nonoptions);
1473 update_command_add_nonoption(op, next_string,
1474 command, num_nonoptions);
1479 if (num_nonoptions != update_command_num_nonoptions[op]) {
1480 imagex_error(T("Not enough arguments to update command "
1481 "\"%"TS"\" on line %zu"), command_name, line_number);
1487 static struct wimlib_update_command *
1488 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1489 size_t *num_cmds_ret)
1493 struct wimlib_update_command *cmds;
1496 nlines = text_file_count_lines(cmd_file_contents_p,
1501 /* Always allocate at least 1 slot, just in case the implementation of
1502 * calloc() returns NULL if 0 bytes are requested. */
1503 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1505 imagex_error(T("out of memory"));
1508 p = *cmd_file_contents_p;
1510 for (i = 0; i < nlines; i++) {
1511 /* XXX: Could use rawmemchr() here instead, but it may not be
1512 * available on all platforms. */
1513 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1514 size_t len = endp - p + 1;
1516 if (!is_comment_line(p, len)) {
1517 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1528 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1529 * one image from a WIM file to an NTFS volume. */
1531 imagex_apply(int argc, tchar **argv, int cmd)
1535 int image = WIMLIB_NO_IMAGE;
1537 struct wimlib_wim_info info;
1539 const tchar *wimfile;
1540 const tchar *target;
1541 const tchar *image_num_or_name = NULL;
1542 int extract_flags = 0;
1544 STRING_SET(refglobs);
1546 for_opt(c, apply_options) {
1548 case IMAGEX_CHECK_OPTION:
1549 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1551 case IMAGEX_VERBOSE_OPTION:
1552 /* No longer does anything. */
1554 case IMAGEX_REF_OPTION:
1555 ret = string_set_append(&refglobs, optarg);
1557 goto out_free_refglobs;
1559 case IMAGEX_UNIX_DATA_OPTION:
1560 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1562 case IMAGEX_NO_ACLS_OPTION:
1563 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1565 case IMAGEX_STRICT_ACLS_OPTION:
1566 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1568 case IMAGEX_NO_ATTRIBUTES_OPTION:
1569 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1571 case IMAGEX_NORPFIX_OPTION:
1572 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1574 case IMAGEX_RPFIX_OPTION:
1575 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1577 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1578 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1579 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1581 case IMAGEX_RESUME_OPTION:
1582 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1584 case IMAGEX_WIMBOOT_OPTION:
1585 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1593 if (argc != 2 && argc != 3)
1598 if (!tstrcmp(wimfile, T("-"))) {
1599 /* Attempt to apply pipable WIM from standard input. */
1601 image_num_or_name = NULL;
1604 image_num_or_name = argv[1];
1609 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1610 imagex_progress_func, NULL);
1612 goto out_free_refglobs;
1614 wimlib_get_wim_info(wim, &info);
1617 /* Image explicitly specified. */
1618 image_num_or_name = argv[1];
1619 image = wimlib_resolve_image(wim, image_num_or_name);
1620 ret = verify_image_exists(image, image_num_or_name, wimfile);
1622 goto out_wimlib_free;
1625 /* No image specified; default to image 1, but only if the WIM
1626 * contains exactly one image. */
1628 if (info.image_count != 1) {
1629 imagex_error(T("\"%"TS"\" contains %d images; "
1630 "Please select one (or all)."),
1631 wimfile, info.image_count);
1640 if (refglobs.num_strings) {
1642 imagex_error(T("Can't specify --ref when applying from stdin!"));
1644 goto out_wimlib_free;
1646 ret = wim_reference_globs(wim, &refglobs, open_flags);
1648 goto out_wimlib_free;
1653 /* Interpret a regular file or block device target as an NTFS
1657 if (tstat(target, &stbuf)) {
1658 if (errno != ENOENT) {
1659 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1662 goto out_wimlib_free;
1665 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1666 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1672 ret = wimlib_extract_image(wim, image, target, extract_flags);
1674 set_fd_to_binary_mode(STDIN_FILENO);
1675 ret = wimlib_extract_image_from_pipe_with_progress(
1680 imagex_progress_func,
1684 imagex_printf(T("Done applying WIM image.\n"));
1685 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1687 do_resource_not_found_warning(wimfile, &info, &refglobs);
1689 imagex_error(T( "If you are applying an image "
1690 "from a split pipable WIM,\n"
1691 " make sure you have "
1692 "concatenated together all parts."));
1698 string_set_destroy(&refglobs);
1702 usage(CMD_APPLY, stderr);
1704 goto out_free_refglobs;
1707 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1708 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1709 * the desired image. 'wimlib-imagex append': add a new image to an existing
1712 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1715 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
1716 int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE |
1717 WIMLIB_ADD_IMAGE_FLAG_WINCONFIG |
1718 WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1719 int write_flags = 0;
1720 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1721 uint32_t chunk_size = UINT32_MAX;
1722 uint32_t solid_chunk_size = UINT32_MAX;
1723 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1724 const tchar *wimfile;
1728 const tchar *flags_element = NULL;
1731 STRING_SET(base_wimfiles);
1732 WIMStruct **base_wims;
1734 WIMStruct *template_wim;
1735 const tchar *template_wimfile = NULL;
1736 const tchar *template_image_name_or_num = NULL;
1737 int template_image = WIMLIB_NO_IMAGE;
1740 unsigned num_threads = 0;
1745 tchar *config_file = NULL;
1747 bool source_list = false;
1748 size_t source_list_nchars = 0;
1749 tchar *source_list_contents;
1750 bool capture_sources_malloced;
1751 struct wimlib_capture_source *capture_sources;
1753 bool name_defaulted;
1755 for_opt(c, capture_or_append_options) {
1757 case IMAGEX_BOOT_OPTION:
1758 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
1760 case IMAGEX_CHECK_OPTION:
1761 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1762 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1764 case IMAGEX_NOCHECK_OPTION:
1765 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1767 case IMAGEX_CONFIG_OPTION:
1768 config_file = optarg;
1769 add_image_flags &= ~WIMLIB_ADD_IMAGE_FLAG_WINCONFIG;
1771 case IMAGEX_COMPRESS_OPTION:
1772 compression_type = get_compression_type(optarg);
1773 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1776 case IMAGEX_COMPRESS_SLOW_OPTION:
1777 set_compress_slow();
1779 case IMAGEX_CHUNK_SIZE_OPTION:
1780 chunk_size = parse_chunk_size(optarg);
1781 if (chunk_size == UINT32_MAX)
1784 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1785 solid_chunk_size = parse_chunk_size(optarg);
1786 if (solid_chunk_size == UINT32_MAX)
1789 case IMAGEX_SOLID_COMPRESS_OPTION:
1790 solid_ctype = get_compression_type(optarg);
1791 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1794 case IMAGEX_SOLID_OPTION:
1795 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
1797 case IMAGEX_FLAGS_OPTION:
1798 flags_element = optarg;
1800 case IMAGEX_DEREFERENCE_OPTION:
1801 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1803 case IMAGEX_VERBOSE_OPTION:
1804 /* No longer does anything. */
1806 case IMAGEX_THREADS_OPTION:
1807 num_threads = parse_num_threads(optarg);
1808 if (num_threads == UINT_MAX)
1811 case IMAGEX_REBUILD_OPTION:
1812 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1814 case IMAGEX_UNIX_DATA_OPTION:
1815 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1817 case IMAGEX_SOURCE_LIST_OPTION:
1820 case IMAGEX_NO_ACLS_OPTION:
1821 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1823 case IMAGEX_STRICT_ACLS_OPTION:
1824 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1826 case IMAGEX_RPFIX_OPTION:
1827 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_RPFIX;
1829 case IMAGEX_NORPFIX_OPTION:
1830 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NORPFIX;
1832 case IMAGEX_PIPABLE_OPTION:
1833 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1835 case IMAGEX_NOT_PIPABLE_OPTION:
1836 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1838 case IMAGEX_UPDATE_OF_OPTION:
1839 if (template_image_name_or_num) {
1840 imagex_error(T("'--update-of' can only be "
1841 "specified one time!"));
1845 colon = tstrrchr(optarg, T(':'));
1848 template_wimfile = optarg;
1850 template_image_name_or_num = colon + 1;
1852 template_wimfile = NULL;
1853 template_image_name_or_num = optarg;
1857 case IMAGEX_DELTA_FROM_OPTION:
1858 if (cmd != CMD_CAPTURE) {
1859 imagex_error(T("'--delta-from' is only "
1860 "valid for capture!"));
1863 ret = string_set_append(&base_wimfiles, optarg);
1865 goto out_free_base_wimfiles;
1866 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1868 case IMAGEX_WIMBOOT_OPTION:
1869 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_WIMBOOT;
1878 if (argc < 2 || argc > 4)
1884 /* Set default compression type and parameters. */
1887 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1888 /* No compression type specified. Use the default. */
1890 if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) {
1891 /* With --wimboot, default to XPRESS compression. */
1892 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1893 } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
1894 /* With --solid, default to LZMS compression. (However,
1895 * this will not affect solid blocks!) */
1896 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1898 /* Otherwise, default to LZX compression. */
1899 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1903 if (!tstrcmp(wimfile, T("-"))) {
1904 /* Writing captured WIM to standard output. */
1906 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1907 imagex_error("Can't write a non-pipable WIM to "
1908 "standard output! Specify --pipable\n"
1909 " if you want to create a pipable WIM "
1910 "(but read the docs first).");
1914 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1916 if (cmd == CMD_APPEND) {
1917 imagex_error(T("Using standard output for append does "
1918 "not make sense."));
1921 wim_fd = STDOUT_FILENO;
1923 imagex_info_file = stderr;
1924 set_fd_to_binary_mode(wim_fd);
1927 /* If template image was specified using --update-of=IMAGE rather
1928 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1929 if (template_image_name_or_num && !template_wimfile) {
1930 if (base_wimfiles.num_strings == 1) {
1931 /* Capturing delta WIM based on single WIM: default to
1933 template_wimfile = base_wimfiles.strings[0];
1934 } else if (cmd == CMD_APPEND) {
1935 /* Appending to WIM: default to WIM being appended to.
1937 template_wimfile = wimfile;
1939 /* Capturing a normal (non-delta) WIM, so the WIM file
1940 * *must* be explicitly specified. */
1941 if (base_wimfiles.num_strings > 1) {
1942 imagex_error(T("For capture of delta WIM "
1943 "based on multiple existing "
1945 " '--update-of' must "
1946 "specify WIMFILE:IMAGE!"));
1948 imagex_error(T("For capture of non-delta WIM, "
1949 "'--update-of' must specify "
1958 name_defaulted = false;
1960 /* Set default name to SOURCE argument, omitting any directory
1961 * prefixes and trailing slashes. This requires making a copy
1962 * of @source. Leave some free characters at the end in case we
1963 * append a number to keep the name unique. */
1964 size_t source_name_len;
1966 source_name_len = tstrlen(source);
1967 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1968 name = tbasename(tstrcpy(source_copy, source));
1969 name_defaulted = true;
1971 /* Image description defaults to NULL if not given. */
1978 /* Set up capture sources in source list mode */
1979 if (source[0] == T('-') && source[1] == T('\0')) {
1980 source_list_contents = stdin_get_text_contents(&source_list_nchars);
1982 source_list_contents = file_get_text_contents(source,
1983 &source_list_nchars);
1985 if (!source_list_contents)
1988 capture_sources = parse_source_list(&source_list_contents,
1991 if (!capture_sources) {
1993 goto out_free_source_list_contents;
1995 capture_sources_malloced = true;
1997 /* Set up capture source in non-source-list mode. */
1998 capture_sources = alloca(sizeof(struct wimlib_capture_source));
1999 capture_sources[0].fs_source_path = source;
2000 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2001 capture_sources[0].reserved = 0;
2003 capture_sources_malloced = false;
2004 source_list_contents = NULL;
2007 /* Open the existing WIM, or create a new one. */
2008 if (cmd == CMD_APPEND) {
2009 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2010 imagex_progress_func, NULL);
2012 goto out_free_capture_sources;
2014 ret = wimlib_create_new_wim(compression_type, &wim);
2016 goto out_free_capture_sources;
2017 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2020 /* Set chunk size if non-default. */
2021 if (chunk_size != UINT32_MAX) {
2022 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2025 } else if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) &&
2026 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2027 ret = wimlib_set_output_chunk_size(wim, 4096);
2031 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2032 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2036 if (solid_chunk_size != UINT32_MAX) {
2037 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2043 /* Detect if source is regular file or block device and set NTFS volume
2048 if (tstat(source, &stbuf) == 0) {
2049 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2050 imagex_printf(T("Capturing WIM image from NTFS "
2051 "filesystem on \"%"TS"\"\n"), source);
2052 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
2055 if (errno != ENOENT) {
2056 imagex_error_with_errno(T("Failed to stat "
2057 "\"%"TS"\""), source);
2065 /* If the user did not specify an image name, and the basename of the
2066 * source already exists as an image name in the WIM file, append a
2067 * suffix to make it unique. */
2068 if (cmd == CMD_APPEND && name_defaulted) {
2069 unsigned long conflict_idx;
2070 tchar *name_end = tstrchr(name, T('\0'));
2071 for (conflict_idx = 1;
2072 wimlib_image_name_in_use(wim, name);
2075 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2079 /* If capturing a delta WIM, reference resources from the base WIMs
2080 * before adding the new image. */
2081 if (base_wimfiles.num_strings) {
2082 base_wims = calloc(base_wimfiles.num_strings,
2083 sizeof(base_wims[0]));
2084 if (base_wims == NULL) {
2085 imagex_error(T("Out of memory!"));
2090 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2091 ret = wimlib_open_wim_with_progress(
2092 base_wimfiles.strings[i],
2093 open_flags, &base_wims[i],
2094 imagex_progress_func, NULL);
2096 goto out_free_base_wims;
2100 ret = wimlib_reference_resources(wim, base_wims,
2101 base_wimfiles.num_strings, 0);
2103 goto out_free_base_wims;
2105 if (base_wimfiles.num_strings == 1) {
2106 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2107 base_wimfiles.strings[0]);
2109 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2110 base_wimfiles.num_strings);
2117 /* If capturing or appending as an update of an existing (template) image,
2118 * open the WIM if needed and parse the image index. */
2119 if (template_image_name_or_num) {
2122 if (base_wimfiles.num_strings == 1 &&
2123 template_wimfile == base_wimfiles.strings[0]) {
2124 template_wim = base_wims[0];
2125 } else if (template_wimfile == wimfile) {
2128 ret = wimlib_open_wim_with_progress(template_wimfile,
2131 imagex_progress_func,
2134 goto out_free_base_wims;
2137 template_image = wimlib_resolve_image(template_wim,
2138 template_image_name_or_num);
2140 if (template_image_name_or_num[0] == T('-')) {
2143 struct wimlib_wim_info info;
2145 wimlib_get_wim_info(template_wim, &info);
2146 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2147 if (n >= 1 && n <= info.image_count &&
2149 tmp != template_image_name_or_num + 1)
2151 template_image = info.image_count - (n - 1);
2154 ret = verify_image_exists_and_is_single(template_image,
2155 template_image_name_or_num,
2158 goto out_free_template_wim;
2160 template_wim = NULL;
2163 ret = wimlib_add_image_multisource(wim,
2170 goto out_free_template_wim;
2172 if (desc || flags_element || template_image_name_or_num) {
2173 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2174 * on which the added one is to be based has been specified with
2175 * --update-of. Get the index of the image we just
2176 * added, then use it to call the appropriate functions. */
2177 struct wimlib_wim_info info;
2179 wimlib_get_wim_info(wim, &info);
2182 ret = wimlib_set_image_descripton(wim,
2186 goto out_free_template_wim;
2189 if (flags_element) {
2190 ret = wimlib_set_image_flags(wim, info.image_count,
2193 goto out_free_template_wim;
2196 /* Reference template image if the user provided one. */
2197 if (template_image_name_or_num) {
2198 imagex_printf(T("Using image %d "
2199 "from \"%"TS"\" as template\n"),
2200 template_image, template_wimfile);
2201 ret = wimlib_reference_template_image(wim,
2207 goto out_free_template_wim;
2211 /* Write the new WIM or overwrite the existing WIM with the new image
2213 if (cmd == CMD_APPEND) {
2214 ret = wimlib_overwrite(wim, write_flags, num_threads);
2215 } else if (wimfile) {
2216 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2217 write_flags, num_threads);
2219 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2220 write_flags, num_threads);
2222 out_free_template_wim:
2223 /* template_wim may alias base_wims[0] or wim. */
2224 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2225 template_wim != wim)
2226 wimlib_free(template_wim);
2228 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2229 wimlib_free(base_wims[i]);
2233 out_free_capture_sources:
2234 if (capture_sources_malloced)
2235 free(capture_sources);
2236 out_free_source_list_contents:
2237 free(source_list_contents);
2238 out_free_base_wimfiles:
2239 string_set_destroy(&base_wimfiles);
2246 goto out_free_base_wimfiles;
2249 /* Remove image(s) from a WIM. */
2251 imagex_delete(int argc, tchar **argv, int cmd)
2254 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2255 int write_flags = 0;
2256 const tchar *wimfile;
2257 const tchar *image_num_or_name;
2262 for_opt(c, delete_options) {
2264 case IMAGEX_CHECK_OPTION:
2265 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2266 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2268 case IMAGEX_SOFT_OPTION:
2269 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2280 imagex_error(T("Must specify a WIM file"));
2282 imagex_error(T("Must specify an image"));
2286 image_num_or_name = argv[1];
2288 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2289 imagex_progress_func, NULL);
2293 image = wimlib_resolve_image(wim, image_num_or_name);
2295 ret = verify_image_exists(image, image_num_or_name, wimfile);
2297 goto out_wimlib_free;
2299 ret = wimlib_delete_image(wim, image);
2301 imagex_error(T("Failed to delete image from \"%"TS"\""),
2303 goto out_wimlib_free;
2306 ret = wimlib_overwrite(wim, write_flags, 0);
2308 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2309 "deleted"), wimfile);
2317 usage(CMD_DELETE, stderr);
2322 struct print_dentry_options {
2327 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2329 tprintf(T("%"TS"\n"), dentry->full_path);
2332 static const struct {
2335 } file_attr_flags[] = {
2336 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2337 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2338 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2339 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2340 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2341 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2342 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2343 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2344 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2345 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2346 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2347 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2348 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2349 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2350 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2353 #define TIMESTR_MAX 100
2356 timespec_to_string(const struct timespec *spec, tchar *buf)
2358 time_t t = spec->tv_sec;
2361 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2362 buf[TIMESTR_MAX - 1] = '\0';
2366 print_time(const tchar *type, const struct timespec *spec)
2368 tchar timestr[TIMESTR_MAX];
2370 timespec_to_string(spec, timestr);
2372 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2375 static void print_byte_field(const uint8_t field[], size_t len)
2378 tprintf(T("%02hhx"), *field++);
2382 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2384 tputs(T("WIM Information:"));
2385 tputs(T("----------------"));
2386 tprintf(T("Path: %"TS"\n"), wimfile);
2387 tprintf(T("GUID: 0x"));
2388 print_byte_field(info->guid, sizeof(info->guid));
2390 tprintf(T("Version: %u\n"), info->wim_version);
2391 tprintf(T("Image Count: %d\n"), info->image_count);
2392 tprintf(T("Compression: %"TS"\n"),
2393 wimlib_get_compression_type_string(info->compression_type));
2394 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2396 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2397 tprintf(T("Boot Index: %d\n"), info->boot_index);
2398 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2399 tprintf(T("Integrity Info: %"TS"\n"),
2400 info->has_integrity_table ? T("yes") : T("no"));
2401 tprintf(T("Relative path junction: %"TS"\n"),
2402 info->has_rpfix ? T("yes") : T("no"));
2403 tprintf(T("Pipable: %"TS"\n"),
2404 info->pipable ? T("yes") : T("no"));
2409 print_resource(const struct wimlib_resource_entry *resource,
2412 tprintf(T("Hash = 0x"));
2413 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2416 if (!resource->is_missing) {
2417 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2418 resource->uncompressed_size);
2419 if (resource->packed) {
2420 tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2421 resource->raw_resource_compressed_size);
2423 tprintf(T("Raw offset in WIM = %"PRIu64" bytes\n"),
2424 resource->raw_resource_offset_in_wim);
2426 tprintf(T("Offset in raw = %"PRIu64" bytes\n"),
2429 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2430 resource->compressed_size);
2432 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2436 tprintf(T("Part Number = %u\n"), resource->part_number);
2437 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2439 tprintf(T("Flags = "));
2440 if (resource->is_compressed)
2441 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2442 if (resource->is_metadata)
2443 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2444 if (resource->is_free)
2445 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2446 if (resource->is_spanned)
2447 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2448 if (resource->packed)
2449 tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS "));
2457 print_lookup_table(WIMStruct *wim)
2459 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2463 default_print_security_descriptor(const uint8_t *sd, size_t size)
2465 tprintf(T("Security Descriptor = "));
2466 print_byte_field(sd, size);
2471 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2475 "----------------------------------------------------------------------------\n"));
2476 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2477 if (dentry->dos_name)
2478 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2479 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2480 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2481 if (file_attr_flags[i].flag & dentry->attributes)
2482 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2483 file_attr_flags[i].name);
2485 if (dentry->security_descriptor) {
2486 print_security_descriptor(dentry->security_descriptor,
2487 dentry->security_descriptor_size);
2490 print_time(T("Creation Time"), &dentry->creation_time);
2491 print_time(T("Last Write Time"), &dentry->last_write_time);
2492 print_time(T("Last Access Time"), &dentry->last_access_time);
2495 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2496 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2498 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2499 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2501 if (dentry->unix_mode != 0) {
2502 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2503 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2504 dentry->unix_uid, dentry->unix_gid,
2505 dentry->unix_mode, dentry->unix_rdev);
2508 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2509 if (dentry->streams[i].stream_name) {
2510 tprintf(T("\tData stream \"%"TS"\":\n"),
2511 dentry->streams[i].stream_name);
2513 tprintf(T("\tUnnamed data stream:\n"));
2515 print_resource(&dentry->streams[i].resource, NULL);
2520 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2522 const struct print_dentry_options *options = _options;
2523 if (!options->detailed)
2524 print_dentry_full_path(dentry);
2526 print_dentry_detailed(dentry);
2530 /* Print the files contained in an image(s) in a WIM file. */
2532 imagex_dir(int argc, tchar **argv, int cmd)
2534 const tchar *wimfile;
2535 WIMStruct *wim = NULL;
2538 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2540 struct print_dentry_options options = {
2543 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2545 for_opt(c, dir_options) {
2547 case IMAGEX_PATH_OPTION:
2550 case IMAGEX_DETAILED_OPTION:
2551 options.detailed = true;
2553 case IMAGEX_ONE_FILE_ONLY_OPTION:
2554 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2564 imagex_error(T("Must specify a WIM file"));
2568 imagex_error(T("Too many arguments"));
2573 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2574 imagex_progress_func, NULL);
2579 image = wimlib_resolve_image(wim, argv[1]);
2580 ret = verify_image_exists(image, argv[1], wimfile);
2582 goto out_wimlib_free;
2584 /* No image specified; default to image 1, but only if the WIM
2585 * contains exactly one image. */
2587 struct wimlib_wim_info info;
2589 wimlib_get_wim_info(wim, &info);
2590 if (info.image_count != 1) {
2591 imagex_error(T("\"%"TS"\" contains %d images; Please "
2592 "select one (or all)."),
2593 wimfile, info.image_count);
2600 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2601 print_dentry, &options);
2608 usage(CMD_DIR, stderr);
2613 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2616 imagex_export(int argc, tchar **argv, int cmd)
2620 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2621 int write_flags = 0;
2622 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2623 const tchar *src_wimfile;
2624 const tchar *src_image_num_or_name;
2625 const tchar *dest_wimfile;
2627 const tchar *dest_name;
2628 const tchar *dest_desc;
2630 struct wimlib_wim_info src_info;
2631 WIMStruct *dest_wim;
2636 STRING_SET(refglobs);
2637 unsigned num_threads = 0;
2638 uint32_t chunk_size = UINT32_MAX;
2639 uint32_t solid_chunk_size = UINT32_MAX;
2640 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2642 for_opt(c, export_options) {
2644 case IMAGEX_BOOT_OPTION:
2645 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2647 case IMAGEX_CHECK_OPTION:
2648 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2649 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2651 case IMAGEX_NOCHECK_OPTION:
2652 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2654 case IMAGEX_COMPRESS_OPTION:
2655 compression_type = get_compression_type(optarg);
2656 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2659 case IMAGEX_COMPRESS_SLOW_OPTION:
2660 set_compress_slow();
2661 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2663 case IMAGEX_RECOMPRESS_OPTION:
2664 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2666 case IMAGEX_SOLID_OPTION:
2667 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
2669 case IMAGEX_CHUNK_SIZE_OPTION:
2670 chunk_size = parse_chunk_size(optarg);
2671 if (chunk_size == UINT32_MAX)
2674 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2675 solid_chunk_size = parse_chunk_size(optarg);
2676 if (solid_chunk_size == UINT32_MAX)
2679 case IMAGEX_SOLID_COMPRESS_OPTION:
2680 solid_ctype = get_compression_type(optarg);
2681 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2684 case IMAGEX_REF_OPTION:
2685 ret = string_set_append(&refglobs, optarg);
2687 goto out_free_refglobs;
2689 case IMAGEX_THREADS_OPTION:
2690 num_threads = parse_num_threads(optarg);
2691 if (num_threads == UINT_MAX)
2694 case IMAGEX_REBUILD_OPTION:
2695 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2697 case IMAGEX_PIPABLE_OPTION:
2698 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2700 case IMAGEX_NOT_PIPABLE_OPTION:
2701 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2703 case IMAGEX_WIMBOOT_OPTION:
2704 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2712 if (argc < 3 || argc > 5)
2714 src_wimfile = argv[0];
2715 src_image_num_or_name = argv[1];
2716 dest_wimfile = argv[2];
2717 dest_name = (argc >= 4) ? argv[3] : NULL;
2718 dest_desc = (argc >= 5) ? argv[4] : NULL;
2719 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2720 imagex_progress_func, NULL);
2722 goto out_free_refglobs;
2724 wimlib_get_wim_info(src_wim, &src_info);
2726 /* Determine if the destination is an existing file or not. If so, we
2727 * try to append the exported image(s) to it; otherwise, we create a new
2728 * WIM containing the exported image(s). Furthermore, determine if we
2729 * need to write a pipable WIM directly to standard output. */
2731 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2733 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2734 imagex_error("Can't write a non-pipable WIM to "
2735 "standard output! Specify --pipable\n"
2736 " if you want to create a pipable WIM "
2737 "(but read the docs first).");
2739 goto out_free_src_wim;
2742 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2744 dest_wimfile = NULL;
2745 dest_wim_fd = STDOUT_FILENO;
2746 imagex_info_file = stderr;
2747 set_fd_to_binary_mode(dest_wim_fd);
2750 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2752 /* Destination file exists. */
2754 if (!S_ISREG(stbuf.st_mode)) {
2755 imagex_error(T("\"%"TS"\" is not a regular file"),
2758 goto out_free_src_wim;
2760 ret = wimlib_open_wim_with_progress(dest_wimfile,
2762 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2764 imagex_progress_func,
2767 goto out_free_src_wim;
2769 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2770 /* The user specified a compression type, but we're
2771 * exporting to an existing WIM. Make sure the
2772 * specified compression type is the same as the
2773 * compression type of the existing destination WIM. */
2774 struct wimlib_wim_info dest_info;
2776 wimlib_get_wim_info(dest_wim, &dest_info);
2777 if (compression_type != dest_info.compression_type) {
2778 imagex_error(T("Cannot specify a compression type that is "
2779 "not the same as that used in the "
2780 "destination WIM"));
2782 goto out_free_dest_wim;
2788 if (errno != ENOENT) {
2789 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2792 goto out_free_src_wim;
2795 /* dest_wimfile is not an existing file, so create a new WIM. */
2797 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2798 /* The user did not specify a compression type; default
2799 * to that of the source WIM, unless --solid or
2800 * --wimboot was specified. */
2802 if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
2803 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2804 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2805 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2807 compression_type = src_info.compression_type;
2809 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2811 goto out_free_src_wim;
2813 wimlib_register_progress_function(dest_wim,
2814 imagex_progress_func, NULL);
2816 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2817 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2819 /* For --wimboot export, use small XPRESS chunks. */
2820 wimlib_set_output_chunk_size(dest_wim, 4096);
2821 } else if (compression_type == src_info.compression_type &&
2822 chunk_size == UINT32_MAX)
2824 /* Use same chunk size if compression type is the same. */
2825 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2829 if (chunk_size != UINT32_MAX) {
2830 /* Set destination chunk size. */
2831 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2833 goto out_free_dest_wim;
2835 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2836 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2838 goto out_free_dest_wim;
2840 if (solid_chunk_size != UINT32_MAX) {
2841 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2843 goto out_free_dest_wim;
2846 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2847 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2849 goto out_free_dest_wim;
2851 if (refglobs.num_strings) {
2852 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2854 goto out_free_dest_wim;
2857 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2858 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2860 imagex_error(T("--boot specified for all-images export, but source WIM "
2861 "has no bootable image."));
2863 goto out_free_dest_wim;
2866 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2867 dest_desc, export_flags);
2869 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2870 do_resource_not_found_warning(src_wimfile,
2871 &src_info, &refglobs);
2873 goto out_free_dest_wim;
2877 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2878 else if (dest_wimfile)
2879 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2880 write_flags, num_threads);
2882 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2883 WIMLIB_ALL_IMAGES, write_flags,
2886 wimlib_free(dest_wim);
2888 wimlib_free(src_wim);
2890 string_set_destroy(&refglobs);
2894 usage(CMD_EXPORT, stderr);
2897 goto out_free_refglobs;
2900 /* Extract files or directories from a WIM image */
2902 imagex_extract(int argc, tchar **argv, int cmd)
2909 const tchar *wimfile;
2910 const tchar *image_num_or_name;
2911 tchar *dest_dir = T(".");
2912 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2913 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2914 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2915 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2917 STRING_SET(refglobs);
2919 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2921 for_opt(c, extract_options) {
2923 case IMAGEX_CHECK_OPTION:
2924 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2926 case IMAGEX_VERBOSE_OPTION:
2927 /* No longer does anything. */
2929 case IMAGEX_REF_OPTION:
2930 ret = string_set_append(&refglobs, optarg);
2932 goto out_free_refglobs;
2934 case IMAGEX_UNIX_DATA_OPTION:
2935 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2937 case IMAGEX_NO_ACLS_OPTION:
2938 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2940 case IMAGEX_STRICT_ACLS_OPTION:
2941 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2943 case IMAGEX_NO_ATTRIBUTES_OPTION:
2944 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2946 case IMAGEX_DEST_DIR_OPTION:
2949 case IMAGEX_TO_STDOUT_OPTION:
2950 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2951 imagex_info_file = stderr;
2952 imagex_be_quiet = true;
2953 set_fd_to_binary_mode(STDOUT_FILENO);
2955 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2956 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2957 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2959 case IMAGEX_NO_GLOBS_OPTION:
2960 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2962 case IMAGEX_NULLGLOB_OPTION:
2963 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2965 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2966 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2968 case IMAGEX_WIMBOOT_OPTION:
2969 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
2981 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2982 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
2984 imagex_error(T("Can't combine --no-globs and --nullglob!"));
2989 image_num_or_name = argv[1];
2994 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2995 imagex_progress_func, NULL);
2997 goto out_free_refglobs;
2999 image = wimlib_resolve_image(wim, image_num_or_name);
3000 ret = verify_image_exists_and_is_single(image,
3004 goto out_wimlib_free;
3006 if (refglobs.num_strings) {
3007 ret = wim_reference_globs(wim, &refglobs, open_flags);
3009 goto out_wimlib_free;
3015 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3018 while (argc != 0 && ret == 0) {
3022 num_paths < argc && argv[num_paths][0] != T('@');
3027 ret = wimlib_extract_paths(wim, image, dest_dir,
3028 (const tchar **)argv,
3030 extract_flags | notlist_extract_flags);
3034 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3043 if (!imagex_be_quiet)
3044 imagex_printf(T("Done extracting files.\n"));
3045 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3046 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3047 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3048 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3049 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3052 T("Note: You can use the '--nullglob' "
3053 "option to ignore missing files.\n"));
3055 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3056 "files and directories\n"
3057 " are in the WIM image.\n"),
3058 get_cmd_string(CMD_DIR, false));
3059 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3060 struct wimlib_wim_info info;
3062 wimlib_get_wim_info(wim, &info);
3063 do_resource_not_found_warning(wimfile, &info, &refglobs);
3068 string_set_destroy(&refglobs);
3072 usage(CMD_EXTRACT, stderr);
3075 goto out_free_refglobs;
3078 /* Prints information about a WIM file; also can mark an image as bootable,
3079 * change the name of an image, or change the description of an image. */
3081 imagex_info(int argc, tchar **argv, int cmd)
3086 bool nocheck = false;
3087 bool header = false;
3088 bool lookup_table = false;
3090 bool short_header = true;
3091 const tchar *xml_out_file = NULL;
3092 const tchar *wimfile;
3093 const tchar *image_num_or_name;
3094 const tchar *new_name;
3095 const tchar *new_desc;
3100 struct wimlib_wim_info info;
3102 for_opt(c, info_options) {
3104 case IMAGEX_BOOT_OPTION:
3107 case IMAGEX_CHECK_OPTION:
3110 case IMAGEX_NOCHECK_OPTION:
3113 case IMAGEX_HEADER_OPTION:
3115 short_header = false;
3117 case IMAGEX_LOOKUP_TABLE_OPTION:
3118 lookup_table = true;
3119 short_header = false;
3121 case IMAGEX_XML_OPTION:
3123 short_header = false;
3125 case IMAGEX_EXTRACT_XML_OPTION:
3126 xml_out_file = optarg;
3127 short_header = false;
3129 case IMAGEX_METADATA_OPTION:
3130 imagex_error(T("The --metadata option has been removed. "
3131 "Use 'wimdir --detail' instead."));
3140 if (argc < 1 || argc > 4)
3144 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3145 new_name = (argc >= 3) ? argv[2] : NULL;
3146 new_desc = (argc >= 4) ? argv[3] : NULL;
3148 if (check && nocheck) {
3149 imagex_error(T("Can't specify both --check and --nocheck"));
3154 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3156 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3157 imagex_progress_func, NULL);
3161 wimlib_get_wim_info(wim, &info);
3163 image = wimlib_resolve_image(wim, image_num_or_name);
3164 ret = WIMLIB_ERR_INVALID_IMAGE;
3165 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3166 verify_image_exists(image, image_num_or_name, wimfile);
3168 imagex_error(T("If you would like to set the boot "
3169 "index to 0, specify image \"0\" with "
3170 "the --boot flag."));
3172 goto out_wimlib_free;
3175 if (boot && info.image_count == 0) {
3176 imagex_error(T("--boot is meaningless on a WIM with no images"));
3177 goto out_wimlib_free;
3180 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3182 imagex_error(T("Cannot specify the --boot flag "
3183 "without specifying a specific "
3184 "image in a multi-image WIM"));
3185 goto out_wimlib_free;
3188 imagex_error(T("Cannot specify the NEW_NAME "
3189 "without specifying a specific "
3190 "image in a multi-image WIM"));
3191 goto out_wimlib_free;
3195 /* Operations that print information are separated from operations that
3196 * recreate the WIM file. */
3197 if (!new_name && !boot) {
3199 /* Read-only operations */
3201 if (image == WIMLIB_NO_IMAGE) {
3202 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3203 image_num_or_name, wimfile);
3204 goto out_wimlib_free;
3207 if (image == WIMLIB_ALL_IMAGES && short_header)
3208 print_wim_information(wimfile, &info);
3211 wimlib_print_header(wim);
3214 if (info.total_parts != 1) {
3215 tfprintf(stderr, T("Warning: Only showing the lookup table "
3216 "for part %d of a %d-part WIM.\n"),
3217 info.part_number, info.total_parts);
3219 print_lookup_table(wim);
3223 ret = wimlib_extract_xml_data(wim, stdout);
3225 goto out_wimlib_free;
3231 fp = tfopen(xml_out_file, T("wb"));
3233 imagex_error_with_errno(T("Failed to open the "
3234 "file \"%"TS"\" for "
3238 goto out_wimlib_free;
3240 ret = wimlib_extract_xml_data(wim, fp);
3242 imagex_error(T("Failed to close the file "
3248 goto out_wimlib_free;
3252 wimlib_print_available_images(wim, image);
3257 /* Modification operations */
3259 if (image == WIMLIB_ALL_IMAGES)
3262 if (image == WIMLIB_NO_IMAGE && new_name) {
3263 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3264 "when using image 0"), new_name);
3266 goto out_wimlib_free;
3270 if (image == info.boot_index) {
3271 imagex_printf(T("Image %d is already marked as "
3272 "bootable.\n"), image);
3275 imagex_printf(T("Marking image %d as bootable.\n"),
3277 info.boot_index = image;
3278 ret = wimlib_set_wim_info(wim, &info,
3279 WIMLIB_CHANGE_BOOT_INDEX);
3281 goto out_wimlib_free;
3285 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3287 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3291 imagex_printf(T("Changing the name of image %d to "
3292 "\"%"TS"\".\n"), image, new_name);
3293 ret = wimlib_set_image_name(wim, image, new_name);
3295 goto out_wimlib_free;
3299 const tchar *old_desc;
3300 old_desc = wimlib_get_image_description(wim, image);
3301 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3302 imagex_printf(T("The description of image %d is already "
3303 "\"%"TS"\".\n"), image, new_desc);
3306 imagex_printf(T("Changing the description of image %d "
3307 "to \"%"TS"\".\n"), image, new_desc);
3308 ret = wimlib_set_image_descripton(wim, image,
3311 goto out_wimlib_free;
3315 /* Only call wimlib_overwrite() if something actually needs to
3317 if (boot || new_name || new_desc ||
3318 (check && !info.has_integrity_table) ||
3319 (nocheck && info.has_integrity_table))
3321 int write_flags = 0;
3324 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3326 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3327 ret = wimlib_overwrite(wim, write_flags, 1);
3329 imagex_printf(T("The file \"%"TS"\" was not modified "
3330 "because nothing needed to be done.\n"),
3341 usage(CMD_INFO, stderr);
3347 /* Join split WIMs into one part WIM */
3349 imagex_join(int argc, tchar **argv, int cmd)
3352 int swm_open_flags = 0;
3353 int wim_write_flags = 0;
3354 const tchar *output_path;
3357 for_opt(c, join_options) {
3359 case IMAGEX_CHECK_OPTION:
3360 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3361 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3371 imagex_error(T("Must specify one or more split WIM (.swm) "
3375 output_path = argv[0];
3376 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3381 imagex_progress_func,
3387 usage(CMD_JOIN, stderr);
3392 #if WIM_MOUNTING_SUPPORTED
3394 /* Mounts a WIM image. */
3396 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3399 int mount_flags = 0;
3401 const tchar *staging_dir = NULL;
3402 const tchar *wimfile;
3405 struct wimlib_wim_info info;
3409 STRING_SET(refglobs);
3411 if (cmd == CMD_MOUNTRW) {
3412 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3413 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3416 for_opt(c, mount_options) {
3418 case IMAGEX_ALLOW_OTHER_OPTION:
3419 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3421 case IMAGEX_CHECK_OPTION:
3422 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3424 case IMAGEX_DEBUG_OPTION:
3425 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3427 case IMAGEX_STREAMS_INTERFACE_OPTION:
3428 if (!tstrcasecmp(optarg, T("none")))
3429 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3430 else if (!tstrcasecmp(optarg, T("xattr")))
3431 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3432 else if (!tstrcasecmp(optarg, T("windows")))
3433 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3435 imagex_error(T("Unknown stream interface \"%"TS"\""),
3440 case IMAGEX_REF_OPTION:
3441 ret = string_set_append(&refglobs, optarg);
3443 goto out_free_refglobs;
3445 case IMAGEX_STAGING_DIR_OPTION:
3446 staging_dir = optarg;
3448 case IMAGEX_UNIX_DATA_OPTION:
3449 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3457 if (argc != 2 && argc != 3)
3462 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3463 imagex_progress_func, NULL);
3465 goto out_free_refglobs;
3467 wimlib_get_wim_info(wim, &info);
3470 /* Image explicitly specified. */
3471 image = wimlib_resolve_image(wim, argv[1]);
3473 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3477 /* No image specified; default to image 1, but only if the WIM
3478 * contains exactly one image. */
3480 if (info.image_count != 1) {
3481 imagex_error(T("\"%"TS"\" contains %d images; Please "
3482 "select one."), wimfile, info.image_count);
3490 if (refglobs.num_strings) {
3491 ret = wim_reference_globs(wim, &refglobs, open_flags);
3496 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3498 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3500 image, wimfile, dir);
3505 string_set_destroy(&refglobs);
3511 goto out_free_refglobs;
3513 #endif /* WIM_MOUNTING_SUPPORTED */
3515 /* Rebuild a WIM file */
3517 imagex_optimize(int argc, tchar **argv, int cmd)
3520 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3521 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3522 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3523 uint32_t chunk_size = UINT32_MAX;
3524 uint32_t solid_chunk_size = UINT32_MAX;
3525 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3528 const tchar *wimfile;
3531 unsigned num_threads = 0;
3533 for_opt(c, optimize_options) {
3535 case IMAGEX_CHECK_OPTION:
3536 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3537 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3539 case IMAGEX_NOCHECK_OPTION:
3540 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3542 case IMAGEX_COMPRESS_OPTION:
3543 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3544 compression_type = get_compression_type(optarg);
3545 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3548 case IMAGEX_COMPRESS_SLOW_OPTION:
3549 set_compress_slow();
3550 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3552 case IMAGEX_RECOMPRESS_OPTION:
3553 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3555 case IMAGEX_CHUNK_SIZE_OPTION:
3556 chunk_size = parse_chunk_size(optarg);
3557 if (chunk_size == UINT32_MAX)
3560 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3561 solid_chunk_size = parse_chunk_size(optarg);
3562 if (solid_chunk_size == UINT32_MAX)
3565 case IMAGEX_SOLID_COMPRESS_OPTION:
3566 solid_ctype = get_compression_type(optarg);
3567 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3570 case IMAGEX_SOLID_OPTION:
3571 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
3572 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3574 case IMAGEX_THREADS_OPTION:
3575 num_threads = parse_num_threads(optarg);
3576 if (num_threads == UINT_MAX)
3579 case IMAGEX_PIPABLE_OPTION:
3580 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3582 case IMAGEX_NOT_PIPABLE_OPTION:
3583 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3597 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3598 imagex_progress_func, NULL);
3602 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3603 /* Change compression type. */
3604 ret = wimlib_set_output_compression_type(wim, compression_type);
3606 goto out_wimlib_free;
3609 if (chunk_size != UINT32_MAX) {
3610 /* Change chunk size. */
3611 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3613 goto out_wimlib_free;
3615 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3616 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3618 goto out_wimlib_free;
3620 if (solid_chunk_size != UINT32_MAX) {
3621 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3623 goto out_wimlib_free;
3626 old_size = file_get_size(wimfile);
3627 tprintf(T("\"%"TS"\" original size: "), wimfile);
3629 tputs(T("Unknown"));
3631 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3633 ret = wimlib_overwrite(wim, write_flags, num_threads);
3635 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3636 goto out_wimlib_free;
3639 new_size = file_get_size(wimfile);
3640 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3642 tputs(T("Unknown"));
3644 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3646 tfputs(T("Space saved: "), stdout);
3647 if (new_size != -1 && old_size != -1) {
3648 tprintf(T("%lld KiB\n"),
3649 ((long long)old_size - (long long)new_size) >> 10);
3651 tputs(T("Unknown"));
3660 usage(CMD_OPTIMIZE, stderr);
3666 /* Split a WIM into a spanned set */
3668 imagex_split(int argc, tchar **argv, int cmd)
3672 int write_flags = 0;
3673 unsigned long part_size;
3678 for_opt(c, split_options) {
3680 case IMAGEX_CHECK_OPTION:
3681 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3682 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3694 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3695 if (tmp == argv[2] || *tmp) {
3696 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3697 imagex_error(T("The part size must be an integer or "
3698 "floating-point number of megabytes."));
3701 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3702 imagex_progress_func, NULL);
3706 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3712 usage(CMD_SPLIT, stderr);
3718 #if WIM_MOUNTING_SUPPORTED
3719 /* Unmounts a mounted WIM image. */
3721 imagex_unmount(int argc, tchar **argv, int cmd)
3724 int unmount_flags = 0;
3727 for_opt(c, unmount_options) {
3729 case IMAGEX_COMMIT_OPTION:
3730 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3732 case IMAGEX_CHECK_OPTION:
3733 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3735 case IMAGEX_REBUILD_OPTION:
3736 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3738 case IMAGEX_LAZY_OPTION:
3739 case IMAGEX_FORCE_OPTION:
3740 /* Now, unmount is lazy by default. However, committing
3741 * the image will fail with
3742 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3743 * file descriptors on the WIM image. The
3744 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3745 * descriptors to be closed. */
3746 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3748 case IMAGEX_NEW_IMAGE_OPTION:
3749 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3760 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3761 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3762 imagex_error(T("--new-image is meaningless "
3763 "without --commit also specified!"));
3768 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3769 imagex_progress_func, NULL);
3771 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3772 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3774 "\tNote: Use --commit --force to force changes "
3775 "to be committed, regardless\n"
3776 "\t of open files.\n"));
3783 usage(CMD_UNMOUNT, stderr);
3788 #endif /* WIM_MOUNTING_SUPPORTED */
3791 * Add, delete, or rename files in a WIM image.
3794 imagex_update(int argc, tchar **argv, int cmd)
3796 const tchar *wimfile;
3800 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3801 int write_flags = 0;
3802 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3803 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3804 WIMLIB_ADD_FLAG_VERBOSE |
3805 WIMLIB_ADD_FLAG_WINCONFIG;
3806 int default_delete_flags = 0;
3807 unsigned num_threads = 0;
3809 tchar *cmd_file_contents;
3810 size_t cmd_file_nchars;
3811 struct wimlib_update_command *cmds;
3813 tchar *command_str = NULL;
3814 tchar *config_file = NULL;
3815 tchar *wimboot_config = NULL;
3817 for_opt(c, update_options) {
3819 /* Generic or write options */
3820 case IMAGEX_THREADS_OPTION:
3821 num_threads = parse_num_threads(optarg);
3822 if (num_threads == UINT_MAX)
3825 case IMAGEX_CHECK_OPTION:
3826 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3827 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3829 case IMAGEX_REBUILD_OPTION:
3830 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3832 case IMAGEX_COMMAND_OPTION:
3834 imagex_error(T("--command may only be specified "
3835 "one time. Please provide\n"
3836 " the update commands "
3837 "on standard input instead."));
3840 command_str = tstrdup(optarg);
3842 imagex_error(T("Out of memory!"));
3846 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3847 wimboot_config = optarg;
3849 /* Default delete options */
3850 case IMAGEX_FORCE_OPTION:
3851 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3853 case IMAGEX_RECURSIVE_OPTION:
3854 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3857 /* Global add option */
3858 case IMAGEX_CONFIG_OPTION:
3859 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3860 config_file = optarg;
3863 /* Default add options */
3864 case IMAGEX_VERBOSE_OPTION:
3865 /* No longer does anything. */
3867 case IMAGEX_DEREFERENCE_OPTION:
3868 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3870 case IMAGEX_UNIX_DATA_OPTION:
3871 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3873 case IMAGEX_NO_ACLS_OPTION:
3874 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3876 case IMAGEX_STRICT_ACLS_OPTION:
3877 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3879 case IMAGEX_NO_REPLACE_OPTION:
3880 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3889 if (argc != 1 && argc != 2)
3893 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3894 imagex_progress_func, NULL);
3896 goto out_free_command_str;
3899 /* Image explicitly specified. */
3900 image = wimlib_resolve_image(wim, argv[1]);
3901 ret = verify_image_exists_and_is_single(image, argv[1],
3904 goto out_wimlib_free;
3906 /* No image specified; default to image 1, but only if the WIM
3907 * contains exactly one image. */
3908 struct wimlib_wim_info info;
3910 wimlib_get_wim_info(wim, &info);
3911 if (info.image_count != 1) {
3912 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3913 wimfile, info.image_count);
3920 /* Read update commands from standard input, or the command string if
3923 cmd_file_contents = NULL;
3924 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3928 goto out_free_cmd_file_contents;
3930 } else if (!wimboot_config) {
3931 if (isatty(STDIN_FILENO)) {
3932 tputs(T("Reading update commands from standard input..."));
3933 recommend_man_page(CMD_UPDATE, stdout);
3935 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3936 if (!cmd_file_contents) {
3938 goto out_wimlib_free;
3941 /* Parse the update commands */
3942 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3946 goto out_free_cmd_file_contents;
3949 cmd_file_contents = NULL;
3954 /* Set default flags and capture config on the update commands */
3955 for (size_t i = 0; i < num_cmds; i++) {
3956 switch (cmds[i].op) {
3957 case WIMLIB_UPDATE_OP_ADD:
3958 cmds[i].add.add_flags |= default_add_flags;
3959 cmds[i].add.config_file = config_file;
3961 case WIMLIB_UPDATE_OP_DELETE:
3962 cmds[i].delete_.delete_flags |= default_delete_flags;
3969 /* Execute the update commands */
3970 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3974 if (wimboot_config) {
3975 /* --wimboot-config=FILE is short for an
3976 * "add FILE /Windows/System32/WimBootCompress.ini" command.
3978 struct wimlib_update_command cmd;
3980 cmd.op = WIMLIB_UPDATE_OP_ADD;
3981 cmd.add.fs_source_path = wimboot_config;
3982 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
3983 cmd.add.config_file = NULL;
3984 cmd.add.add_flags = 0;
3986 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
3991 /* Overwrite the updated WIM */
3992 ret = wimlib_overwrite(wim, write_flags, num_threads);
3995 out_free_cmd_file_contents:
3996 free(cmd_file_contents);
3999 out_free_command_str:
4004 usage(CMD_UPDATE, stderr);
4007 goto out_free_command_str;
4010 /* Verify a WIM file. */
4012 imagex_verify(int argc, tchar **argv, int cmd)
4015 const tchar *wimfile;
4017 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4018 int verify_flags = 0;
4019 STRING_SET(refglobs);
4022 for_opt(c, verify_options) {
4024 case IMAGEX_REF_OPTION:
4025 ret = string_set_append(&refglobs, optarg);
4027 goto out_free_refglobs;
4039 imagex_error(T("Must specify a WIM file!"));
4041 imagex_error(T("At most one WIM file can be specified!"));
4047 ret = wimlib_open_wim_with_progress(wimfile,
4050 imagex_progress_func,
4053 goto out_free_refglobs;
4055 ret = wim_reference_globs(wim, &refglobs, open_flags);
4057 goto out_wimlib_free;
4059 ret = wimlib_verify_wim(wim, verify_flags);
4061 tputc(T('\n'), stderr);
4062 imagex_error(T("\"%"TS"\" failed verification!"),
4064 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4065 refglobs.num_strings == 0)
4067 imagex_printf(T("Note: if this WIM file is not standalone, "
4068 "use the --ref option to specify the other parts.\n"));
4071 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4078 string_set_destroy(&refglobs);
4082 usage(CMD_VERIFY, stderr);
4084 goto out_free_refglobs;
4087 struct imagex_command {
4089 int (*func)(int argc, tchar **argv, int cmd);
4092 static const struct imagex_command imagex_commands[] = {
4093 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4094 [CMD_APPLY] = {T("apply"), imagex_apply},
4095 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4096 [CMD_DELETE] = {T("delete"), imagex_delete},
4097 [CMD_DIR ] = {T("dir"), imagex_dir},
4098 [CMD_EXPORT] = {T("export"), imagex_export},
4099 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4100 [CMD_INFO] = {T("info"), imagex_info},
4101 [CMD_JOIN] = {T("join"), imagex_join},
4102 #if WIM_MOUNTING_SUPPORTED
4103 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4104 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4106 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4107 [CMD_SPLIT] = {T("split"), imagex_split},
4108 #if WIM_MOUNTING_SUPPORTED
4109 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4111 [CMD_UPDATE] = {T("update"), imagex_update},
4112 [CMD_VERIFY] = {T("verify"), imagex_verify},
4117 /* Can be a directory or source list file. But source list file is probably
4118 * a rare use case, so just say directory. */
4119 # define SOURCE_STR T("DIRECTORY")
4121 /* Can only be a directory */
4122 # define TARGET_STR T("DIRECTORY")
4125 /* Can be a directory, NTFS volume, or source list file. */
4126 # define SOURCE_STR T("SOURCE")
4128 /* Can be a directory or NTFS volume. */
4129 # define TARGET_STR T("TARGET")
4133 static const tchar *usage_strings[] = {
4136 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4137 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4138 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4139 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4140 " [--wimboot] [--unix-data] [--dereference]\n"
4144 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4145 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4146 " [--no-attributes] [--rpfix] [--norpfix]\n"
4147 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4151 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4152 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4153 " [--config=FILE] [--threads=NUM_THREADS]\n"
4154 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4155 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4156 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4160 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4164 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4168 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4169 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4170 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4171 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4176 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4177 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4178 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4179 " [--no-attributes] [--include-invalid-names]\n"
4180 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4184 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4185 " [--boot] [--check] [--nocheck] [--xml]\n"
4186 " [--extract-xml FILE] [--header] [--lookup-table]\n"
4190 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4192 #if WIM_MOUNTING_SUPPORTED
4195 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4196 " [--check] [--streams-interface=INTERFACE]\n"
4197 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4201 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4202 " [--check] [--streams-interface=INTERFACE]\n"
4203 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4209 " [--recompress] [--compress=TYPE]\n"
4210 " [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4215 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4217 #if WIM_MOUNTING_SUPPORTED
4220 " %"TS" DIRECTORY\n"
4221 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4226 " %"TS" WIMFILE [IMAGE]\n"
4227 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4228 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4229 " [--command=STRING] [--wimboot-config=FILE]\n"
4234 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4238 static const tchar *invocation_name;
4239 static int invocation_cmd = CMD_NONE;
4241 static const tchar *get_cmd_string(int cmd, bool nospace)
4243 static tchar buf[50];
4244 if (cmd == CMD_NONE) {
4245 tsprintf(buf, T("%"TS), T(IMAGEX_PROGNAME));
4246 } else if (invocation_cmd != CMD_NONE) {
4247 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4249 const tchar *format;
4252 format = T("%"TS"-%"TS"");
4254 format = T("%"TS" %"TS"");
4255 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4263 static const tchar *s =
4265 IMAGEX_PROGNAME " (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4266 "Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
4267 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4268 "This is free software: you are free to change and redistribute it.\n"
4269 "There is NO WARRANTY, to the extent permitted by law.\n"
4271 "Report bugs to "PACKAGE_BUGREPORT".\n"
4278 help_or_version(int argc, tchar **argv, int cmd)
4283 for (i = 1; i < argc; i++) {
4285 if (p[0] == T('-') && p[1] == T('-')) {
4287 if (!tstrcmp(p, T("help"))) {
4288 if (cmd == CMD_NONE)
4293 } else if (!tstrcmp(p, T("version"))) {
4302 print_usage_string(int cmd, FILE *fp)
4304 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4308 recommend_man_page(int cmd, FILE *fp)
4310 const tchar *format_str;
4312 format_str = T("Some uncommon options are not listed;\n"
4313 "See %"TS".pdf in the doc directory for more details.\n");
4315 format_str = T("Some uncommon options are not listed;\n"
4316 "Try `man %"TS"' for more details.\n");
4318 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4322 usage(int cmd, FILE *fp)
4324 tfprintf(fp, T("Usage:\n"));
4325 print_usage_string(cmd, fp);
4326 tfprintf(fp, T("\n"));
4327 recommend_man_page(cmd, fp);
4333 tfprintf(fp, T("Usage:\n"));
4334 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4335 print_usage_string(cmd, fp);
4336 tfprintf(fp, T("\n"));
4338 static const tchar *extra =
4341 " %"TS" --version\n"
4344 tfprintf(fp, extra, invocation_name, invocation_name);
4346 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4347 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4348 "For some commands IMAGE may be \"all\".\n"
4350 recommend_man_page(CMD_NONE, fp);
4353 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4354 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4355 * something else), while on Windows the command arguments will be UTF-16LE
4356 * encoded 'wchar_t' strings. */
4359 wmain(int argc, wchar_t **argv, wchar_t **envp)
4361 main(int argc, char **argv)
4368 imagex_info_file = stdout;
4369 invocation_name = tbasename(argv[0]);
4372 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4373 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4377 setlocale(LC_ALL, "");
4378 codeset = nl_langinfo(CODESET);
4379 if (!strstr(codeset, "UTF-8") &&
4380 !strstr(codeset, "UTF8") &&
4381 !strstr(codeset, "utf-8") &&
4382 !strstr(codeset, "utf8"))
4385 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4386 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4387 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4388 " to any value to force wimlib to use UTF-8.\n",
4394 #endif /* !__WIN32__ */
4397 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4398 if (igcase != NULL) {
4399 if (!tstrcmp(igcase, T("no")) ||
4400 !tstrcmp(igcase, T("0")))
4401 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4402 else if (!tstrcmp(igcase, T("yes")) ||
4403 !tstrcmp(igcase, T("1")))
4404 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4407 "WARNING: Ignoring unknown setting of "
4408 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4413 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4415 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4416 tstrcmp(invocation_name, T(IMAGEX_PROGNAME))) {
4417 for (int i = 0; i < CMD_MAX; i++) {
4418 if (!tstrcmp(invocation_name + 3,
4419 imagex_commands[i].name))
4428 /* Unless already known from the invocation name, determine which
4429 * command was specified. */
4430 if (cmd == CMD_NONE) {
4432 imagex_error(T("No command specified!\n"));
4436 for (int i = 0; i < CMD_MAX; i++) {
4437 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4442 if (cmd != CMD_NONE) {
4448 /* Handle --help and --version. --help can be either for the program as
4449 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4450 * CMD_NONE). Note: help_or_version() will not return if a --help or
4451 * --version argument was found. */
4452 help_or_version(argc, argv, cmd);
4454 /* Bail if a valid command was not specified. */
4455 if (cmd == CMD_NONE) {
4456 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4461 /* Enable warning and error messages in wimlib to be more user-friendly.
4463 wimlib_set_print_errors(true);
4465 /* Initialize wimlib. */
4466 ret = wimlib_global_init(init_flags);
4468 goto out_check_status;
4470 /* Call the command handler function. */
4471 ret = imagex_commands[cmd].func(argc, argv, cmd);
4473 /* Check for error writing to standard output, especially since for some
4474 * commands, writing to standard output is part of the program's actual
4475 * behavior and not just for informational purposes. */
4476 if (ferror(stdout) || fclose(stdout)) {
4477 imagex_error_with_errno(T("error writing to standard output"));
4482 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4483 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4484 * error code from which an error message can be printed. */
4486 imagex_error(T("Exiting with error code %d:\n"
4488 wimlib_get_error_string(ret));
4489 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4490 imagex_error_with_errno(T("errno"));
4492 /* Make wimlib free any resources it's holding (although this is not
4493 * strictly necessary because the process is ending anyway). */
4494 wimlib_global_cleanup();