4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012-2018 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 only_short_form);
136 static FILE *imagex_info_file;
138 #define imagex_printf(format, ...) \
139 if (imagex_info_file) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
142 static void imagex_suppress_output(void)
144 imagex_info_file = NULL;
147 static void imagex_output_to_stderr(void)
149 if (imagex_info_file)
150 imagex_info_file = stderr;
153 static void imagex_flush_output(void)
155 if (imagex_info_file)
156 fflush(imagex_info_file);
160 IMAGEX_ALLOW_OTHER_OPTION,
164 IMAGEX_CHUNK_SIZE_OPTION,
165 IMAGEX_COMMAND_OPTION,
166 IMAGEX_COMMIT_OPTION,
167 IMAGEX_COMPACT_OPTION,
168 IMAGEX_COMPRESS_OPTION,
169 IMAGEX_CONFIG_OPTION,
171 IMAGEX_DELTA_FROM_OPTION,
172 IMAGEX_DEREFERENCE_OPTION,
173 IMAGEX_DEST_DIR_OPTION,
174 IMAGEX_DETAILED_OPTION,
175 IMAGEX_EXTRACT_XML_OPTION,
178 IMAGEX_HEADER_OPTION,
179 IMAGEX_IMAGE_PROPERTY_OPTION,
180 IMAGEX_INCLUDE_INTEGRITY_OPTION,
181 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
183 IMAGEX_METADATA_OPTION,
184 IMAGEX_NEW_IMAGE_OPTION,
185 IMAGEX_NOCHECK_OPTION,
186 IMAGEX_NORPFIX_OPTION,
187 IMAGEX_NOT_PIPABLE_OPTION,
188 IMAGEX_NO_ACLS_OPTION,
189 IMAGEX_NO_ATTRIBUTES_OPTION,
190 IMAGEX_NO_GLOBS_OPTION,
191 IMAGEX_NO_REPLACE_OPTION,
192 IMAGEX_NO_SOLID_SORT_OPTION,
193 IMAGEX_NULLGLOB_OPTION,
194 IMAGEX_ONE_FILE_ONLY_OPTION,
196 IMAGEX_PIPABLE_OPTION,
197 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
198 IMAGEX_REBUILD_OPTION,
199 IMAGEX_RECOMPRESS_OPTION,
200 IMAGEX_RECURSIVE_OPTION,
203 IMAGEX_SNAPSHOT_OPTION,
205 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
206 IMAGEX_SOLID_COMPRESS_OPTION,
208 IMAGEX_SOURCE_LIST_OPTION,
209 IMAGEX_STAGING_DIR_OPTION,
210 IMAGEX_STREAMS_INTERFACE_OPTION,
211 IMAGEX_STRICT_ACLS_OPTION,
212 IMAGEX_THREADS_OPTION,
213 IMAGEX_TO_STDOUT_OPTION,
214 IMAGEX_UNIX_DATA_OPTION,
215 IMAGEX_UNSAFE_COMPACT_OPTION,
216 IMAGEX_UPDATE_OF_OPTION,
217 IMAGEX_VERBOSE_OPTION,
218 IMAGEX_WIMBOOT_CONFIG_OPTION,
219 IMAGEX_WIMBOOT_OPTION,
223 static const struct option apply_options[] = {
224 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
225 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
226 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
227 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
228 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
229 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
230 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
231 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
232 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
233 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
234 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
235 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
236 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
240 static const struct option capture_or_append_options[] = {
241 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
242 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
243 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
244 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
245 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
246 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
247 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
248 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
249 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
250 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
251 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
252 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
253 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
254 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
255 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
256 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
257 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
258 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
259 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
260 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
261 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
262 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
263 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
264 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
265 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
266 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
267 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
268 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
269 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
270 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
271 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
272 {T("snapshot"), no_argument, NULL, IMAGEX_SNAPSHOT_OPTION},
276 static const struct option delete_options[] = {
277 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
278 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
279 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
280 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
284 static const struct option dir_options[] = {
285 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
286 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
287 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
288 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
292 static const struct option export_options[] = {
293 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
294 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
295 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
296 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
297 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
298 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
299 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
300 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
301 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
302 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
303 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
304 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
305 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
306 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
307 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
308 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
309 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
310 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
311 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
315 static const struct option extract_options[] = {
316 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
317 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
318 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
319 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
320 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
321 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
322 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
323 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
324 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
325 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
326 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
327 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
328 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
329 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
330 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
331 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
332 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
336 static const struct option info_options[] = {
337 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
338 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
339 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
340 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
341 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
342 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
343 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
344 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
345 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
346 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
347 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
351 static const struct option join_options[] = {
352 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
353 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
357 #if WIM_MOUNTING_SUPPORTED
358 static const struct option mount_options[] = {
359 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
360 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
361 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
362 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
363 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
364 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
365 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
370 static const struct option optimize_options[] = {
371 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
372 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
373 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
374 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
375 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
376 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
377 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
378 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
379 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
380 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
381 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
382 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
383 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
384 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
385 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
389 static const struct option split_options[] = {
390 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
391 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
395 #if WIM_MOUNTING_SUPPORTED
396 static const struct option unmount_options[] = {
397 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
398 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
399 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
400 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
401 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
402 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
407 static const struct option update_options[] = {
408 /* Careful: some of the options here set the defaults for update
409 * commands, but the flags given to an actual update command (and not to
410 * `imagex update' itself are also handled in
411 * update_command_add_option(). */
412 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
413 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
414 {T("include-integrity"), no_argument, NULL, IMAGEX_INCLUDE_INTEGRITY_OPTION},
415 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
416 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
417 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
419 /* Default delete options */
420 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
421 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
423 /* Global add option */
424 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
426 /* Default add options */
427 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
428 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
429 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
430 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
431 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
432 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
433 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
434 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
439 static const struct option verify_options[] = {
440 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
441 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
447 # define _format_attribute(type, format_str, args_start) \
448 __attribute__((format(type, format_str, args_start)))
450 # define _format_attribute(type, format_str, args_start)
453 /* Print formatted error message to stderr. */
454 static void _format_attribute(printf, 1, 2)
455 imagex_error(const tchar *format, ...)
458 va_start(va, format);
459 tfputs(T("ERROR: "), stderr);
460 tvfprintf(stderr, format, va);
461 tputc(T('\n'), stderr);
465 /* Print formatted error message to stderr. */
466 static void _format_attribute(printf, 1, 2)
467 imagex_error_with_errno(const tchar *format, ...)
469 int errno_save = errno;
471 va_start(va, format);
472 tfputs(T("ERROR: "), stderr);
473 tvfprintf(stderr, format, va);
474 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
479 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
481 if (image == WIMLIB_NO_IMAGE) {
482 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
483 " Please specify a 1-based image index or "
484 "image name. To list the images\n"
485 " contained in the WIM archive, run\n"
487 " %"TS" \"%"TS"\"\n"),
488 image_name, wim_name,
489 get_cmd_string(CMD_INFO, false), wim_name);
490 return WIMLIB_ERR_INVALID_IMAGE;
496 verify_image_is_single(int image)
498 if (image == WIMLIB_ALL_IMAGES) {
499 imagex_error(T("Cannot specify all images for this action!"));
500 return WIMLIB_ERR_INVALID_IMAGE;
506 verify_image_exists_and_is_single(int image, const tchar *image_name,
507 const tchar *wim_name)
510 ret = verify_image_exists(image, image_name, wim_name);
512 ret = verify_image_is_single(image);
517 print_available_compression_types(FILE *fp)
519 static const tchar * const s =
521 "Available compression types:\n"
524 " xpress (alias: \"fast\")\n"
525 " lzx (alias: \"maximum\") (default for capture)\n"
526 " lzms (alias: \"recovery\")\n"
532 /* Parse the argument to --compress or --solid-compress */
534 get_compression_type(tchar *optarg, bool solid)
537 unsigned int compression_level = 0;
540 plevel = tstrchr(optarg, T(':'));
546 ultmp = tstrtoul(plevel, &ptmp, 10);
547 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
548 imagex_error(T("Compression level must be a positive integer! "
549 "e.g. --compress=lzx:80"));
550 return WIMLIB_COMPRESSION_TYPE_INVALID;
552 compression_level = ultmp;
555 if (!tstrcasecmp(optarg, T("maximum")) ||
556 !tstrcasecmp(optarg, T("lzx")) ||
557 !tstrcasecmp(optarg, T("max"))) {
558 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
559 } else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress"))) {
560 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
561 } else if (!tstrcasecmp(optarg, T("recovery"))) {
565 "Warning: use of '--compress=recovery' is discouraged because it behaves\n"
566 " differently from DISM. Instead, you typically want to use '--solid' to\n"
567 " create a solid LZMS-compressed WIM or \"ESD file\", similar to DISM's\n"
568 " /compress:recovery. But if you really want *non-solid* LZMS compression,\n"
569 " then you may suppress this warning by specifying '--compress=lzms' instead\n"
570 " of '--compress=recovery'.\n"));
572 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
573 } else if (!tstrcasecmp(optarg, T("lzms"))) {
574 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
575 } else if (!tstrcasecmp(optarg, T("none"))) {
576 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
578 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
579 print_available_compression_types(stderr);
580 return WIMLIB_COMPRESSION_TYPE_INVALID;
583 if (compression_level != 0)
584 wimlib_set_default_compression_level(ctype, compression_level);
588 /* Parse the argument to --compact */
590 set_compact_mode(const tchar *arg, int *extract_flags)
593 if (!tstrcasecmp(arg, T("xpress4k")))
594 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
595 else if (!tstrcasecmp(arg, T("xpress8k")))
596 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
597 else if (!tstrcasecmp(arg, T("xpress16k")))
598 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
599 else if (!tstrcasecmp(arg, T("lzx")))
600 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
603 *extract_flags |= flag;
608 "\"%"TS"\" is not a recognized System Compression format. The options are:"
610 " --compact=xpress4k\n"
611 " --compact=xpress8k\n"
612 " --compact=xpress16k\n"
621 unsigned num_strings;
622 unsigned num_alloc_strings;
625 #define STRING_LIST_INITIALIZER \
626 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
628 #define STRING_LIST(_strings) \
629 struct string_list _strings = STRING_LIST_INITIALIZER
632 string_list_append(struct string_list *list, tchar *glob)
634 unsigned num_alloc_strings = list->num_alloc_strings;
636 if (list->num_strings == num_alloc_strings) {
639 num_alloc_strings += 4;
640 new_strings = realloc(list->strings,
641 sizeof(list->strings[0]) * num_alloc_strings);
643 imagex_error(T("Out of memory!"));
646 list->strings = new_strings;
647 list->num_alloc_strings = num_alloc_strings;
649 list->strings[list->num_strings++] = glob;
654 string_list_destroy(struct string_list *list)
660 wim_reference_globs(WIMStruct *wim, struct string_list *list, int open_flags)
662 return wimlib_reference_resource_files(wim, (const tchar **)list->strings,
664 WIMLIB_REF_FLAG_GLOB_ENABLE,
669 append_image_property_argument(struct string_list *image_properties)
671 if (!tstrchr(optarg, '=')) {
672 imagex_error(T("'--image-property' argument "
673 "must be in the form NAME=VALUE"));
676 return string_list_append(image_properties, optarg);
680 apply_image_properties(struct string_list *image_properties,
681 WIMStruct *wim, int image, bool *any_changes_ret)
683 bool any_changes = false;
684 for (unsigned i = 0; i < image_properties->num_strings; i++) {
686 const tchar *current_value;
689 name = image_properties->strings[i];
690 value = tstrchr(name, '=');
693 current_value = wimlib_get_image_property(wim, image, name);
694 if (current_value && !tstrcmp(current_value, value)) {
695 imagex_printf(T("The %"TS" property of image %d "
696 "already has value \"%"TS"\".\n"),
699 imagex_printf(T("Setting the %"TS" property of image "
700 "%d to \"%"TS"\".\n"),
702 ret = wimlib_set_image_property(wim, image, name, value);
709 *any_changes_ret = any_changes;
714 do_resource_not_found_warning(const tchar *wimfile,
715 const struct wimlib_wim_info *info,
716 const struct string_list *refglobs)
718 if (info->total_parts > 1) {
719 if (refglobs->num_strings == 0) {
720 imagex_error(T("\"%"TS"\" is part of a split WIM. "
721 "Use --ref to specify the other parts."),
724 imagex_error(T("Perhaps the '--ref' argument did not "
725 "specify all other parts of the split "
729 imagex_error(T("If this is a delta WIM, use the --ref argument "
730 "to specify the WIM(s) on which it is based."));
735 do_metadata_not_found_warning(const tchar *wimfile,
736 const struct wimlib_wim_info *info)
738 if (info->part_number != 1) {
739 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
740 " You must specify the first part."),
745 /* Returns the size of a file given its name, or -1 if the file does not exist
746 * or its size cannot be determined. */
748 file_get_size(const tchar *filename)
751 if (tstat(filename, &st) == 0)
758 PARSE_STRING_SUCCESS = 0,
759 PARSE_STRING_FAILURE = 1,
760 PARSE_STRING_NONE = 2,
764 * Parses a string token from an array of characters.
766 * Tokens are either whitespace-delimited, or double or single-quoted.
768 * @line_p: Pointer to the pointer to the line of data. Will be updated
769 * to point past the string token iff the return value is
770 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
773 * @len_p: @len_p initially stores the length of the line of data, which may
774 * be 0, and it will be updated to the number of bytes remaining in
775 * the line iff the return value is PARSE_STRING_SUCCESS.
777 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
778 * parsed string token will be returned here.
780 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
781 * PARSE_STRING_FAILURE if the data was invalid due to a missing
782 * closing quote; or PARSE_STRING_NONE if the line ended before the
783 * beginning of a string token was found.
786 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
789 tchar *line = *line_p;
793 /* Skip leading whitespace */
796 return PARSE_STRING_NONE;
797 if (!istspace(*line) && *line != T('\0'))
803 if (quote_char == T('"') || quote_char == T('\'')) {
808 line = tmemchr(line, quote_char, len);
810 imagex_error(T("Missing closing quote: %"TS), fn - 1);
811 return PARSE_STRING_FAILURE;
814 /* Unquoted string. Go until whitespace. Line is terminated
815 * by '\0', so no need to check 'len'. */
819 } while (!istspace(*line) && *line != T('\0'));
826 return PARSE_STRING_SUCCESS;
829 /* Parses a line of data (not an empty line or comment) in the source list file
830 * format. (See the man page for 'wimlib-imagex capture' for details on this
831 * format and the meaning.)
833 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
834 * len == 0. The data in @line will be modified by this function call.
836 * @len: Length of the line of data.
838 * @source: On success, the capture source and target described by the line is
839 * written into this destination. Note that it will contain pointers
840 * to data in the @line array.
842 * Returns true if the line was valid; false otherwise. */
844 parse_source_list_line(tchar *line, size_t len,
845 struct wimlib_capture_source *source)
849 ret = parse_string(&line, &len, &source->fs_source_path);
850 if (ret != PARSE_STRING_SUCCESS)
852 ret = parse_string(&line, &len, &source->wim_target_path);
853 if (ret == PARSE_STRING_NONE)
854 source->wim_target_path = source->fs_source_path;
855 return ret != PARSE_STRING_FAILURE;
858 /* Returns %true if the given line of length @len > 0 is a comment or empty line
859 * in the source list file format. */
861 is_comment_line(const tchar *line, size_t len)
864 if (*line == T('#') || *line == T(';'))
866 if (!istspace(*line) && *line != T('\0'))
876 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
879 tchar *contents = *contents_p;
880 size_t nchars = *nchars_p;
883 for (i = 0; i < nchars; i++)
884 if (contents[i] == T('\n'))
887 /* Handle last line not terminated by a newline */
888 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
889 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
891 imagex_error(T("Out of memory!"));
894 contents[nchars] = T('\n');
895 *contents_p = contents;
903 /* Parses a file in the source list format. (See the man page for
904 * 'wimlib-imagex capture' for details on this format and the meaning.)
906 * @source_list_contents: Contents of the source list file. Note that this
907 * buffer will be modified to save memory allocations,
908 * and cannot be freed until the returned array of
909 * wimlib_capture_source's has also been freed.
911 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
914 * @nsources_ret: On success, the length of the returned array is
917 * Returns: An array of `struct wimlib_capture_source's that can be passed to
918 * the wimlib_add_image_multisource() function to specify how a WIM image is to
920 static struct wimlib_capture_source *
921 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
922 size_t *nsources_ret)
926 struct wimlib_capture_source *sources;
929 nlines = text_file_count_lines(source_list_contents_p,
930 &source_list_nchars);
934 /* Always allocate at least 1 slot, just in case the implementation of
935 * calloc() returns NULL if 0 bytes are requested. */
936 sources = calloc(nlines ?: 1, sizeof(*sources));
938 imagex_error(T("out of memory"));
941 p = *source_list_contents_p;
943 for (i = 0; i < nlines; i++) {
944 /* XXX: Could use rawmemchr() here instead, but it may not be
945 * available on all platforms. */
946 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
947 size_t len = endp - p + 1;
949 if (!is_comment_line(p, len)) {
950 if (!parse_source_list_line(p, len, &sources[j++])) {
962 /* Reads the contents of a file into memory. */
964 file_get_contents(const tchar *filename, size_t *len_ret)
971 if (tstat(filename, &stbuf) != 0) {
972 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
977 fp = tfopen(filename, T("rb"));
979 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
983 buf = malloc(len ? len : 1);
985 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
986 "contents of file \"%"TS"\""), len, filename);
989 if (fread(buf, 1, len, fp) != len) {
990 imagex_error_with_errno(T("Failed to read %zu bytes from the "
991 "file \"%"TS"\""), len, filename);
1005 /* Read standard input until EOF and return the full contents in a malloc()ed
1006 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
1009 stdin_get_contents(size_t *len_ret)
1011 /* stdin can, of course, be a pipe or other non-seekable file, so the
1012 * total length of the data cannot be pre-determined */
1014 size_t newlen = 1024;
1018 char *p = realloc(buf, newlen);
1019 size_t bytes_read, bytes_to_read;
1021 imagex_error(T("out of memory while reading stdin"));
1025 bytes_to_read = newlen - pos;
1026 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
1028 if (bytes_read != bytes_to_read) {
1033 imagex_error_with_errno(T("error reading stdin"));
1047 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1050 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1052 *num_tchars_ret = num_bytes;
1054 #else /* !__WIN32__ */
1055 /* On Windows, translate the text to UTF-16LE */
1059 if (num_bytes >= 2 &&
1060 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1061 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1063 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1064 * with something that looks like an ASCII character encoded as
1065 * a UTF-16LE code unit. Assume the file is encoded as
1066 * UTF-16LE. This is not a 100% reliable check. */
1067 num_wchars = num_bytes / 2;
1068 text_wstr = (wchar_t*)text;
1070 /* File does not look like UTF-16LE. Assume it is encoded in
1071 * the current Windows code page. I think these are always
1072 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1073 * should work as expected. */
1074 text_wstr = win32_mbs_to_wcs(text,
1079 *num_tchars_ret = num_wchars;
1081 #endif /* __WIN32__ */
1085 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1090 contents = file_get_contents(filename, &num_bytes);
1093 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1097 stdin_get_text_contents(size_t *num_tchars_ret)
1102 contents = stdin_get_contents(&num_bytes);
1105 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1108 #define TO_PERCENT(numerator, denominator) \
1109 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1111 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1112 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1113 #define KIBIBYTE_MIN_NBYTES 10000ULL
1116 get_unit(uint64_t total_bytes, const tchar **name_ret)
1118 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1119 *name_ret = T("GiB");
1121 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1122 *name_ret = T("MiB");
1124 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1125 *name_ret = T("KiB");
1128 *name_ret = T("bytes");
1133 static struct wimlib_progress_info_scan last_scan_progress;
1136 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1138 uint64_t prev_count, cur_count;
1140 prev_count = last_scan_progress.num_nondirs_scanned +
1141 last_scan_progress.num_dirs_scanned;
1142 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1144 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1145 cur_count % 128 == 0)
1147 unsigned unit_shift;
1148 const tchar *unit_name;
1150 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1151 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1152 "%"PRIu64" directories) "),
1153 scan->num_bytes_scanned >> unit_shift,
1155 scan->num_nondirs_scanned,
1156 scan->num_dirs_scanned);
1157 last_scan_progress = *scan;
1160 /* Progress callback function passed to various wimlib functions. */
1161 static enum wimlib_progress_status
1162 imagex_progress_func(enum wimlib_progress_msg msg,
1163 union wimlib_progress_info *info,
1164 void *_ignored_context)
1166 unsigned percent_done;
1167 unsigned unit_shift;
1168 const tchar *unit_name;
1171 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1173 static bool started;
1175 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1176 imagex_printf(T("Using %"TS" compression "
1177 "with %u thread%"TS"\n"),
1178 wimlib_get_compression_type_string(
1179 info->write_streams.compression_type),
1180 info->write_streams.num_threads,
1181 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1186 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1187 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1188 info->write_streams.total_bytes);
1190 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1191 info->write_streams.completed_bytes >> unit_shift,
1193 info->write_streams.total_bytes >> unit_shift,
1196 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1197 imagex_printf(T("\n"));
1199 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1200 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1201 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1202 imagex_printf(T("\n"));
1204 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1205 info->scan.wim_target_path);
1207 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1209 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1210 switch (info->scan.status) {
1211 case WIMLIB_SCAN_DENTRY_OK:
1212 report_scan_progress(&info->scan, false);
1214 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1215 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1217 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1218 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1219 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1221 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1222 /* Symlink fixups are enabled by default. This is
1223 * mainly intended for Windows, which for some reason
1224 * uses absolute junctions (with drive letters!) in the
1225 * default installation. On UNIX-like systems, warn the
1226 * user when fixing the target of an absolute symbolic
1227 * link, so they know to disable this if they want. */
1229 imagex_printf(T("\nWARNING: Adjusted target of "
1230 "absolute symbolic link \"%"TS"\"\n"
1231 " (Use --norpfix to capture "
1232 "absolute symbolic links as-is)\n"),
1233 info->scan.cur_path);
1240 case WIMLIB_PROGRESS_MSG_SCAN_END:
1241 report_scan_progress(&info->scan, true);
1242 imagex_printf(T("\n"));
1244 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1245 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1246 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1247 info->integrity.total_bytes);
1248 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1249 "of %"PRIu64" %"TS" (%u%%) done"),
1250 info->integrity.filename,
1251 info->integrity.completed_bytes >> unit_shift,
1253 info->integrity.total_bytes >> unit_shift,
1256 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1257 imagex_printf(T("\n"));
1259 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1260 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1261 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1262 info->integrity.total_bytes);
1263 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1264 "of %"PRIu64" %"TS" (%u%%) done"),
1265 info->integrity.completed_bytes >> unit_shift,
1267 info->integrity.total_bytes >> unit_shift,
1270 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1271 imagex_printf(T("\n"));
1273 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1274 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1275 "to %"TS" \"%"TS"\"\n"),
1276 info->extract.image,
1277 info->extract.image_name,
1278 info->extract.wimfile_name,
1279 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1280 T("NTFS volume") : T("directory")),
1281 info->extract.target);
1283 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1284 if (info->extract.end_file_count >= 2000) {
1285 percent_done = TO_PERCENT(info->extract.current_file_count,
1286 info->extract.end_file_count);
1287 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1288 info->extract.current_file_count,
1289 info->extract.end_file_count, percent_done);
1290 if (info->extract.current_file_count == info->extract.end_file_count)
1291 imagex_printf(T("\n"));
1294 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1295 percent_done = TO_PERCENT(info->extract.completed_bytes,
1296 info->extract.total_bytes);
1297 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1298 imagex_printf(T("\rExtracting file data: "
1299 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1300 info->extract.completed_bytes >> unit_shift,
1302 info->extract.total_bytes >> unit_shift,
1305 if (info->extract.completed_bytes >= info->extract.total_bytes)
1306 imagex_printf(T("\n"));
1308 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1309 if (info->extract.end_file_count >= 2000) {
1310 percent_done = TO_PERCENT(info->extract.current_file_count,
1311 info->extract.end_file_count);
1312 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1313 info->extract.current_file_count,
1314 info->extract.end_file_count, percent_done);
1315 if (info->extract.current_file_count == info->extract.end_file_count)
1316 imagex_printf(T("\n"));
1319 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1320 if (info->extract.total_parts != 1) {
1321 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1322 info->extract.part_number,
1323 info->extract.total_parts);
1326 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1327 percent_done = TO_PERCENT(info->split.completed_bytes,
1328 info->split.total_bytes);
1329 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1330 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1331 "%"PRIu64" %"TS" (%u%%) written\n"),
1332 info->split.part_name,
1333 info->split.cur_part_number,
1334 info->split.total_parts,
1335 info->split.completed_bytes >> unit_shift,
1337 info->split.total_bytes >> unit_shift,
1341 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1342 if (info->split.completed_bytes == info->split.total_bytes) {
1343 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1344 info->split.cur_part_number,
1345 info->split.total_parts);
1348 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1349 switch (info->update.command->op) {
1350 case WIMLIB_UPDATE_OP_DELETE:
1351 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1352 info->update.command->delete_.wim_path);
1354 case WIMLIB_UPDATE_OP_RENAME:
1355 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1356 info->update.command->rename.wim_source_path,
1357 info->update.command->rename.wim_target_path);
1359 case WIMLIB_UPDATE_OP_ADD:
1364 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1365 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1366 info->replace.path_in_wim);
1368 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1369 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1370 info->wimboot_exclude.path_in_wim);
1372 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1373 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1374 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1375 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1376 info->unmount.mounted_wim,
1377 info->unmount.mounted_image);
1379 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1380 info->unmount.mounted_wim,
1381 info->unmount.mounted_image);
1382 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1386 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1387 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1388 info->verify_image.current_image,
1389 info->verify_image.total_images);
1391 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1392 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1393 info->verify_streams.total_bytes);
1394 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1395 imagex_printf(T("\rVerifying file data: "
1396 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1397 info->verify_streams.completed_bytes >> unit_shift,
1399 info->verify_streams.total_bytes >> unit_shift,
1402 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1403 imagex_printf(T("\n"));
1408 imagex_flush_output();
1409 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1413 parse_num_threads(const tchar *optarg)
1416 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1417 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1418 imagex_error(T("Number of threads must be a non-negative integer!"));
1426 parse_chunk_size(const tchar *optarg)
1429 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1430 if (chunk_size == 0) {
1431 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1432 " with optional K, M, or G suffix"));
1436 if (*tmp == T('k') || *tmp == T('K')) {
1439 } else if (*tmp == T('m') || *tmp == T('M')) {
1442 } else if (*tmp == T('g') || *tmp == T('G')) {
1446 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1447 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1451 if (chunk_size >= UINT32_MAX) {
1452 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1460 * Parse an option passed to an update command.
1462 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1465 * @option: Text string for the option (beginning with --)
1467 * @cmd: `struct wimlib_update_command' that is being constructed for
1470 * Returns true if the option was recognized; false if not.
1473 update_command_add_option(int op, const tchar *option,
1474 struct wimlib_update_command *cmd)
1476 bool recognized = true;
1478 case WIMLIB_UPDATE_OP_ADD:
1479 if (!tstrcmp(option, T("--verbose")))
1480 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1481 else if (!tstrcmp(option, T("--unix-data")))
1482 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1483 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1484 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1485 else if (!tstrcmp(option, T("--strict-acls")))
1486 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1487 else if (!tstrcmp(option, T("--dereference")))
1488 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1489 else if (!tstrcmp(option, T("--no-replace")))
1490 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1494 case WIMLIB_UPDATE_OP_DELETE:
1495 if (!tstrcmp(option, T("--force")))
1496 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1497 else if (!tstrcmp(option, T("--recursive")))
1498 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1509 /* How many nonoption arguments each `imagex update' command expects */
1510 static const unsigned update_command_num_nonoptions[] = {
1511 [WIMLIB_UPDATE_OP_ADD] = 2,
1512 [WIMLIB_UPDATE_OP_DELETE] = 1,
1513 [WIMLIB_UPDATE_OP_RENAME] = 2,
1517 update_command_add_nonoption(int op, const tchar *nonoption,
1518 struct wimlib_update_command *cmd,
1519 unsigned num_nonoptions)
1522 case WIMLIB_UPDATE_OP_ADD:
1523 if (num_nonoptions == 0)
1524 cmd->add.fs_source_path = (tchar*)nonoption;
1526 cmd->add.wim_target_path = (tchar*)nonoption;
1528 case WIMLIB_UPDATE_OP_DELETE:
1529 cmd->delete_.wim_path = (tchar*)nonoption;
1531 case WIMLIB_UPDATE_OP_RENAME:
1532 if (num_nonoptions == 0)
1533 cmd->rename.wim_source_path = (tchar*)nonoption;
1535 cmd->rename.wim_target_path = (tchar*)nonoption;
1541 * Parse a command passed on stdin to `imagex update'.
1543 * @line: Text of the command.
1544 * @len: Length of the line, including a null terminator
1547 * @command: A `struct wimlib_update_command' to fill in from the parsed
1550 * @line_number: Line number of the command, for diagnostics.
1552 * Returns true on success; returns false on parse error.
1555 parse_update_command(tchar *line, size_t len,
1556 struct wimlib_update_command *command,
1560 tchar *command_name;
1562 size_t num_nonoptions;
1564 /* Get the command name ("add", "delete", "rename") */
1565 ret = parse_string(&line, &len, &command_name);
1566 if (ret != PARSE_STRING_SUCCESS)
1569 if (!tstrcasecmp(command_name, T("add"))) {
1570 op = WIMLIB_UPDATE_OP_ADD;
1571 } else if (!tstrcasecmp(command_name, T("delete"))) {
1572 op = WIMLIB_UPDATE_OP_DELETE;
1573 } else if (!tstrcasecmp(command_name, T("rename"))) {
1574 op = WIMLIB_UPDATE_OP_RENAME;
1576 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1577 command_name, line_number);
1582 /* Parse additional options and non-options as needed */
1587 ret = parse_string(&line, &len, &next_string);
1588 if (ret == PARSE_STRING_NONE) /* End of line */
1590 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1592 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1594 if (!update_command_add_option(op, next_string, command))
1596 imagex_error(T("Unrecognized option \"%"TS"\" to "
1597 "update command \"%"TS"\" on line %zu"),
1598 next_string, command_name, line_number);
1604 if (num_nonoptions == update_command_num_nonoptions[op])
1606 imagex_error(T("Unexpected argument \"%"TS"\" in "
1607 "update command on line %zu\n"
1608 " (The \"%"TS"\" command only "
1609 "takes %zu nonoption arguments!)\n"),
1610 next_string, line_number,
1611 command_name, num_nonoptions);
1614 update_command_add_nonoption(op, next_string,
1615 command, num_nonoptions);
1620 if (num_nonoptions != update_command_num_nonoptions[op]) {
1621 imagex_error(T("Not enough arguments to update command "
1622 "\"%"TS"\" on line %zu"), command_name, line_number);
1628 static struct wimlib_update_command *
1629 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1630 size_t *num_cmds_ret)
1634 struct wimlib_update_command *cmds;
1637 nlines = text_file_count_lines(cmd_file_contents_p,
1642 /* Always allocate at least 1 slot, just in case the implementation of
1643 * calloc() returns NULL if 0 bytes are requested. */
1644 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1646 imagex_error(T("out of memory"));
1649 p = *cmd_file_contents_p;
1651 for (i = 0; i < nlines; i++) {
1652 /* XXX: Could use rawmemchr() here instead, but it may not be
1653 * available on all platforms. */
1654 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1655 size_t len = endp - p + 1;
1657 if (!is_comment_line(p, len)) {
1658 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1669 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1670 * one image from a WIM file to an NTFS volume. */
1672 imagex_apply(int argc, tchar **argv, int cmd)
1676 int image = WIMLIB_NO_IMAGE;
1678 struct wimlib_wim_info info;
1680 const tchar *wimfile;
1681 const tchar *target;
1682 const tchar *image_num_or_name = NULL;
1683 int extract_flags = 0;
1685 STRING_LIST(refglobs);
1687 for_opt(c, apply_options) {
1689 case IMAGEX_CHECK_OPTION:
1690 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1692 case IMAGEX_VERBOSE_OPTION:
1693 /* No longer does anything. */
1695 case IMAGEX_REF_OPTION:
1696 ret = string_list_append(&refglobs, optarg);
1698 goto out_free_refglobs;
1700 case IMAGEX_UNIX_DATA_OPTION:
1701 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1703 case IMAGEX_NO_ACLS_OPTION:
1704 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1706 case IMAGEX_STRICT_ACLS_OPTION:
1707 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1709 case IMAGEX_NO_ATTRIBUTES_OPTION:
1710 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1712 case IMAGEX_NORPFIX_OPTION:
1713 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1715 case IMAGEX_RPFIX_OPTION:
1716 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1718 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1719 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1720 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1722 case IMAGEX_WIMBOOT_OPTION:
1723 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1725 case IMAGEX_COMPACT_OPTION:
1726 ret = set_compact_mode(optarg, &extract_flags);
1728 goto out_free_refglobs;
1736 if (argc != 2 && argc != 3)
1741 if (!tstrcmp(wimfile, T("-"))) {
1742 /* Attempt to apply pipable WIM from standard input. */
1744 image_num_or_name = NULL;
1747 image_num_or_name = argv[1];
1752 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1753 imagex_progress_func, NULL);
1755 goto out_free_refglobs;
1757 wimlib_get_wim_info(wim, &info);
1760 /* Image explicitly specified. */
1761 image_num_or_name = argv[1];
1762 image = wimlib_resolve_image(wim, image_num_or_name);
1763 ret = verify_image_exists(image, image_num_or_name, wimfile);
1765 goto out_wimlib_free;
1768 /* No image specified; default to image 1, but only if the WIM
1769 * contains exactly one image. */
1771 if (info.image_count != 1) {
1772 imagex_error(T("\"%"TS"\" contains %d images; "
1773 "Please select one (or all)."),
1774 wimfile, info.image_count);
1783 if (refglobs.num_strings) {
1785 imagex_error(T("Can't specify --ref when applying from stdin!"));
1787 goto out_wimlib_free;
1789 ret = wim_reference_globs(wim, &refglobs, open_flags);
1791 goto out_wimlib_free;
1796 /* Interpret a regular file or block device target as an NTFS
1800 if (tstat(target, &stbuf)) {
1801 if (errno != ENOENT) {
1802 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1805 goto out_wimlib_free;
1808 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1809 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1815 ret = wimlib_extract_image(wim, image, target, extract_flags);
1817 set_fd_to_binary_mode(STDIN_FILENO);
1818 ret = wimlib_extract_image_from_pipe_with_progress(
1823 imagex_progress_func,
1827 imagex_printf(T("Done applying WIM image.\n"));
1828 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1830 do_resource_not_found_warning(wimfile, &info, &refglobs);
1832 imagex_error(T( "If you are applying an image "
1833 "from a split pipable WIM,\n"
1834 " make sure you have "
1835 "concatenated together all parts."));
1837 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1838 do_metadata_not_found_warning(wimfile, &info);
1843 string_list_destroy(&refglobs);
1847 usage(CMD_APPLY, stderr);
1849 goto out_free_refglobs;
1852 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1853 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1854 * the desired image. 'wimlib-imagex append': add a new image to an existing
1857 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1861 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1862 WIMLIB_ADD_FLAG_WINCONFIG |
1863 WIMLIB_ADD_FLAG_VERBOSE |
1864 WIMLIB_ADD_FLAG_FILE_PATHS_UNNEEDED;
1865 int write_flags = 0;
1866 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1867 uint32_t chunk_size = UINT32_MAX;
1868 uint32_t solid_chunk_size = UINT32_MAX;
1869 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1870 const tchar *wimfile;
1873 STRING_LIST(image_properties);
1876 STRING_LIST(base_wimfiles);
1877 WIMStruct **base_wims;
1879 WIMStruct *template_wim = NULL;
1880 const tchar *template_wimfile = NULL;
1881 const tchar *template_image_name_or_num = NULL;
1882 int template_image = WIMLIB_NO_IMAGE;
1885 unsigned num_threads = 0;
1890 tchar *config_file = NULL;
1892 bool source_list = false;
1893 size_t source_list_nchars = 0;
1894 tchar *source_list_contents;
1895 bool capture_sources_malloced;
1896 struct wimlib_capture_source *capture_sources;
1898 bool name_defaulted;
1900 for_opt(c, capture_or_append_options) {
1902 case IMAGEX_BOOT_OPTION:
1903 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1905 case IMAGEX_CHECK_OPTION:
1906 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1908 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
1909 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1911 case IMAGEX_NOCHECK_OPTION:
1912 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1914 case IMAGEX_CONFIG_OPTION:
1915 config_file = optarg;
1916 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1918 case IMAGEX_COMPRESS_OPTION:
1919 compression_type = get_compression_type(optarg, false);
1920 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1923 case IMAGEX_CHUNK_SIZE_OPTION:
1924 chunk_size = parse_chunk_size(optarg);
1925 if (chunk_size == UINT32_MAX)
1928 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1929 solid_chunk_size = parse_chunk_size(optarg);
1930 if (solid_chunk_size == UINT32_MAX)
1933 case IMAGEX_SOLID_COMPRESS_OPTION:
1934 solid_ctype = get_compression_type(optarg, true);
1935 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1938 case IMAGEX_SOLID_OPTION:
1939 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1941 case IMAGEX_NO_SOLID_SORT_OPTION:
1942 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1944 case IMAGEX_FLAGS_OPTION: {
1945 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1946 tsprintf(p, T("FLAGS=%"TS), optarg);
1947 ret = string_list_append(&image_properties, p);
1952 case IMAGEX_IMAGE_PROPERTY_OPTION:
1953 ret = append_image_property_argument(&image_properties);
1957 case IMAGEX_DEREFERENCE_OPTION:
1958 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1960 case IMAGEX_VERBOSE_OPTION:
1961 /* No longer does anything. */
1963 case IMAGEX_THREADS_OPTION:
1964 num_threads = parse_num_threads(optarg);
1965 if (num_threads == UINT_MAX)
1968 case IMAGEX_REBUILD_OPTION:
1969 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1971 case IMAGEX_UNIX_DATA_OPTION:
1972 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1974 case IMAGEX_SOURCE_LIST_OPTION:
1977 case IMAGEX_NO_ACLS_OPTION:
1978 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1980 case IMAGEX_STRICT_ACLS_OPTION:
1981 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1983 case IMAGEX_RPFIX_OPTION:
1984 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1986 case IMAGEX_NORPFIX_OPTION:
1987 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1989 case IMAGEX_PIPABLE_OPTION:
1990 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1992 case IMAGEX_NOT_PIPABLE_OPTION:
1993 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1995 case IMAGEX_UPDATE_OF_OPTION:
1996 if (template_image_name_or_num) {
1997 imagex_error(T("'--update-of' can only be "
1998 "specified one time!"));
2002 colon = tstrrchr(optarg, T(':'));
2005 template_wimfile = optarg;
2007 template_image_name_or_num = colon + 1;
2009 template_wimfile = NULL;
2010 template_image_name_or_num = optarg;
2014 case IMAGEX_DELTA_FROM_OPTION:
2015 ret = string_list_append(&base_wimfiles, optarg);
2018 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2020 case IMAGEX_WIMBOOT_OPTION:
2021 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2023 case IMAGEX_UNSAFE_COMPACT_OPTION:
2024 if (cmd != CMD_APPEND) {
2025 imagex_error(T("'--unsafe-compact' is only "
2026 "valid for append!"));
2029 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2031 case IMAGEX_SNAPSHOT_OPTION:
2032 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2041 if (argc < 2 || argc > 4)
2047 /* Set default compression type and parameters. */
2050 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2051 /* No compression type specified. Use the default. */
2053 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2054 /* With --wimboot, default to XPRESS compression. */
2055 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2056 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2057 /* With --solid, default to LZMS compression. (However,
2058 * this will not affect solid resources!) */
2059 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2061 /* Otherwise, default to LZX compression. */
2062 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2066 if (!tstrcmp(wimfile, T("-"))) {
2067 /* Writing captured WIM to standard output. */
2069 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2070 imagex_error("Can't write a non-pipable WIM to "
2071 "standard output! Specify --pipable\n"
2072 " if you want to create a pipable WIM "
2073 "(but read the docs first).");
2077 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2079 if (cmd == CMD_APPEND) {
2080 imagex_error(T("Using standard output for append does "
2081 "not make sense."));
2084 wim_fd = STDOUT_FILENO;
2086 imagex_output_to_stderr();
2087 set_fd_to_binary_mode(wim_fd);
2090 /* If template image was specified using --update-of=IMAGE rather
2091 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2092 if (template_image_name_or_num && !template_wimfile) {
2093 if (base_wimfiles.num_strings == 1) {
2094 /* Capturing delta WIM based on single WIM: default to
2096 template_wimfile = base_wimfiles.strings[0];
2097 } else if (cmd == CMD_APPEND) {
2098 /* Appending to WIM: default to WIM being appended to.
2100 template_wimfile = wimfile;
2102 /* Capturing a normal (non-delta) WIM, so the WIM file
2103 * *must* be explicitly specified. */
2104 if (base_wimfiles.num_strings > 1) {
2105 imagex_error(T("For capture of delta WIM "
2106 "based on multiple existing "
2108 " '--update-of' must "
2109 "specify WIMFILE:IMAGE!"));
2111 imagex_error(T("For capture of non-delta WIM, "
2112 "'--update-of' must specify "
2121 name_defaulted = false;
2123 /* Set default name to SOURCE argument, omitting any directory
2124 * prefixes and trailing slashes. This requires making a copy
2125 * of @source. Leave some free characters at the end in case we
2126 * append a number to keep the name unique. */
2127 size_t source_name_len;
2129 source_name_len = tstrlen(source);
2130 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2131 name = tbasename(tstrcpy(source_copy, source));
2132 name_defaulted = true;
2135 /* Image description (if given). */
2137 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2138 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2139 ret = string_list_append(&image_properties, p);
2145 /* Set up capture sources in source list mode */
2146 if (source[0] == T('-') && source[1] == T('\0')) {
2147 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2149 source_list_contents = file_get_text_contents(source,
2150 &source_list_nchars);
2152 if (!source_list_contents)
2155 capture_sources = parse_source_list(&source_list_contents,
2158 if (!capture_sources) {
2160 goto out_free_source_list_contents;
2162 capture_sources_malloced = true;
2164 /* Set up capture source in non-source-list mode. */
2165 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2166 capture_sources[0].fs_source_path = source;
2167 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2168 capture_sources[0].reserved = 0;
2170 capture_sources_malloced = false;
2171 source_list_contents = NULL;
2174 /* Open the existing WIM, or create a new one. */
2175 if (cmd == CMD_APPEND) {
2176 ret = wimlib_open_wim_with_progress(wimfile,
2177 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2179 imagex_progress_func,
2182 goto out_free_capture_sources;
2184 ret = wimlib_create_new_wim(compression_type, &wim);
2186 goto out_free_capture_sources;
2187 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2190 /* Set chunk size if non-default. */
2191 if (chunk_size != UINT32_MAX) {
2192 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2195 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2197 int ctype = compression_type;
2199 if (cmd == CMD_APPEND) {
2200 struct wimlib_wim_info info;
2201 wimlib_get_wim_info(wim, &info);
2202 ctype = info.compression_type;
2205 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2206 ret = wimlib_set_output_chunk_size(wim, 4096);
2211 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2212 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2216 if (solid_chunk_size != UINT32_MAX) {
2217 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2223 /* Detect if source is regular file or block device and set NTFS volume
2228 if (tstat(source, &stbuf) == 0) {
2229 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2230 imagex_printf(T("Capturing WIM image from NTFS "
2231 "filesystem on \"%"TS"\"\n"), source);
2232 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2235 if (errno != ENOENT) {
2236 imagex_error_with_errno(T("Failed to stat "
2237 "\"%"TS"\""), source);
2245 /* If the user did not specify an image name, and the basename of the
2246 * source already exists as an image name in the WIM file, append a
2247 * suffix to make it unique. */
2248 if (cmd == CMD_APPEND && name_defaulted) {
2249 unsigned long conflict_idx;
2250 tchar *name_end = tstrchr(name, T('\0'));
2251 for (conflict_idx = 1;
2252 wimlib_image_name_in_use(wim, name);
2255 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2259 /* If capturing a delta WIM, reference resources from the base WIMs
2260 * before adding the new image. */
2261 if (base_wimfiles.num_strings) {
2262 base_wims = calloc(base_wimfiles.num_strings,
2263 sizeof(base_wims[0]));
2264 if (base_wims == NULL) {
2265 imagex_error(T("Out of memory!"));
2270 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2271 ret = wimlib_open_wim_with_progress(
2272 base_wimfiles.strings[i], open_flags,
2273 &base_wims[i], imagex_progress_func, NULL);
2275 goto out_free_base_wims;
2279 ret = wimlib_reference_resources(wim, base_wims,
2280 base_wimfiles.num_strings, 0);
2282 goto out_free_base_wims;
2284 if (base_wimfiles.num_strings == 1) {
2285 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2286 base_wimfiles.strings[0]);
2288 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2289 base_wimfiles.num_strings);
2296 /* If capturing or appending as an update of an existing (template) image,
2297 * open the WIM if needed and parse the image index. */
2298 if (template_image_name_or_num) {
2300 if (cmd == CMD_APPEND && !tstrcmp(template_wimfile, wimfile)) {
2303 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2304 if (!tstrcmp(template_wimfile,
2305 base_wimfiles.strings[i])) {
2306 template_wim = base_wims[i];
2312 if (!template_wim) {
2313 ret = wimlib_open_wim_with_progress(template_wimfile,
2316 imagex_progress_func,
2319 goto out_free_base_wims;
2322 template_image = wimlib_resolve_image(template_wim,
2323 template_image_name_or_num);
2325 if (template_image_name_or_num[0] == T('-')) {
2328 struct wimlib_wim_info info;
2330 wimlib_get_wim_info(template_wim, &info);
2331 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2332 if (n >= 1 && n <= info.image_count &&
2334 tmp != template_image_name_or_num + 1)
2336 template_image = info.image_count - (n - 1);
2339 ret = verify_image_exists_and_is_single(template_image,
2340 template_image_name_or_num,
2343 goto out_free_template_wim;
2346 ret = wimlib_add_image_multisource(wim,
2353 goto out_free_template_wim;
2355 if (image_properties.num_strings || template_image_name_or_num) {
2356 /* User asked to set additional image properties, or an image on
2357 * which the added one is to be based has been specified with
2359 struct wimlib_wim_info info;
2361 wimlib_get_wim_info(wim, &info);
2363 ret = apply_image_properties(&image_properties, wim,
2364 info.image_count, NULL);
2366 goto out_free_template_wim;
2368 /* Reference template image if the user provided one. */
2369 if (template_image_name_or_num) {
2370 imagex_printf(T("Using image %d "
2371 "from \"%"TS"\" as template\n"),
2372 template_image, template_wimfile);
2373 ret = wimlib_reference_template_image(wim,
2379 goto out_free_template_wim;
2383 /* Write the new WIM or overwrite the existing WIM with the new image
2385 if (cmd == CMD_APPEND) {
2386 ret = wimlib_overwrite(wim, write_flags, num_threads);
2387 } else if (wimfile) {
2388 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2389 write_flags, num_threads);
2391 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2392 write_flags, num_threads);
2394 out_free_template_wim:
2395 /* 'template_wim' may alias 'wim' or any of the 'base_wims' */
2396 if (template_wim == wim)
2397 goto out_free_base_wims;
2398 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2399 if (template_wim == base_wims[i])
2400 goto out_free_base_wims;
2401 wimlib_free(template_wim);
2403 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2404 wimlib_free(base_wims[i]);
2408 out_free_capture_sources:
2409 if (capture_sources_malloced)
2410 free(capture_sources);
2411 out_free_source_list_contents:
2412 free(source_list_contents);
2414 string_list_destroy(&image_properties);
2415 string_list_destroy(&base_wimfiles);
2425 /* Remove image(s) from a WIM. */
2427 imagex_delete(int argc, tchar **argv, int cmd)
2430 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2431 int write_flags = 0;
2432 const tchar *wimfile;
2433 const tchar *image_num_or_name;
2438 for_opt(c, delete_options) {
2440 case IMAGEX_CHECK_OPTION:
2441 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2443 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2444 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2446 case IMAGEX_SOFT_OPTION:
2447 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2449 case IMAGEX_UNSAFE_COMPACT_OPTION:
2450 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2461 imagex_error(T("Must specify a WIM file"));
2463 imagex_error(T("Must specify an image"));
2467 image_num_or_name = argv[1];
2469 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2470 imagex_progress_func, NULL);
2474 image = wimlib_resolve_image(wim, image_num_or_name);
2476 ret = verify_image_exists(image, image_num_or_name, wimfile);
2478 goto out_wimlib_free;
2480 ret = wimlib_delete_image(wim, image);
2482 imagex_error(T("Failed to delete image from \"%"TS"\""),
2484 goto out_wimlib_free;
2487 ret = wimlib_overwrite(wim, write_flags, 0);
2489 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2490 "deleted"), wimfile);
2498 usage(CMD_DELETE, stderr);
2503 struct print_dentry_options {
2508 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2510 tprintf(T("%"TS"\n"), dentry->full_path);
2513 static const struct {
2516 } file_attr_flags[] = {
2517 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2518 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2519 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2520 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2521 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2522 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2523 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2524 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2525 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2526 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2527 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2528 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2529 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2530 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2531 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2534 #define TIMESTR_MAX 100
2537 print_time(const tchar *type, const struct wimlib_timespec *wts,
2540 tchar timestr[TIMESTR_MAX];
2544 if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
2545 t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
2550 tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2551 timestr[TIMESTR_MAX - 1] = '\0';
2553 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2556 static void print_byte_field(const uint8_t field[], size_t len)
2559 tprintf(T("%02hhx"), *field++);
2563 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2565 tchar attr_string[256];
2568 tputs(T("WIM Information:"));
2569 tputs(T("----------------"));
2570 tprintf(T("Path: %"TS"\n"), wimfile);
2571 tprintf(T("GUID: 0x"));
2572 print_byte_field(info->guid, sizeof(info->guid));
2574 tprintf(T("Version: %u\n"), info->wim_version);
2575 tprintf(T("Image Count: %d\n"), info->image_count);
2576 tprintf(T("Compression: %"TS"\n"),
2577 wimlib_get_compression_type_string(info->compression_type));
2578 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2580 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2581 tprintf(T("Boot Index: %d\n"), info->boot_index);
2582 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2584 attr_string[0] = T('\0');
2587 tstrcat(attr_string, T("Pipable, "));
2589 if (info->has_integrity_table)
2590 tstrcat(attr_string, T("Integrity info, "));
2592 if (info->has_rpfix)
2593 tstrcat(attr_string, T("Relative path junction, "));
2595 if (info->resource_only)
2596 tstrcat(attr_string, T("Resource only, "));
2598 if (info->metadata_only)
2599 tstrcat(attr_string, T("Metadata only, "));
2601 if (info->is_marked_readonly)
2602 tstrcat(attr_string, T("Readonly, "));
2604 p = tstrchr(attr_string, T('\0'));
2605 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2608 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2612 print_resource(const struct wimlib_resource_entry *resource,
2615 tprintf(T("Hash = 0x"));
2616 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2619 if (!resource->is_missing) {
2620 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2621 resource->uncompressed_size);
2622 if (resource->packed) {
2623 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2624 "bytes @ offset %"PRIu64"\n"),
2625 resource->raw_resource_uncompressed_size,
2626 resource->raw_resource_compressed_size,
2627 resource->raw_resource_offset_in_wim);
2629 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2632 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2633 resource->compressed_size);
2635 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2639 tprintf(T("Part Number = %u\n"), resource->part_number);
2640 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2642 tprintf(T("Flags = "));
2643 if (resource->is_compressed)
2644 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2645 if (resource->is_metadata)
2646 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2647 if (resource->is_free)
2648 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2649 if (resource->is_spanned)
2650 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2651 if (resource->packed)
2652 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2660 print_blobs(WIMStruct *wim)
2662 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2667 default_print_security_descriptor(const uint8_t *sd, size_t size)
2669 tprintf(T("Security Descriptor = "));
2670 print_byte_field(sd, size);
2676 is_null_guid(const uint8_t *guid)
2678 static const uint8_t null_guid[WIMLIB_GUID_LEN];
2680 return !memcmp(guid, null_guid, WIMLIB_GUID_LEN);
2684 print_guid(const tchar *label, const uint8_t *guid)
2686 if (is_null_guid(guid))
2688 tprintf(T("%-20"TS"= 0x"), label);
2689 print_byte_field(guid, WIMLIB_GUID_LEN);
2694 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2697 "----------------------------------------------------------------------------\n"));
2698 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2699 if (dentry->dos_name)
2700 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2701 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2702 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2703 if (file_attr_flags[i].flag & dentry->attributes)
2704 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2705 file_attr_flags[i].name);
2707 if (dentry->security_descriptor) {
2708 print_security_descriptor(dentry->security_descriptor,
2709 dentry->security_descriptor_size);
2712 print_time(T("Creation Time"),
2713 &dentry->creation_time, dentry->creation_time_high);
2714 print_time(T("Last Write Time"),
2715 &dentry->last_write_time, dentry->last_write_time_high);
2716 print_time(T("Last Access Time"),
2717 &dentry->last_access_time, dentry->last_access_time_high);
2720 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2721 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2723 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2724 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2726 if (dentry->unix_mode != 0) {
2727 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2728 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2729 dentry->unix_uid, dentry->unix_gid,
2730 dentry->unix_mode, dentry->unix_rdev);
2733 if (!is_null_guid(dentry->object_id.object_id)) {
2734 print_guid(T("Object ID"), dentry->object_id.object_id);
2735 print_guid(T("Birth Volume ID"), dentry->object_id.birth_volume_id);
2736 print_guid(T("Birth Object ID"), dentry->object_id.birth_object_id);
2737 print_guid(T("Domain ID"), dentry->object_id.domain_id);
2740 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2741 if (dentry->streams[i].stream_name) {
2742 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2743 dentry->streams[i].stream_name);
2744 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2745 tprintf(T("\tRaw encrypted data stream:\n"));
2746 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2747 tprintf(T("\tReparse point stream:\n"));
2749 tprintf(T("\tUnnamed data stream:\n"));
2751 print_resource(&dentry->streams[i].resource, NULL);
2756 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2758 const struct print_dentry_options *options = _options;
2759 if (!options->detailed)
2760 print_dentry_full_path(dentry);
2762 print_dentry_detailed(dentry);
2766 /* Print the files contained in an image(s) in a WIM file. */
2768 imagex_dir(int argc, tchar **argv, int cmd)
2770 const tchar *wimfile;
2771 WIMStruct *wim = NULL;
2774 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2776 struct print_dentry_options options = {
2779 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2781 STRING_LIST(refglobs);
2783 for_opt(c, dir_options) {
2785 case IMAGEX_PATH_OPTION:
2788 case IMAGEX_DETAILED_OPTION:
2789 options.detailed = true;
2791 case IMAGEX_ONE_FILE_ONLY_OPTION:
2792 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2794 case IMAGEX_REF_OPTION:
2795 ret = string_list_append(&refglobs, optarg);
2797 goto out_free_refglobs;
2807 imagex_error(T("Must specify a WIM file"));
2811 imagex_error(T("Too many arguments"));
2816 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2817 imagex_progress_func, NULL);
2819 goto out_free_refglobs;
2822 image = wimlib_resolve_image(wim, argv[1]);
2823 ret = verify_image_exists(image, argv[1], wimfile);
2825 goto out_wimlib_free;
2827 /* No image specified; default to image 1, but only if the WIM
2828 * contains exactly one image. */
2830 struct wimlib_wim_info info;
2832 wimlib_get_wim_info(wim, &info);
2833 if (info.image_count != 1) {
2834 imagex_error(T("\"%"TS"\" contains %d images; Please "
2835 "select one (or all)."),
2836 wimfile, info.image_count);
2843 if (refglobs.num_strings) {
2844 ret = wim_reference_globs(wim, &refglobs, 0);
2846 goto out_wimlib_free;
2849 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2850 print_dentry, &options);
2851 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2852 struct wimlib_wim_info info;
2854 wimlib_get_wim_info(wim, &info);
2855 do_metadata_not_found_warning(wimfile, &info);
2860 string_list_destroy(&refglobs);
2864 usage(CMD_DIR, stderr);
2866 goto out_free_refglobs;
2869 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2872 imagex_export(int argc, tchar **argv, int cmd)
2876 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2877 int write_flags = 0;
2878 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2879 const tchar *src_wimfile;
2880 const tchar *src_image_num_or_name;
2881 const tchar *dest_wimfile;
2883 const tchar *dest_name;
2884 const tchar *dest_desc;
2886 struct wimlib_wim_info src_info;
2887 WIMStruct *dest_wim;
2892 STRING_LIST(refglobs);
2893 unsigned num_threads = 0;
2894 uint32_t chunk_size = UINT32_MAX;
2895 uint32_t solid_chunk_size = UINT32_MAX;
2896 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2898 for_opt(c, export_options) {
2900 case IMAGEX_BOOT_OPTION:
2901 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2903 case IMAGEX_CHECK_OPTION:
2904 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2906 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
2907 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2909 case IMAGEX_NOCHECK_OPTION:
2910 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2912 case IMAGEX_COMPRESS_OPTION:
2913 compression_type = get_compression_type(optarg, false);
2914 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2917 case IMAGEX_RECOMPRESS_OPTION:
2918 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2920 case IMAGEX_SOLID_OPTION:
2921 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2923 case IMAGEX_NO_SOLID_SORT_OPTION:
2924 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2926 case IMAGEX_CHUNK_SIZE_OPTION:
2927 chunk_size = parse_chunk_size(optarg);
2928 if (chunk_size == UINT32_MAX)
2931 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2932 solid_chunk_size = parse_chunk_size(optarg);
2933 if (solid_chunk_size == UINT32_MAX)
2936 case IMAGEX_SOLID_COMPRESS_OPTION:
2937 solid_ctype = get_compression_type(optarg, true);
2938 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2941 case IMAGEX_REF_OPTION:
2942 ret = string_list_append(&refglobs, optarg);
2944 goto out_free_refglobs;
2946 case IMAGEX_THREADS_OPTION:
2947 num_threads = parse_num_threads(optarg);
2948 if (num_threads == UINT_MAX)
2951 case IMAGEX_REBUILD_OPTION:
2952 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2954 case IMAGEX_PIPABLE_OPTION:
2955 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2957 case IMAGEX_NOT_PIPABLE_OPTION:
2958 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2960 case IMAGEX_WIMBOOT_OPTION:
2961 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2963 case IMAGEX_UNSAFE_COMPACT_OPTION:
2964 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2972 if (argc < 3 || argc > 5)
2974 src_wimfile = argv[0];
2975 src_image_num_or_name = argv[1];
2976 dest_wimfile = argv[2];
2977 dest_name = (argc >= 4) ? argv[3] : NULL;
2978 dest_desc = (argc >= 5) ? argv[4] : NULL;
2979 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2980 imagex_progress_func, NULL);
2982 goto out_free_refglobs;
2984 wimlib_get_wim_info(src_wim, &src_info);
2986 /* Determine if the destination is an existing file or not. If so, we
2987 * try to append the exported image(s) to it; otherwise, we create a new
2988 * WIM containing the exported image(s). Furthermore, determine if we
2989 * need to write a pipable WIM directly to standard output. */
2991 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2993 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2994 imagex_error("Can't write a non-pipable WIM to "
2995 "standard output! Specify --pipable\n"
2996 " if you want to create a pipable WIM "
2997 "(but read the docs first).");
2999 goto out_free_src_wim;
3002 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3004 dest_wimfile = NULL;
3005 dest_wim_fd = STDOUT_FILENO;
3006 imagex_output_to_stderr();
3007 set_fd_to_binary_mode(dest_wim_fd);
3010 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
3012 /* Destination file exists. */
3014 if (!S_ISREG(stbuf.st_mode) && !S_ISBLK(stbuf.st_mode)) {
3015 imagex_error(T("\"%"TS"\" is not a regular file "
3016 "or block device"), dest_wimfile);
3018 goto out_free_src_wim;
3020 ret = wimlib_open_wim_with_progress(dest_wimfile,
3022 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
3024 imagex_progress_func,
3027 goto out_free_src_wim;
3029 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3030 /* The user specified a compression type, but we're
3031 * exporting to an existing WIM. Make sure the
3032 * specified compression type is the same as the
3033 * compression type of the existing destination WIM. */
3034 struct wimlib_wim_info dest_info;
3036 wimlib_get_wim_info(dest_wim, &dest_info);
3037 if (compression_type != dest_info.compression_type) {
3038 imagex_error(T("Cannot specify a compression type that is "
3039 "not the same as that used in the "
3040 "destination WIM"));
3042 goto out_free_dest_wim;
3048 if (errno != ENOENT) {
3049 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3052 goto out_free_src_wim;
3055 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3056 imagex_error(T("'--unsafe-compact' is only valid when "
3057 "exporting to an existing WIM file!"));
3059 goto out_free_src_wim;
3062 /* dest_wimfile is not an existing file, so create a new WIM. */
3064 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3065 /* The user did not specify a compression type; default
3066 * to that of the source WIM, unless --solid or
3067 * --wimboot was specified. */
3069 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3070 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3071 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3072 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3074 compression_type = src_info.compression_type;
3076 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3078 goto out_free_src_wim;
3080 wimlib_register_progress_function(dest_wim,
3081 imagex_progress_func, NULL);
3083 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3084 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3086 /* For --wimboot export, use small XPRESS chunks. */
3087 wimlib_set_output_chunk_size(dest_wim, 4096);
3088 } else if (compression_type == src_info.compression_type &&
3089 chunk_size == UINT32_MAX)
3091 /* Use same chunk size if compression type is the same. */
3092 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3096 if (chunk_size != UINT32_MAX) {
3097 /* Set destination chunk size. */
3098 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3100 goto out_free_dest_wim;
3102 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3103 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3105 goto out_free_dest_wim;
3107 if (solid_chunk_size != UINT32_MAX) {
3108 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3110 goto out_free_dest_wim;
3113 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3114 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3116 goto out_free_dest_wim;
3118 if (refglobs.num_strings) {
3119 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3121 goto out_free_dest_wim;
3124 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3125 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3127 imagex_error(T("--boot specified for all-images export, but source WIM "
3128 "has no bootable image."));
3130 goto out_free_dest_wim;
3133 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3134 dest_desc, export_flags);
3136 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3137 do_resource_not_found_warning(src_wimfile,
3138 &src_info, &refglobs);
3139 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3140 do_metadata_not_found_warning(src_wimfile, &src_info);
3142 goto out_free_dest_wim;
3146 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3147 else if (dest_wimfile)
3148 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3149 write_flags, num_threads);
3151 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3152 WIMLIB_ALL_IMAGES, write_flags,
3155 wimlib_free(dest_wim);
3157 wimlib_free(src_wim);
3159 string_list_destroy(&refglobs);
3163 usage(CMD_EXPORT, stderr);
3166 goto out_free_refglobs;
3169 /* Extract files or directories from a WIM image */
3171 imagex_extract(int argc, tchar **argv, int cmd)
3178 const tchar *wimfile;
3179 const tchar *image_num_or_name;
3180 tchar *dest_dir = T(".");
3181 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3182 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3183 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3184 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3186 STRING_LIST(refglobs);
3188 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3190 for_opt(c, extract_options) {
3192 case IMAGEX_CHECK_OPTION:
3193 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3195 case IMAGEX_VERBOSE_OPTION:
3196 /* No longer does anything. */
3198 case IMAGEX_REF_OPTION:
3199 ret = string_list_append(&refglobs, optarg);
3201 goto out_free_refglobs;
3203 case IMAGEX_UNIX_DATA_OPTION:
3204 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3206 case IMAGEX_NO_ACLS_OPTION:
3207 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3209 case IMAGEX_STRICT_ACLS_OPTION:
3210 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3212 case IMAGEX_NO_ATTRIBUTES_OPTION:
3213 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3215 case IMAGEX_DEST_DIR_OPTION:
3218 case IMAGEX_TO_STDOUT_OPTION:
3219 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3220 imagex_suppress_output();
3221 set_fd_to_binary_mode(STDOUT_FILENO);
3223 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3224 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3225 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3227 case IMAGEX_NO_GLOBS_OPTION:
3228 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3230 case IMAGEX_NULLGLOB_OPTION:
3231 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3233 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3234 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3236 case IMAGEX_WIMBOOT_OPTION:
3237 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3239 case IMAGEX_COMPACT_OPTION:
3240 ret = set_compact_mode(optarg, &extract_flags);
3242 goto out_free_refglobs;
3254 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3255 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3257 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3262 image_num_or_name = argv[1];
3267 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3268 imagex_progress_func, NULL);
3270 goto out_free_refglobs;
3272 image = wimlib_resolve_image(wim, image_num_or_name);
3273 ret = verify_image_exists_and_is_single(image,
3277 goto out_wimlib_free;
3279 if (refglobs.num_strings) {
3280 ret = wim_reference_globs(wim, &refglobs, open_flags);
3282 goto out_wimlib_free;
3288 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3291 while (argc != 0 && ret == 0) {
3295 num_paths < argc && argv[num_paths][0] != T('@');
3300 ret = wimlib_extract_paths(wim, image, dest_dir,
3301 (const tchar **)argv,
3303 extract_flags | notlist_extract_flags);
3307 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3316 imagex_printf(T("Done extracting files.\n"));
3317 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3318 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3319 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3320 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3321 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3324 T("Note: You can use the '--nullglob' "
3325 "option to ignore missing files.\n"));
3327 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3328 "files and directories\n"
3329 " are in the WIM image.\n"),
3330 get_cmd_string(CMD_DIR, false));
3331 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3332 struct wimlib_wim_info info;
3334 wimlib_get_wim_info(wim, &info);
3335 do_resource_not_found_warning(wimfile, &info, &refglobs);
3336 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3337 struct wimlib_wim_info info;
3339 wimlib_get_wim_info(wim, &info);
3340 do_metadata_not_found_warning(wimfile, &info);
3345 string_list_destroy(&refglobs);
3349 usage(CMD_EXTRACT, stderr);
3352 goto out_free_refglobs;
3355 /* Prints information about a WIM file; also can mark an image as bootable,
3356 * change the name of an image, or change the description of an image. */
3358 imagex_info(int argc, tchar **argv, int cmd)
3362 bool header = false;
3365 bool short_header = true;
3366 const tchar *xml_out_file = NULL;
3367 const tchar *wimfile;
3368 const tchar *image_num_or_name;
3369 STRING_LIST(image_properties);
3374 int write_flags = 0;
3375 struct wimlib_wim_info info;
3377 for_opt(c, info_options) {
3379 case IMAGEX_BOOT_OPTION:
3382 case IMAGEX_CHECK_OPTION:
3383 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3385 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3386 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3388 case IMAGEX_NOCHECK_OPTION:
3389 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3391 case IMAGEX_HEADER_OPTION:
3393 short_header = false;
3395 case IMAGEX_BLOBS_OPTION:
3397 short_header = false;
3399 case IMAGEX_XML_OPTION:
3401 short_header = false;
3403 case IMAGEX_EXTRACT_XML_OPTION:
3404 xml_out_file = optarg;
3405 short_header = false;
3407 case IMAGEX_IMAGE_PROPERTY_OPTION:
3408 ret = append_image_property_argument(&image_properties);
3419 if (argc < 1 || argc > 4)
3423 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3427 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3428 tsprintf(p, T("NAME=%"TS), argv[2]);
3429 ret = string_list_append(&image_properties, p);
3436 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3437 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3438 ret = string_list_append(&image_properties, p);
3443 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3444 imagex_progress_func, NULL);
3448 wimlib_get_wim_info(wim, &info);
3450 image = wimlib_resolve_image(wim, image_num_or_name);
3451 ret = WIMLIB_ERR_INVALID_IMAGE;
3452 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3453 verify_image_exists(image, image_num_or_name, wimfile);
3455 imagex_error(T("If you would like to set the boot "
3456 "index to 0, specify image \"0\" with "
3457 "the --boot flag."));
3459 goto out_wimlib_free;
3462 if (boot && info.image_count == 0) {
3463 imagex_error(T("--boot is meaningless on a WIM with no images"));
3464 goto out_wimlib_free;
3467 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3469 imagex_error(T("Cannot specify the --boot flag "
3470 "without specifying a specific "
3471 "image in a multi-image WIM"));
3472 goto out_wimlib_free;
3474 if (image_properties.num_strings) {
3475 imagex_error(T("Can't change image properties without "
3476 "specifying a specific image in a "
3477 "multi-image WIM"));
3478 goto out_wimlib_free;
3482 /* Operations that print information are separated from operations that
3483 * recreate the WIM file. */
3484 if (!image_properties.num_strings && !boot) {
3486 /* Read-only operations */
3488 if (image == WIMLIB_NO_IMAGE) {
3489 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3490 image_num_or_name, wimfile);
3491 goto out_wimlib_free;
3494 if (image == WIMLIB_ALL_IMAGES && short_header)
3495 print_wim_information(wimfile, &info);
3498 wimlib_print_header(wim);
3501 if (info.total_parts != 1) {
3502 tfprintf(stderr, T("Warning: Only showing the blobs "
3503 "for part %d of a %d-part WIM.\n"),
3504 info.part_number, info.total_parts);
3510 ret = wimlib_extract_xml_data(wim, stdout);
3512 goto out_wimlib_free;
3518 fp = tfopen(xml_out_file, T("wb"));
3520 imagex_error_with_errno(T("Failed to open the "
3521 "file \"%"TS"\" for "
3525 goto out_wimlib_free;
3527 ret = wimlib_extract_xml_data(wim, fp);
3529 imagex_error(T("Failed to close the file "
3535 goto out_wimlib_free;
3539 wimlib_print_available_images(wim, image);
3543 /* Modification operations */
3544 bool any_property_changes;
3546 if (image == WIMLIB_ALL_IMAGES)
3549 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3550 imagex_error(T("Cannot change image properties "
3551 "when using image 0"));
3553 goto out_wimlib_free;
3557 if (image == info.boot_index) {
3558 imagex_printf(T("Image %d is already marked as "
3559 "bootable.\n"), image);
3562 imagex_printf(T("Marking image %d as bootable.\n"),
3564 info.boot_index = image;
3565 ret = wimlib_set_wim_info(wim, &info,
3566 WIMLIB_CHANGE_BOOT_INDEX);
3568 goto out_wimlib_free;
3572 ret = apply_image_properties(&image_properties, wim, image,
3573 &any_property_changes);
3575 goto out_wimlib_free;
3577 /* Only call wimlib_overwrite() if something actually needs to
3579 if (boot || any_property_changes ||
3580 ((write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) &&
3581 !info.has_integrity_table) ||
3582 ((write_flags & WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY) &&
3583 info.has_integrity_table))
3585 ret = wimlib_overwrite(wim, write_flags, 1);
3587 imagex_printf(T("The file \"%"TS"\" was not modified "
3588 "because nothing needed to be done.\n"),
3596 string_list_destroy(&image_properties);
3600 usage(CMD_INFO, stderr);
3605 /* Join split WIMs into one part WIM */
3607 imagex_join(int argc, tchar **argv, int cmd)
3610 int swm_open_flags = 0;
3611 int wim_write_flags = 0;
3612 const tchar *output_path;
3615 for_opt(c, join_options) {
3617 case IMAGEX_CHECK_OPTION:
3618 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3620 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3621 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3631 imagex_error(T("Must specify one or more split WIM (.swm) "
3635 output_path = argv[0];
3636 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3641 imagex_progress_func,
3647 usage(CMD_JOIN, stderr);
3652 #if WIM_MOUNTING_SUPPORTED
3654 /* Mounts a WIM image. */
3656 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3659 int mount_flags = 0;
3661 const tchar *staging_dir = NULL;
3662 const tchar *wimfile;
3665 struct wimlib_wim_info info;
3669 STRING_LIST(refglobs);
3671 if (cmd == CMD_MOUNTRW) {
3672 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3673 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3676 for_opt(c, mount_options) {
3678 case IMAGEX_ALLOW_OTHER_OPTION:
3679 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3681 case IMAGEX_CHECK_OPTION:
3682 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3684 case IMAGEX_DEBUG_OPTION:
3685 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3687 case IMAGEX_STREAMS_INTERFACE_OPTION:
3688 if (!tstrcasecmp(optarg, T("none")))
3689 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3690 else if (!tstrcasecmp(optarg, T("xattr")))
3691 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3692 else if (!tstrcasecmp(optarg, T("windows")))
3693 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3695 imagex_error(T("Unknown stream interface \"%"TS"\""),
3700 case IMAGEX_REF_OPTION:
3701 ret = string_list_append(&refglobs, optarg);
3703 goto out_free_refglobs;
3705 case IMAGEX_STAGING_DIR_OPTION:
3706 staging_dir = optarg;
3708 case IMAGEX_UNIX_DATA_OPTION:
3709 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3717 if (argc != 2 && argc != 3)
3722 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3723 imagex_progress_func, NULL);
3725 goto out_free_refglobs;
3727 wimlib_get_wim_info(wim, &info);
3730 /* Image explicitly specified. */
3731 image = wimlib_resolve_image(wim, argv[1]);
3733 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3737 /* No image specified; default to image 1, but only if the WIM
3738 * contains exactly one image. */
3740 if (info.image_count != 1) {
3741 imagex_error(T("\"%"TS"\" contains %d images; Please "
3742 "select one."), wimfile, info.image_count);
3750 if (refglobs.num_strings) {
3751 ret = wim_reference_globs(wim, &refglobs, open_flags);
3756 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3758 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3759 do_metadata_not_found_warning(wimfile, &info);
3761 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3763 image, wimfile, dir);
3769 string_list_destroy(&refglobs);
3775 goto out_free_refglobs;
3777 #endif /* WIM_MOUNTING_SUPPORTED */
3779 /* Rebuild a WIM file */
3781 imagex_optimize(int argc, tchar **argv, int cmd)
3784 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3785 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3786 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3787 uint32_t chunk_size = UINT32_MAX;
3788 uint32_t solid_chunk_size = UINT32_MAX;
3789 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3792 const tchar *wimfile;
3795 unsigned num_threads = 0;
3797 for_opt(c, optimize_options) {
3799 case IMAGEX_CHECK_OPTION:
3800 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3802 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3803 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3805 case IMAGEX_NOCHECK_OPTION:
3806 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3808 case IMAGEX_COMPRESS_OPTION:
3809 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3810 compression_type = get_compression_type(optarg, false);
3811 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3814 case IMAGEX_RECOMPRESS_OPTION:
3815 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3817 case IMAGEX_CHUNK_SIZE_OPTION:
3818 chunk_size = parse_chunk_size(optarg);
3819 if (chunk_size == UINT32_MAX)
3822 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3823 solid_chunk_size = parse_chunk_size(optarg);
3824 if (solid_chunk_size == UINT32_MAX)
3827 case IMAGEX_SOLID_COMPRESS_OPTION:
3828 solid_ctype = get_compression_type(optarg, true);
3829 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3832 case IMAGEX_SOLID_OPTION:
3833 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3834 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3836 case IMAGEX_NO_SOLID_SORT_OPTION:
3837 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3839 case IMAGEX_THREADS_OPTION:
3840 num_threads = parse_num_threads(optarg);
3841 if (num_threads == UINT_MAX)
3844 case IMAGEX_PIPABLE_OPTION:
3845 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3847 case IMAGEX_NOT_PIPABLE_OPTION:
3848 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3850 case IMAGEX_UNSAFE_COMPACT_OPTION:
3851 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3865 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3866 imagex_progress_func, NULL);
3870 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3871 /* Change compression type. */
3872 ret = wimlib_set_output_compression_type(wim, compression_type);
3874 goto out_wimlib_free;
3877 if (chunk_size != UINT32_MAX) {
3878 /* Change chunk size. */
3879 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3881 goto out_wimlib_free;
3883 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3884 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3886 goto out_wimlib_free;
3888 if (solid_chunk_size != UINT32_MAX) {
3889 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3891 goto out_wimlib_free;
3894 old_size = file_get_size(wimfile);
3895 tprintf(T("\"%"TS"\" original size: "), wimfile);
3897 tputs(T("Unknown"));
3899 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3901 ret = wimlib_overwrite(wim, write_flags, num_threads);
3903 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3904 goto out_wimlib_free;
3907 new_size = file_get_size(wimfile);
3908 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3910 tputs(T("Unknown"));
3912 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3914 tfputs(T("Space saved: "), stdout);
3915 if (new_size != -1 && old_size != -1) {
3916 tprintf(T("%lld KiB\n"),
3917 ((long long)old_size - (long long)new_size) >> 10);
3919 tputs(T("Unknown"));
3928 usage(CMD_OPTIMIZE, stderr);
3934 /* Split a WIM into a spanned set */
3936 imagex_split(int argc, tchar **argv, int cmd)
3940 int write_flags = 0;
3941 unsigned long part_size;
3946 for_opt(c, split_options) {
3948 case IMAGEX_CHECK_OPTION:
3949 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3951 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
3952 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3964 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3965 if (tmp == argv[2] || *tmp) {
3966 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3967 imagex_error(T("The part size must be an integer or "
3968 "floating-point number of megabytes."));
3971 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3972 imagex_progress_func, NULL);
3976 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3982 usage(CMD_SPLIT, stderr);
3988 #if WIM_MOUNTING_SUPPORTED
3989 /* Unmounts a mounted WIM image. */
3991 imagex_unmount(int argc, tchar **argv, int cmd)
3994 int unmount_flags = 0;
3997 for_opt(c, unmount_options) {
3999 case IMAGEX_COMMIT_OPTION:
4000 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
4002 case IMAGEX_CHECK_OPTION:
4003 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
4005 case IMAGEX_REBUILD_OPTION:
4006 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
4008 case IMAGEX_LAZY_OPTION:
4009 case IMAGEX_FORCE_OPTION:
4010 /* Now, unmount is lazy by default. However, committing
4011 * the image will fail with
4012 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
4013 * file descriptors on the WIM image. The
4014 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
4015 * descriptors to be closed. */
4016 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
4018 case IMAGEX_NEW_IMAGE_OPTION:
4019 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4030 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4031 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4032 imagex_error(T("--new-image is meaningless "
4033 "without --commit also specified!"));
4038 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4039 imagex_progress_func, NULL);
4041 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4042 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4044 "\tNote: Use --commit --force to force changes "
4045 "to be committed, regardless\n"
4046 "\t of open files.\n"));
4053 usage(CMD_UNMOUNT, stderr);
4058 #endif /* WIM_MOUNTING_SUPPORTED */
4061 * Add, delete, or rename files in a WIM image.
4064 imagex_update(int argc, tchar **argv, int cmd)
4066 const tchar *wimfile;
4070 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4071 int write_flags = 0;
4072 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4073 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4074 WIMLIB_ADD_FLAG_VERBOSE |
4075 WIMLIB_ADD_FLAG_WINCONFIG;
4076 int default_delete_flags = 0;
4077 unsigned num_threads = 0;
4079 tchar *cmd_file_contents;
4080 size_t cmd_file_nchars;
4081 struct wimlib_update_command *cmds;
4083 tchar *command_str = NULL;
4084 tchar *config_file = NULL;
4085 tchar *wimboot_config = NULL;
4087 for_opt(c, update_options) {
4089 /* Generic or write options */
4090 case IMAGEX_THREADS_OPTION:
4091 num_threads = parse_num_threads(optarg);
4092 if (num_threads == UINT_MAX)
4095 case IMAGEX_CHECK_OPTION:
4096 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4098 case IMAGEX_INCLUDE_INTEGRITY_OPTION:
4099 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4101 case IMAGEX_REBUILD_OPTION:
4102 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4104 case IMAGEX_COMMAND_OPTION:
4106 imagex_error(T("--command may only be specified "
4107 "one time. Please provide\n"
4108 " the update commands "
4109 "on standard input instead."));
4112 command_str = tstrdup(optarg);
4114 imagex_error(T("Out of memory!"));
4118 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4119 wimboot_config = optarg;
4121 /* Default delete options */
4122 case IMAGEX_FORCE_OPTION:
4123 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4125 case IMAGEX_RECURSIVE_OPTION:
4126 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4129 /* Global add option */
4130 case IMAGEX_CONFIG_OPTION:
4131 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4132 config_file = optarg;
4135 /* Default add options */
4136 case IMAGEX_VERBOSE_OPTION:
4137 /* No longer does anything. */
4139 case IMAGEX_DEREFERENCE_OPTION:
4140 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4142 case IMAGEX_UNIX_DATA_OPTION:
4143 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4145 case IMAGEX_NO_ACLS_OPTION:
4146 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4148 case IMAGEX_STRICT_ACLS_OPTION:
4149 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4151 case IMAGEX_NO_REPLACE_OPTION:
4152 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4154 case IMAGEX_UNSAFE_COMPACT_OPTION:
4155 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4164 if (argc != 1 && argc != 2)
4168 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4169 imagex_progress_func, NULL);
4171 goto out_free_command_str;
4174 /* Image explicitly specified. */
4175 image = wimlib_resolve_image(wim, argv[1]);
4176 ret = verify_image_exists_and_is_single(image, argv[1],
4179 goto out_wimlib_free;
4181 /* No image specified; default to image 1, but only if the WIM
4182 * contains exactly one image. */
4183 struct wimlib_wim_info info;
4185 wimlib_get_wim_info(wim, &info);
4186 if (info.image_count != 1) {
4187 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4188 wimfile, info.image_count);
4195 /* Read update commands from standard input, or the command string if
4198 cmd_file_contents = NULL;
4199 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4203 goto out_free_cmd_file_contents;
4205 } else if (!wimboot_config) {
4206 if (isatty(STDIN_FILENO)) {
4207 tputs(T("Reading update commands from standard input..."));
4208 recommend_man_page(CMD_UPDATE, stdout);
4210 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4211 if (!cmd_file_contents) {
4213 goto out_wimlib_free;
4216 /* Parse the update commands */
4217 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4221 goto out_free_cmd_file_contents;
4224 cmd_file_contents = NULL;
4229 /* Set default flags and capture config on the update commands */
4230 for (size_t i = 0; i < num_cmds; i++) {
4231 switch (cmds[i].op) {
4232 case WIMLIB_UPDATE_OP_ADD:
4233 cmds[i].add.add_flags |= default_add_flags;
4234 cmds[i].add.config_file = config_file;
4236 case WIMLIB_UPDATE_OP_DELETE:
4237 cmds[i].delete_.delete_flags |= default_delete_flags;
4244 /* Execute the update commands */
4245 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4249 if (wimboot_config) {
4250 /* --wimboot-config=FILE is short for an
4251 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4253 struct wimlib_update_command cmd;
4255 cmd.op = WIMLIB_UPDATE_OP_ADD;
4256 cmd.add.fs_source_path = wimboot_config;
4257 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4258 cmd.add.config_file = NULL;
4259 cmd.add.add_flags = 0;
4261 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4266 /* Overwrite the updated WIM */
4267 ret = wimlib_overwrite(wim, write_flags, num_threads);
4270 out_free_cmd_file_contents:
4271 free(cmd_file_contents);
4274 out_free_command_str:
4279 usage(CMD_UPDATE, stderr);
4282 goto out_free_command_str;
4285 /* Verify a WIM file. */
4287 imagex_verify(int argc, tchar **argv, int cmd)
4290 const tchar *wimfile;
4292 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4293 int verify_flags = 0;
4294 STRING_LIST(refglobs);
4297 for_opt(c, verify_options) {
4299 case IMAGEX_REF_OPTION:
4300 ret = string_list_append(&refglobs, optarg);
4302 goto out_free_refglobs;
4304 case IMAGEX_NOCHECK_OPTION:
4305 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4317 imagex_error(T("Must specify a WIM file!"));
4319 imagex_error(T("At most one WIM file can be specified!"));
4325 ret = wimlib_open_wim_with_progress(wimfile,
4328 imagex_progress_func,
4331 goto out_free_refglobs;
4333 ret = wim_reference_globs(wim, &refglobs, open_flags);
4335 goto out_wimlib_free;
4337 ret = wimlib_verify_wim(wim, verify_flags);
4339 tputc(T('\n'), stderr);
4340 imagex_error(T("\"%"TS"\" failed verification!"),
4342 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4343 refglobs.num_strings == 0)
4345 imagex_printf(T("Note: if this WIM file is not standalone, "
4346 "use the --ref option to specify the other parts.\n"));
4349 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4356 string_list_destroy(&refglobs);
4360 usage(CMD_VERIFY, stderr);
4362 goto out_free_refglobs;
4365 struct imagex_command {
4367 int (*func)(int argc, tchar **argv, int cmd);
4370 static const struct imagex_command imagex_commands[] = {
4371 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4372 [CMD_APPLY] = {T("apply"), imagex_apply},
4373 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4374 [CMD_DELETE] = {T("delete"), imagex_delete},
4375 [CMD_DIR ] = {T("dir"), imagex_dir},
4376 [CMD_EXPORT] = {T("export"), imagex_export},
4377 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4378 [CMD_INFO] = {T("info"), imagex_info},
4379 [CMD_JOIN] = {T("join"), imagex_join},
4380 #if WIM_MOUNTING_SUPPORTED
4381 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4382 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4384 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4385 [CMD_SPLIT] = {T("split"), imagex_split},
4386 #if WIM_MOUNTING_SUPPORTED
4387 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4389 [CMD_UPDATE] = {T("update"), imagex_update},
4390 [CMD_VERIFY] = {T("verify"), imagex_verify},
4395 /* Can be a directory or source list file. But source list file is probably
4396 * a rare use case, so just say directory. */
4397 # define SOURCE_STR T("DIRECTORY")
4399 /* Can only be a directory */
4400 # define TARGET_STR T("DIRECTORY")
4403 /* Can be a directory, NTFS volume, or source list file. */
4404 # define SOURCE_STR T("SOURCE")
4406 /* Can be a directory or NTFS volume. */
4407 # define TARGET_STR T("TARGET")
4411 static const tchar * const usage_strings[] = {
4414 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4415 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4416 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4417 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4418 " [--delta-from=WIMFILE] [--wimboot] [--unix-data]\n"
4419 " [--dereference] [--snapshot]\n"
4423 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4424 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4425 " [--no-attributes] [--rpfix] [--norpfix]\n"
4426 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4427 " [--compact=FORMAT]\n"
4431 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4432 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4433 " [--config=FILE] [--threads=NUM_THREADS]\n"
4434 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4435 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4436 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4441 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4445 " %"TS" WIMFILE [IMAGE] [--path=PATH] [--detailed]\n"
4449 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4450 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4451 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4452 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4453 " [--wimboot] [--solid]\n"
4457 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4458 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4459 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4460 " [--no-attributes] [--include-invalid-names]\n"
4461 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4465 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4466 " [--boot] [--check] [--nocheck] [--xml]\n"
4467 " [--extract-xml FILE] [--header] [--blobs]\n"
4468 " [--image-property NAME=VALUE]\n"
4472 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4474 #if WIM_MOUNTING_SUPPORTED
4477 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4478 " [--check] [--streams-interface=INTERFACE]\n"
4479 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4483 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4484 " [--check] [--streams-interface=INTERFACE]\n"
4485 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4491 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4492 " [--check] [--nocheck] [--solid]\n"
4497 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4499 #if WIM_MOUNTING_SUPPORTED
4502 " %"TS" DIRECTORY\n"
4503 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4508 " %"TS" WIMFILE [IMAGE]\n"
4509 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4510 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4511 " [--command=STRING] [--wimboot-config=FILE]\n"
4516 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4520 static const tchar *invocation_name;
4521 static int invocation_cmd = CMD_NONE;
4523 static const tchar *get_cmd_string(int cmd, bool only_short_form)
4525 static tchar buf[50];
4527 if (cmd == CMD_NONE)
4528 return T("wimlib-imagex");
4530 if (only_short_form || invocation_cmd != CMD_NONE) {
4531 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4533 tsprintf(buf, T("%"TS" %"TS), invocation_name,
4534 imagex_commands[cmd].name);
4542 static const tchar * const fmt =
4544 "wimlib-imagex " PACKAGE_VERSION " (using wimlib %"TS")\n"
4545 "Copyright (C) 2012-2018 Eric Biggers\n"
4546 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4547 "This is free software: you are free to change and redistribute it.\n"
4548 "There is NO WARRANTY, to the extent permitted by law.\n"
4550 "Report bugs to "PACKAGE_BUGREPORT".\n"
4552 tfprintf(stdout, fmt, wimlib_get_version_string());
4556 do_common_options(int *argc_p, tchar **argv, int cmd)
4562 for (i = 1; i < argc; i++) {
4564 if (p[0] == T('-') && p[1] == T('-')) {
4566 if (!tstrcmp(p, T("help"))) {
4567 if (cmd == CMD_NONE)
4572 } else if (!tstrcmp(p, T("version"))) {
4575 } else if (!tstrcmp(p, T("quiet"))) {
4576 imagex_suppress_output();
4577 memmove(&argv[i], &argv[i + 1],
4578 (argc - i) * sizeof(argv[i]));
4581 } else if (!*p) /* reached "--", no more options */
4590 print_usage_string(int cmd, FILE *fp)
4592 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4596 recommend_man_page(int cmd, FILE *fp)
4598 const tchar *format_str;
4600 format_str = T("Some uncommon options are not listed;\n"
4601 "See %"TS".pdf in the doc directory for more details.\n");
4603 format_str = T("Some uncommon options are not listed; see `man %"TS"' for more details.\n");
4605 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4609 usage(int cmd, FILE *fp)
4611 tfprintf(fp, T("Usage:\n"));
4612 print_usage_string(cmd, fp);
4613 tfprintf(fp, T("\n"));
4614 recommend_man_page(cmd, fp);
4620 tfprintf(fp, T("Usage:\n"));
4621 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4622 print_usage_string(cmd, fp);
4623 tfprintf(fp, T("\n"));
4625 static const tchar * const extra =
4628 " %"TS" --version\n"
4631 tfprintf(fp, extra, invocation_name, invocation_name);
4633 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4634 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4635 "For some commands IMAGE may be \"all\".\n"
4637 recommend_man_page(CMD_NONE, fp);
4641 extern int wmain(int argc, wchar_t **argv);
4645 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4646 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4647 * something else), while on Windows the command arguments will be UTF-16LE
4648 * encoded 'wchar_t' strings. */
4650 main(int argc, tchar **argv)
4656 imagex_info_file = stdout;
4657 invocation_name = tbasename(argv[0]);
4660 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4661 if (igcase != NULL) {
4662 if (!tstrcmp(igcase, T("no")) ||
4663 !tstrcmp(igcase, T("0")))
4664 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4665 else if (!tstrcmp(igcase, T("yes")) ||
4666 !tstrcmp(igcase, T("1")))
4667 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4670 "WARNING: Ignoring unknown setting of "
4671 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4676 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4678 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4679 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4680 for (int i = 0; i < CMD_MAX; i++) {
4681 if (!tstrcmp(invocation_name + 3,
4682 imagex_commands[i].name))
4691 /* Unless already known from the invocation name, determine which
4692 * command was specified. */
4693 if (cmd == CMD_NONE) {
4695 imagex_error(T("No command specified!\n"));
4699 for (int i = 0; i < CMD_MAX; i++) {
4700 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4705 if (cmd != CMD_NONE) {
4711 /* Handle common options. May exit early (for --help or --version). */
4712 do_common_options(&argc, argv, cmd);
4714 /* Bail if a valid command was not specified. */
4715 if (cmd == CMD_NONE) {
4716 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4721 /* Enable warning and error messages in wimlib to be more user-friendly.
4723 wimlib_set_print_errors(true);
4725 /* Initialize wimlib. */
4726 ret = wimlib_global_init(init_flags);
4728 goto out_check_status;
4730 /* Call the command handler function. */
4731 ret = imagex_commands[cmd].func(argc, argv, cmd);
4733 /* Check for error writing to standard output, especially since for some
4734 * commands, writing to standard output is part of the program's actual
4735 * behavior and not just for informational purposes. */
4736 if (ferror(stdout) || fclose(stdout)) {
4737 imagex_error_with_errno(T("error writing to standard output"));
4742 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4743 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4744 * error code from which an error message can be printed. */
4746 imagex_error(T("Exiting with error code %d:\n"
4748 wimlib_get_error_string(ret));
4749 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4750 imagex_error_with_errno(T("errno"));
4752 /* Make wimlib free any resources it's holding (although this is not
4753 * strictly necessary because the process is ending anyway). */
4754 wimlib_global_cleanup();