4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # include "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
147 IMAGEX_CHUNK_SIZE_OPTION,
148 IMAGEX_COMMAND_OPTION,
149 IMAGEX_COMMIT_OPTION,
150 IMAGEX_COMPRESS_OPTION,
151 IMAGEX_COMPRESS_SLOW_OPTION,
152 IMAGEX_CONFIG_OPTION,
154 IMAGEX_DELTA_FROM_OPTION,
155 IMAGEX_DEREFERENCE_OPTION,
156 IMAGEX_DEST_DIR_OPTION,
157 IMAGEX_DETAILED_OPTION,
158 IMAGEX_EXTRACT_XML_OPTION,
161 IMAGEX_HEADER_OPTION,
162 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
164 IMAGEX_METADATA_OPTION,
165 IMAGEX_NEW_IMAGE_OPTION,
166 IMAGEX_NOCHECK_OPTION,
167 IMAGEX_NORPFIX_OPTION,
168 IMAGEX_NOT_PIPABLE_OPTION,
169 IMAGEX_NO_ACLS_OPTION,
170 IMAGEX_NO_ATTRIBUTES_OPTION,
171 IMAGEX_NO_GLOBS_OPTION,
172 IMAGEX_NO_REPLACE_OPTION,
173 IMAGEX_NO_SOLID_SORT_OPTION,
174 IMAGEX_NULLGLOB_OPTION,
175 IMAGEX_ONE_FILE_ONLY_OPTION,
177 IMAGEX_PIPABLE_OPTION,
178 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
179 IMAGEX_REBUILD_OPTION,
180 IMAGEX_RECOMPRESS_OPTION,
181 IMAGEX_RECURSIVE_OPTION,
183 IMAGEX_RESUME_OPTION,
186 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
187 IMAGEX_SOLID_COMPRESS_OPTION,
189 IMAGEX_SOURCE_LIST_OPTION,
190 IMAGEX_STAGING_DIR_OPTION,
191 IMAGEX_STREAMS_INTERFACE_OPTION,
192 IMAGEX_STRICT_ACLS_OPTION,
193 IMAGEX_THREADS_OPTION,
194 IMAGEX_TO_STDOUT_OPTION,
195 IMAGEX_UNIX_DATA_OPTION,
196 IMAGEX_UPDATE_OF_OPTION,
197 IMAGEX_VERBOSE_OPTION,
198 IMAGEX_WIMBOOT_CONFIG_OPTION,
199 IMAGEX_WIMBOOT_OPTION,
203 static const struct option apply_options[] = {
204 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
205 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
206 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
207 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
208 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
209 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
210 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
211 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
212 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
213 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
214 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
216 /* --resume is undocumented for now as it needs improvement. */
217 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
218 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
222 static const struct option capture_or_append_options[] = {
223 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
224 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
225 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
226 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
227 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
228 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
229 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
230 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
231 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
232 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
233 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
234 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
235 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
236 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
237 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
238 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
239 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
240 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
241 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
242 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
243 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
244 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
245 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
246 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
247 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
248 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
249 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
250 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
251 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
252 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
253 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
254 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
258 static const struct option delete_options[] = {
259 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
260 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
264 static const struct option dir_options[] = {
265 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
266 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
267 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
271 static const struct option export_options[] = {
272 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
273 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
274 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
275 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
276 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
277 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
278 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
279 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
280 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
281 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
282 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
283 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
284 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
285 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
286 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
287 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
288 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
289 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
290 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
291 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
292 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
296 static const struct option extract_options[] = {
297 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
298 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
299 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
300 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
301 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
302 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
303 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
304 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
305 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
306 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
307 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
308 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
309 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
310 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
311 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
312 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
316 static const struct option info_options[] = {
317 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
318 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
319 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
320 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
321 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
322 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
323 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
324 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
325 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
326 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
330 static const struct option join_options[] = {
331 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
335 static const struct option mount_options[] = {
336 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
337 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
338 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
339 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
340 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
341 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
342 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
346 static const struct option optimize_options[] = {
347 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
348 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
349 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
350 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
351 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
352 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
353 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
354 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
355 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
356 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
357 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
358 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
359 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
360 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
361 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
362 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
363 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
364 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
368 static const struct option split_options[] = {
369 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
373 static const struct option unmount_options[] = {
374 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
375 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
376 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
377 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
378 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
379 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
383 static const struct option update_options[] = {
384 /* Careful: some of the options here set the defaults for update
385 * commands, but the flags given to an actual update command (and not to
386 * `imagex update' itself are also handled in
387 * update_command_add_option(). */
388 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
389 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
390 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
391 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
392 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
394 /* Default delete options */
395 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
396 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
398 /* Global add option */
399 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
401 /* Default add options */
402 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
403 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
404 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
405 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
406 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
407 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
408 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
413 static const struct option verify_options[] = {
414 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
415 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
421 # define _format_attribute(type, format_str, args_start) \
422 __attribute__((format(type, format_str, args_start)))
424 # define _format_attribute(type, format_str, args_start)
427 /* Print formatted error message to stderr. */
428 static void _format_attribute(printf, 1, 2)
429 imagex_error(const tchar *format, ...)
432 va_start(va, format);
433 tfputs(T("ERROR: "), stderr);
434 tvfprintf(stderr, format, va);
435 tputc(T('\n'), stderr);
439 /* Print formatted error message to stderr. */
440 static void _format_attribute(printf, 1, 2)
441 imagex_error_with_errno(const tchar *format, ...)
443 int errno_save = errno;
445 va_start(va, format);
446 tfputs(T("ERROR: "), stderr);
447 tvfprintf(stderr, format, va);
448 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
453 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
455 if (image == WIMLIB_NO_IMAGE) {
456 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
457 " Please specify a 1-based image index or "
458 "image name. To list the images\n"
459 " contained in the WIM archive, run\n"
461 " %"TS" \"%"TS"\"\n"),
462 image_name, wim_name,
463 get_cmd_string(CMD_INFO, false), wim_name);
464 return WIMLIB_ERR_INVALID_IMAGE;
470 verify_image_is_single(int image)
472 if (image == WIMLIB_ALL_IMAGES) {
473 imagex_error(T("Cannot specify all images for this action!"));
474 return WIMLIB_ERR_INVALID_IMAGE;
480 verify_image_exists_and_is_single(int image, const tchar *image_name,
481 const tchar *wim_name)
484 ret = verify_image_exists(image, image_name, wim_name);
486 ret = verify_image_is_single(image);
491 print_available_compression_types(FILE *fp)
493 static const tchar *s =
495 "Available compression types:\n"
498 " xpress (alias: \"fast\")\n"
499 " lzx (alias: \"maximum\") (default for capture)\n"
500 " lzms (alias: \"recovery\")\n"
506 /* Parse the argument to --compress */
508 get_compression_type(tchar *optarg)
511 unsigned int compression_level = 0;
514 plevel = tstrchr(optarg, T(':'));
520 ultmp = tstrtoul(plevel, &ptmp, 10);
521 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
522 imagex_error(T("Compression level must be a positive integer! "
523 "e.g. --compress=lzx:80"));
524 return WIMLIB_COMPRESSION_TYPE_INVALID;
526 compression_level = ultmp;
529 if (!tstrcasecmp(optarg, T("maximum")) ||
530 !tstrcasecmp(optarg, T("lzx")) ||
531 !tstrcasecmp(optarg, T("max")))
532 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
533 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
534 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
535 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
536 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
537 else if (!tstrcasecmp(optarg, T("none")))
538 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
540 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
541 print_available_compression_types(stderr);
542 return WIMLIB_COMPRESSION_TYPE_INVALID;
545 if (compression_level != 0)
546 wimlib_set_default_compression_level(ctype, compression_level);
551 set_compress_slow(void)
554 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
555 " Use the '--compress=TYPE:LEVEL' option instead.\n");
557 wimlib_set_default_compression_level(-1, 100);
561 const tchar **strings;
562 unsigned num_strings;
563 unsigned num_alloc_strings;
566 #define STRING_SET_INITIALIZER \
567 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
569 #define STRING_SET(_strings) \
570 struct string_set _strings = STRING_SET_INITIALIZER
573 string_set_append(struct string_set *set, const tchar *glob)
575 unsigned num_alloc_strings = set->num_alloc_strings;
577 if (set->num_strings == num_alloc_strings) {
578 const tchar **new_strings;
580 num_alloc_strings += 4;
581 new_strings = realloc(set->strings,
582 sizeof(set->strings[0]) * num_alloc_strings);
584 imagex_error(T("Out of memory!"));
587 set->strings = new_strings;
588 set->num_alloc_strings = num_alloc_strings;
590 set->strings[set->num_strings++] = glob;
595 string_set_destroy(struct string_set *set)
601 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
603 return wimlib_reference_resource_files(wim, set->strings,
605 WIMLIB_REF_FLAG_GLOB_ENABLE,
610 do_resource_not_found_warning(const tchar *wimfile,
611 const struct wimlib_wim_info *info,
612 const struct string_set *refglobs)
614 if (info->total_parts > 1) {
615 if (refglobs->num_strings == 0) {
616 imagex_error(T("\"%"TS"\" is part of a split WIM. "
617 "Use --ref to specify the other parts."),
620 imagex_error(T("Perhaps the '--ref' argument did not "
621 "specify all other parts of the split "
625 imagex_error(T("If this is a delta WIM, use the --ref argument "
626 "to specify the WIM(s) on which it is based."));
630 /* Returns the size of a file given its name, or -1 if the file does not exist
631 * or its size cannot be determined. */
633 file_get_size(const tchar *filename)
636 if (tstat(filename, &st) == 0)
643 PARSE_STRING_SUCCESS = 0,
644 PARSE_STRING_FAILURE = 1,
645 PARSE_STRING_NONE = 2,
649 * Parses a string token from an array of characters.
651 * Tokens are either whitespace-delimited, or double or single-quoted.
653 * @line_p: Pointer to the pointer to the line of data. Will be updated
654 * to point past the string token iff the return value is
655 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
658 * @len_p: @len_p initially stores the length of the line of data, which may
659 * be 0, and it will be updated to the number of bytes remaining in
660 * the line iff the return value is PARSE_STRING_SUCCESS.
662 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
663 * parsed string token will be returned here.
665 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
666 * PARSE_STRING_FAILURE if the data was invalid due to a missing
667 * closing quote; or PARSE_STRING_NONE if the line ended before the
668 * beginning of a string token was found.
671 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
674 tchar *line = *line_p;
678 /* Skip leading whitespace */
681 return PARSE_STRING_NONE;
682 if (!istspace(*line) && *line != T('\0'))
688 if (quote_char == T('"') || quote_char == T('\'')) {
693 line = tmemchr(line, quote_char, len);
695 imagex_error(T("Missing closing quote: %"TS), fn - 1);
696 return PARSE_STRING_FAILURE;
699 /* Unquoted string. Go until whitespace. Line is terminated
700 * by '\0', so no need to check 'len'. */
704 } while (!istspace(*line) && *line != T('\0'));
711 return PARSE_STRING_SUCCESS;
714 /* Parses a line of data (not an empty line or comment) in the source list file
715 * format. (See the man page for 'wimlib-imagex capture' for details on this
716 * format and the meaning.)
718 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
719 * len == 0. The data in @line will be modified by this function call.
721 * @len: Length of the line of data.
723 * @source: On success, the capture source and target described by the line is
724 * written into this destination. Note that it will contain pointers
725 * to data in the @line array.
727 * Returns true if the line was valid; false otherwise. */
729 parse_source_list_line(tchar *line, size_t len,
730 struct wimlib_capture_source *source)
734 ret = parse_string(&line, &len, &source->fs_source_path);
735 if (ret != PARSE_STRING_SUCCESS)
737 ret = parse_string(&line, &len, &source->wim_target_path);
738 if (ret == PARSE_STRING_NONE)
739 source->wim_target_path = source->fs_source_path;
740 return ret != PARSE_STRING_FAILURE;
743 /* Returns %true if the given line of length @len > 0 is a comment or empty line
744 * in the source list file format. */
746 is_comment_line(const tchar *line, size_t len)
749 if (*line == T('#') || *line == T(';'))
751 if (!istspace(*line) && *line != T('\0'))
761 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
764 tchar *contents = *contents_p;
765 size_t nchars = *nchars_p;
768 for (i = 0; i < nchars; i++)
769 if (contents[i] == T('\n'))
772 /* Handle last line not terminated by a newline */
773 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
774 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
776 imagex_error(T("Out of memory!"));
779 contents[nchars] = T('\n');
780 *contents_p = contents;
788 /* Parses a file in the source list format. (See the man page for
789 * 'wimlib-imagex capture' for details on this format and the meaning.)
791 * @source_list_contents: Contents of the source list file. Note that this
792 * buffer will be modified to save memory allocations,
793 * and cannot be freed until the returned array of
794 * wimlib_capture_source's has also been freed.
796 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
799 * @nsources_ret: On success, the length of the returned array is
802 * Returns: An array of `struct wimlib_capture_source's that can be passed to
803 * the wimlib_add_image_multisource() function to specify how a WIM image is to
805 static struct wimlib_capture_source *
806 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
807 size_t *nsources_ret)
811 struct wimlib_capture_source *sources;
814 nlines = text_file_count_lines(source_list_contents_p,
815 &source_list_nchars);
819 /* Always allocate at least 1 slot, just in case the implementation of
820 * calloc() returns NULL if 0 bytes are requested. */
821 sources = calloc(nlines ?: 1, sizeof(*sources));
823 imagex_error(T("out of memory"));
826 p = *source_list_contents_p;
828 for (i = 0; i < nlines; i++) {
829 /* XXX: Could use rawmemchr() here instead, but it may not be
830 * available on all platforms. */
831 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
832 size_t len = endp - p + 1;
834 if (!is_comment_line(p, len)) {
835 if (!parse_source_list_line(p, len, &sources[j++])) {
847 /* Reads the contents of a file into memory. */
849 file_get_contents(const tchar *filename, size_t *len_ret)
856 if (tstat(filename, &stbuf) != 0) {
857 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
862 fp = tfopen(filename, T("rb"));
864 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
868 buf = malloc(len ? len : 1);
870 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
871 "contents of file \"%"TS"\""), len, filename);
874 if (fread(buf, 1, len, fp) != len) {
875 imagex_error_with_errno(T("Failed to read %zu bytes from the "
876 "file \"%"TS"\""), len, filename);
890 /* Read standard input until EOF and return the full contents in a malloc()ed
891 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
894 stdin_get_contents(size_t *len_ret)
896 /* stdin can, of course, be a pipe or other non-seekable file, so the
897 * total length of the data cannot be pre-determined */
899 size_t newlen = 1024;
903 char *p = realloc(buf, newlen);
904 size_t bytes_read, bytes_to_read;
906 imagex_error(T("out of memory while reading stdin"));
910 bytes_to_read = newlen - pos;
911 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
913 if (bytes_read != bytes_to_read) {
918 imagex_error_with_errno(T("error reading stdin"));
932 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
935 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
937 *num_tchars_ret = num_bytes;
939 #else /* !__WIN32__ */
940 /* On Windows, translate the text to UTF-16LE */
944 if (num_bytes >= 2 &&
945 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
946 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
948 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
949 * with something that looks like an ASCII character encoded as
950 * a UTF-16LE code unit. Assume the file is encoded as
951 * UTF-16LE. This is not a 100% reliable check. */
952 num_wchars = num_bytes / 2;
953 text_wstr = (wchar_t*)text;
955 /* File does not look like UTF-16LE. Assume it is encoded in
956 * the current Windows code page. I think these are always
957 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
958 * should work as expected. */
959 text_wstr = win32_mbs_to_wcs(text,
964 *num_tchars_ret = num_wchars;
966 #endif /* __WIN32__ */
970 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
975 contents = file_get_contents(filename, &num_bytes);
978 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
982 stdin_get_text_contents(size_t *num_tchars_ret)
987 contents = stdin_get_contents(&num_bytes);
990 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
993 #define TO_PERCENT(numerator, denominator) \
994 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
996 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
997 #define MEBIBYTE_MIN_NBYTES 10000000ULL
998 #define KIBIBYTE_MIN_NBYTES 10000ULL
1001 get_unit(uint64_t total_bytes, const tchar **name_ret)
1003 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1004 *name_ret = T("GiB");
1006 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1007 *name_ret = T("MiB");
1009 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1010 *name_ret = T("KiB");
1013 *name_ret = T("bytes");
1018 static struct wimlib_progress_info_scan last_scan_progress;
1021 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1023 uint64_t prev_count, cur_count;
1025 prev_count = last_scan_progress.num_nondirs_scanned +
1026 last_scan_progress.num_dirs_scanned;
1027 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1029 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1030 cur_count % 128 == 0)
1032 unsigned unit_shift;
1033 const tchar *unit_name;
1035 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1036 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1037 "%"PRIu64" directories) "),
1038 scan->num_bytes_scanned >> unit_shift,
1040 scan->num_nondirs_scanned,
1041 scan->num_dirs_scanned);
1042 last_scan_progress = *scan;
1045 /* Progress callback function passed to various wimlib functions. */
1046 static enum wimlib_progress_status
1047 imagex_progress_func(enum wimlib_progress_msg msg,
1048 union wimlib_progress_info *info,
1049 void *_ignored_context)
1051 unsigned percent_done;
1052 unsigned unit_shift;
1053 const tchar *unit_name;
1055 if (imagex_be_quiet)
1056 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1058 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1060 static bool first = true;
1062 imagex_printf(T("Writing %"TS"-compressed data "
1063 "using %u thread%"TS"\n"),
1064 wimlib_get_compression_type_string(
1065 info->write_streams.compression_type),
1066 info->write_streams.num_threads,
1067 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1071 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1072 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1073 info->write_streams.total_bytes);
1075 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1076 "written (%u%% done)"),
1077 info->write_streams.completed_bytes >> unit_shift,
1079 info->write_streams.total_bytes >> unit_shift,
1082 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1083 imagex_printf(T("\n"));
1085 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1086 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1087 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1088 imagex_printf(T("\n"));
1090 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1091 info->scan.wim_target_path);
1093 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1095 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1096 switch (info->scan.status) {
1097 case WIMLIB_SCAN_DENTRY_OK:
1098 report_scan_progress(&info->scan, false);
1100 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1101 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1103 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1104 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1105 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1107 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1108 /* Symlink fixups are enabled by default. This is
1109 * mainly intended for Windows, which for some reason
1110 * uses absolute junctions (with drive letters!) in the
1111 * default installation. On UNIX-like systems, warn the
1112 * user when fixing the target of an absolute symbolic
1113 * link, so they know to disable this if they want. */
1115 imagex_printf(T("\nWARNING: Adjusted target of "
1116 "absolute symbolic link \"%"TS"\"\n"
1117 " (Use --norpfix to capture "
1118 "absolute symbolic links as-is)\n"),
1119 info->scan.cur_path);
1126 case WIMLIB_PROGRESS_MSG_SCAN_END:
1127 report_scan_progress(&info->scan, true);
1128 imagex_printf(T("\n"));
1130 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1131 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1132 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1133 info->integrity.total_bytes);
1134 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1135 "of %"PRIu64" %"TS" (%u%%) done"),
1136 info->integrity.filename,
1137 info->integrity.completed_bytes >> unit_shift,
1139 info->integrity.total_bytes >> unit_shift,
1142 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1143 imagex_printf(T("\n"));
1145 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1146 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1147 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1148 info->integrity.total_bytes);
1149 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1150 "of %"PRIu64" %"TS" (%u%%) done"),
1151 info->integrity.completed_bytes >> unit_shift,
1153 info->integrity.total_bytes >> unit_shift,
1156 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1157 imagex_printf(T("\n"));
1159 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1160 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1161 "to %"TS" \"%"TS"\"\n"),
1162 info->extract.image,
1163 info->extract.image_name,
1164 info->extract.wimfile_name,
1165 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1166 T("NTFS volume") : T("directory")),
1167 info->extract.target);
1169 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1170 if (info->extract.end_file_count >= 2000) {
1171 percent_done = TO_PERCENT(info->extract.current_file_count,
1172 info->extract.end_file_count);
1173 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1174 info->extract.current_file_count,
1175 info->extract.end_file_count, percent_done);
1176 if (info->extract.current_file_count == info->extract.end_file_count)
1177 imagex_printf(T("\n"));
1180 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1181 percent_done = TO_PERCENT(info->extract.completed_bytes,
1182 info->extract.total_bytes);
1183 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1184 imagex_printf(T("\rExtracting file data: "
1185 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1186 info->extract.completed_bytes >> unit_shift,
1188 info->extract.total_bytes >> unit_shift,
1191 if (info->extract.completed_bytes >= info->extract.total_bytes)
1192 imagex_printf(T("\n"));
1194 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1195 if (info->extract.end_file_count >= 2000) {
1196 percent_done = TO_PERCENT(info->extract.current_file_count,
1197 info->extract.end_file_count);
1198 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1199 info->extract.current_file_count,
1200 info->extract.end_file_count, percent_done);
1201 if (info->extract.current_file_count == info->extract.end_file_count)
1202 imagex_printf(T("\n"));
1205 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1206 if (info->extract.total_parts != 1) {
1207 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1208 info->extract.part_number,
1209 info->extract.total_parts);
1212 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1213 percent_done = TO_PERCENT(info->split.completed_bytes,
1214 info->split.total_bytes);
1215 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1216 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1217 "%"PRIu64" %"TS" (%u%%) written\n"),
1218 info->split.part_name,
1219 info->split.cur_part_number,
1220 info->split.total_parts,
1221 info->split.completed_bytes >> unit_shift,
1223 info->split.total_bytes >> unit_shift,
1227 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1228 if (info->split.completed_bytes == info->split.total_bytes) {
1229 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1230 info->split.cur_part_number,
1231 info->split.total_parts);
1234 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1235 switch (info->update.command->op) {
1236 case WIMLIB_UPDATE_OP_DELETE:
1237 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1238 info->update.command->delete_.wim_path);
1240 case WIMLIB_UPDATE_OP_RENAME:
1241 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1242 info->update.command->rename.wim_source_path,
1243 info->update.command->rename.wim_target_path);
1245 case WIMLIB_UPDATE_OP_ADD:
1250 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1251 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1252 info->replace.path_in_wim);
1254 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1255 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1256 info->wimboot_exclude.path_in_wim);
1258 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1259 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1260 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1261 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1262 info->unmount.mounted_wim,
1263 info->unmount.mounted_image);
1265 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1266 info->unmount.mounted_wim,
1267 info->unmount.mounted_image);
1268 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1272 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1273 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1274 info->verify_image.current_image,
1275 info->verify_image.total_images);
1277 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1278 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1279 info->verify_streams.total_bytes);
1280 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1281 imagex_printf(T("\rVerifying file data: "
1282 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1283 info->verify_streams.completed_bytes >> unit_shift,
1285 info->verify_streams.total_bytes >> unit_shift,
1288 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1289 imagex_printf(T("\n"));
1294 fflush(imagex_info_file);
1295 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1299 parse_num_threads(const tchar *optarg)
1302 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1303 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1304 imagex_error(T("Number of threads must be a non-negative integer!"));
1312 parse_chunk_size(const tchar *optarg)
1315 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1316 if (chunk_size == 0) {
1317 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1318 " with optional K, M, or G suffix"));
1322 if (*tmp == T('k') || *tmp == T('K')) {
1325 } else if (*tmp == T('m') || *tmp == T('M')) {
1328 } else if (*tmp == T('g') || *tmp == T('G')) {
1332 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1333 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1337 if (chunk_size >= UINT32_MAX) {
1338 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1346 * Parse an option passed to an update command.
1348 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1351 * @option: Text string for the option (beginning with --)
1353 * @cmd: `struct wimlib_update_command' that is being constructed for
1356 * Returns true if the option was recognized; false if not.
1359 update_command_add_option(int op, const tchar *option,
1360 struct wimlib_update_command *cmd)
1362 bool recognized = true;
1364 case WIMLIB_UPDATE_OP_ADD:
1365 if (!tstrcmp(option, T("--verbose")))
1366 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1367 else if (!tstrcmp(option, T("--unix-data")))
1368 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1369 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1370 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1371 else if (!tstrcmp(option, T("--strict-acls")))
1372 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1373 else if (!tstrcmp(option, T("--dereference")))
1374 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1375 else if (!tstrcmp(option, T("--no-replace")))
1376 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1380 case WIMLIB_UPDATE_OP_DELETE:
1381 if (!tstrcmp(option, T("--force")))
1382 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1383 else if (!tstrcmp(option, T("--recursive")))
1384 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1395 /* How many nonoption arguments each `imagex update' command expects */
1396 static const unsigned update_command_num_nonoptions[] = {
1397 [WIMLIB_UPDATE_OP_ADD] = 2,
1398 [WIMLIB_UPDATE_OP_DELETE] = 1,
1399 [WIMLIB_UPDATE_OP_RENAME] = 2,
1403 update_command_add_nonoption(int op, const tchar *nonoption,
1404 struct wimlib_update_command *cmd,
1405 unsigned num_nonoptions)
1408 case WIMLIB_UPDATE_OP_ADD:
1409 if (num_nonoptions == 0)
1410 cmd->add.fs_source_path = (tchar*)nonoption;
1412 cmd->add.wim_target_path = (tchar*)nonoption;
1414 case WIMLIB_UPDATE_OP_DELETE:
1415 cmd->delete_.wim_path = (tchar*)nonoption;
1417 case WIMLIB_UPDATE_OP_RENAME:
1418 if (num_nonoptions == 0)
1419 cmd->rename.wim_source_path = (tchar*)nonoption;
1421 cmd->rename.wim_target_path = (tchar*)nonoption;
1427 * Parse a command passed on stdin to `imagex update'.
1429 * @line: Text of the command.
1430 * @len: Length of the line, including a null terminator
1433 * @command: A `struct wimlib_update_command' to fill in from the parsed
1436 * @line_number: Line number of the command, for diagnostics.
1438 * Returns true on success; returns false on parse error.
1441 parse_update_command(tchar *line, size_t len,
1442 struct wimlib_update_command *command,
1446 tchar *command_name;
1448 size_t num_nonoptions;
1450 /* Get the command name ("add", "delete", "rename") */
1451 ret = parse_string(&line, &len, &command_name);
1452 if (ret != PARSE_STRING_SUCCESS)
1455 if (!tstrcasecmp(command_name, T("add"))) {
1456 op = WIMLIB_UPDATE_OP_ADD;
1457 } else if (!tstrcasecmp(command_name, T("delete"))) {
1458 op = WIMLIB_UPDATE_OP_DELETE;
1459 } else if (!tstrcasecmp(command_name, T("rename"))) {
1460 op = WIMLIB_UPDATE_OP_RENAME;
1462 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1463 command_name, line_number);
1468 /* Parse additional options and non-options as needed */
1473 ret = parse_string(&line, &len, &next_string);
1474 if (ret == PARSE_STRING_NONE) /* End of line */
1476 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1478 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1480 if (!update_command_add_option(op, next_string, command))
1482 imagex_error(T("Unrecognized option \"%"TS"\" to "
1483 "update command \"%"TS"\" on line %zu"),
1484 next_string, command_name, line_number);
1490 if (num_nonoptions == update_command_num_nonoptions[op])
1492 imagex_error(T("Unexpected argument \"%"TS"\" in "
1493 "update command on line %zu\n"
1494 " (The \"%"TS"\" command only "
1495 "takes %zu nonoption arguments!)\n"),
1496 next_string, line_number,
1497 command_name, num_nonoptions);
1500 update_command_add_nonoption(op, next_string,
1501 command, num_nonoptions);
1506 if (num_nonoptions != update_command_num_nonoptions[op]) {
1507 imagex_error(T("Not enough arguments to update command "
1508 "\"%"TS"\" on line %zu"), command_name, line_number);
1514 static struct wimlib_update_command *
1515 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1516 size_t *num_cmds_ret)
1520 struct wimlib_update_command *cmds;
1523 nlines = text_file_count_lines(cmd_file_contents_p,
1528 /* Always allocate at least 1 slot, just in case the implementation of
1529 * calloc() returns NULL if 0 bytes are requested. */
1530 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1532 imagex_error(T("out of memory"));
1535 p = *cmd_file_contents_p;
1537 for (i = 0; i < nlines; i++) {
1538 /* XXX: Could use rawmemchr() here instead, but it may not be
1539 * available on all platforms. */
1540 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1541 size_t len = endp - p + 1;
1543 if (!is_comment_line(p, len)) {
1544 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1555 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1556 * one image from a WIM file to an NTFS volume. */
1558 imagex_apply(int argc, tchar **argv, int cmd)
1562 int image = WIMLIB_NO_IMAGE;
1564 struct wimlib_wim_info info;
1566 const tchar *wimfile;
1567 const tchar *target;
1568 const tchar *image_num_or_name = NULL;
1569 int extract_flags = 0;
1571 STRING_SET(refglobs);
1573 for_opt(c, apply_options) {
1575 case IMAGEX_CHECK_OPTION:
1576 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1578 case IMAGEX_VERBOSE_OPTION:
1579 /* No longer does anything. */
1581 case IMAGEX_REF_OPTION:
1582 ret = string_set_append(&refglobs, optarg);
1584 goto out_free_refglobs;
1586 case IMAGEX_UNIX_DATA_OPTION:
1587 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1589 case IMAGEX_NO_ACLS_OPTION:
1590 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1592 case IMAGEX_STRICT_ACLS_OPTION:
1593 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1595 case IMAGEX_NO_ATTRIBUTES_OPTION:
1596 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1598 case IMAGEX_NORPFIX_OPTION:
1599 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1601 case IMAGEX_RPFIX_OPTION:
1602 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1604 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1605 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1606 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1608 case IMAGEX_RESUME_OPTION:
1609 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1611 case IMAGEX_WIMBOOT_OPTION:
1612 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1620 if (argc != 2 && argc != 3)
1625 if (!tstrcmp(wimfile, T("-"))) {
1626 /* Attempt to apply pipable WIM from standard input. */
1628 image_num_or_name = NULL;
1631 image_num_or_name = argv[1];
1636 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1637 imagex_progress_func, NULL);
1639 goto out_free_refglobs;
1641 wimlib_get_wim_info(wim, &info);
1644 /* Image explicitly specified. */
1645 image_num_or_name = argv[1];
1646 image = wimlib_resolve_image(wim, image_num_or_name);
1647 ret = verify_image_exists(image, image_num_or_name, wimfile);
1649 goto out_wimlib_free;
1652 /* No image specified; default to image 1, but only if the WIM
1653 * contains exactly one image. */
1655 if (info.image_count != 1) {
1656 imagex_error(T("\"%"TS"\" contains %d images; "
1657 "Please select one (or all)."),
1658 wimfile, info.image_count);
1667 if (refglobs.num_strings) {
1669 imagex_error(T("Can't specify --ref when applying from stdin!"));
1671 goto out_wimlib_free;
1673 ret = wim_reference_globs(wim, &refglobs, open_flags);
1675 goto out_wimlib_free;
1680 /* Interpret a regular file or block device target as an NTFS
1684 if (tstat(target, &stbuf)) {
1685 if (errno != ENOENT) {
1686 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1689 goto out_wimlib_free;
1692 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1693 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1699 ret = wimlib_extract_image(wim, image, target, extract_flags);
1701 set_fd_to_binary_mode(STDIN_FILENO);
1702 ret = wimlib_extract_image_from_pipe_with_progress(
1707 imagex_progress_func,
1711 imagex_printf(T("Done applying WIM image.\n"));
1712 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1714 do_resource_not_found_warning(wimfile, &info, &refglobs);
1716 imagex_error(T( "If you are applying an image "
1717 "from a split pipable WIM,\n"
1718 " make sure you have "
1719 "concatenated together all parts."));
1725 string_set_destroy(&refglobs);
1729 usage(CMD_APPLY, stderr);
1731 goto out_free_refglobs;
1734 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1735 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1736 * the desired image. 'wimlib-imagex append': add a new image to an existing
1739 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1743 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1744 WIMLIB_ADD_FLAG_WINCONFIG |
1745 WIMLIB_ADD_FLAG_VERBOSE;
1746 int write_flags = 0;
1747 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1748 uint32_t chunk_size = UINT32_MAX;
1749 uint32_t solid_chunk_size = UINT32_MAX;
1750 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1751 const tchar *wimfile;
1755 const tchar *flags_element = NULL;
1758 STRING_SET(base_wimfiles);
1759 WIMStruct **base_wims;
1761 WIMStruct *template_wim;
1762 const tchar *template_wimfile = NULL;
1763 const tchar *template_image_name_or_num = NULL;
1764 int template_image = WIMLIB_NO_IMAGE;
1767 unsigned num_threads = 0;
1772 tchar *config_file = NULL;
1774 bool source_list = false;
1775 size_t source_list_nchars = 0;
1776 tchar *source_list_contents;
1777 bool capture_sources_malloced;
1778 struct wimlib_capture_source *capture_sources;
1780 bool name_defaulted;
1782 for_opt(c, capture_or_append_options) {
1784 case IMAGEX_BOOT_OPTION:
1785 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1787 case IMAGEX_CHECK_OPTION:
1788 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1789 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1791 case IMAGEX_NOCHECK_OPTION:
1792 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1794 case IMAGEX_CONFIG_OPTION:
1795 config_file = optarg;
1796 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1798 case IMAGEX_COMPRESS_OPTION:
1799 compression_type = get_compression_type(optarg);
1800 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1803 case IMAGEX_COMPRESS_SLOW_OPTION:
1804 set_compress_slow();
1806 case IMAGEX_CHUNK_SIZE_OPTION:
1807 chunk_size = parse_chunk_size(optarg);
1808 if (chunk_size == UINT32_MAX)
1811 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1812 solid_chunk_size = parse_chunk_size(optarg);
1813 if (solid_chunk_size == UINT32_MAX)
1816 case IMAGEX_SOLID_COMPRESS_OPTION:
1817 solid_ctype = get_compression_type(optarg);
1818 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1821 case IMAGEX_SOLID_OPTION:
1822 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1824 case IMAGEX_NO_SOLID_SORT_OPTION:
1825 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1827 case IMAGEX_FLAGS_OPTION:
1828 flags_element = optarg;
1830 case IMAGEX_DEREFERENCE_OPTION:
1831 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1833 case IMAGEX_VERBOSE_OPTION:
1834 /* No longer does anything. */
1836 case IMAGEX_THREADS_OPTION:
1837 num_threads = parse_num_threads(optarg);
1838 if (num_threads == UINT_MAX)
1841 case IMAGEX_REBUILD_OPTION:
1842 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1844 case IMAGEX_UNIX_DATA_OPTION:
1845 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1847 case IMAGEX_SOURCE_LIST_OPTION:
1850 case IMAGEX_NO_ACLS_OPTION:
1851 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1853 case IMAGEX_STRICT_ACLS_OPTION:
1854 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1856 case IMAGEX_RPFIX_OPTION:
1857 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1859 case IMAGEX_NORPFIX_OPTION:
1860 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1862 case IMAGEX_PIPABLE_OPTION:
1863 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1865 case IMAGEX_NOT_PIPABLE_OPTION:
1866 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1868 case IMAGEX_UPDATE_OF_OPTION:
1869 if (template_image_name_or_num) {
1870 imagex_error(T("'--update-of' can only be "
1871 "specified one time!"));
1875 colon = tstrrchr(optarg, T(':'));
1878 template_wimfile = optarg;
1880 template_image_name_or_num = colon + 1;
1882 template_wimfile = NULL;
1883 template_image_name_or_num = optarg;
1887 case IMAGEX_DELTA_FROM_OPTION:
1888 if (cmd != CMD_CAPTURE) {
1889 imagex_error(T("'--delta-from' is only "
1890 "valid for capture!"));
1893 ret = string_set_append(&base_wimfiles, optarg);
1895 goto out_free_base_wimfiles;
1896 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
1898 case IMAGEX_WIMBOOT_OPTION:
1899 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
1908 if (argc < 2 || argc > 4)
1914 /* Set default compression type and parameters. */
1917 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
1918 /* No compression type specified. Use the default. */
1920 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
1921 /* With --wimboot, default to XPRESS compression. */
1922 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
1923 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
1924 /* With --solid, default to LZMS compression. (However,
1925 * this will not affect solid resources!) */
1926 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
1928 /* Otherwise, default to LZX compression. */
1929 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
1933 if (!tstrcmp(wimfile, T("-"))) {
1934 /* Writing captured WIM to standard output. */
1936 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
1937 imagex_error("Can't write a non-pipable WIM to "
1938 "standard output! Specify --pipable\n"
1939 " if you want to create a pipable WIM "
1940 "(but read the docs first).");
1944 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1946 if (cmd == CMD_APPEND) {
1947 imagex_error(T("Using standard output for append does "
1948 "not make sense."));
1951 wim_fd = STDOUT_FILENO;
1953 imagex_info_file = stderr;
1954 set_fd_to_binary_mode(wim_fd);
1957 /* If template image was specified using --update-of=IMAGE rather
1958 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
1959 if (template_image_name_or_num && !template_wimfile) {
1960 if (base_wimfiles.num_strings == 1) {
1961 /* Capturing delta WIM based on single WIM: default to
1963 template_wimfile = base_wimfiles.strings[0];
1964 } else if (cmd == CMD_APPEND) {
1965 /* Appending to WIM: default to WIM being appended to.
1967 template_wimfile = wimfile;
1969 /* Capturing a normal (non-delta) WIM, so the WIM file
1970 * *must* be explicitly specified. */
1971 if (base_wimfiles.num_strings > 1) {
1972 imagex_error(T("For capture of delta WIM "
1973 "based on multiple existing "
1975 " '--update-of' must "
1976 "specify WIMFILE:IMAGE!"));
1978 imagex_error(T("For capture of non-delta WIM, "
1979 "'--update-of' must specify "
1988 name_defaulted = false;
1990 /* Set default name to SOURCE argument, omitting any directory
1991 * prefixes and trailing slashes. This requires making a copy
1992 * of @source. Leave some free characters at the end in case we
1993 * append a number to keep the name unique. */
1994 size_t source_name_len;
1996 source_name_len = tstrlen(source);
1997 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
1998 name = tbasename(tstrcpy(source_copy, source));
1999 name_defaulted = true;
2001 /* Image description defaults to NULL if not given. */
2008 /* Set up capture sources in source list mode */
2009 if (source[0] == T('-') && source[1] == T('\0')) {
2010 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2012 source_list_contents = file_get_text_contents(source,
2013 &source_list_nchars);
2015 if (!source_list_contents)
2018 capture_sources = parse_source_list(&source_list_contents,
2021 if (!capture_sources) {
2023 goto out_free_source_list_contents;
2025 capture_sources_malloced = true;
2027 /* Set up capture source in non-source-list mode. */
2028 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2029 capture_sources[0].fs_source_path = source;
2030 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2031 capture_sources[0].reserved = 0;
2033 capture_sources_malloced = false;
2034 source_list_contents = NULL;
2037 /* Open the existing WIM, or create a new one. */
2038 if (cmd == CMD_APPEND) {
2039 ret = wimlib_open_wim_with_progress(wimfile,
2040 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2042 imagex_progress_func,
2045 goto out_free_capture_sources;
2047 ret = wimlib_create_new_wim(compression_type, &wim);
2049 goto out_free_capture_sources;
2050 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2053 /* Set chunk size if non-default. */
2054 if (chunk_size != UINT32_MAX) {
2055 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2058 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT) &&
2059 compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2060 ret = wimlib_set_output_chunk_size(wim, 4096);
2064 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2065 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2069 if (solid_chunk_size != UINT32_MAX) {
2070 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2076 /* Detect if source is regular file or block device and set NTFS volume
2081 if (tstat(source, &stbuf) == 0) {
2082 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2083 imagex_printf(T("Capturing WIM image from NTFS "
2084 "filesystem on \"%"TS"\"\n"), source);
2085 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2088 if (errno != ENOENT) {
2089 imagex_error_with_errno(T("Failed to stat "
2090 "\"%"TS"\""), source);
2098 /* If the user did not specify an image name, and the basename of the
2099 * source already exists as an image name in the WIM file, append a
2100 * suffix to make it unique. */
2101 if (cmd == CMD_APPEND && name_defaulted) {
2102 unsigned long conflict_idx;
2103 tchar *name_end = tstrchr(name, T('\0'));
2104 for (conflict_idx = 1;
2105 wimlib_image_name_in_use(wim, name);
2108 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2112 /* If capturing a delta WIM, reference resources from the base WIMs
2113 * before adding the new image. */
2114 if (base_wimfiles.num_strings) {
2115 base_wims = calloc(base_wimfiles.num_strings,
2116 sizeof(base_wims[0]));
2117 if (base_wims == NULL) {
2118 imagex_error(T("Out of memory!"));
2123 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2124 ret = wimlib_open_wim_with_progress(
2125 base_wimfiles.strings[i], open_flags,
2126 &base_wims[i], imagex_progress_func, NULL);
2128 goto out_free_base_wims;
2132 ret = wimlib_reference_resources(wim, base_wims,
2133 base_wimfiles.num_strings, 0);
2135 goto out_free_base_wims;
2137 if (base_wimfiles.num_strings == 1) {
2138 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2139 base_wimfiles.strings[0]);
2141 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2142 base_wimfiles.num_strings);
2149 /* If capturing or appending as an update of an existing (template) image,
2150 * open the WIM if needed and parse the image index. */
2151 if (template_image_name_or_num) {
2154 if (base_wimfiles.num_strings == 1 &&
2155 template_wimfile == base_wimfiles.strings[0]) {
2156 template_wim = base_wims[0];
2157 } else if (template_wimfile == wimfile) {
2160 ret = wimlib_open_wim_with_progress(template_wimfile,
2163 imagex_progress_func,
2166 goto out_free_base_wims;
2169 template_image = wimlib_resolve_image(template_wim,
2170 template_image_name_or_num);
2172 if (template_image_name_or_num[0] == T('-')) {
2175 struct wimlib_wim_info info;
2177 wimlib_get_wim_info(template_wim, &info);
2178 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2179 if (n >= 1 && n <= info.image_count &&
2181 tmp != template_image_name_or_num + 1)
2183 template_image = info.image_count - (n - 1);
2186 ret = verify_image_exists_and_is_single(template_image,
2187 template_image_name_or_num,
2190 goto out_free_template_wim;
2192 template_wim = NULL;
2195 ret = wimlib_add_image_multisource(wim,
2202 goto out_free_template_wim;
2204 if (desc || flags_element || template_image_name_or_num) {
2205 /* User provided <DESCRIPTION> or <FLAGS> element, or an image
2206 * on which the added one is to be based has been specified with
2207 * --update-of. Get the index of the image we just
2208 * added, then use it to call the appropriate functions. */
2209 struct wimlib_wim_info info;
2211 wimlib_get_wim_info(wim, &info);
2214 ret = wimlib_set_image_descripton(wim,
2218 goto out_free_template_wim;
2221 if (flags_element) {
2222 ret = wimlib_set_image_flags(wim, info.image_count,
2225 goto out_free_template_wim;
2228 /* Reference template image if the user provided one. */
2229 if (template_image_name_or_num) {
2230 imagex_printf(T("Using image %d "
2231 "from \"%"TS"\" as template\n"),
2232 template_image, template_wimfile);
2233 ret = wimlib_reference_template_image(wim,
2239 goto out_free_template_wim;
2243 /* Write the new WIM or overwrite the existing WIM with the new image
2245 if (cmd == CMD_APPEND) {
2246 ret = wimlib_overwrite(wim, write_flags, num_threads);
2247 } else if (wimfile) {
2248 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2249 write_flags, num_threads);
2251 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2252 write_flags, num_threads);
2254 out_free_template_wim:
2255 /* template_wim may alias base_wims[0] or wim. */
2256 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2257 template_wim != wim)
2258 wimlib_free(template_wim);
2260 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2261 wimlib_free(base_wims[i]);
2265 out_free_capture_sources:
2266 if (capture_sources_malloced)
2267 free(capture_sources);
2268 out_free_source_list_contents:
2269 free(source_list_contents);
2270 out_free_base_wimfiles:
2271 string_set_destroy(&base_wimfiles);
2278 goto out_free_base_wimfiles;
2281 /* Remove image(s) from a WIM. */
2283 imagex_delete(int argc, tchar **argv, int cmd)
2286 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2287 int write_flags = 0;
2288 const tchar *wimfile;
2289 const tchar *image_num_or_name;
2294 for_opt(c, delete_options) {
2296 case IMAGEX_CHECK_OPTION:
2297 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2298 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2300 case IMAGEX_SOFT_OPTION:
2301 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2312 imagex_error(T("Must specify a WIM file"));
2314 imagex_error(T("Must specify an image"));
2318 image_num_or_name = argv[1];
2320 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2321 imagex_progress_func, NULL);
2325 image = wimlib_resolve_image(wim, image_num_or_name);
2327 ret = verify_image_exists(image, image_num_or_name, wimfile);
2329 goto out_wimlib_free;
2331 ret = wimlib_delete_image(wim, image);
2333 imagex_error(T("Failed to delete image from \"%"TS"\""),
2335 goto out_wimlib_free;
2338 ret = wimlib_overwrite(wim, write_flags, 0);
2340 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2341 "deleted"), wimfile);
2349 usage(CMD_DELETE, stderr);
2354 struct print_dentry_options {
2359 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2361 tprintf(T("%"TS"\n"), dentry->full_path);
2364 static const struct {
2367 } file_attr_flags[] = {
2368 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2369 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2370 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2371 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2372 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2373 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2374 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2375 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2376 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2377 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2378 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2379 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2380 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2381 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2382 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2385 #define TIMESTR_MAX 100
2388 timespec_to_string(const struct timespec *spec, tchar *buf)
2390 time_t t = spec->tv_sec;
2393 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2394 buf[TIMESTR_MAX - 1] = '\0';
2398 print_time(const tchar *type, const struct timespec *spec)
2400 tchar timestr[TIMESTR_MAX];
2402 timespec_to_string(spec, timestr);
2404 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2407 static void print_byte_field(const uint8_t field[], size_t len)
2410 tprintf(T("%02hhx"), *field++);
2414 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2416 tchar attr_string[256];
2419 tputs(T("WIM Information:"));
2420 tputs(T("----------------"));
2421 tprintf(T("Path: %"TS"\n"), wimfile);
2422 tprintf(T("GUID: 0x"));
2423 print_byte_field(info->guid, sizeof(info->guid));
2425 tprintf(T("Version: %u\n"), info->wim_version);
2426 tprintf(T("Image Count: %d\n"), info->image_count);
2427 tprintf(T("Compression: %"TS"\n"),
2428 wimlib_get_compression_type_string(info->compression_type));
2429 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2431 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2432 tprintf(T("Boot Index: %d\n"), info->boot_index);
2433 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2435 attr_string[0] = T('\0');
2438 tstrcat(attr_string, T("Pipable, "));
2440 if (info->has_integrity_table)
2441 tstrcat(attr_string, T("Integrity info, "));
2443 if (info->has_rpfix)
2444 tstrcat(attr_string, T("Relative path junction, "));
2446 if (info->resource_only)
2447 tstrcat(attr_string, T("Resource only, "));
2449 if (info->metadata_only)
2450 tstrcat(attr_string, T("Metadata only, "));
2452 if (info->is_marked_readonly)
2453 tstrcat(attr_string, T("Readonly, "));
2455 p = tstrchr(attr_string, T('\0'));
2456 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2459 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2463 print_resource(const struct wimlib_resource_entry *resource,
2466 tprintf(T("Hash = 0x"));
2467 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2470 if (!resource->is_missing) {
2471 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2472 resource->uncompressed_size);
2473 if (resource->packed) {
2474 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2475 "bytes @ offset %"PRIu64"\n"),
2476 resource->raw_resource_uncompressed_size,
2477 resource->raw_resource_compressed_size,
2478 resource->raw_resource_offset_in_wim);
2480 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2483 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2484 resource->compressed_size);
2486 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2490 tprintf(T("Part Number = %u\n"), resource->part_number);
2491 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2493 tprintf(T("Flags = "));
2494 if (resource->is_compressed)
2495 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2496 if (resource->is_metadata)
2497 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2498 if (resource->is_free)
2499 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2500 if (resource->is_spanned)
2501 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2502 if (resource->packed)
2503 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2511 print_blobs(WIMStruct *wim)
2513 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2517 default_print_security_descriptor(const uint8_t *sd, size_t size)
2519 tprintf(T("Security Descriptor = "));
2520 print_byte_field(sd, size);
2525 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2529 "----------------------------------------------------------------------------\n"));
2530 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2531 if (dentry->dos_name)
2532 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2533 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2534 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2535 if (file_attr_flags[i].flag & dentry->attributes)
2536 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2537 file_attr_flags[i].name);
2539 if (dentry->security_descriptor) {
2540 print_security_descriptor(dentry->security_descriptor,
2541 dentry->security_descriptor_size);
2544 print_time(T("Creation Time"), &dentry->creation_time);
2545 print_time(T("Last Write Time"), &dentry->last_write_time);
2546 print_time(T("Last Access Time"), &dentry->last_access_time);
2549 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2550 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2552 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2553 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2555 if (dentry->unix_mode != 0) {
2556 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2557 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2558 dentry->unix_uid, dentry->unix_gid,
2559 dentry->unix_mode, dentry->unix_rdev);
2562 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2563 if (dentry->streams[i].stream_name) {
2564 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2565 dentry->streams[i].stream_name);
2566 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2567 tprintf(T("\tRaw encrypted data stream:\n"));
2568 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2569 tprintf(T("\tReparse point stream:\n"));
2571 tprintf(T("\tUnnamed data stream:\n"));
2573 print_resource(&dentry->streams[i].resource, NULL);
2578 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2580 const struct print_dentry_options *options = _options;
2581 if (!options->detailed)
2582 print_dentry_full_path(dentry);
2584 print_dentry_detailed(dentry);
2588 /* Print the files contained in an image(s) in a WIM file. */
2590 imagex_dir(int argc, tchar **argv, int cmd)
2592 const tchar *wimfile;
2593 WIMStruct *wim = NULL;
2596 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2598 struct print_dentry_options options = {
2601 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2603 for_opt(c, dir_options) {
2605 case IMAGEX_PATH_OPTION:
2608 case IMAGEX_DETAILED_OPTION:
2609 options.detailed = true;
2611 case IMAGEX_ONE_FILE_ONLY_OPTION:
2612 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2622 imagex_error(T("Must specify a WIM file"));
2626 imagex_error(T("Too many arguments"));
2631 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2632 imagex_progress_func, NULL);
2637 image = wimlib_resolve_image(wim, argv[1]);
2638 ret = verify_image_exists(image, argv[1], wimfile);
2640 goto out_wimlib_free;
2642 /* No image specified; default to image 1, but only if the WIM
2643 * contains exactly one image. */
2645 struct wimlib_wim_info info;
2647 wimlib_get_wim_info(wim, &info);
2648 if (info.image_count != 1) {
2649 imagex_error(T("\"%"TS"\" contains %d images; Please "
2650 "select one (or all)."),
2651 wimfile, info.image_count);
2658 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2659 print_dentry, &options);
2666 usage(CMD_DIR, stderr);
2671 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2674 imagex_export(int argc, tchar **argv, int cmd)
2678 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2679 int write_flags = 0;
2680 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2681 const tchar *src_wimfile;
2682 const tchar *src_image_num_or_name;
2683 const tchar *dest_wimfile;
2685 const tchar *dest_name;
2686 const tchar *dest_desc;
2688 struct wimlib_wim_info src_info;
2689 WIMStruct *dest_wim;
2694 STRING_SET(refglobs);
2695 unsigned num_threads = 0;
2696 uint32_t chunk_size = UINT32_MAX;
2697 uint32_t solid_chunk_size = UINT32_MAX;
2698 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2700 for_opt(c, export_options) {
2702 case IMAGEX_BOOT_OPTION:
2703 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2705 case IMAGEX_CHECK_OPTION:
2706 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2707 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2709 case IMAGEX_NOCHECK_OPTION:
2710 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2712 case IMAGEX_COMPRESS_OPTION:
2713 compression_type = get_compression_type(optarg);
2714 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2717 case IMAGEX_COMPRESS_SLOW_OPTION:
2718 set_compress_slow();
2719 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2721 case IMAGEX_RECOMPRESS_OPTION:
2722 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2724 case IMAGEX_SOLID_OPTION:
2725 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2727 case IMAGEX_NO_SOLID_SORT_OPTION:
2728 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2730 case IMAGEX_CHUNK_SIZE_OPTION:
2731 chunk_size = parse_chunk_size(optarg);
2732 if (chunk_size == UINT32_MAX)
2735 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2736 solid_chunk_size = parse_chunk_size(optarg);
2737 if (solid_chunk_size == UINT32_MAX)
2740 case IMAGEX_SOLID_COMPRESS_OPTION:
2741 solid_ctype = get_compression_type(optarg);
2742 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2745 case IMAGEX_REF_OPTION:
2746 ret = string_set_append(&refglobs, optarg);
2748 goto out_free_refglobs;
2750 case IMAGEX_THREADS_OPTION:
2751 num_threads = parse_num_threads(optarg);
2752 if (num_threads == UINT_MAX)
2755 case IMAGEX_REBUILD_OPTION:
2756 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2758 case IMAGEX_PIPABLE_OPTION:
2759 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2761 case IMAGEX_NOT_PIPABLE_OPTION:
2762 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2764 case IMAGEX_WIMBOOT_OPTION:
2765 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2773 if (argc < 3 || argc > 5)
2775 src_wimfile = argv[0];
2776 src_image_num_or_name = argv[1];
2777 dest_wimfile = argv[2];
2778 dest_name = (argc >= 4) ? argv[3] : NULL;
2779 dest_desc = (argc >= 5) ? argv[4] : NULL;
2780 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2781 imagex_progress_func, NULL);
2783 goto out_free_refglobs;
2785 wimlib_get_wim_info(src_wim, &src_info);
2787 /* Determine if the destination is an existing file or not. If so, we
2788 * try to append the exported image(s) to it; otherwise, we create a new
2789 * WIM containing the exported image(s). Furthermore, determine if we
2790 * need to write a pipable WIM directly to standard output. */
2792 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2794 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2795 imagex_error("Can't write a non-pipable WIM to "
2796 "standard output! Specify --pipable\n"
2797 " if you want to create a pipable WIM "
2798 "(but read the docs first).");
2800 goto out_free_src_wim;
2803 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2805 dest_wimfile = NULL;
2806 dest_wim_fd = STDOUT_FILENO;
2807 imagex_info_file = stderr;
2808 set_fd_to_binary_mode(dest_wim_fd);
2811 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2813 /* Destination file exists. */
2815 if (!S_ISREG(stbuf.st_mode)) {
2816 imagex_error(T("\"%"TS"\" is not a regular file"),
2819 goto out_free_src_wim;
2821 ret = wimlib_open_wim_with_progress(dest_wimfile,
2823 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2825 imagex_progress_func,
2828 goto out_free_src_wim;
2830 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2831 /* The user specified a compression type, but we're
2832 * exporting to an existing WIM. Make sure the
2833 * specified compression type is the same as the
2834 * compression type of the existing destination WIM. */
2835 struct wimlib_wim_info dest_info;
2837 wimlib_get_wim_info(dest_wim, &dest_info);
2838 if (compression_type != dest_info.compression_type) {
2839 imagex_error(T("Cannot specify a compression type that is "
2840 "not the same as that used in the "
2841 "destination WIM"));
2843 goto out_free_dest_wim;
2849 if (errno != ENOENT) {
2850 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
2853 goto out_free_src_wim;
2856 /* dest_wimfile is not an existing file, so create a new WIM. */
2858 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2859 /* The user did not specify a compression type; default
2860 * to that of the source WIM, unless --solid or
2861 * --wimboot was specified. */
2863 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
2864 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2865 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2866 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2868 compression_type = src_info.compression_type;
2870 ret = wimlib_create_new_wim(compression_type, &dest_wim);
2872 goto out_free_src_wim;
2874 wimlib_register_progress_function(dest_wim,
2875 imagex_progress_func, NULL);
2877 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
2878 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
2880 /* For --wimboot export, use small XPRESS chunks. */
2881 wimlib_set_output_chunk_size(dest_wim, 4096);
2882 } else if (compression_type == src_info.compression_type &&
2883 chunk_size == UINT32_MAX)
2885 /* Use same chunk size if compression type is the same. */
2886 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
2890 if (chunk_size != UINT32_MAX) {
2891 /* Set destination chunk size. */
2892 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
2894 goto out_free_dest_wim;
2896 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2897 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
2899 goto out_free_dest_wim;
2901 if (solid_chunk_size != UINT32_MAX) {
2902 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
2904 goto out_free_dest_wim;
2907 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
2908 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
2910 goto out_free_dest_wim;
2912 if (refglobs.num_strings) {
2913 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
2915 goto out_free_dest_wim;
2918 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
2919 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
2921 imagex_error(T("--boot specified for all-images export, but source WIM "
2922 "has no bootable image."));
2924 goto out_free_dest_wim;
2927 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
2928 dest_desc, export_flags);
2930 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
2931 do_resource_not_found_warning(src_wimfile,
2932 &src_info, &refglobs);
2934 goto out_free_dest_wim;
2938 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
2939 else if (dest_wimfile)
2940 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
2941 write_flags, num_threads);
2943 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
2944 WIMLIB_ALL_IMAGES, write_flags,
2947 wimlib_free(dest_wim);
2949 wimlib_free(src_wim);
2951 string_set_destroy(&refglobs);
2955 usage(CMD_EXPORT, stderr);
2958 goto out_free_refglobs;
2961 /* Extract files or directories from a WIM image */
2963 imagex_extract(int argc, tchar **argv, int cmd)
2970 const tchar *wimfile;
2971 const tchar *image_num_or_name;
2972 tchar *dest_dir = T(".");
2973 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
2974 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
2975 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
2976 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
2978 STRING_SET(refglobs);
2980 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
2982 for_opt(c, extract_options) {
2984 case IMAGEX_CHECK_OPTION:
2985 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2987 case IMAGEX_VERBOSE_OPTION:
2988 /* No longer does anything. */
2990 case IMAGEX_REF_OPTION:
2991 ret = string_set_append(&refglobs, optarg);
2993 goto out_free_refglobs;
2995 case IMAGEX_UNIX_DATA_OPTION:
2996 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
2998 case IMAGEX_NO_ACLS_OPTION:
2999 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3001 case IMAGEX_STRICT_ACLS_OPTION:
3002 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3004 case IMAGEX_NO_ATTRIBUTES_OPTION:
3005 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3007 case IMAGEX_DEST_DIR_OPTION:
3010 case IMAGEX_TO_STDOUT_OPTION:
3011 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3012 imagex_info_file = stderr;
3013 imagex_be_quiet = true;
3014 set_fd_to_binary_mode(STDOUT_FILENO);
3016 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3017 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3018 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3020 case IMAGEX_NO_GLOBS_OPTION:
3021 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3023 case IMAGEX_NULLGLOB_OPTION:
3024 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3026 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3027 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3029 case IMAGEX_WIMBOOT_OPTION:
3030 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3042 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3043 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3045 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3050 image_num_or_name = argv[1];
3055 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3056 imagex_progress_func, NULL);
3058 goto out_free_refglobs;
3060 image = wimlib_resolve_image(wim, image_num_or_name);
3061 ret = verify_image_exists_and_is_single(image,
3065 goto out_wimlib_free;
3067 if (refglobs.num_strings) {
3068 ret = wim_reference_globs(wim, &refglobs, open_flags);
3070 goto out_wimlib_free;
3076 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3079 while (argc != 0 && ret == 0) {
3083 num_paths < argc && argv[num_paths][0] != T('@');
3088 ret = wimlib_extract_paths(wim, image, dest_dir,
3089 (const tchar **)argv,
3091 extract_flags | notlist_extract_flags);
3095 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3104 if (!imagex_be_quiet)
3105 imagex_printf(T("Done extracting files.\n"));
3106 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3107 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3108 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3109 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3110 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3113 T("Note: You can use the '--nullglob' "
3114 "option to ignore missing files.\n"));
3116 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3117 "files and directories\n"
3118 " are in the WIM image.\n"),
3119 get_cmd_string(CMD_DIR, false));
3120 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3121 struct wimlib_wim_info info;
3123 wimlib_get_wim_info(wim, &info);
3124 do_resource_not_found_warning(wimfile, &info, &refglobs);
3129 string_set_destroy(&refglobs);
3133 usage(CMD_EXTRACT, stderr);
3136 goto out_free_refglobs;
3139 /* Prints information about a WIM file; also can mark an image as bootable,
3140 * change the name of an image, or change the description of an image. */
3142 imagex_info(int argc, tchar **argv, int cmd)
3147 bool nocheck = false;
3148 bool header = false;
3151 bool short_header = true;
3152 const tchar *xml_out_file = NULL;
3153 const tchar *wimfile;
3154 const tchar *image_num_or_name;
3155 const tchar *new_name;
3156 const tchar *new_desc;
3161 struct wimlib_wim_info info;
3163 for_opt(c, info_options) {
3165 case IMAGEX_BOOT_OPTION:
3168 case IMAGEX_CHECK_OPTION:
3171 case IMAGEX_NOCHECK_OPTION:
3174 case IMAGEX_HEADER_OPTION:
3176 short_header = false;
3178 case IMAGEX_BLOBS_OPTION:
3180 short_header = false;
3182 case IMAGEX_XML_OPTION:
3184 short_header = false;
3186 case IMAGEX_EXTRACT_XML_OPTION:
3187 xml_out_file = optarg;
3188 short_header = false;
3190 case IMAGEX_METADATA_OPTION:
3191 imagex_error(T("The --metadata option has been removed. "
3192 "Use 'wimdir --detail' instead."));
3201 if (argc < 1 || argc > 4)
3205 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3206 new_name = (argc >= 3) ? argv[2] : NULL;
3207 new_desc = (argc >= 4) ? argv[3] : NULL;
3209 if (check && nocheck) {
3210 imagex_error(T("Can't specify both --check and --nocheck"));
3215 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3217 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3218 imagex_progress_func, NULL);
3222 wimlib_get_wim_info(wim, &info);
3224 image = wimlib_resolve_image(wim, image_num_or_name);
3225 ret = WIMLIB_ERR_INVALID_IMAGE;
3226 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3227 verify_image_exists(image, image_num_or_name, wimfile);
3229 imagex_error(T("If you would like to set the boot "
3230 "index to 0, specify image \"0\" with "
3231 "the --boot flag."));
3233 goto out_wimlib_free;
3236 if (boot && info.image_count == 0) {
3237 imagex_error(T("--boot is meaningless on a WIM with no images"));
3238 goto out_wimlib_free;
3241 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3243 imagex_error(T("Cannot specify the --boot flag "
3244 "without specifying a specific "
3245 "image in a multi-image WIM"));
3246 goto out_wimlib_free;
3249 imagex_error(T("Cannot specify the NEW_NAME "
3250 "without specifying a specific "
3251 "image in a multi-image WIM"));
3252 goto out_wimlib_free;
3256 /* Operations that print information are separated from operations that
3257 * recreate the WIM file. */
3258 if (!new_name && !boot) {
3260 /* Read-only operations */
3262 if (image == WIMLIB_NO_IMAGE) {
3263 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3264 image_num_or_name, wimfile);
3265 goto out_wimlib_free;
3268 if (image == WIMLIB_ALL_IMAGES && short_header)
3269 print_wim_information(wimfile, &info);
3272 wimlib_print_header(wim);
3275 if (info.total_parts != 1) {
3276 tfprintf(stderr, T("Warning: Only showing the blobs "
3277 "for part %d of a %d-part WIM.\n"),
3278 info.part_number, info.total_parts);
3284 ret = wimlib_extract_xml_data(wim, stdout);
3286 goto out_wimlib_free;
3292 fp = tfopen(xml_out_file, T("wb"));
3294 imagex_error_with_errno(T("Failed to open the "
3295 "file \"%"TS"\" for "
3299 goto out_wimlib_free;
3301 ret = wimlib_extract_xml_data(wim, fp);
3303 imagex_error(T("Failed to close the file "
3309 goto out_wimlib_free;
3313 wimlib_print_available_images(wim, image);
3318 /* Modification operations */
3320 if (image == WIMLIB_ALL_IMAGES)
3323 if (image == WIMLIB_NO_IMAGE && new_name) {
3324 imagex_error(T("Cannot specify new_name (\"%"TS"\") "
3325 "when using image 0"), new_name);
3327 goto out_wimlib_free;
3331 if (image == info.boot_index) {
3332 imagex_printf(T("Image %d is already marked as "
3333 "bootable.\n"), image);
3336 imagex_printf(T("Marking image %d as bootable.\n"),
3338 info.boot_index = image;
3339 ret = wimlib_set_wim_info(wim, &info,
3340 WIMLIB_CHANGE_BOOT_INDEX);
3342 goto out_wimlib_free;
3346 if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
3348 imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
3352 imagex_printf(T("Changing the name of image %d to "
3353 "\"%"TS"\".\n"), image, new_name);
3354 ret = wimlib_set_image_name(wim, image, new_name);
3356 goto out_wimlib_free;
3360 const tchar *old_desc;
3361 old_desc = wimlib_get_image_description(wim, image);
3362 if (old_desc && !tstrcmp(old_desc, new_desc)) {
3363 imagex_printf(T("The description of image %d is already "
3364 "\"%"TS"\".\n"), image, new_desc);
3367 imagex_printf(T("Changing the description of image %d "
3368 "to \"%"TS"\".\n"), image, new_desc);
3369 ret = wimlib_set_image_descripton(wim, image,
3372 goto out_wimlib_free;
3376 /* Only call wimlib_overwrite() if something actually needs to
3378 if (boot || new_name || new_desc ||
3379 (check && !info.has_integrity_table) ||
3380 (nocheck && info.has_integrity_table))
3382 int write_flags = 0;
3385 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3387 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3388 ret = wimlib_overwrite(wim, write_flags, 1);
3390 imagex_printf(T("The file \"%"TS"\" was not modified "
3391 "because nothing needed to be done.\n"),
3402 usage(CMD_INFO, stderr);
3408 /* Join split WIMs into one part WIM */
3410 imagex_join(int argc, tchar **argv, int cmd)
3413 int swm_open_flags = 0;
3414 int wim_write_flags = 0;
3415 const tchar *output_path;
3418 for_opt(c, join_options) {
3420 case IMAGEX_CHECK_OPTION:
3421 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3422 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3432 imagex_error(T("Must specify one or more split WIM (.swm) "
3436 output_path = argv[0];
3437 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3442 imagex_progress_func,
3448 usage(CMD_JOIN, stderr);
3453 #if WIM_MOUNTING_SUPPORTED
3455 /* Mounts a WIM image. */
3457 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3460 int mount_flags = 0;
3462 const tchar *staging_dir = NULL;
3463 const tchar *wimfile;
3466 struct wimlib_wim_info info;
3470 STRING_SET(refglobs);
3472 if (cmd == CMD_MOUNTRW) {
3473 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3474 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3477 for_opt(c, mount_options) {
3479 case IMAGEX_ALLOW_OTHER_OPTION:
3480 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3482 case IMAGEX_CHECK_OPTION:
3483 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3485 case IMAGEX_DEBUG_OPTION:
3486 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3488 case IMAGEX_STREAMS_INTERFACE_OPTION:
3489 if (!tstrcasecmp(optarg, T("none")))
3490 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3491 else if (!tstrcasecmp(optarg, T("xattr")))
3492 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3493 else if (!tstrcasecmp(optarg, T("windows")))
3494 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3496 imagex_error(T("Unknown stream interface \"%"TS"\""),
3501 case IMAGEX_REF_OPTION:
3502 ret = string_set_append(&refglobs, optarg);
3504 goto out_free_refglobs;
3506 case IMAGEX_STAGING_DIR_OPTION:
3507 staging_dir = optarg;
3509 case IMAGEX_UNIX_DATA_OPTION:
3510 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3518 if (argc != 2 && argc != 3)
3523 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3524 imagex_progress_func, NULL);
3526 goto out_free_refglobs;
3528 wimlib_get_wim_info(wim, &info);
3531 /* Image explicitly specified. */
3532 image = wimlib_resolve_image(wim, argv[1]);
3534 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3538 /* No image specified; default to image 1, but only if the WIM
3539 * contains exactly one image. */
3541 if (info.image_count != 1) {
3542 imagex_error(T("\"%"TS"\" contains %d images; Please "
3543 "select one."), wimfile, info.image_count);
3551 if (refglobs.num_strings) {
3552 ret = wim_reference_globs(wim, &refglobs, open_flags);
3557 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3559 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3561 image, wimfile, dir);
3566 string_set_destroy(&refglobs);
3572 goto out_free_refglobs;
3574 #endif /* WIM_MOUNTING_SUPPORTED */
3576 /* Rebuild a WIM file */
3578 imagex_optimize(int argc, tchar **argv, int cmd)
3581 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3582 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3583 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3584 uint32_t chunk_size = UINT32_MAX;
3585 uint32_t solid_chunk_size = UINT32_MAX;
3586 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3589 const tchar *wimfile;
3592 unsigned num_threads = 0;
3594 for_opt(c, optimize_options) {
3596 case IMAGEX_CHECK_OPTION:
3597 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3598 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3600 case IMAGEX_NOCHECK_OPTION:
3601 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3603 case IMAGEX_COMPRESS_OPTION:
3604 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3605 compression_type = get_compression_type(optarg);
3606 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3609 case IMAGEX_COMPRESS_SLOW_OPTION:
3610 set_compress_slow();
3611 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3613 case IMAGEX_RECOMPRESS_OPTION:
3614 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3616 case IMAGEX_CHUNK_SIZE_OPTION:
3617 chunk_size = parse_chunk_size(optarg);
3618 if (chunk_size == UINT32_MAX)
3621 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3622 solid_chunk_size = parse_chunk_size(optarg);
3623 if (solid_chunk_size == UINT32_MAX)
3626 case IMAGEX_SOLID_COMPRESS_OPTION:
3627 solid_ctype = get_compression_type(optarg);
3628 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3631 case IMAGEX_SOLID_OPTION:
3632 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3633 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3635 case IMAGEX_NO_SOLID_SORT_OPTION:
3636 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3638 case IMAGEX_THREADS_OPTION:
3639 num_threads = parse_num_threads(optarg);
3640 if (num_threads == UINT_MAX)
3643 case IMAGEX_PIPABLE_OPTION:
3644 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3646 case IMAGEX_NOT_PIPABLE_OPTION:
3647 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3661 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3662 imagex_progress_func, NULL);
3666 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3667 /* Change compression type. */
3668 ret = wimlib_set_output_compression_type(wim, compression_type);
3670 goto out_wimlib_free;
3673 if (chunk_size != UINT32_MAX) {
3674 /* Change chunk size. */
3675 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3677 goto out_wimlib_free;
3679 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3680 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3682 goto out_wimlib_free;
3684 if (solid_chunk_size != UINT32_MAX) {
3685 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3687 goto out_wimlib_free;
3690 old_size = file_get_size(wimfile);
3691 tprintf(T("\"%"TS"\" original size: "), wimfile);
3693 tputs(T("Unknown"));
3695 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3697 ret = wimlib_overwrite(wim, write_flags, num_threads);
3699 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3700 goto out_wimlib_free;
3703 new_size = file_get_size(wimfile);
3704 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3706 tputs(T("Unknown"));
3708 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3710 tfputs(T("Space saved: "), stdout);
3711 if (new_size != -1 && old_size != -1) {
3712 tprintf(T("%lld KiB\n"),
3713 ((long long)old_size - (long long)new_size) >> 10);
3715 tputs(T("Unknown"));
3724 usage(CMD_OPTIMIZE, stderr);
3730 /* Split a WIM into a spanned set */
3732 imagex_split(int argc, tchar **argv, int cmd)
3736 int write_flags = 0;
3737 unsigned long part_size;
3742 for_opt(c, split_options) {
3744 case IMAGEX_CHECK_OPTION:
3745 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3746 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3758 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3759 if (tmp == argv[2] || *tmp) {
3760 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3761 imagex_error(T("The part size must be an integer or "
3762 "floating-point number of megabytes."));
3765 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3766 imagex_progress_func, NULL);
3770 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3776 usage(CMD_SPLIT, stderr);
3782 #if WIM_MOUNTING_SUPPORTED
3783 /* Unmounts a mounted WIM image. */
3785 imagex_unmount(int argc, tchar **argv, int cmd)
3788 int unmount_flags = 0;
3791 for_opt(c, unmount_options) {
3793 case IMAGEX_COMMIT_OPTION:
3794 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3796 case IMAGEX_CHECK_OPTION:
3797 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3799 case IMAGEX_REBUILD_OPTION:
3800 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3802 case IMAGEX_LAZY_OPTION:
3803 case IMAGEX_FORCE_OPTION:
3804 /* Now, unmount is lazy by default. However, committing
3805 * the image will fail with
3806 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3807 * file descriptors on the WIM image. The
3808 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3809 * descriptors to be closed. */
3810 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3812 case IMAGEX_NEW_IMAGE_OPTION:
3813 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
3824 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
3825 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
3826 imagex_error(T("--new-image is meaningless "
3827 "without --commit also specified!"));
3832 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
3833 imagex_progress_func, NULL);
3835 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
3836 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
3838 "\tNote: Use --commit --force to force changes "
3839 "to be committed, regardless\n"
3840 "\t of open files.\n"));
3847 usage(CMD_UNMOUNT, stderr);
3852 #endif /* WIM_MOUNTING_SUPPORTED */
3855 * Add, delete, or rename files in a WIM image.
3858 imagex_update(int argc, tchar **argv, int cmd)
3860 const tchar *wimfile;
3864 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3865 int write_flags = 0;
3866 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
3867 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
3868 WIMLIB_ADD_FLAG_VERBOSE |
3869 WIMLIB_ADD_FLAG_WINCONFIG;
3870 int default_delete_flags = 0;
3871 unsigned num_threads = 0;
3873 tchar *cmd_file_contents;
3874 size_t cmd_file_nchars;
3875 struct wimlib_update_command *cmds;
3877 tchar *command_str = NULL;
3878 tchar *config_file = NULL;
3879 tchar *wimboot_config = NULL;
3881 for_opt(c, update_options) {
3883 /* Generic or write options */
3884 case IMAGEX_THREADS_OPTION:
3885 num_threads = parse_num_threads(optarg);
3886 if (num_threads == UINT_MAX)
3889 case IMAGEX_CHECK_OPTION:
3890 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3891 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3893 case IMAGEX_REBUILD_OPTION:
3894 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
3896 case IMAGEX_COMMAND_OPTION:
3898 imagex_error(T("--command may only be specified "
3899 "one time. Please provide\n"
3900 " the update commands "
3901 "on standard input instead."));
3904 command_str = tstrdup(optarg);
3906 imagex_error(T("Out of memory!"));
3910 case IMAGEX_WIMBOOT_CONFIG_OPTION:
3911 wimboot_config = optarg;
3913 /* Default delete options */
3914 case IMAGEX_FORCE_OPTION:
3915 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
3917 case IMAGEX_RECURSIVE_OPTION:
3918 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
3921 /* Global add option */
3922 case IMAGEX_CONFIG_OPTION:
3923 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
3924 config_file = optarg;
3927 /* Default add options */
3928 case IMAGEX_VERBOSE_OPTION:
3929 /* No longer does anything. */
3931 case IMAGEX_DEREFERENCE_OPTION:
3932 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
3934 case IMAGEX_UNIX_DATA_OPTION:
3935 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
3937 case IMAGEX_NO_ACLS_OPTION:
3938 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
3940 case IMAGEX_STRICT_ACLS_OPTION:
3941 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
3943 case IMAGEX_NO_REPLACE_OPTION:
3944 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
3953 if (argc != 1 && argc != 2)
3957 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3958 imagex_progress_func, NULL);
3960 goto out_free_command_str;
3963 /* Image explicitly specified. */
3964 image = wimlib_resolve_image(wim, argv[1]);
3965 ret = verify_image_exists_and_is_single(image, argv[1],
3968 goto out_wimlib_free;
3970 /* No image specified; default to image 1, but only if the WIM
3971 * contains exactly one image. */
3972 struct wimlib_wim_info info;
3974 wimlib_get_wim_info(wim, &info);
3975 if (info.image_count != 1) {
3976 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
3977 wimfile, info.image_count);
3984 /* Read update commands from standard input, or the command string if
3987 cmd_file_contents = NULL;
3988 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
3992 goto out_free_cmd_file_contents;
3994 } else if (!wimboot_config) {
3995 if (isatty(STDIN_FILENO)) {
3996 tputs(T("Reading update commands from standard input..."));
3997 recommend_man_page(CMD_UPDATE, stdout);
3999 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4000 if (!cmd_file_contents) {
4002 goto out_wimlib_free;
4005 /* Parse the update commands */
4006 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4010 goto out_free_cmd_file_contents;
4013 cmd_file_contents = NULL;
4018 /* Set default flags and capture config on the update commands */
4019 for (size_t i = 0; i < num_cmds; i++) {
4020 switch (cmds[i].op) {
4021 case WIMLIB_UPDATE_OP_ADD:
4022 cmds[i].add.add_flags |= default_add_flags;
4023 cmds[i].add.config_file = config_file;
4025 case WIMLIB_UPDATE_OP_DELETE:
4026 cmds[i].delete_.delete_flags |= default_delete_flags;
4033 /* Execute the update commands */
4034 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4038 if (wimboot_config) {
4039 /* --wimboot-config=FILE is short for an
4040 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4042 struct wimlib_update_command cmd;
4044 cmd.op = WIMLIB_UPDATE_OP_ADD;
4045 cmd.add.fs_source_path = wimboot_config;
4046 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4047 cmd.add.config_file = NULL;
4048 cmd.add.add_flags = 0;
4050 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4055 /* Overwrite the updated WIM */
4056 ret = wimlib_overwrite(wim, write_flags, num_threads);
4059 out_free_cmd_file_contents:
4060 free(cmd_file_contents);
4063 out_free_command_str:
4068 usage(CMD_UPDATE, stderr);
4071 goto out_free_command_str;
4074 /* Verify a WIM file. */
4076 imagex_verify(int argc, tchar **argv, int cmd)
4079 const tchar *wimfile;
4081 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4082 int verify_flags = 0;
4083 STRING_SET(refglobs);
4086 for_opt(c, verify_options) {
4088 case IMAGEX_REF_OPTION:
4089 ret = string_set_append(&refglobs, optarg);
4091 goto out_free_refglobs;
4093 case IMAGEX_NOCHECK_OPTION:
4094 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4106 imagex_error(T("Must specify a WIM file!"));
4108 imagex_error(T("At most one WIM file can be specified!"));
4114 ret = wimlib_open_wim_with_progress(wimfile,
4117 imagex_progress_func,
4120 goto out_free_refglobs;
4122 ret = wim_reference_globs(wim, &refglobs, open_flags);
4124 goto out_wimlib_free;
4126 ret = wimlib_verify_wim(wim, verify_flags);
4128 tputc(T('\n'), stderr);
4129 imagex_error(T("\"%"TS"\" failed verification!"),
4131 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4132 refglobs.num_strings == 0)
4134 imagex_printf(T("Note: if this WIM file is not standalone, "
4135 "use the --ref option to specify the other parts.\n"));
4138 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4145 string_set_destroy(&refglobs);
4149 usage(CMD_VERIFY, stderr);
4151 goto out_free_refglobs;
4154 struct imagex_command {
4156 int (*func)(int argc, tchar **argv, int cmd);
4159 static const struct imagex_command imagex_commands[] = {
4160 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4161 [CMD_APPLY] = {T("apply"), imagex_apply},
4162 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4163 [CMD_DELETE] = {T("delete"), imagex_delete},
4164 [CMD_DIR ] = {T("dir"), imagex_dir},
4165 [CMD_EXPORT] = {T("export"), imagex_export},
4166 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4167 [CMD_INFO] = {T("info"), imagex_info},
4168 [CMD_JOIN] = {T("join"), imagex_join},
4169 #if WIM_MOUNTING_SUPPORTED
4170 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4171 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4173 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4174 [CMD_SPLIT] = {T("split"), imagex_split},
4175 #if WIM_MOUNTING_SUPPORTED
4176 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4178 [CMD_UPDATE] = {T("update"), imagex_update},
4179 [CMD_VERIFY] = {T("verify"), imagex_verify},
4184 /* Can be a directory or source list file. But source list file is probably
4185 * a rare use case, so just say directory. */
4186 # define SOURCE_STR T("DIRECTORY")
4188 /* Can only be a directory */
4189 # define TARGET_STR T("DIRECTORY")
4192 /* Can be a directory, NTFS volume, or source list file. */
4193 # define SOURCE_STR T("SOURCE")
4195 /* Can be a directory or NTFS volume. */
4196 # define TARGET_STR T("TARGET")
4200 static const tchar *usage_strings[] = {
4203 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4204 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4205 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4206 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4207 " [--wimboot] [--unix-data] [--dereference]\n"
4211 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4212 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4213 " [--no-attributes] [--rpfix] [--norpfix]\n"
4214 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4218 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4219 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4220 " [--config=FILE] [--threads=NUM_THREADS]\n"
4221 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4222 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4223 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4227 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4231 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4235 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4236 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4237 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4238 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4239 " [--wimboot] [--solid]\n"
4243 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4244 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4245 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4246 " [--no-attributes] [--include-invalid-names]\n"
4247 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4251 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4252 " [--boot] [--check] [--nocheck] [--xml]\n"
4253 " [--extract-xml FILE] [--header] [--blobs]\n"
4257 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4259 #if WIM_MOUNTING_SUPPORTED
4262 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4263 " [--check] [--streams-interface=INTERFACE]\n"
4264 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4268 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4269 " [--check] [--streams-interface=INTERFACE]\n"
4270 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4276 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4277 " [--check] [--nocheck] [--solid]\n"
4282 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4284 #if WIM_MOUNTING_SUPPORTED
4287 " %"TS" DIRECTORY\n"
4288 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4293 " %"TS" WIMFILE [IMAGE]\n"
4294 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4295 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4296 " [--command=STRING] [--wimboot-config=FILE]\n"
4301 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4305 static const tchar *invocation_name;
4306 static int invocation_cmd = CMD_NONE;
4308 static const tchar *get_cmd_string(int cmd, bool nospace)
4310 static tchar buf[50];
4311 if (cmd == CMD_NONE) {
4312 return T("wimlib-imagex");
4313 } else if (invocation_cmd != CMD_NONE) {
4314 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4316 const tchar *format;
4319 format = T("%"TS"-%"TS"");
4321 format = T("%"TS" %"TS"");
4322 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4330 static const tchar *s =
4332 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4333 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4334 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4335 "This is free software: you are free to change and redistribute it.\n"
4336 "There is NO WARRANTY, to the extent permitted by law.\n"
4338 "Report bugs to "PACKAGE_BUGREPORT".\n"
4345 help_or_version(int argc, tchar **argv, int cmd)
4350 for (i = 1; i < argc; i++) {
4352 if (p[0] == T('-') && p[1] == T('-')) {
4354 if (!tstrcmp(p, T("help"))) {
4355 if (cmd == CMD_NONE)
4360 } else if (!tstrcmp(p, T("version"))) {
4369 print_usage_string(int cmd, FILE *fp)
4371 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4375 recommend_man_page(int cmd, FILE *fp)
4377 const tchar *format_str;
4379 format_str = T("Some uncommon options are not listed;\n"
4380 "See %"TS".pdf in the doc directory for more details.\n");
4382 format_str = T("Some uncommon options are not listed;\n"
4383 "Try `man %"TS"' for more details.\n");
4385 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4389 usage(int cmd, FILE *fp)
4391 tfprintf(fp, T("Usage:\n"));
4392 print_usage_string(cmd, fp);
4393 tfprintf(fp, T("\n"));
4394 recommend_man_page(cmd, fp);
4400 tfprintf(fp, T("Usage:\n"));
4401 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4402 print_usage_string(cmd, fp);
4403 tfprintf(fp, T("\n"));
4405 static const tchar *extra =
4408 " %"TS" --version\n"
4411 tfprintf(fp, extra, invocation_name, invocation_name);
4413 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4414 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4415 "For some commands IMAGE may be \"all\".\n"
4417 recommend_man_page(CMD_NONE, fp);
4420 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4421 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4422 * something else), while on Windows the command arguments will be UTF-16LE
4423 * encoded 'wchar_t' strings. */
4426 wmain(int argc, wchar_t **argv, wchar_t **envp)
4428 main(int argc, char **argv)
4435 imagex_info_file = stdout;
4436 invocation_name = tbasename(argv[0]);
4439 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4440 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4444 setlocale(LC_ALL, "");
4445 codeset = nl_langinfo(CODESET);
4446 if (!strstr(codeset, "UTF-8") &&
4447 !strstr(codeset, "UTF8") &&
4448 !strstr(codeset, "utf-8") &&
4449 !strstr(codeset, "utf8"))
4452 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4453 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4454 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4455 " to any value to force wimlib to use UTF-8.\n",
4461 #endif /* !__WIN32__ */
4464 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4465 if (igcase != NULL) {
4466 if (!tstrcmp(igcase, T("no")) ||
4467 !tstrcmp(igcase, T("0")))
4468 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4469 else if (!tstrcmp(igcase, T("yes")) ||
4470 !tstrcmp(igcase, T("1")))
4471 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4474 "WARNING: Ignoring unknown setting of "
4475 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4480 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4482 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4483 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4484 for (int i = 0; i < CMD_MAX; i++) {
4485 if (!tstrcmp(invocation_name + 3,
4486 imagex_commands[i].name))
4495 /* Unless already known from the invocation name, determine which
4496 * command was specified. */
4497 if (cmd == CMD_NONE) {
4499 imagex_error(T("No command specified!\n"));
4503 for (int i = 0; i < CMD_MAX; i++) {
4504 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4509 if (cmd != CMD_NONE) {
4515 /* Handle --help and --version. --help can be either for the program as
4516 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4517 * CMD_NONE). Note: help_or_version() will not return if a --help or
4518 * --version argument was found. */
4519 help_or_version(argc, argv, cmd);
4521 /* Bail if a valid command was not specified. */
4522 if (cmd == CMD_NONE) {
4523 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4528 /* Enable warning and error messages in wimlib to be more user-friendly.
4530 wimlib_set_print_errors(true);
4532 /* Initialize wimlib. */
4533 ret = wimlib_global_init(init_flags);
4535 goto out_check_status;
4537 /* Call the command handler function. */
4538 ret = imagex_commands[cmd].func(argc, argv, cmd);
4540 /* Check for error writing to standard output, especially since for some
4541 * commands, writing to standard output is part of the program's actual
4542 * behavior and not just for informational purposes. */
4543 if (ferror(stdout) || fclose(stdout)) {
4544 imagex_error_with_errno(T("error writing to standard output"));
4549 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4550 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4551 * error code from which an error message can be printed. */
4553 imagex_error(T("Exiting with error code %d:\n"
4555 wimlib_get_error_string(ret));
4556 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4557 imagex_error_with_errno(T("errno"));
4559 /* Make wimlib free any resources it's holding (although this is not
4560 * strictly necessary because the process is ending anyway). */
4561 wimlib_global_cleanup();