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
130 static void usage(int cmd, FILE *fp);
131 static void usage_all(FILE *fp);
132 static void recommend_man_page(int cmd, FILE *fp);
133 static const tchar *get_cmd_string(int cmd, bool nospace);
135 static bool imagex_be_quiet = false;
136 static FILE *imagex_info_file;
138 #define imagex_printf(format, ...) \
139 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
142 IMAGEX_ALLOW_OTHER_OPTION,
145 IMAGEX_CHUNK_SIZE_OPTION,
146 IMAGEX_COMMAND_OPTION,
147 IMAGEX_COMMIT_OPTION,
148 IMAGEX_COMPRESS_OPTION,
149 IMAGEX_COMPRESS_SLOW_OPTION,
150 IMAGEX_CONFIG_OPTION,
152 IMAGEX_DELTA_FROM_OPTION,
153 IMAGEX_DEREFERENCE_OPTION,
154 IMAGEX_DEST_DIR_OPTION,
155 IMAGEX_DETAILED_OPTION,
156 IMAGEX_EXTRACT_XML_OPTION,
159 IMAGEX_HEADER_OPTION,
160 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
162 IMAGEX_LOOKUP_TABLE_OPTION,
163 IMAGEX_METADATA_OPTION,
164 IMAGEX_NEW_IMAGE_OPTION,
165 IMAGEX_NOCHECK_OPTION,
166 IMAGEX_NORPFIX_OPTION,
167 IMAGEX_NOT_PIPABLE_OPTION,
168 IMAGEX_NO_ACLS_OPTION,
169 IMAGEX_NO_ATTRIBUTES_OPTION,
170 IMAGEX_NO_REPLACE_OPTION,
171 IMAGEX_NO_GLOBS_OPTION,
172 IMAGEX_NULLGLOB_OPTION,
173 IMAGEX_ONE_FILE_ONLY_OPTION,
175 IMAGEX_PIPABLE_OPTION,
176 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
177 IMAGEX_REBUILD_OPTION,
178 IMAGEX_RECOMPRESS_OPTION,
179 IMAGEX_RECURSIVE_OPTION,
181 IMAGEX_RESUME_OPTION,
185 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
186 IMAGEX_SOLID_COMPRESS_OPTION,
187 IMAGEX_SOURCE_LIST_OPTION,
188 IMAGEX_STAGING_DIR_OPTION,
189 IMAGEX_STREAMS_INTERFACE_OPTION,
190 IMAGEX_STRICT_ACLS_OPTION,
191 IMAGEX_THREADS_OPTION,
192 IMAGEX_TO_STDOUT_OPTION,
193 IMAGEX_UNIX_DATA_OPTION,
194 IMAGEX_UPDATE_OF_OPTION,
195 IMAGEX_VERBOSE_OPTION,
196 IMAGEX_WIMBOOT_OPTION,
197 IMAGEX_WIMBOOT_CONFIG_OPTION,
201 static const struct option apply_options[] = {
202 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
203 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
204 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
205 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
206 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
207 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
208 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
209 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
210 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
211 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
212 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
214 /* --resume is undocumented for now as it needs improvement. */
215 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
216 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
220 static const struct option capture_or_append_options[] = {
221 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
222 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
223 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
224 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
225 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
226 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
227 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
228 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
229 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
230 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
231 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
232 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
233 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
234 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
235 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
236 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
237 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
238 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
239 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
240 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
241 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
242 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
243 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
244 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
245 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
246 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
247 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
248 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
249 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
250 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
251 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
255 static const struct option delete_options[] = {
256 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
257 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
261 static const struct option dir_options[] = {
262 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
263 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
264 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
268 static const struct option export_options[] = {
269 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
270 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
271 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
272 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
273 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
274 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
275 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
276 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
277 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
278 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
279 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
280 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
281 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
282 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
283 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
284 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
285 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
286 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
287 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
288 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
292 static const struct option extract_options[] = {
293 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
294 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
295 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
296 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
297 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
298 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
299 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
300 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
301 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
302 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
303 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
304 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
305 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
306 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
307 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
308 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
312 static const struct option info_options[] = {
313 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
314 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
315 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
316 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
317 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
318 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
319 {T("lookup-table"), no_argument, NULL, IMAGEX_LOOKUP_TABLE_OPTION},
320 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
321 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
325 static const struct option join_options[] = {
326 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
330 static const struct option mount_options[] = {
331 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
332 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
333 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
334 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
335 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
336 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
337 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
341 static const struct option optimize_options[] = {
342 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
343 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
344 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
345 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
346 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
347 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
348 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
349 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
350 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
351 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
352 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
353 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
354 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
355 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
356 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
357 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
358 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
362 static const struct option split_options[] = {
363 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
367 static const struct option unmount_options[] = {
368 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
369 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
370 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
371 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
372 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
373 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
377 static const struct option update_options[] = {
378 /* Careful: some of the options here set the defaults for update
379 * commands, but the flags given to an actual update command (and not to
380 * `imagex update' itself are also handled in
381 * update_command_add_option(). */
382 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
383 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
384 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
385 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
386 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
388 /* Default delete options */
389 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
390 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
392 /* Global add option */
393 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
395 /* Default add options */
396 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
397 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
398 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
399 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
400 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
401 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
402 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
408 # define _format_attribute(type, format_str, args_start) \
409 __attribute__((format(type, format_str, args_start)))
411 # define _format_attribute(type, format_str, args_start)
414 /* Print formatted error message to stderr. */
415 static void _format_attribute(printf, 1, 2)
416 imagex_error(const tchar *format, ...)
419 va_start(va, format);
420 tfputs(T("ERROR: "), stderr);
421 tvfprintf(stderr, format, va);
422 tputc(T('\n'), stderr);
426 /* Print formatted error message to stderr. */
427 static void _format_attribute(printf, 1, 2)
428 imagex_error_with_errno(const tchar *format, ...)
430 int errno_save = errno;
432 va_start(va, format);
433 tfputs(T("ERROR: "), stderr);
434 tvfprintf(stderr, format, va);
435 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
440 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
442 if (image == WIMLIB_NO_IMAGE) {
443 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
444 " Please specify a 1-based image index or "
445 "image name. To list the images\n"
446 " contained in the WIM archive, run\n"
448 " %"TS" \"%"TS"\"\n"),
449 image_name, wim_name,
450 get_cmd_string(CMD_INFO, false), wim_name);
451 return WIMLIB_ERR_INVALID_IMAGE;
457 verify_image_is_single(int image)
459 if (image == WIMLIB_ALL_IMAGES) {
460 imagex_error(T("Cannot specify all images for this action!"));
461 return WIMLIB_ERR_INVALID_IMAGE;
467 verify_image_exists_and_is_single(int image, const tchar *image_name,
468 const tchar *wim_name)
471 ret = verify_image_exists(image, image_name, wim_name);
473 ret = verify_image_is_single(image);
478 print_available_compression_types(FILE *fp)
480 static const tchar *s =
482 "Available compression types:\n"
485 " xpress (alias: \"fast\")\n"
486 " lzx (alias: \"maximum\") (default for capture)\n"
487 " lzms (alias: \"recovery\")\n"
493 /* Parse the argument to --compress */
495 get_compression_type(tchar *optarg)
498 unsigned int compression_level = 0;
501 plevel = tstrchr(optarg, T(':'));
507 ultmp = tstrtoul(plevel, &ptmp, 10);
508 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
509 imagex_error(T("Compression level must be a positive integer! "
510 "e.g. --compress=lzx:80"));
511 return WIMLIB_COMPRESSION_TYPE_INVALID;
513 compression_level = ultmp;
516 if (!tstrcasecmp(optarg, T("maximum")) ||
517 !tstrcasecmp(optarg, T("lzx")) ||
518 !tstrcasecmp(optarg, T("max")))
519 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
520 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
521 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
522 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
523 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
524 else if (!tstrcasecmp(optarg, T("none")))
525 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
527 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
528 print_available_compression_types(stderr);
529 return WIMLIB_COMPRESSION_TYPE_INVALID;
532 if (compression_level != 0)
533 wimlib_set_default_compression_level(ctype, compression_level);
538 set_compress_slow(void)
541 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
542 " Use the '--compress=TYPE:LEVEL' option instead.\n");
544 wimlib_set_default_compression_level(-1, 100);
548 const tchar **strings;
549 unsigned num_strings;
550 unsigned num_alloc_strings;
553 #define STRING_SET_INITIALIZER \
554 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
556 #define STRING_SET(_strings) \
557 struct string_set _strings = STRING_SET_INITIALIZER
560 string_set_append(struct string_set *set, const tchar *glob)
562 unsigned num_alloc_strings = set->num_alloc_strings;
564 if (set->num_strings == num_alloc_strings) {
565 const tchar **new_strings;
567 num_alloc_strings += 4;
568 new_strings = realloc(set->strings,
569 sizeof(set->strings[0]) * num_alloc_strings);
571 imagex_error(T("Out of memory!"));
574 set->strings = new_strings;
575 set->num_alloc_strings = num_alloc_strings;
577 set->strings[set->num_strings++] = glob;
582 string_set_destroy(struct string_set *set)
588 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
590 return wimlib_reference_resource_files(wim, set->strings,
592 WIMLIB_REF_FLAG_GLOB_ENABLE,
597 do_resource_not_found_warning(const tchar *wimfile,
598 const struct wimlib_wim_info *info,
599 const struct string_set *refglobs)
601 if (info->total_parts > 1) {
602 if (refglobs->num_strings == 0) {
603 imagex_error(T("\"%"TS"\" is part of a split WIM. "
604 "Use --ref to specify the other parts."),
607 imagex_error(T("Perhaps the '--ref' argument did not "
608 "specify all other parts of the split "
612 imagex_error(T("If this is a delta WIM, use the --ref argument "
613 "to specify the WIM(s) on which it is based."));
617 /* Returns the size of a file given its name, or -1 if the file does not exist
618 * or its size cannot be determined. */
620 file_get_size(const tchar *filename)
623 if (tstat(filename, &st) == 0)
630 PARSE_STRING_SUCCESS = 0,
631 PARSE_STRING_FAILURE = 1,
632 PARSE_STRING_NONE = 2,
636 * Parses a string token from an array of characters.
638 * Tokens are either whitespace-delimited, or double or single-quoted.
640 * @line_p: Pointer to the pointer to the line of data. Will be updated
641 * to point past the string token iff the return value is
642 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
645 * @len_p: @len_p initially stores the length of the line of data, which may
646 * be 0, and it will be updated to the number of bytes remaining in
647 * the line iff the return value is PARSE_STRING_SUCCESS.
649 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
650 * parsed string token will be returned here.
652 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
653 * PARSE_STRING_FAILURE if the data was invalid due to a missing
654 * closing quote; or PARSE_STRING_NONE if the line ended before the
655 * beginning of a string token was found.
658 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
661 tchar *line = *line_p;
665 /* Skip leading whitespace */
668 return PARSE_STRING_NONE;
669 if (!istspace(*line) && *line != T('\0'))
675 if (quote_char == T('"') || quote_char == T('\'')) {
680 line = tmemchr(line, quote_char, len);
682 imagex_error(T("Missing closing quote: %"TS), fn - 1);
683 return PARSE_STRING_FAILURE;
686 /* Unquoted string. Go until whitespace. Line is terminated
687 * by '\0', so no need to check 'len'. */
691 } while (!istspace(*line) && *line != T('\0'));
698 return PARSE_STRING_SUCCESS;
701 /* Parses a line of data (not an empty line or comment) in the source list file
702 * format. (See the man page for 'wimlib-imagex capture' for details on this
703 * format and the meaning.)
705 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
706 * len == 0. The data in @line will be modified by this function call.
708 * @len: Length of the line of data.
710 * @source: On success, the capture source and target described by the line is
711 * written into this destination. Note that it will contain pointers
712 * to data in the @line array.
714 * Returns true if the line was valid; false otherwise. */
716 parse_source_list_line(tchar *line, size_t len,
717 struct wimlib_capture_source *source)
721 ret = parse_string(&line, &len, &source->fs_source_path);
722 if (ret != PARSE_STRING_SUCCESS)
724 ret = parse_string(&line, &len, &source->wim_target_path);
725 if (ret == PARSE_STRING_NONE)
726 source->wim_target_path = source->fs_source_path;
727 return ret != PARSE_STRING_FAILURE;
730 /* Returns %true if the given line of length @len > 0 is a comment or empty line
731 * in the source list file format. */
733 is_comment_line(const tchar *line, size_t len)
736 if (*line == T('#') || *line == T(';'))
738 if (!istspace(*line) && *line != T('\0'))
748 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
751 tchar *contents = *contents_p;
752 size_t nchars = *nchars_p;
755 for (i = 0; i < nchars; i++)
756 if (contents[i] == T('\n'))
759 /* Handle last line not terminated by a newline */
760 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
761 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
763 imagex_error(T("Out of memory!"));
766 contents[nchars] = T('\n');
767 *contents_p = contents;
775 /* Parses a file in the source list format. (See the man page for
776 * 'wimlib-imagex capture' for details on this format and the meaning.)
778 * @source_list_contents: Contents of the source list file. Note that this
779 * buffer will be modified to save memory allocations,
780 * and cannot be freed until the returned array of
781 * wimlib_capture_source's has also been freed.
783 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
786 * @nsources_ret: On success, the length of the returned array is
789 * Returns: An array of `struct wimlib_capture_source's that can be passed to
790 * the wimlib_add_image_multisource() function to specify how a WIM image is to
792 static struct wimlib_capture_source *
793 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
794 size_t *nsources_ret)
798 struct wimlib_capture_source *sources;
801 nlines = text_file_count_lines(source_list_contents_p,
802 &source_list_nchars);
806 /* Always allocate at least 1 slot, just in case the implementation of
807 * calloc() returns NULL if 0 bytes are requested. */
808 sources = calloc(nlines ?: 1, sizeof(*sources));
810 imagex_error(T("out of memory"));
813 p = *source_list_contents_p;
815 for (i = 0; i < nlines; i++) {
816 /* XXX: Could use rawmemchr() here instead, but it may not be
817 * available on all platforms. */
818 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
819 size_t len = endp - p + 1;
821 if (!is_comment_line(p, len)) {
822 if (!parse_source_list_line(p, len, &sources[j++])) {
834 /* Reads the contents of a file into memory. */
836 file_get_contents(const tchar *filename, size_t *len_ret)
843 if (tstat(filename, &stbuf) != 0) {
844 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
849 fp = tfopen(filename, T("rb"));
851 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
855 buf = malloc(len ? len : 1);
857 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
858 "contents of file \"%"TS"\""), len, filename);
861 if (fread(buf, 1, len, fp) != len) {
862 imagex_error_with_errno(T("Failed to read %zu bytes from the "
863 "file \"%"TS"\""), len, filename);
877 /* Read standard input until EOF and return the full contents in a malloc()ed
878 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
881 stdin_get_contents(size_t *len_ret)
883 /* stdin can, of course, be a pipe or other non-seekable file, so the
884 * total length of the data cannot be pre-determined */
886 size_t newlen = 1024;
890 char *p = realloc(buf, newlen);
891 size_t bytes_read, bytes_to_read;
893 imagex_error(T("out of memory while reading stdin"));
897 bytes_to_read = newlen - pos;
898 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
900 if (bytes_read != bytes_to_read) {
905 imagex_error_with_errno(T("error reading stdin"));
919 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
922 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
924 *num_tchars_ret = num_bytes;
926 #else /* !__WIN32__ */
927 /* On Windows, translate the text to UTF-16LE */
931 if (num_bytes >= 2 &&
932 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
933 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
935 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
936 * with something that looks like an ASCII character encoded as
937 * a UTF-16LE code unit. Assume the file is encoded as
938 * UTF-16LE. This is not a 100% reliable check. */
939 num_wchars = num_bytes / 2;
940 text_wstr = (wchar_t*)text;
942 /* File does not look like UTF-16LE. Assume it is encoded in
943 * the current Windows code page. I think these are always
944 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
945 * should work as expected. */
946 text_wstr = win32_mbs_to_wcs(text,
951 *num_tchars_ret = num_wchars;
953 #endif /* __WIN32__ */
957 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
962 contents = file_get_contents(filename, &num_bytes);
965 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
969 stdin_get_text_contents(size_t *num_tchars_ret)
974 contents = stdin_get_contents(&num_bytes);
977 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
980 #define TO_PERCENT(numerator, denominator) \
981 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
983 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
984 #define MEBIBYTE_MIN_NBYTES 10000000ULL
985 #define KIBIBYTE_MIN_NBYTES 10000ULL
988 get_unit(uint64_t total_bytes, const tchar **name_ret)
990 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
991 *name_ret = T("GiB");
993 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
994 *name_ret = T("MiB");
996 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
997 *name_ret = T("KiB");
1000 *name_ret = T("bytes");
1005 static struct wimlib_progress_info_scan last_scan_progress;
1008 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1010 uint64_t prev_count, cur_count;
1012 prev_count = last_scan_progress.num_nondirs_scanned +
1013 last_scan_progress.num_dirs_scanned;
1014 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1016 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1017 cur_count % 128 == 0)
1019 unsigned unit_shift;
1020 const tchar *unit_name;
1022 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1023 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1024 "%"PRIu64" directories) "),
1025 scan->num_bytes_scanned >> unit_shift,
1027 scan->num_nondirs_scanned,
1028 scan->num_dirs_scanned);
1029 last_scan_progress = *scan;
1032 /* Progress callback function passed to various wimlib functions. */
1033 static enum wimlib_progress_status
1034 imagex_progress_func(enum wimlib_progress_msg msg,
1035 union wimlib_progress_info *info,
1036 void *_ignored_context)
1038 unsigned percent_done;
1039 unsigned unit_shift;
1040 const tchar *unit_name;
1042 if (imagex_be_quiet)
1043 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1045 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1047 static bool first = true;
1049 imagex_printf(T("Writing %"TS"-compressed data "
1050 "using %u thread%"TS"\n"),
1051 wimlib_get_compression_type_string(
1052 info->write_streams.compression_type),
1053 info->write_streams.num_threads,
1054 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1058 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1059 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1060 info->write_streams.total_bytes);
1062 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1063 "written (%u%% done)"),
1064 info->write_streams.completed_bytes >> unit_shift,
1066 info->write_streams.total_bytes >> unit_shift,
1069 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1070 imagex_printf(T("\n"));
1072 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1073 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1074 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1075 imagex_printf(T("\n"));
1077 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1078 info->scan.wim_target_path);
1080 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1082 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1083 switch (info->scan.status) {
1084 case WIMLIB_SCAN_DENTRY_OK:
1085 report_scan_progress(&info->scan, false);
1087 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1088 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1090 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1091 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1092 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1094 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1095 /* Symlink fixups are enabled by default. This is
1096 * mainly intended for Windows, which for some reason
1097 * uses absolute junctions (with drive letters!) in the
1098 * default installation. On UNIX-like systems, warn the
1099 * user when fixing the target of an absolute symbolic
1100 * link, so they know to disable this if they want. */
1102 imagex_printf(T("\nWARNING: Adjusted target of "
1103 "absolute symbolic link \"%"TS"\"\n"
1104 " (Use --norpfix to capture "
1105 "absolute symbolic links as-is)\n"),
1106 info->scan.cur_path);
1113 case WIMLIB_PROGRESS_MSG_SCAN_END:
1114 report_scan_progress(&info->scan, true);
1115 imagex_printf(T("\n"));
1117 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1118 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1119 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1120 info->integrity.total_bytes);
1121 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1122 "of %"PRIu64" %"TS" (%u%%) done"),
1123 info->integrity.filename,
1124 info->integrity.completed_bytes >> unit_shift,
1126 info->integrity.total_bytes >> unit_shift,
1129 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1130 imagex_printf(T("\n"));
1132 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1133 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1134 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1135 info->integrity.total_bytes);
1136 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1137 "of %"PRIu64" %"TS" (%u%%) done"),
1138 info->integrity.completed_bytes >> unit_shift,
1140 info->integrity.total_bytes >> unit_shift,
1143 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1144 imagex_printf(T("\n"));
1146 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1147 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1148 "to %"TS" \"%"TS"\"\n"),
1149 info->extract.image,
1150 info->extract.image_name,
1151 info->extract.wimfile_name,
1152 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1153 T("NTFS volume") : T("directory")),
1154 info->extract.target);
1156 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1157 percent_done = TO_PERCENT(info->extract.completed_bytes,
1158 info->extract.total_bytes);
1159 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1160 imagex_printf(T("\rExtracting files: "
1161 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1162 info->extract.completed_bytes >> unit_shift,
1164 info->extract.total_bytes >> unit_shift,
1167 if (info->extract.completed_bytes >= info->extract.total_bytes)
1168 imagex_printf(T("\n"));
1170 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1171 if (info->extract.total_parts != 1) {
1172 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1173 info->extract.part_number,
1174 info->extract.total_parts);
1177 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1178 percent_done = TO_PERCENT(info->split.completed_bytes,
1179 info->split.total_bytes);
1180 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1181 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1182 "%"PRIu64" %"TS" (%u%%) written\n"),
1183 info->split.part_name,
1184 info->split.cur_part_number,
1185 info->split.total_parts,
1186 info->split.completed_bytes >> unit_shift,
1188 info->split.total_bytes >> unit_shift,
1192 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1193 if (info->split.completed_bytes == info->split.total_bytes) {
1194 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1195 info->split.cur_part_number,
1196 info->split.total_parts);
1199 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1200 switch (info->update.command->op) {
1201 case WIMLIB_UPDATE_OP_DELETE:
1202 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1203 info->update.command->delete_.wim_path);
1205 case WIMLIB_UPDATE_OP_RENAME:
1206 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1207 info->update.command->rename.wim_source_path,
1208 info->update.command->rename.wim_target_path);
1210 case WIMLIB_UPDATE_OP_ADD:
1215 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1216 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1217 info->replace.path_in_wim);
1219 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1220 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1221 info->wimboot_exclude.path_in_wim);
1223 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1224 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1225 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1226 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1227 info->unmount.mounted_wim,
1228 info->unmount.mounted_image);
1230 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1231 info->unmount.mounted_wim,
1232 info->unmount.mounted_image);
1233 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1240 fflush(imagex_info_file);
1241 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1245 parse_num_threads(const tchar *optarg)
1248 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1249 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1250 imagex_error(T("Number of threads must be a non-negative integer!"));
1257 static uint32_t parse_chunk_size(const tchar *optarg)
1260 unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
1261 if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
1262 imagex_error(T("Chunk size must be a non-negative integer!"));
1271 * Parse an option passed to an update command.
1273 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1276 * @option: Text string for the option (beginning with --)
1278 * @cmd: `struct wimlib_update_command' that is being constructed for
1281 * Returns true if the option was recognized; false if not.
1284 update_command_add_option(int op, const tchar *option,
1285 struct wimlib_update_command *cmd)
1287 bool recognized = true;
1289 case WIMLIB_UPDATE_OP_ADD:
1290 if (!tstrcmp(option, T("--verbose")))
1291 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1292 else if (!tstrcmp(option, T("--unix-data")))
1293 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1294 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1295 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1296 else if (!tstrcmp(option, T("--strict-acls")))
1297 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1298 else if (!tstrcmp(option, T("--dereference")))
1299 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1300 else if (!tstrcmp(option, T("--no-replace")))
1301 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1305 case WIMLIB_UPDATE_OP_DELETE:
1306 if (!tstrcmp(option, T("--force")))
1307 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1308 else if (!tstrcmp(option, T("--recursive")))
1309 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1320 /* How many nonoption arguments each `imagex update' command expects */
1321 static const unsigned update_command_num_nonoptions[] = {
1322 [WIMLIB_UPDATE_OP_ADD] = 2,
1323 [WIMLIB_UPDATE_OP_DELETE] = 1,
1324 [WIMLIB_UPDATE_OP_RENAME] = 2,
1328 update_command_add_nonoption(int op, const tchar *nonoption,
1329 struct wimlib_update_command *cmd,
1330 unsigned num_nonoptions)
1333 case WIMLIB_UPDATE_OP_ADD:
1334 if (num_nonoptions == 0)
1335 cmd->add.fs_source_path = (tchar*)nonoption;
1337 cmd->add.wim_target_path = (tchar*)nonoption;
1339 case WIMLIB_UPDATE_OP_DELETE:
1340 cmd->delete_.wim_path = (tchar*)nonoption;
1342 case WIMLIB_UPDATE_OP_RENAME:
1343 if (num_nonoptions == 0)
1344 cmd->rename.wim_source_path = (tchar*)nonoption;
1346 cmd->rename.wim_target_path = (tchar*)nonoption;
1352 * Parse a command passed on stdin to `imagex update'.
1354 * @line: Text of the command.
1355 * @len: Length of the line, including a null terminator
1358 * @command: A `struct wimlib_update_command' to fill in from the parsed
1361 * @line_number: Line number of the command, for diagnostics.
1363 * Returns true on success; returns false on parse error.
1366 parse_update_command(tchar *line, size_t len,
1367 struct wimlib_update_command *command,
1371 tchar *command_name;
1373 size_t num_nonoptions;
1375 /* Get the command name ("add", "delete", "rename") */
1376 ret = parse_string(&line, &len, &command_name);
1377 if (ret != PARSE_STRING_SUCCESS)
1380 if (!tstrcasecmp(command_name, T("add"))) {
1381 op = WIMLIB_UPDATE_OP_ADD;
1382 } else if (!tstrcasecmp(command_name, T("delete"))) {
1383 op = WIMLIB_UPDATE_OP_DELETE;
1384 } else if (!tstrcasecmp(command_name, T("rename"))) {
1385 op = WIMLIB_UPDATE_OP_RENAME;
1387 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1388 command_name, line_number);
1393 /* Parse additional options and non-options as needed */
1398 ret = parse_string(&line, &len, &next_string);
1399 if (ret == PARSE_STRING_NONE) /* End of line */
1401 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1403 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1405 if (!update_command_add_option(op, next_string, command))
1407 imagex_error(T("Unrecognized option \"%"TS"\" to "
1408 "update command \"%"TS"\" on line %zu"),
1409 next_string, command_name, line_number);
1415 if (num_nonoptions == update_command_num_nonoptions[op])
1417 imagex_error(T("Unexpected argument \"%"TS"\" in "
1418 "update command on line %zu\n"
1419 " (The \"%"TS"\" command only "
1420 "takes %zu nonoption arguments!)\n"),
1421 next_string, line_number,
1422 command_name, num_nonoptions);
1425 update_command_add_nonoption(op, next_string,
1426 command, num_nonoptions);
1431 if (num_nonoptions != update_command_num_nonoptions[op]) {
1432 imagex_error(T("Not enough arguments to update command "
1433 "\"%"TS"\" on line %zu"), command_name, line_number);
1439 static struct wimlib_update_command *
1440 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1441 size_t *num_cmds_ret)
1445 struct wimlib_update_command *cmds;
1448 nlines = text_file_count_lines(cmd_file_contents_p,
1453 /* Always allocate at least 1 slot, just in case the implementation of
1454 * calloc() returns NULL if 0 bytes are requested. */
1455 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1457 imagex_error(T("out of memory"));
1460 p = *cmd_file_contents_p;
1462 for (i = 0; i < nlines; i++) {
1463 /* XXX: Could use rawmemchr() here instead, but it may not be
1464 * available on all platforms. */
1465 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1466 size_t len = endp - p + 1;
1468 if (!is_comment_line(p, len)) {
1469 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1480 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1481 * one image from a WIM file to an NTFS volume. */
1483 imagex_apply(int argc, tchar **argv, int cmd)
1487 int image = WIMLIB_NO_IMAGE;
1489 struct wimlib_wim_info info;
1491 const tchar *wimfile;
1492 const tchar *target;
1493 const tchar *image_num_or_name = NULL;
1494 int extract_flags = 0;
1496 STRING_SET(refglobs);
1498 for_opt(c, apply_options) {
1500 case IMAGEX_CHECK_OPTION:
1501 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1503 case IMAGEX_VERBOSE_OPTION:
1504 /* No longer does anything. */
1506 case IMAGEX_REF_OPTION:
1507 ret = string_set_append(&refglobs, optarg);
1509 goto out_free_refglobs;
1511 case IMAGEX_UNIX_DATA_OPTION:
1512 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1514 case IMAGEX_NO_ACLS_OPTION:
1515 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1517 case IMAGEX_STRICT_ACLS_OPTION:
1518 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1520 case IMAGEX_NO_ATTRIBUTES_OPTION:
1521 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1523 case IMAGEX_NORPFIX_OPTION:
1524 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1526 case IMAGEX_RPFIX_OPTION:
1527 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1529 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1530 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1531 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1533 case IMAGEX_RESUME_OPTION:
1534 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1536 case IMAGEX_WIMBOOT_OPTION:
1537 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1545 if (argc != 2 && argc != 3)
1550 if (!tstrcmp(wimfile, T("-"))) {
1551 /* Attempt to apply pipable WIM from standard input. */
1553 image_num_or_name = NULL;
1556 image_num_or_name = argv[1];
1561 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1562 imagex_progress_func, NULL);
1564 goto out_free_refglobs;
1566 wimlib_get_wim_info(wim, &info);
1569 /* Image explicitly specified. */
1570 image_num_or_name = argv[1];
1571 image = wimlib_resolve_image(wim, image_num_or_name);
1572 ret = verify_image_exists(image, image_num_or_name, wimfile);
1574 goto out_wimlib_free;
1577 /* No image specified; default to image 1, but only if the WIM
1578 * contains exactly one image. */
1580 if (info.image_count != 1) {
1581 imagex_error(T("\"%"TS"\" contains %d images; "
1582 "Please select one (or all)."),
1583 wimfile, info.image_count);
1592 if (refglobs.num_strings) {
1594 imagex_error(T("Can't specify --ref when applying from stdin!"));
1596 goto out_wimlib_free;
1598 ret = wim_reference_globs(wim, &refglobs, open_flags);
1600 goto out_wimlib_free;
1605 /* Interpret a regular file or block device target as an NTFS
1609 if (tstat(target, &stbuf)) {
1610 if (errno != ENOENT) {
1611 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1614 goto out_wimlib_free;
1617 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1618 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1624 ret = wimlib_extract_image(wim, image, target, extract_flags);
1626 set_fd_to_binary_mode(STDIN_FILENO);
1627 ret = wimlib_extract_image_from_pipe_with_progress(
1632 imagex_progress_func,
1636 imagex_printf(T("Done applying WIM image.\n"));
1637 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1639 do_resource_not_found_warning(wimfile, &info, &refglobs);
1641 imagex_error(T( "If you are applying an image "
1642 "from a split pipable WIM,\n"
1643 " make sure you have "
1644 "concatenated together all parts."));
1650 string_set_destroy(&refglobs);
1654 usage(CMD_APPLY, stderr);
1656 goto out_free_refglobs;
1659 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1660 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1661 * the desired image. 'wimlib-imagex append': add a new image to an existing
1664 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1667 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
1668 int add_image_flags = WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE |
1669 WIMLIB_ADD_IMAGE_FLAG_WINCONFIG |
1670 WIMLIB_ADD_IMAGE_FLAG_VERBOSE;
1671 int write_flags = 0;
1672 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1673 uint32_t chunk_size = UINT32_MAX;
1674 uint32_t solid_chunk_size = UINT32_MAX;
1675 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1676 const tchar *wimfile;
1680 const tchar *flags_element = NULL;
1683 STRING_SET(base_wimfiles);
1684 WIMStruct **base_wims;
1686 WIMStruct *template_wim;
1687 const tchar *template_wimfile = NULL;
1688 const tchar *template_image_name_or_num = NULL;
1689 int template_image = WIMLIB_NO_IMAGE;
1692 unsigned num_threads = 0;
1697 tchar *config_file = NULL;
1699 bool source_list = false;
1700 size_t source_list_nchars = 0;
1701 tchar *source_list_contents;
1702 bool capture_sources_malloced;
1703 struct wimlib_capture_source *capture_sources;
1705 bool name_defaulted;
1707 for_opt(c, capture_or_append_options) {
1709 case IMAGEX_BOOT_OPTION:
1710 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_BOOT;
1712 case IMAGEX_CHECK_OPTION:
1713 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1714 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1716 case IMAGEX_NOCHECK_OPTION:
1717 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1719 case IMAGEX_CONFIG_OPTION:
1720 config_file = optarg;
1721 add_image_flags &= ~WIMLIB_ADD_IMAGE_FLAG_WINCONFIG;
1723 case IMAGEX_COMPRESS_OPTION:
1724 compression_type = get_compression_type(optarg);
1725 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1728 case IMAGEX_COMPRESS_SLOW_OPTION:
1729 set_compress_slow();
1731 case IMAGEX_CHUNK_SIZE_OPTION:
1732 chunk_size = parse_chunk_size(optarg);
1733 if (chunk_size == UINT32_MAX)
1736 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1737 solid_chunk_size = parse_chunk_size(optarg);
1738 if (solid_chunk_size == UINT32_MAX)
1741 case IMAGEX_SOLID_COMPRESS_OPTION:
1742 solid_ctype = get_compression_type(optarg);
1743 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1746 case IMAGEX_SOLID_OPTION:
1747 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
1749 case IMAGEX_FLAGS_OPTION:
1750 flags_element = optarg;
1752 case IMAGEX_DEREFERENCE_OPTION:
1753 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_DEREFERENCE;
1755 case IMAGEX_VERBOSE_OPTION:
1756 /* No longer does anything. */
1758 case IMAGEX_THREADS_OPTION:
1759 num_threads = parse_num_threads(optarg);
1760 if (num_threads == UINT_MAX)
1763 case IMAGEX_REBUILD_OPTION:
1764 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1766 case IMAGEX_UNIX_DATA_OPTION:
1767 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA;
1769 case IMAGEX_SOURCE_LIST_OPTION:
1772 case IMAGEX_NO_ACLS_OPTION:
1773 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NO_ACLS;
1775 case IMAGEX_STRICT_ACLS_OPTION:
1776 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS;
1778 case IMAGEX_RPFIX_OPTION:
1779 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_RPFIX;
1781 case IMAGEX_NORPFIX_OPTION:
1782 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NORPFIX;
1784 case IMAGEX_PIPABLE_OPTION:
1785 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1787 case IMAGEX_NOT_PIPABLE_OPTION:
1788 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1790 case IMAGEX_UPDATE_OF_OPTION:
1791 if (template_image_name_or_num) {
1792 imagex_error(T("'--update-of' can only be "
1793 "specified one time!"));
1797 colon = tstrrchr(optarg, T(':'));
1800 template_wimfile = optarg;
1802 template_image_name_or_num = colon + 1;
1804 template_wimfile = NULL;
1805 template_image_name_or_num = optarg;
1809 case IMAGEX_DELTA_FROM_OPTION:
1810 if (cmd != CMD_CAPTURE) {
1811 imagex_error(T("'--delta-from' is only "
1812 "valid for capture!"));
1815 ret = string_set_append(&base_wimfiles, optarg);
1817 goto out_free_base_wimfiles;
1818 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1820 case IMAGEX_WIMBOOT_OPTION:
1821 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_WIMBOOT;
1830 if (argc < 2 || argc > 4)
1836 /* Set default compression type and parameters. */
1839 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1840 /* No compression type specified. Use the default. */
1842 if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) {
1843 /* With --wimboot, default to XPRESS compression. */
1844 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1845 } else if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS) {
1846 /* With --solid, default to LZMS compression. (However,
1847 * this will not affect solid blocks!) */
1848 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1850 /* Otherwise, default to LZX compression. */
1851 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1855 if (!tstrcmp(wimfile, T("-"))) {
1856 /* Writing captured WIM to standard output. */
1858 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1859 imagex_error("Can't write a non-pipable WIM to "
1860 "standard output! Specify --pipable\n"
1861 " if you want to create a pipable WIM "
1862 "(but read the docs first).");
1866 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1868 if (cmd == CMD_APPEND) {
1869 imagex_error(T("Using standard output for append does "
1870 "not make sense."));
1873 wim_fd = STDOUT_FILENO;
1875 imagex_info_file = stderr;
1876 set_fd_to_binary_mode(wim_fd);
1879 /* If template image was specified using --update-of=IMAGE rather
1880 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1881 if (template_image_name_or_num && !template_wimfile) {
1882 if (base_wimfiles.num_strings == 1) {
1883 /* Capturing delta WIM based on single WIM: default to
1885 template_wimfile = base_wimfiles.strings[0];
1886 } else if (cmd == CMD_APPEND) {
1887 /* Appending to WIM: default to WIM being appended to.
1889 template_wimfile = wimfile;
1891 /* Capturing a normal (non-delta) WIM, so the WIM file
1892 * *must* be explicitly specified. */
1893 if (base_wimfiles.num_strings > 1) {
1894 imagex_error(T("For capture of delta WIM "
1895 "based on multiple existing "
1897 " '--update-of' must "
1898 "specify WIMFILE:IMAGE!"));
1900 imagex_error(T("For capture of non-delta WIM, "
1901 "'--update-of' must specify "
1910 name_defaulted = false;
1912 /* Set default name to SOURCE argument, omitting any directory
1913 * prefixes and trailing slashes. This requires making a copy
1914 * of @source. Leave some free characters at the end in case we
1915 * append a number to keep the name unique. */
1916 size_t source_name_len;
1918 source_name_len = tstrlen(source);
1919 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1920 name = tbasename(tstrcpy(source_copy, source));
1921 name_defaulted = true;
1923 /* Image description defaults to NULL if not given. */
1930 /* Set up capture sources in source list mode */
1931 if (source[0] == T('-') && source[1] == T('\0')) {
1932 source_list_contents = stdin_get_text_contents(&source_list_nchars);
1934 source_list_contents = file_get_text_contents(source,
1935 &source_list_nchars);
1937 if (!source_list_contents)
1940 capture_sources = parse_source_list(&source_list_contents,
1943 if (!capture_sources) {
1945 goto out_free_source_list_contents;
1947 capture_sources_malloced = true;
1949 /* Set up capture source in non-source-list mode. */
1950 capture_sources = alloca(sizeof(struct wimlib_capture_source));
1951 capture_sources[0].fs_source_path = source;
1952 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
1953 capture_sources[0].reserved = 0;
1955 capture_sources_malloced = false;
1956 source_list_contents = NULL;
1959 /* Open the existing WIM, or create a new one. */
1960 if (cmd == CMD_APPEND) {
1961 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1962 imagex_progress_func, NULL);
1964 goto out_free_capture_sources;
1966 ret = wimlib_create_new_wim(compression_type, &wim);
1968 goto out_free_capture_sources;
1969 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
1972 /* Set chunk size if non-default. */
1973 if (chunk_size != UINT32_MAX) {
1974 ret = wimlib_set_output_chunk_size(wim, chunk_size);
1977 } else if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_WIMBOOT) &&
1978 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
1979 ret = wimlib_set_output_chunk_size(wim, 4096);
1983 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
1984 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
1988 if (solid_chunk_size != UINT32_MAX) {
1989 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
1995 /* Detect if source is regular file or block device and set NTFS volume
2000 if (tstat(source, &stbuf) == 0) {
2001 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2002 imagex_printf(T("Capturing WIM image from NTFS "
2003 "filesystem on \"%"TS"\"\n"), source);
2004 add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
2007 if (errno != ENOENT) {
2008 imagex_error_with_errno(T("Failed to stat "
2009 "\"%"TS"\""), source);
2017 /* If the user did not specify an image name, and the basename of the
2018 * source already exists as an image name in the WIM file, append a
2019 * suffix to make it unique. */
2020 if (cmd == CMD_APPEND && name_defaulted) {
2021 unsigned long conflict_idx;
2022 tchar *name_end = tstrchr(name, T('\0'));
2023 for (conflict_idx = 1;
2024 wimlib_image_name_in_use(wim, name);
2027 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2031 /* If capturing a delta WIM, reference resources from the base WIMs
2032 * before adding the new image. */
2033 if (base_wimfiles.num_strings) {
2034 base_wims = calloc(base_wimfiles.num_strings,
2035 sizeof(base_wims[0]));
2036 if (base_wims == NULL) {
2037 imagex_error(T("Out of memory!"));
2042 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2043 ret = wimlib_open_wim_with_progress(
2044 base_wimfiles.strings[i],
2045 open_flags, &base_wims[i],
2046 imagex_progress_func, NULL);
2048 goto out_free_base_wims;
2052 ret = wimlib_reference_resources(wim, base_wims,
2053 base_wimfiles.num_strings, 0);
2055 goto out_free_base_wims;
2057 if (base_wimfiles.num_strings == 1) {
2058 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2059 base_wimfiles.strings[0]);
2061 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2062 base_wimfiles.num_strings);
2069 /* If capturing or appending as an update of an existing (template) image,
2070 * open the WIM if needed and parse the image index. */
2071 if (template_image_name_or_num) {
2074 if (base_wimfiles.num_strings == 1 &&
2075 template_wimfile == base_wimfiles.strings[0]) {
2076 template_wim = base_wims[0];
2077 } else if (template_wimfile == wimfile) {
2080 ret = wimlib_open_wim_with_progress(template_wimfile,
2083 imagex_progress_func,
2086 goto out_free_base_wims;
2089 template_image = wimlib_resolve_image(template_wim,
2090 template_image_name_or_num);
2092 if (template_image_name_or_num[0] == T('-')) {
2095 struct wimlib_wim_info info;
2097 wimlib_get_wim_info(template_wim, &info);
2098 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2099 if (n >= 1 && n <= info.image_count &&
2101 tmp != template_image_name_or_num + 1)
2103 template_image = info.image_count - (n - 1);
2106 ret = verify_image_exists_and_is_single(template_image,
2107 template_image_name_or_num,
2110 goto out_free_template_wim;
2112 template_wim = NULL;
2115 ret = wimlib_add_image_multisource(wim,
2122 goto out_free_template_wim;
2124 if (desc || flags_element || template_image_name_or_num) {
2125 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2126 * on which the added one is to be based has been specified with
2127 * --update-of. Get the index of the image we just
2128 * added, then use it to call the appropriate functions. */
2129 struct wimlib_wim_info info;
2131 wimlib_get_wim_info(wim, &info);
2134 ret = wimlib_set_image_descripton(wim,
2138 goto out_free_template_wim;
2141 if (flags_element) {
2142 ret = wimlib_set_image_flags(wim, info.image_count,
2145 goto out_free_template_wim;
2148 /* Reference template image if the user provided one. */
2149 if (template_image_name_or_num) {
2150 imagex_printf(T("Using image %d "
2151 "from \"%"TS"\" as template\n"),
2152 template_image, template_wimfile);
2153 ret = wimlib_reference_template_image(wim,
2159 goto out_free_template_wim;
2163 /* Write the new WIM or overwrite the existing WIM with the new image
2165 if (cmd == CMD_APPEND) {
2166 ret = wimlib_overwrite(wim, write_flags, num_threads);
2167 } else if (wimfile) {
2168 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2169 write_flags, num_threads);
2171 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2172 write_flags, num_threads);
2174 out_free_template_wim:
2175 /* template_wim may alias base_wims[0] or wim. */
2176 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2177 template_wim != wim)
2178 wimlib_free(template_wim);
2180 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2181 wimlib_free(base_wims[i]);
2185 out_free_capture_sources:
2186 if (capture_sources_malloced)
2187 free(capture_sources);
2188 out_free_source_list_contents:
2189 free(source_list_contents);
2190 out_free_base_wimfiles:
2191 string_set_destroy(&base_wimfiles);
2198 goto out_free_base_wimfiles;
2201 /* Remove image(s) from a WIM. */
2203 imagex_delete(int argc, tchar **argv, int cmd)
2206 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2207 int write_flags = 0;
2208 const tchar *wimfile;
2209 const tchar *image_num_or_name;
2214 for_opt(c, delete_options) {
2216 case IMAGEX_CHECK_OPTION:
2217 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2218 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2220 case IMAGEX_SOFT_OPTION:
2221 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2232 imagex_error(T("Must specify a WIM file"));
2234 imagex_error(T("Must specify an image"));
2238 image_num_or_name = argv[1];
2240 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2241 imagex_progress_func, NULL);
2245 image = wimlib_resolve_image(wim, image_num_or_name);
2247 ret = verify_image_exists(image, image_num_or_name, wimfile);
2249 goto out_wimlib_free;
2251 ret = wimlib_delete_image(wim, image);
2253 imagex_error(T("Failed to delete image from \"%"TS"\""),
2255 goto out_wimlib_free;
2258 ret = wimlib_overwrite(wim, write_flags, 0);
2260 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2261 "deleted"), wimfile);
2269 usage(CMD_DELETE, stderr);
2274 struct print_dentry_options {
2279 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2281 tprintf(T("%"TS"\n"), dentry->full_path);
2284 static const struct {
2287 } file_attr_flags[] = {
2288 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2289 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2290 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2291 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2292 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2293 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2294 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2295 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2296 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2297 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2298 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2299 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2300 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2301 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2302 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2305 #define TIMESTR_MAX 100
2308 timespec_to_string(const struct timespec *spec, tchar *buf)
2310 time_t t = spec->tv_sec;
2313 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2314 buf[TIMESTR_MAX - 1] = '\0';
2318 print_time(const tchar *type, const struct timespec *spec)
2320 tchar timestr[TIMESTR_MAX];
2322 timespec_to_string(spec, timestr);
2324 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2327 static void print_byte_field(const uint8_t field[], size_t len)
2330 tprintf(T("%02hhx"), *field++);
2334 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2336 tputs(T("WIM Information:"));
2337 tputs(T("----------------"));
2338 tprintf(T("Path: %"TS"\n"), wimfile);
2339 tprintf(T("GUID: 0x"));
2340 print_byte_field(info->guid, sizeof(info->guid));
2342 tprintf(T("Version: %u\n"), info->wim_version);
2343 tprintf(T("Image Count: %d\n"), info->image_count);
2344 tprintf(T("Compression: %"TS"\n"),
2345 wimlib_get_compression_type_string(info->compression_type));
2346 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2348 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2349 tprintf(T("Boot Index: %d\n"), info->boot_index);
2350 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2351 tprintf(T("Integrity Info: %"TS"\n"),
2352 info->has_integrity_table ? T("yes") : T("no"));
2353 tprintf(T("Relative path junction: %"TS"\n"),
2354 info->has_rpfix ? T("yes") : T("no"));
2355 tprintf(T("Pipable: %"TS"\n"),
2356 info->pipable ? T("yes") : T("no"));
2361 print_resource(const struct wimlib_resource_entry *resource,
2364 tprintf(T("Hash = 0x"));
2365 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2368 if (!resource->is_missing) {
2369 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2370 resource->uncompressed_size);
2371 if (resource->packed) {
2372 tprintf(T("Raw compressed size = %"PRIu64" bytes\n"),
2373 resource->raw_resource_compressed_size);
2375 tprintf(T("Raw offset in WIM = %"PRIu64" bytes\n"),
2376 resource->raw_resource_offset_in_wim);
2378 tprintf(T("Offset in raw = %"PRIu64" bytes\n"),
2381 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2382 resource->compressed_size);
2384 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2388 tprintf(T("Part Number = %u\n"), resource->part_number);
2389 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2391 tprintf(T("Flags = "));
2392 if (resource->is_compressed)
2393 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2394 if (resource->is_metadata)
2395 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2396 if (resource->is_free)
2397 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2398 if (resource->is_spanned)
2399 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2400 if (resource->packed)
2401 tprintf(T("WIM_RESHDR_FLAG_PACKED_STREAMS "));
2409 print_lookup_table(WIMStruct *wim)
2411 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2415 default_print_security_descriptor(const uint8_t *sd, size_t size)
2417 tprintf(T("Security Descriptor = "));
2418 print_byte_field(sd, size);
2423 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2427 "----------------------------------------------------------------------------\n"));
2428 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2429 if (dentry->dos_name)
2430 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2431 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2432 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2433 if (file_attr_flags[i].flag & dentry->attributes)
2434 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2435 file_attr_flags[i].name);
2437 if (dentry->security_descriptor) {
2438 print_security_descriptor(dentry->security_descriptor,
2439 dentry->security_descriptor_size);
2442 print_time(T("Creation Time"), &dentry->creation_time);
2443 print_time(T("Last Write Time"), &dentry->last_write_time);
2444 print_time(T("Last Access Time"), &dentry->last_access_time);
2447 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2448 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2450 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2451 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2453 if (dentry->unix_mode != 0) {
2454 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2455 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2456 dentry->unix_uid, dentry->unix_gid,
2457 dentry->unix_mode, dentry->unix_rdev);
2460 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2461 if (dentry->streams[i].stream_name) {
2462 tprintf(T("\tData stream \"%"TS"\":\n"),
2463 dentry->streams[i].stream_name);
2465 tprintf(T("\tUnnamed data stream:\n"));
2467 print_resource(&dentry->streams[i].resource, NULL);
2472 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2474 const struct print_dentry_options *options = _options;
2475 if (!options->detailed)
2476 print_dentry_full_path(dentry);
2478 print_dentry_detailed(dentry);
2482 /* Print the files contained in an image(s) in a WIM file. */
2484 imagex_dir(int argc, tchar **argv, int cmd)
2486 const tchar *wimfile;
2487 WIMStruct *wim = NULL;
2490 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2492 struct print_dentry_options options = {
2495 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2497 for_opt(c, dir_options) {
2499 case IMAGEX_PATH_OPTION:
2502 case IMAGEX_DETAILED_OPTION:
2503 options.detailed = true;
2505 case IMAGEX_ONE_FILE_ONLY_OPTION:
2506 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2516 imagex_error(T("Must specify a WIM file"));
2520 imagex_error(T("Too many arguments"));
2525 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2526 imagex_progress_func, NULL);
2531 image = wimlib_resolve_image(wim, argv[1]);
2532 ret = verify_image_exists(image, argv[1], wimfile);
2534 goto out_wimlib_free;
2536 /* No image specified; default to image 1, but only if the WIM
2537 * contains exactly one image. */
2539 struct wimlib_wim_info info;
2541 wimlib_get_wim_info(wim, &info);
2542 if (info.image_count != 1) {
2543 imagex_error(T("\"%"TS"\" contains %d images; Please "
2544 "select one (or all)."),
2545 wimfile, info.image_count);
2552 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2553 print_dentry, &options);
2560 usage(CMD_DIR, stderr);
2565 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2568 imagex_export(int argc, tchar **argv, int cmd)
2572 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2573 int write_flags = 0;
2574 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2575 const tchar *src_wimfile;
2576 const tchar *src_image_num_or_name;
2577 const tchar *dest_wimfile;
2579 const tchar *dest_name;
2580 const tchar *dest_desc;
2582 struct wimlib_wim_info src_info;
2583 WIMStruct *dest_wim;
2588 STRING_SET(refglobs);
2589 unsigned num_threads = 0;
2590 uint32_t chunk_size = UINT32_MAX;
2591 uint32_t solid_chunk_size = UINT32_MAX;
2592 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2594 for_opt(c, export_options) {
2596 case IMAGEX_BOOT_OPTION:
2597 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2599 case IMAGEX_CHECK_OPTION:
2600 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2601 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2603 case IMAGEX_NOCHECK_OPTION:
2604 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2606 case IMAGEX_COMPRESS_OPTION:
2607 compression_type = get_compression_type(optarg);
2608 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2611 case IMAGEX_COMPRESS_SLOW_OPTION:
2612 set_compress_slow();
2613 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2615 case IMAGEX_RECOMPRESS_OPTION:
2616 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2618 case IMAGEX_SOLID_OPTION:
2619 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
2621 case IMAGEX_CHUNK_SIZE_OPTION:
2622 chunk_size = parse_chunk_size(optarg);
2623 if (chunk_size == UINT32_MAX)
2626 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2627 solid_chunk_size = parse_chunk_size(optarg);
2628 if (solid_chunk_size == UINT32_MAX)
2631 case IMAGEX_SOLID_COMPRESS_OPTION:
2632 solid_ctype = get_compression_type(optarg);
2633 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2636 case IMAGEX_REF_OPTION:
2637 ret = string_set_append(&refglobs, optarg);
2639 goto out_free_refglobs;
2641 case IMAGEX_THREADS_OPTION:
2642 num_threads = parse_num_threads(optarg);
2643 if (num_threads == UINT_MAX)
2646 case IMAGEX_REBUILD_OPTION:
2647 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2649 case IMAGEX_PIPABLE_OPTION:
2650 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2652 case IMAGEX_NOT_PIPABLE_OPTION:
2653 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2655 case IMAGEX_WIMBOOT_OPTION:
2656 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2664 if (argc < 3 || argc > 5)
2666 src_wimfile = argv[0];
2667 src_image_num_or_name = argv[1];
2668 dest_wimfile = argv[2];
2669 dest_name = (argc >= 4) ? argv[3] : NULL;
2670 dest_desc = (argc >= 5) ? argv[4] : NULL;
2671 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2672 imagex_progress_func, NULL);
2674 goto out_free_refglobs;
2676 wimlib_get_wim_info(src_wim, &src_info);
2678 /* Determine if the destination is an existing file or not. If so, we
2679 * try to append the exported image(s) to it; otherwise, we create a new
2680 * WIM containing the exported image(s). Furthermore, determine if we
2681 * need to write a pipable WIM directly to standard output. */
2683 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2685 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2686 imagex_error("Can't write a non-pipable WIM to "
2687 "standard output! Specify --pipable\n"
2688 " if you want to create a pipable WIM "
2689 "(but read the docs first).");
2691 goto out_free_src_wim;
2694 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2696 dest_wimfile = NULL;
2697 dest_wim_fd = STDOUT_FILENO;
2698 imagex_info_file = stderr;
2699 set_fd_to_binary_mode(dest_wim_fd);
2702 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2704 /* Destination file exists. */
2706 if (!S_ISREG(stbuf.st_mode)) {
2707 imagex_error(T("\"%"TS"\" is not a regular file"),
2710 goto out_free_src_wim;
2712 ret = wimlib_open_wim_with_progress(dest_wimfile,
2714 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2716 imagex_progress_func,
2719 goto out_free_src_wim;
2721 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2722 /* The user specified a compression type, but we're
2723 * exporting to an existing WIM. Make sure the
2724 * specified compression type is the same as the
2725 * compression type of the existing destination WIM. */
2726 struct wimlib_wim_info dest_info;
2728 wimlib_get_wim_info(dest_wim, &dest_info);
2729 if (compression_type != dest_info.compression_type) {
2730 imagex_error(T("Cannot specify a compression type that is "
2731 "not the same as that used in the "
2732 "destination WIM"));
2734 goto out_free_dest_wim;
2740 if (errno != ENOENT) {
2741 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2744 goto out_free_src_wim;
2747 /* dest_wimfile is not an existing file, so create a new WIM. */
2749 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2750 /* The user did not specify a compression type; default
2751 * to that of the source WIM, unless --solid or
2752 * --wimboot was specified. */
2754 if (write_flags & WIMLIB_WRITE_FLAG_PACK_STREAMS)
2755 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2756 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2757 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2759 compression_type = src_info.compression_type;
2761 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2763 goto out_free_src_wim;
2765 wimlib_register_progress_function(dest_wim,
2766 imagex_progress_func, NULL);
2768 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2769 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2771 /* For --wimboot export, use small XPRESS chunks. */
2772 wimlib_set_output_chunk_size(dest_wim, 4096);
2773 } else if (compression_type == src_info.compression_type &&
2774 chunk_size == UINT32_MAX)
2776 /* Use same chunk size if compression type is the same. */
2777 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2781 if (chunk_size != UINT32_MAX) {
2782 /* Set destination chunk size. */
2783 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2785 goto out_free_dest_wim;
2787 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2788 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2790 goto out_free_dest_wim;
2792 if (solid_chunk_size != UINT32_MAX) {
2793 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2795 goto out_free_dest_wim;
2798 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2799 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2801 goto out_free_dest_wim;
2803 if (refglobs.num_strings) {
2804 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2806 goto out_free_dest_wim;
2809 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2810 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2812 imagex_error(T("--boot specified for all-images export, but source WIM "
2813 "has no bootable image."));
2815 goto out_free_dest_wim;
2818 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2819 dest_desc, export_flags);
2821 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2822 do_resource_not_found_warning(src_wimfile,
2823 &src_info, &refglobs);
2825 goto out_free_dest_wim;
2829 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2830 else if (dest_wimfile)
2831 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2832 write_flags, num_threads);
2834 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2835 WIMLIB_ALL_IMAGES, write_flags,
2838 wimlib_free(dest_wim);
2840 wimlib_free(src_wim);
2842 string_set_destroy(&refglobs);
2846 usage(CMD_EXPORT, stderr);
2849 goto out_free_refglobs;
2852 /* Extract files or directories from a WIM image */
2854 imagex_extract(int argc, tchar **argv, int cmd)
2861 const tchar *wimfile;
2862 const tchar *image_num_or_name;
2863 tchar *dest_dir = T(".");
2864 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2865 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2866 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2867 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2869 STRING_SET(refglobs);
2871 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2873 for_opt(c, extract_options) {
2875 case IMAGEX_CHECK_OPTION:
2876 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2878 case IMAGEX_VERBOSE_OPTION:
2879 /* No longer does anything. */
2881 case IMAGEX_REF_OPTION:
2882 ret = string_set_append(&refglobs, optarg);
2884 goto out_free_refglobs;
2886 case IMAGEX_UNIX_DATA_OPTION:
2887 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2889 case IMAGEX_NO_ACLS_OPTION:
2890 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
2892 case IMAGEX_STRICT_ACLS_OPTION:
2893 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
2895 case IMAGEX_NO_ATTRIBUTES_OPTION:
2896 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
2898 case IMAGEX_DEST_DIR_OPTION:
2901 case IMAGEX_TO_STDOUT_OPTION:
2902 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
2903 imagex_info_file = stderr;
2904 imagex_be_quiet = true;
2905 set_fd_to_binary_mode(STDOUT_FILENO);
2907 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
2908 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
2909 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
2911 case IMAGEX_NO_GLOBS_OPTION:
2912 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2914 case IMAGEX_NULLGLOB_OPTION:
2915 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2917 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
2918 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2920 case IMAGEX_WIMBOOT_OPTION:
2921 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
2933 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2934 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
2936 imagex_error(T("Can't combine --no-globs and --nullglob!"));
2941 image_num_or_name = argv[1];
2946 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2947 imagex_progress_func, NULL);
2949 goto out_free_refglobs;
2951 image = wimlib_resolve_image(wim, image_num_or_name);
2952 ret = verify_image_exists_and_is_single(image,
2956 goto out_wimlib_free;
2958 if (refglobs.num_strings) {
2959 ret = wim_reference_globs(wim, &refglobs, open_flags);
2961 goto out_wimlib_free;
2967 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
2970 while (argc != 0 && ret == 0) {
2974 num_paths < argc && argv[num_paths][0] != T('@');
2979 ret = wimlib_extract_paths(wim, image, dest_dir,
2980 (const tchar **)argv,
2982 extract_flags | notlist_extract_flags);
2986 ret = wimlib_extract_pathlist(wim, image, dest_dir,
2995 if (!imagex_be_quiet)
2996 imagex_printf(T("Done extracting files.\n"));
2997 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
2998 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
2999 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3000 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3001 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3004 T("Note: You can use the '--nullglob' "
3005 "option to ignore missing files.\n"));
3007 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3008 "files and directories\n"
3009 " are in the WIM image.\n"),
3010 get_cmd_string(CMD_DIR, false));
3011 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3012 struct wimlib_wim_info info;
3014 wimlib_get_wim_info(wim, &info);
3015 do_resource_not_found_warning(wimfile, &info, &refglobs);
3020 string_set_destroy(&refglobs);
3024 usage(CMD_EXTRACT, stderr);
3027 goto out_free_refglobs;
3030 /* Prints information about a WIM file; also can mark an image as bootable,
3031 * change the name of an image, or change the description of an image. */
3033 imagex_info(int argc, tchar **argv, int cmd)
3038 bool nocheck = false;
3039 bool header = false;
3040 bool lookup_table = false;
3042 bool short_header = true;
3043 const tchar *xml_out_file = NULL;
3044 const tchar *wimfile;
3045 const tchar *image_num_or_name;
3046 const tchar *new_name;
3047 const tchar *new_desc;
3052 struct wimlib_wim_info info;
3054 for_opt(c, info_options) {
3056 case IMAGEX_BOOT_OPTION:
3059 case IMAGEX_CHECK_OPTION:
3062 case IMAGEX_NOCHECK_OPTION:
3065 case IMAGEX_HEADER_OPTION:
3067 short_header = false;
3069 case IMAGEX_LOOKUP_TABLE_OPTION:
3070 lookup_table = true;
3071 short_header = false;
3073 case IMAGEX_XML_OPTION:
3075 short_header = false;
3077 case IMAGEX_EXTRACT_XML_OPTION:
3078 xml_out_file = optarg;
3079 short_header = false;
3081 case IMAGEX_METADATA_OPTION:
3082 imagex_error(T("The --metadata option has been removed. "
3083 "Use 'wimdir --detail' instead."));
3092 if (argc < 1 || argc > 4)
3096 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3097 new_name = (argc >= 3) ? argv[2] : NULL;
3098 new_desc = (argc >= 4) ? argv[3] : NULL;
3100 if (check && nocheck) {
3101 imagex_error(T("Can't specify both --check and --nocheck"));
3106 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3108 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3109 imagex_progress_func, NULL);
3113 wimlib_get_wim_info(wim, &info);
3115 image = wimlib_resolve_image(wim, image_num_or_name);
3116 ret = WIMLIB_ERR_INVALID_IMAGE;
3117 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3118 verify_image_exists(image, image_num_or_name, wimfile);
3120 imagex_error(T("If you would like to set the boot "
3121 "index to 0, specify image \"0\" with "
3122 "the --boot flag."));
3124 goto out_wimlib_free;
3127 if (boot && info.image_count == 0) {
3128 imagex_error(T("--boot is meaningless on a WIM with no images"));
3129 goto out_wimlib_free;
3132 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3134 imagex_error(T("Cannot specify the --boot flag "
3135 "without specifying a specific "
3136 "image in a multi-image WIM"));
3137 goto out_wimlib_free;
3140 imagex_error(T("Cannot specify the NEW_NAME "
3141 "without specifying a specific "
3142 "image in a multi-image WIM"));
3143 goto out_wimlib_free;
3147 /* Operations that print information are separated from operations that
3148 * recreate the WIM file. */
3149 if (!new_name && !boot) {
3151 /* Read-only operations */
3153 if (image == WIMLIB_NO_IMAGE) {
3154 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3155 image_num_or_name, wimfile);
3156 goto out_wimlib_free;
3159 if (image == WIMLIB_ALL_IMAGES && short_header)
3160 print_wim_information(wimfile, &info);
3163 wimlib_print_header(wim);
3166 if (info.total_parts != 1) {
3167 tfprintf(stderr, T("Warning: Only showing the lookup table "
3168 "for part %d of a %d-part WIM.\n"),
3169 info.part_number, info.total_parts);
3171 print_lookup_table(wim);
3175 ret = wimlib_extract_xml_data(wim, stdout);
3177 goto out_wimlib_free;
3183 fp = tfopen(xml_out_file, T("wb"));
3185 imagex_error_with_errno(T("Failed to open the "
3186 "file \"%"TS"\" for "
3190 goto out_wimlib_free;
3192 ret = wimlib_extract_xml_data(wim, fp);
3194 imagex_error(T("Failed to close the file "
3200 goto out_wimlib_free;
3204 wimlib_print_available_images(wim, image);
3209 /* Modification operations */
3211 if (image == WIMLIB_ALL_IMAGES)
3214 if (image == WIMLIB_NO_IMAGE && new_name) {
3215 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3216 "when using image 0"), new_name);
3218 goto out_wimlib_free;
3222 if (image == info.boot_index) {
3223 imagex_printf(T("Image %d is already marked as "
3224 "bootable.\n"), image);
3227 imagex_printf(T("Marking image %d as bootable.\n"),
3229 info.boot_index = image;
3230 ret = wimlib_set_wim_info(wim, &info,
3231 WIMLIB_CHANGE_BOOT_INDEX);
3233 goto out_wimlib_free;
3237 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3239 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3243 imagex_printf(T("Changing the name of image %d to "
3244 "\"%"TS"\".\n"), image, new_name);
3245 ret = wimlib_set_image_name(wim, image, new_name);
3247 goto out_wimlib_free;
3251 const tchar *old_desc;
3252 old_desc = wimlib_get_image_description(wim, image);
3253 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3254 imagex_printf(T("The description of image %d is already "
3255 "\"%"TS"\".\n"), image, new_desc);
3258 imagex_printf(T("Changing the description of image %d "
3259 "to \"%"TS"\".\n"), image, new_desc);
3260 ret = wimlib_set_image_descripton(wim, image,
3263 goto out_wimlib_free;
3267 /* Only call wimlib_overwrite() if something actually needs to
3269 if (boot || new_name || new_desc ||
3270 (check && !info.has_integrity_table) ||
3271 (nocheck && info.has_integrity_table))
3273 int write_flags = 0;
3276 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3278 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3279 ret = wimlib_overwrite(wim, write_flags, 1);
3281 imagex_printf(T("The file \"%"TS"\" was not modified "
3282 "because nothing needed to be done.\n"),
3293 usage(CMD_INFO, stderr);
3299 /* Join split WIMs into one part WIM */
3301 imagex_join(int argc, tchar **argv, int cmd)
3304 int swm_open_flags = 0;
3305 int wim_write_flags = 0;
3306 const tchar *output_path;
3309 for_opt(c, join_options) {
3311 case IMAGEX_CHECK_OPTION:
3312 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3313 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3323 imagex_error(T("Must specify one or more split WIM (.swm) "
3327 output_path = argv[0];
3328 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3333 imagex_progress_func,
3339 usage(CMD_JOIN, stderr);
3344 #if WIM_MOUNTING_SUPPORTED
3346 /* Mounts a WIM image. */
3348 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3351 int mount_flags = 0;
3353 const tchar *staging_dir = NULL;
3354 const tchar *wimfile;
3357 struct wimlib_wim_info info;
3361 STRING_SET(refglobs);
3363 if (cmd == CMD_MOUNTRW) {
3364 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3365 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3368 for_opt(c, mount_options) {
3370 case IMAGEX_ALLOW_OTHER_OPTION:
3371 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3373 case IMAGEX_CHECK_OPTION:
3374 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3376 case IMAGEX_DEBUG_OPTION:
3377 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3379 case IMAGEX_STREAMS_INTERFACE_OPTION:
3380 if (!tstrcasecmp(optarg, T("none")))
3381 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3382 else if (!tstrcasecmp(optarg, T("xattr")))
3383 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3384 else if (!tstrcasecmp(optarg, T("windows")))
3385 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3387 imagex_error(T("Unknown stream interface \"%"TS"\""),
3392 case IMAGEX_REF_OPTION:
3393 ret = string_set_append(&refglobs, optarg);
3395 goto out_free_refglobs;
3397 case IMAGEX_STAGING_DIR_OPTION:
3398 staging_dir = optarg;
3400 case IMAGEX_UNIX_DATA_OPTION:
3401 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3409 if (argc != 2 && argc != 3)
3414 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3415 imagex_progress_func, NULL);
3417 goto out_free_refglobs;
3419 wimlib_get_wim_info(wim, &info);
3422 /* Image explicitly specified. */
3423 image = wimlib_resolve_image(wim, argv[1]);
3425 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3429 /* No image specified; default to image 1, but only if the WIM
3430 * contains exactly one image. */
3432 if (info.image_count != 1) {
3433 imagex_error(T("\"%"TS"\" contains %d images; Please "
3434 "select one."), wimfile, info.image_count);
3442 if (refglobs.num_strings) {
3443 ret = wim_reference_globs(wim, &refglobs, open_flags);
3448 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3450 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3452 image, wimfile, dir);
3457 string_set_destroy(&refglobs);
3463 goto out_free_refglobs;
3465 #endif /* WIM_MOUNTING_SUPPORTED */
3467 /* Rebuild a WIM file */
3469 imagex_optimize(int argc, tchar **argv, int cmd)
3472 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3473 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3474 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3475 uint32_t chunk_size = UINT32_MAX;
3476 uint32_t solid_chunk_size = UINT32_MAX;
3477 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3480 const tchar *wimfile;
3483 unsigned num_threads = 0;
3485 for_opt(c, optimize_options) {
3487 case IMAGEX_CHECK_OPTION:
3488 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3489 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3491 case IMAGEX_NOCHECK_OPTION:
3492 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3494 case IMAGEX_COMPRESS_OPTION:
3495 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3496 compression_type = get_compression_type(optarg);
3497 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3500 case IMAGEX_COMPRESS_SLOW_OPTION:
3501 set_compress_slow();
3502 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3504 case IMAGEX_RECOMPRESS_OPTION:
3505 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3507 case IMAGEX_CHUNK_SIZE_OPTION:
3508 chunk_size = parse_chunk_size(optarg);
3509 if (chunk_size == UINT32_MAX)
3512 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3513 solid_chunk_size = parse_chunk_size(optarg);
3514 if (solid_chunk_size == UINT32_MAX)
3517 case IMAGEX_SOLID_COMPRESS_OPTION:
3518 solid_ctype = get_compression_type(optarg);
3519 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3522 case IMAGEX_SOLID_OPTION:
3523 write_flags |= WIMLIB_WRITE_FLAG_PACK_STREAMS;
3524 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3526 case IMAGEX_THREADS_OPTION:
3527 num_threads = parse_num_threads(optarg);
3528 if (num_threads == UINT_MAX)
3531 case IMAGEX_PIPABLE_OPTION:
3532 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3534 case IMAGEX_NOT_PIPABLE_OPTION:
3535 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3549 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3550 imagex_progress_func, NULL);
3554 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3555 /* Change compression type. */
3556 ret = wimlib_set_output_compression_type(wim, compression_type);
3558 goto out_wimlib_free;
3561 if (chunk_size != UINT32_MAX) {
3562 /* Change chunk size. */
3563 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3565 goto out_wimlib_free;
3567 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3568 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3570 goto out_wimlib_free;
3572 if (solid_chunk_size != UINT32_MAX) {
3573 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3575 goto out_wimlib_free;
3578 old_size = file_get_size(wimfile);
3579 tprintf(T("\"%"TS"\" original size: "), wimfile);
3581 tputs(T("Unknown"));
3583 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3585 ret = wimlib_overwrite(wim, write_flags, num_threads);
3587 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3588 goto out_wimlib_free;
3591 new_size = file_get_size(wimfile);
3592 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3594 tputs(T("Unknown"));
3596 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3598 tfputs(T("Space saved: "), stdout);
3599 if (new_size != -1 && old_size != -1) {
3600 tprintf(T("%lld KiB\n"),
3601 ((long long)old_size - (long long)new_size) >> 10);
3603 tputs(T("Unknown"));
3612 usage(CMD_OPTIMIZE, stderr);
3618 /* Split a WIM into a spanned set */
3620 imagex_split(int argc, tchar **argv, int cmd)
3624 int write_flags = 0;
3625 unsigned long part_size;
3630 for_opt(c, split_options) {
3632 case IMAGEX_CHECK_OPTION:
3633 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3634 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3646 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3647 if (tmp == argv[2] || *tmp) {
3648 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3649 imagex_error(T("The part size must be an integer or "
3650 "floating-point number of megabytes."));
3653 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3654 imagex_progress_func, NULL);
3658 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3664 usage(CMD_SPLIT, stderr);
3670 #if WIM_MOUNTING_SUPPORTED
3671 /* Unmounts a mounted WIM image. */
3673 imagex_unmount(int argc, tchar **argv, int cmd)
3676 int unmount_flags = 0;
3679 for_opt(c, unmount_options) {
3681 case IMAGEX_COMMIT_OPTION:
3682 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3684 case IMAGEX_CHECK_OPTION:
3685 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3687 case IMAGEX_REBUILD_OPTION:
3688 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3690 case IMAGEX_LAZY_OPTION:
3691 case IMAGEX_FORCE_OPTION:
3692 /* Now, unmount is lazy by default. However, committing
3693 * the image will fail with
3694 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3695 * file descriptors on the WIM image. The
3696 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3697 * descriptors to be closed. */
3698 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3700 case IMAGEX_NEW_IMAGE_OPTION:
3701 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3712 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3713 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3714 imagex_error(T("--new-image is meaningless "
3715 "without --commit also specified!"));
3720 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3721 imagex_progress_func, NULL);
3723 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3724 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3726 "\tNote: Use --commit --force to force changes "
3727 "to be committed, regardless\n"
3728 "\t of open files.\n"));
3735 usage(CMD_UNMOUNT, stderr);
3740 #endif /* WIM_MOUNTING_SUPPORTED */
3743 * Add, delete, or rename files in a WIM image.
3746 imagex_update(int argc, tchar **argv, int cmd)
3748 const tchar *wimfile;
3752 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3753 int write_flags = 0;
3754 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3755 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3756 WIMLIB_ADD_FLAG_VERBOSE |
3757 WIMLIB_ADD_FLAG_WINCONFIG;
3758 int default_delete_flags = 0;
3759 unsigned num_threads = 0;
3761 tchar *cmd_file_contents;
3762 size_t cmd_file_nchars;
3763 struct wimlib_update_command *cmds;
3765 tchar *command_str = NULL;
3766 tchar *config_file = NULL;
3767 tchar *wimboot_config = NULL;
3769 for_opt(c, update_options) {
3771 /* Generic or write options */
3772 case IMAGEX_THREADS_OPTION:
3773 num_threads = parse_num_threads(optarg);
3774 if (num_threads == UINT_MAX)
3777 case IMAGEX_CHECK_OPTION:
3778 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3779 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3781 case IMAGEX_REBUILD_OPTION:
3782 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3784 case IMAGEX_COMMAND_OPTION:
3786 imagex_error(T("--command may only be specified "
3787 "one time. Please provide\n"
3788 " the update commands "
3789 "on standard input instead."));
3792 command_str = tstrdup(optarg);
3794 imagex_error(T("Out of memory!"));
3798 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3799 wimboot_config = optarg;
3801 /* Default delete options */
3802 case IMAGEX_FORCE_OPTION:
3803 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3805 case IMAGEX_RECURSIVE_OPTION:
3806 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3809 /* Global add option */
3810 case IMAGEX_CONFIG_OPTION:
3811 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3812 config_file = optarg;
3815 /* Default add options */
3816 case IMAGEX_VERBOSE_OPTION:
3817 /* No longer does anything. */
3819 case IMAGEX_DEREFERENCE_OPTION:
3820 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3822 case IMAGEX_UNIX_DATA_OPTION:
3823 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3825 case IMAGEX_NO_ACLS_OPTION:
3826 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3828 case IMAGEX_STRICT_ACLS_OPTION:
3829 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3831 case IMAGEX_NO_REPLACE_OPTION:
3832 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3841 if (argc != 1 && argc != 2)
3845 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3846 imagex_progress_func, NULL);
3848 goto out_free_command_str;
3851 /* Image explicitly specified. */
3852 image = wimlib_resolve_image(wim, argv[1]);
3853 ret = verify_image_exists_and_is_single(image, argv[1],
3856 goto out_wimlib_free;
3858 /* No image specified; default to image 1, but only if the WIM
3859 * contains exactly one image. */
3860 struct wimlib_wim_info info;
3862 wimlib_get_wim_info(wim, &info);
3863 if (info.image_count != 1) {
3864 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3865 wimfile, info.image_count);
3872 /* Read update commands from standard input, or the command string if
3875 cmd_file_contents = NULL;
3876 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3880 goto out_free_cmd_file_contents;
3882 } else if (!wimboot_config) {
3883 if (isatty(STDIN_FILENO)) {
3884 tputs(T("Reading update commands from standard input..."));
3885 recommend_man_page(CMD_UPDATE, stdout);
3887 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
3888 if (!cmd_file_contents) {
3890 goto out_wimlib_free;
3893 /* Parse the update commands */
3894 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
3898 goto out_free_cmd_file_contents;
3901 cmd_file_contents = NULL;
3906 /* Set default flags and capture config on the update commands */
3907 for (size_t i = 0; i < num_cmds; i++) {
3908 switch (cmds[i].op) {
3909 case WIMLIB_UPDATE_OP_ADD:
3910 cmds[i].add.add_flags |= default_add_flags;
3911 cmds[i].add.config_file = config_file;
3913 case WIMLIB_UPDATE_OP_DELETE:
3914 cmds[i].delete_.delete_flags |= default_delete_flags;
3921 /* Execute the update commands */
3922 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
3926 if (wimboot_config) {
3927 /* --wimboot-config=FILE is short for an
3928 * "add FILE /Windows/System32/WimBootCompress.ini" command.
3930 struct wimlib_update_command cmd;
3932 cmd.op = WIMLIB_UPDATE_OP_ADD;
3933 cmd.add.fs_source_path = wimboot_config;
3934 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
3935 cmd.add.config_file = NULL;
3936 cmd.add.add_flags = 0;
3938 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
3943 /* Overwrite the updated WIM */
3944 ret = wimlib_overwrite(wim, write_flags, num_threads);
3947 out_free_cmd_file_contents:
3948 free(cmd_file_contents);
3951 out_free_command_str:
3956 usage(CMD_UPDATE, stderr);
3959 goto out_free_command_str;
3964 struct imagex_command {
3966 int (*func)(int argc, tchar **argv, int cmd);
3969 static const struct imagex_command imagex_commands[] = {
3970 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
3971 [CMD_APPLY] = {T("apply"), imagex_apply},
3972 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
3973 [CMD_DELETE] = {T("delete"), imagex_delete},
3974 [CMD_DIR ] = {T("dir"), imagex_dir},
3975 [CMD_EXPORT] = {T("export"), imagex_export},
3976 [CMD_EXTRACT] = {T("extract"), imagex_extract},
3977 [CMD_INFO] = {T("info"), imagex_info},
3978 [CMD_JOIN] = {T("join"), imagex_join},
3979 #if WIM_MOUNTING_SUPPORTED
3980 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
3981 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
3983 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
3984 [CMD_SPLIT] = {T("split"), imagex_split},
3985 #if WIM_MOUNTING_SUPPORTED
3986 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
3988 [CMD_UPDATE] = {T("update"), imagex_update},
3993 /* Can be a directory or source list file. But source list file is probably
3994 * a rare use case, so just say directory. */
3995 # define SOURCE_STR T("DIRECTORY")
3997 /* Can only be a directory */
3998 # define TARGET_STR T("DIRECTORY")
4001 /* Can be a directory, NTFS volume, or source list file. */
4002 # define SOURCE_STR T("SOURCE")
4004 /* Can be a directory or NTFS volume. */
4005 # define TARGET_STR T("TARGET")
4009 static const tchar *usage_strings[] = {
4012 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4013 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4014 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4015 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4016 " [--wimboot] [--unix-data] [--dereference]\n"
4020 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4021 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4022 " [--no-attributes] [--rpfix] [--norpfix]\n"
4023 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4027 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4028 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4029 " [--config=FILE] [--threads=NUM_THREADS]\n"
4030 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4031 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4032 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4036 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4040 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4044 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4045 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4046 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4047 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4052 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4053 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4054 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4055 " [--no-attributes] [--include-invalid-names]\n"
4056 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4060 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4061 " [--boot] [--check] [--nocheck] [--xml]\n"
4062 " [--extract-xml FILE] [--header] [--lookup-table]\n"
4066 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4068 #if WIM_MOUNTING_SUPPORTED
4071 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4072 " [--check] [--streams-interface=INTERFACE]\n"
4073 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4077 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4078 " [--check] [--streams-interface=INTERFACE]\n"
4079 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4085 " [--recompress] [--compress=TYPE]\n"
4086 " [--threads=NUM_THREADS] [--check] [--nocheck]\n"
4091 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4093 #if WIM_MOUNTING_SUPPORTED
4096 " %"TS" DIRECTORY\n"
4097 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4102 " %"TS" WIMFILE [IMAGE]\n"
4103 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4104 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4105 " [--command=STRING] [--wimboot-config=FILE]\n"
4110 static const tchar *invocation_name;
4111 static int invocation_cmd = CMD_NONE;
4113 static const tchar *get_cmd_string(int cmd, bool nospace)
4115 static tchar buf[50];
4116 if (cmd == CMD_NONE) {
4117 tsprintf(buf, T("%"TS), T(IMAGEX_PROGNAME));
4118 } else if (invocation_cmd != CMD_NONE) {
4119 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4121 const tchar *format;
4124 format = T("%"TS"-%"TS"");
4126 format = T("%"TS" %"TS"");
4127 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4135 static const tchar *s =
4137 IMAGEX_PROGNAME " (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4138 "Copyright (C) 2012, 2013, 2014 Eric Biggers\n"
4139 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4140 "This is free software: you are free to change and redistribute it.\n"
4141 "There is NO WARRANTY, to the extent permitted by law.\n"
4143 "Report bugs to "PACKAGE_BUGREPORT".\n"
4150 help_or_version(int argc, tchar **argv, int cmd)
4155 for (i = 1; i < argc; i++) {
4157 if (p[0] == T('-') && p[1] == T('-')) {
4159 if (!tstrcmp(p, T("help"))) {
4160 if (cmd == CMD_NONE)
4165 } else if (!tstrcmp(p, T("version"))) {
4174 print_usage_string(int cmd, FILE *fp)
4176 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4180 recommend_man_page(int cmd, FILE *fp)
4182 const tchar *format_str;
4184 format_str = T("Some uncommon options are not listed;\n"
4185 "See %"TS".pdf in the doc directory for more details.\n");
4187 format_str = T("Some uncommon options are not listed;\n"
4188 "Try `man %"TS"' for more details.\n");
4190 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4194 usage(int cmd, FILE *fp)
4196 tfprintf(fp, T("Usage:\n"));
4197 print_usage_string(cmd, fp);
4198 tfprintf(fp, T("\n"));
4199 recommend_man_page(cmd, fp);
4205 tfprintf(fp, T("Usage:\n"));
4206 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4207 print_usage_string(cmd, fp);
4208 tfprintf(fp, T("\n"));
4210 static const tchar *extra =
4213 " %"TS" --version\n"
4216 tfprintf(fp, extra, invocation_name, invocation_name);
4218 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4219 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4220 "For some commands IMAGE may be \"all\".\n"
4222 recommend_man_page(CMD_NONE, fp);
4225 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4226 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4227 * something else), while on Windows the command arguments will be UTF-16LE
4228 * encoded 'wchar_t' strings. */
4231 wmain(int argc, wchar_t **argv, wchar_t **envp)
4233 main(int argc, char **argv)
4240 imagex_info_file = stdout;
4241 invocation_name = tbasename(argv[0]);
4244 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4245 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4249 setlocale(LC_ALL, "");
4250 codeset = nl_langinfo(CODESET);
4251 if (!strstr(codeset, "UTF-8") &&
4252 !strstr(codeset, "UTF8") &&
4253 !strstr(codeset, "utf-8") &&
4254 !strstr(codeset, "utf8"))
4257 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4258 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4259 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4260 " to any value to force wimlib to use UTF-8.\n",
4266 #endif /* !__WIN32__ */
4269 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4270 if (igcase != NULL) {
4271 if (!tstrcmp(igcase, T("no")) ||
4272 !tstrcmp(igcase, T("0")))
4273 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4274 else if (!tstrcmp(igcase, T("yes")) ||
4275 !tstrcmp(igcase, T("1")))
4276 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4279 "WARNING: Ignoring unknown setting of "
4280 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4285 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4287 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4288 tstrcmp(invocation_name, T(IMAGEX_PROGNAME))) {
4289 for (int i = 0; i < CMD_MAX; i++) {
4290 if (!tstrcmp(invocation_name + 3,
4291 imagex_commands[i].name))
4300 /* Unless already known from the invocation name, determine which
4301 * command was specified. */
4302 if (cmd == CMD_NONE) {
4304 imagex_error(T("No command specified!\n"));
4308 for (int i = 0; i < CMD_MAX; i++) {
4309 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4314 if (cmd != CMD_NONE) {
4320 /* Handle --help and --version. --help can be either for the program as
4321 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4322 * CMD_NONE). Note: help_or_version() will not return if a --help or
4323 * --version argument was found. */
4324 help_or_version(argc, argv, cmd);
4326 /* Bail if a valid command was not specified. */
4327 if (cmd == CMD_NONE) {
4328 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4333 /* Enable warning and error messages in wimlib to be more user-friendly.
4335 wimlib_set_print_errors(true);
4337 /* Initialize wimlib. */
4338 ret = wimlib_global_init(init_flags);
4340 goto out_check_status;
4342 /* Call the command handler function. */
4343 ret = imagex_commands[cmd].func(argc, argv, cmd);
4345 /* Check for error writing to standard output, especially since for some
4346 * commands, writing to standard output is part of the program's actual
4347 * behavior and not just for informational purposes. */
4348 if (ferror(stdout) || fclose(stdout)) {
4349 imagex_error_with_errno(T("error writing to standard output"));
4354 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4355 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4356 * error code from which an error message can be printed. */
4358 imagex_error(T("Exiting with error code %d:\n"
4360 wimlib_get_error_string(ret));
4361 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4362 imagex_error_with_errno(T("errno"));
4364 /* Make wimlib free any resources it's holding (although this is not
4365 * strictly necessary because the process is ending anyway). */
4366 wimlib_global_cleanup();