4 * Use wimlib to create, modify, extract, mount, unmount, or display information
9 * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # include "config.h" /* Need for PACKAGE_VERSION, etc. */
30 #include "wimlib_tchar.h"
49 #define WIMLIB_COMPRESSION_TYPE_INVALID (-1)
52 # include "imagex-win32.h"
53 # define print_security_descriptor win32_print_security_descriptor
56 # include <langinfo.h>
57 # define print_security_descriptor default_print_security_descriptor
58 static inline void set_fd_to_binary_mode(int fd)
63 /* Don't confuse the user by presenting the mounting commands on Windows when
64 * they will never work. However on UNIX-like systems we always present them,
65 * even if WITH_FUSE is not defined at this point, as to not tie the build of
66 * wimlib-imagex to a specific build of wimlib. */
68 # define WIM_MOUNTING_SUPPORTED 0
70 # define WIM_MOUNTING_SUPPORTED 1
73 #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
76 is_any_path_separator(tchar c)
78 return c == T('/') || c == T('\\');
81 /* Like basename(), but handles both forward and backwards slashes. */
83 tbasename(tchar *path)
85 tchar *p = tstrchr(path, T('\0'));
90 if (!is_any_path_separator(*--p))
98 if (is_any_path_separator(*--p))
103 #define for_opt(c, opts) while ((c = getopt_long_only(argc, (tchar**)argv, T(""), \
117 #if WIM_MOUNTING_SUPPORTED
123 #if WIM_MOUNTING_SUPPORTED
131 static void usage(int cmd, FILE *fp);
132 static void usage_all(FILE *fp);
133 static void recommend_man_page(int cmd, FILE *fp);
134 static const tchar *get_cmd_string(int cmd, bool nospace);
136 static bool imagex_be_quiet = false;
137 static FILE *imagex_info_file;
139 #define imagex_printf(format, ...) \
140 tfprintf(imagex_info_file, format, ##__VA_ARGS__)
143 IMAGEX_ALLOW_OTHER_OPTION,
147 IMAGEX_CHUNK_SIZE_OPTION,
148 IMAGEX_COMMAND_OPTION,
149 IMAGEX_COMMIT_OPTION,
150 IMAGEX_COMPACT_OPTION,
151 IMAGEX_COMPRESS_OPTION,
152 IMAGEX_COMPRESS_SLOW_OPTION,
153 IMAGEX_CONFIG_OPTION,
155 IMAGEX_DELTA_FROM_OPTION,
156 IMAGEX_DEREFERENCE_OPTION,
157 IMAGEX_DEST_DIR_OPTION,
158 IMAGEX_DETAILED_OPTION,
159 IMAGEX_EXTRACT_XML_OPTION,
162 IMAGEX_HEADER_OPTION,
163 IMAGEX_IMAGE_PROPERTY_OPTION,
164 IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
166 IMAGEX_METADATA_OPTION,
167 IMAGEX_NEW_IMAGE_OPTION,
168 IMAGEX_NOCHECK_OPTION,
169 IMAGEX_NORPFIX_OPTION,
170 IMAGEX_NOT_PIPABLE_OPTION,
171 IMAGEX_NO_ACLS_OPTION,
172 IMAGEX_NO_ATTRIBUTES_OPTION,
173 IMAGEX_NO_GLOBS_OPTION,
174 IMAGEX_NO_REPLACE_OPTION,
175 IMAGEX_NO_SOLID_SORT_OPTION,
176 IMAGEX_NULLGLOB_OPTION,
177 IMAGEX_ONE_FILE_ONLY_OPTION,
179 IMAGEX_PIPABLE_OPTION,
180 IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION,
181 IMAGEX_REBUILD_OPTION,
182 IMAGEX_RECOMPRESS_OPTION,
183 IMAGEX_RECURSIVE_OPTION,
185 IMAGEX_RESUME_OPTION,
187 IMAGEX_SNAPSHOT_OPTION,
189 IMAGEX_SOLID_CHUNK_SIZE_OPTION,
190 IMAGEX_SOLID_COMPRESS_OPTION,
192 IMAGEX_SOURCE_LIST_OPTION,
193 IMAGEX_STAGING_DIR_OPTION,
194 IMAGEX_STREAMS_INTERFACE_OPTION,
195 IMAGEX_STRICT_ACLS_OPTION,
196 IMAGEX_THREADS_OPTION,
197 IMAGEX_TO_STDOUT_OPTION,
198 IMAGEX_UNIX_DATA_OPTION,
199 IMAGEX_UNSAFE_COMPACT_OPTION,
200 IMAGEX_UPDATE_OF_OPTION,
201 IMAGEX_VERBOSE_OPTION,
202 IMAGEX_WIMBOOT_CONFIG_OPTION,
203 IMAGEX_WIMBOOT_OPTION,
207 static const struct option apply_options[] = {
208 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
209 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
210 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
211 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
212 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
213 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
214 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
215 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
216 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
217 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
218 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
220 /* --resume is undocumented for now as it needs improvement. */
221 {T("resume"), no_argument, NULL, IMAGEX_RESUME_OPTION},
222 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
223 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
227 static const struct option capture_or_append_options[] = {
228 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
229 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
230 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
231 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
232 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
233 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
234 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
235 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
236 {T("pack-streams"), no_argument, NULL, IMAGEX_SOLID_OPTION},
237 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
238 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
239 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
240 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
241 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
242 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
243 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
244 {T("flags"), required_argument, NULL, IMAGEX_FLAGS_OPTION},
245 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
246 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
247 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
248 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
249 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
250 {T("source-list"), no_argument, NULL, IMAGEX_SOURCE_LIST_OPTION},
251 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
252 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
253 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
254 {T("rpfix"), no_argument, NULL, IMAGEX_RPFIX_OPTION},
255 {T("norpfix"), no_argument, NULL, IMAGEX_NORPFIX_OPTION},
256 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
257 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
258 {T("update-of"), required_argument, NULL, IMAGEX_UPDATE_OF_OPTION},
259 {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION},
260 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
261 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
262 {T("snapshot"), no_argument, NULL, IMAGEX_SNAPSHOT_OPTION},
266 static const struct option delete_options[] = {
267 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
268 {T("soft"), no_argument, NULL, IMAGEX_SOFT_OPTION},
269 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
273 static const struct option dir_options[] = {
274 {T("path"), required_argument, NULL, IMAGEX_PATH_OPTION},
275 {T("detailed"), no_argument, NULL, IMAGEX_DETAILED_OPTION},
276 {T("one-file-only"), no_argument, NULL, IMAGEX_ONE_FILE_ONLY_OPTION},
277 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
281 static const struct option export_options[] = {
282 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
283 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
284 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
285 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
286 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
287 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
288 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
289 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
290 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
291 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
292 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
293 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
294 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
295 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
296 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
297 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
298 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
299 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
300 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
301 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
302 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
303 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
307 static const struct option extract_options[] = {
308 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
309 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
310 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
311 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
312 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
313 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
314 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
315 {T("no-attributes"), no_argument, NULL, IMAGEX_NO_ATTRIBUTES_OPTION},
316 {T("dest-dir"), required_argument, NULL, IMAGEX_DEST_DIR_OPTION},
317 {T("to-stdout"), no_argument, NULL, IMAGEX_TO_STDOUT_OPTION},
318 {T("include-invalid-names"), no_argument, NULL, IMAGEX_INCLUDE_INVALID_NAMES_OPTION},
319 {T("no-wildcards"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
320 {T("no-globs"), no_argument, NULL, IMAGEX_NO_GLOBS_OPTION},
321 {T("nullglob"), no_argument, NULL, IMAGEX_NULLGLOB_OPTION},
322 {T("preserve-dir-structure"), no_argument, NULL, IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION},
323 {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION},
324 {T("compact"), required_argument, NULL, IMAGEX_COMPACT_OPTION},
328 static const struct option info_options[] = {
329 {T("boot"), no_argument, NULL, IMAGEX_BOOT_OPTION},
330 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
331 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
332 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
333 {T("extract-xml"), required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
334 {T("header"), no_argument, NULL, IMAGEX_HEADER_OPTION},
335 {T("lookup-table"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
336 {T("blobs"), no_argument, NULL, IMAGEX_BLOBS_OPTION},
337 {T("metadata"), no_argument, NULL, IMAGEX_METADATA_OPTION},
338 {T("xml"), no_argument, NULL, IMAGEX_XML_OPTION},
339 {T("image-property"), required_argument, NULL, IMAGEX_IMAGE_PROPERTY_OPTION},
343 static const struct option join_options[] = {
344 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
348 static const struct option mount_options[] = {
349 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
350 {T("debug"), no_argument, NULL, IMAGEX_DEBUG_OPTION},
351 {T("streams-interface"), required_argument, NULL, IMAGEX_STREAMS_INTERFACE_OPTION},
352 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
353 {T("staging-dir"), required_argument, NULL, IMAGEX_STAGING_DIR_OPTION},
354 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
355 {T("allow-other"), no_argument, NULL, IMAGEX_ALLOW_OTHER_OPTION},
359 static const struct option optimize_options[] = {
360 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
361 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
362 {T("no-check"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
363 {T("compress"), required_argument, NULL, IMAGEX_COMPRESS_OPTION},
364 {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
365 {T("compress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
366 {T("recompress-slow"), no_argument, NULL, IMAGEX_COMPRESS_SLOW_OPTION},
367 {T("chunk-size"), required_argument, NULL, IMAGEX_CHUNK_SIZE_OPTION},
368 {T("solid"), no_argument, NULL, IMAGEX_SOLID_OPTION},
369 {T("pack-streams"),no_argument, NULL, IMAGEX_SOLID_OPTION},
370 {T("solid-compress"),required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
371 {T("pack-compress"), required_argument, NULL, IMAGEX_SOLID_COMPRESS_OPTION},
372 {T("solid-chunk-size"),required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
373 {T("pack-chunk-size"), required_argument, NULL, IMAGEX_SOLID_CHUNK_SIZE_OPTION},
374 {T("no-solid-sort"), no_argument, NULL, IMAGEX_NO_SOLID_SORT_OPTION},
375 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
376 {T("pipable"), no_argument, NULL, IMAGEX_PIPABLE_OPTION},
377 {T("not-pipable"), no_argument, NULL, IMAGEX_NOT_PIPABLE_OPTION},
378 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
382 static const struct option split_options[] = {
383 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
387 static const struct option unmount_options[] = {
388 {T("commit"), no_argument, NULL, IMAGEX_COMMIT_OPTION},
389 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
390 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
391 {T("lazy"), no_argument, NULL, IMAGEX_LAZY_OPTION},
392 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
393 {T("new-image"), no_argument, NULL, IMAGEX_NEW_IMAGE_OPTION},
397 static const struct option update_options[] = {
398 /* Careful: some of the options here set the defaults for update
399 * commands, but the flags given to an actual update command (and not to
400 * `imagex update' itself are also handled in
401 * update_command_add_option(). */
402 {T("threads"), required_argument, NULL, IMAGEX_THREADS_OPTION},
403 {T("check"), no_argument, NULL, IMAGEX_CHECK_OPTION},
404 {T("rebuild"), no_argument, NULL, IMAGEX_REBUILD_OPTION},
405 {T("command"), required_argument, NULL, IMAGEX_COMMAND_OPTION},
406 {T("wimboot-config"), required_argument, NULL, IMAGEX_WIMBOOT_CONFIG_OPTION},
408 /* Default delete options */
409 {T("force"), no_argument, NULL, IMAGEX_FORCE_OPTION},
410 {T("recursive"), no_argument, NULL, IMAGEX_RECURSIVE_OPTION},
412 /* Global add option */
413 {T("config"), required_argument, NULL, IMAGEX_CONFIG_OPTION},
415 /* Default add options */
416 {T("verbose"), no_argument, NULL, IMAGEX_VERBOSE_OPTION},
417 {T("dereference"), no_argument, NULL, IMAGEX_DEREFERENCE_OPTION},
418 {T("unix-data"), no_argument, NULL, IMAGEX_UNIX_DATA_OPTION},
419 {T("noacls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
420 {T("no-acls"), no_argument, NULL, IMAGEX_NO_ACLS_OPTION},
421 {T("strict-acls"), no_argument, NULL, IMAGEX_STRICT_ACLS_OPTION},
422 {T("no-replace"), no_argument, NULL, IMAGEX_NO_REPLACE_OPTION},
423 {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION},
428 static const struct option verify_options[] = {
429 {T("ref"), required_argument, NULL, IMAGEX_REF_OPTION},
430 {T("nocheck"), no_argument, NULL, IMAGEX_NOCHECK_OPTION},
436 # define _format_attribute(type, format_str, args_start) \
437 __attribute__((format(type, format_str, args_start)))
439 # define _format_attribute(type, format_str, args_start)
442 /* Print formatted error message to stderr. */
443 static void _format_attribute(printf, 1, 2)
444 imagex_error(const tchar *format, ...)
447 va_start(va, format);
448 tfputs(T("ERROR: "), stderr);
449 tvfprintf(stderr, format, va);
450 tputc(T('\n'), stderr);
454 /* Print formatted error message to stderr. */
455 static void _format_attribute(printf, 1, 2)
456 imagex_error_with_errno(const tchar *format, ...)
458 int errno_save = errno;
460 va_start(va, format);
461 tfputs(T("ERROR: "), stderr);
462 tvfprintf(stderr, format, va);
463 tfprintf(stderr, T(": %"TS"\n"), tstrerror(errno_save));
468 verify_image_exists(int image, const tchar *image_name, const tchar *wim_name)
470 if (image == WIMLIB_NO_IMAGE) {
471 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\"!\n"
472 " Please specify a 1-based image index or "
473 "image name. To list the images\n"
474 " contained in the WIM archive, run\n"
476 " %"TS" \"%"TS"\"\n"),
477 image_name, wim_name,
478 get_cmd_string(CMD_INFO, false), wim_name);
479 return WIMLIB_ERR_INVALID_IMAGE;
485 verify_image_is_single(int image)
487 if (image == WIMLIB_ALL_IMAGES) {
488 imagex_error(T("Cannot specify all images for this action!"));
489 return WIMLIB_ERR_INVALID_IMAGE;
495 verify_image_exists_and_is_single(int image, const tchar *image_name,
496 const tchar *wim_name)
499 ret = verify_image_exists(image, image_name, wim_name);
501 ret = verify_image_is_single(image);
506 print_available_compression_types(FILE *fp)
508 static const tchar *s =
510 "Available compression types:\n"
513 " xpress (alias: \"fast\")\n"
514 " lzx (alias: \"maximum\") (default for capture)\n"
515 " lzms (alias: \"recovery\")\n"
521 /* Parse the argument to --compress */
523 get_compression_type(tchar *optarg)
526 unsigned int compression_level = 0;
529 plevel = tstrchr(optarg, T(':'));
535 ultmp = tstrtoul(plevel, &ptmp, 10);
536 if (ultmp >= UINT_MAX || ultmp == 0 || *ptmp || ptmp == plevel) {
537 imagex_error(T("Compression level must be a positive integer! "
538 "e.g. --compress=lzx:80"));
539 return WIMLIB_COMPRESSION_TYPE_INVALID;
541 compression_level = ultmp;
544 if (!tstrcasecmp(optarg, T("maximum")) ||
545 !tstrcasecmp(optarg, T("lzx")) ||
546 !tstrcasecmp(optarg, T("max")))
547 ctype = WIMLIB_COMPRESSION_TYPE_LZX;
548 else if (!tstrcasecmp(optarg, T("fast")) || !tstrcasecmp(optarg, T("xpress")))
549 ctype = WIMLIB_COMPRESSION_TYPE_XPRESS;
550 else if (!tstrcasecmp(optarg, T("recovery")) || !tstrcasecmp(optarg, T("lzms")))
551 ctype = WIMLIB_COMPRESSION_TYPE_LZMS;
552 else if (!tstrcasecmp(optarg, T("none")))
553 ctype = WIMLIB_COMPRESSION_TYPE_NONE;
555 imagex_error(T("Invalid compression type \"%"TS"\"!"), optarg);
556 print_available_compression_types(stderr);
557 return WIMLIB_COMPRESSION_TYPE_INVALID;
560 if (compression_level != 0)
561 wimlib_set_default_compression_level(ctype, compression_level);
565 /* Parse the argument to --compact */
567 set_compact_mode(const tchar *arg, int *extract_flags)
570 if (!tstrcasecmp(arg, T("xpress4k")))
571 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K;
572 else if (!tstrcasecmp(arg, T("xpress8k")))
573 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K;
574 else if (!tstrcasecmp(arg, T("xpress16k")))
575 flag = WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K;
576 else if (!tstrcasecmp(arg, T("lzx")))
577 flag = WIMLIB_EXTRACT_FLAG_COMPACT_LZX;
580 *extract_flags |= flag;
585 "\"%"TS"\" is not a recognized System Compression format. The options are:"
587 " --compact=xpress4k\n"
588 " --compact=xpress8k\n"
589 " --compact=xpress16k\n"
597 set_compress_slow(void)
600 fprintf(stderr, "WARNING: the '--compress-slow' option is deprecated.\n"
601 " Use the '--compress=TYPE:LEVEL' option instead.\n");
603 wimlib_set_default_compression_level(-1, 100);
608 unsigned num_strings;
609 unsigned num_alloc_strings;
612 #define STRING_SET_INITIALIZER \
613 { .strings = NULL, .num_strings = 0, .num_alloc_strings = 0, }
615 #define STRING_SET(_strings) \
616 struct string_set _strings = STRING_SET_INITIALIZER
619 string_set_append(struct string_set *set, tchar *glob)
621 unsigned num_alloc_strings = set->num_alloc_strings;
623 if (set->num_strings == num_alloc_strings) {
626 num_alloc_strings += 4;
627 new_strings = realloc(set->strings,
628 sizeof(set->strings[0]) * num_alloc_strings);
630 imagex_error(T("Out of memory!"));
633 set->strings = new_strings;
634 set->num_alloc_strings = num_alloc_strings;
636 set->strings[set->num_strings++] = glob;
641 string_set_destroy(struct string_set *set)
647 wim_reference_globs(WIMStruct *wim, struct string_set *set, int open_flags)
649 return wimlib_reference_resource_files(wim, (const tchar **)set->strings,
651 WIMLIB_REF_FLAG_GLOB_ENABLE,
656 append_image_property_argument(struct string_set *image_properties)
658 if (!tstrchr(optarg, '=')) {
659 imagex_error(T("'--image-property' argument "
660 "must be in the form NAME=VALUE"));
663 return string_set_append(image_properties, optarg);
667 apply_image_properties(struct string_set *image_properties,
668 WIMStruct *wim, int image, bool *any_changes_ret)
670 bool any_changes = false;
671 for (unsigned i = 0; i < image_properties->num_strings; i++) {
673 const tchar *current_value;
676 name = image_properties->strings[i];
677 value = tstrchr(name, '=');
680 current_value = wimlib_get_image_property(wim, image, name);
681 if (current_value && !tstrcmp(current_value, value)) {
682 imagex_printf(T("The %"TS" property of image %d "
683 "already has value \"%"TS"\".\n"),
686 imagex_printf(T("Setting the %"TS" property of image "
687 "%d to \"%"TS"\".\n"),
689 ret = wimlib_set_image_property(wim, image, name, value);
696 *any_changes_ret = any_changes;
701 do_resource_not_found_warning(const tchar *wimfile,
702 const struct wimlib_wim_info *info,
703 const struct string_set *refglobs)
705 if (info->total_parts > 1) {
706 if (refglobs->num_strings == 0) {
707 imagex_error(T("\"%"TS"\" is part of a split WIM. "
708 "Use --ref to specify the other parts."),
711 imagex_error(T("Perhaps the '--ref' argument did not "
712 "specify all other parts of the split "
716 imagex_error(T("If this is a delta WIM, use the --ref argument "
717 "to specify the WIM(s) on which it is based."));
722 do_metadata_not_found_warning(const tchar *wimfile,
723 const struct wimlib_wim_info *info)
725 if (info->part_number != 1) {
726 imagex_error(T("\"%"TS"\" is not the first part of the split WIM.\n"
727 " You must specify the first part."),
732 /* Returns the size of a file given its name, or -1 if the file does not exist
733 * or its size cannot be determined. */
735 file_get_size(const tchar *filename)
738 if (tstat(filename, &st) == 0)
745 PARSE_STRING_SUCCESS = 0,
746 PARSE_STRING_FAILURE = 1,
747 PARSE_STRING_NONE = 2,
751 * Parses a string token from an array of characters.
753 * Tokens are either whitespace-delimited, or double or single-quoted.
755 * @line_p: Pointer to the pointer to the line of data. Will be updated
756 * to point past the string token iff the return value is
757 * PARSE_STRING_SUCCESS. If *len_p > 0, (*line_p)[*len_p - 1] must
760 * @len_p: @len_p initially stores the length of the line of data, which may
761 * be 0, and it will be updated to the number of bytes remaining in
762 * the line iff the return value is PARSE_STRING_SUCCESS.
764 * @fn_ret: Iff the return value is PARSE_STRING_SUCCESS, a pointer to the
765 * parsed string token will be returned here.
767 * Returns: PARSE_STRING_SUCCESS if a string token was successfully parsed; or
768 * PARSE_STRING_FAILURE if the data was invalid due to a missing
769 * closing quote; or PARSE_STRING_NONE if the line ended before the
770 * beginning of a string token was found.
773 parse_string(tchar **line_p, size_t *len_p, tchar **fn_ret)
776 tchar *line = *line_p;
780 /* Skip leading whitespace */
783 return PARSE_STRING_NONE;
784 if (!istspace(*line) && *line != T('\0'))
790 if (quote_char == T('"') || quote_char == T('\'')) {
795 line = tmemchr(line, quote_char, len);
797 imagex_error(T("Missing closing quote: %"TS), fn - 1);
798 return PARSE_STRING_FAILURE;
801 /* Unquoted string. Go until whitespace. Line is terminated
802 * by '\0', so no need to check 'len'. */
806 } while (!istspace(*line) && *line != T('\0'));
813 return PARSE_STRING_SUCCESS;
816 /* Parses a line of data (not an empty line or comment) in the source list file
817 * format. (See the man page for 'wimlib-imagex capture' for details on this
818 * format and the meaning.)
820 * @line: Line of data to be parsed. line[len - 1] must be '\0', unless
821 * len == 0. The data in @line will be modified by this function call.
823 * @len: Length of the line of data.
825 * @source: On success, the capture source and target described by the line is
826 * written into this destination. Note that it will contain pointers
827 * to data in the @line array.
829 * Returns true if the line was valid; false otherwise. */
831 parse_source_list_line(tchar *line, size_t len,
832 struct wimlib_capture_source *source)
836 ret = parse_string(&line, &len, &source->fs_source_path);
837 if (ret != PARSE_STRING_SUCCESS)
839 ret = parse_string(&line, &len, &source->wim_target_path);
840 if (ret == PARSE_STRING_NONE)
841 source->wim_target_path = source->fs_source_path;
842 return ret != PARSE_STRING_FAILURE;
845 /* Returns %true if the given line of length @len > 0 is a comment or empty line
846 * in the source list file format. */
848 is_comment_line(const tchar *line, size_t len)
851 if (*line == T('#') || *line == T(';'))
853 if (!istspace(*line) && *line != T('\0'))
863 text_file_count_lines(tchar **contents_p, size_t *nchars_p)
866 tchar *contents = *contents_p;
867 size_t nchars = *nchars_p;
870 for (i = 0; i < nchars; i++)
871 if (contents[i] == T('\n'))
874 /* Handle last line not terminated by a newline */
875 if (nchars != 0 && contents[nchars - 1] != T('\n')) {
876 contents = realloc(contents, (nchars + 1) * sizeof(tchar));
878 imagex_error(T("Out of memory!"));
881 contents[nchars] = T('\n');
882 *contents_p = contents;
890 /* Parses a file in the source list format. (See the man page for
891 * 'wimlib-imagex capture' for details on this format and the meaning.)
893 * @source_list_contents: Contents of the source list file. Note that this
894 * buffer will be modified to save memory allocations,
895 * and cannot be freed until the returned array of
896 * wimlib_capture_source's has also been freed.
898 * @source_list_nbytes: Number of bytes of data in the @source_list_contents
901 * @nsources_ret: On success, the length of the returned array is
904 * Returns: An array of `struct wimlib_capture_source's that can be passed to
905 * the wimlib_add_image_multisource() function to specify how a WIM image is to
907 static struct wimlib_capture_source *
908 parse_source_list(tchar **source_list_contents_p, size_t source_list_nchars,
909 size_t *nsources_ret)
913 struct wimlib_capture_source *sources;
916 nlines = text_file_count_lines(source_list_contents_p,
917 &source_list_nchars);
921 /* Always allocate at least 1 slot, just in case the implementation of
922 * calloc() returns NULL if 0 bytes are requested. */
923 sources = calloc(nlines ?: 1, sizeof(*sources));
925 imagex_error(T("out of memory"));
928 p = *source_list_contents_p;
930 for (i = 0; i < nlines; i++) {
931 /* XXX: Could use rawmemchr() here instead, but it may not be
932 * available on all platforms. */
933 tchar *endp = tmemchr(p, T('\n'), source_list_nchars);
934 size_t len = endp - p + 1;
936 if (!is_comment_line(p, len)) {
937 if (!parse_source_list_line(p, len, &sources[j++])) {
949 /* Reads the contents of a file into memory. */
951 file_get_contents(const tchar *filename, size_t *len_ret)
958 if (tstat(filename, &stbuf) != 0) {
959 imagex_error_with_errno(T("Failed to stat the file \"%"TS"\""), filename);
964 fp = tfopen(filename, T("rb"));
966 imagex_error_with_errno(T("Failed to open the file \"%"TS"\""), filename);
970 buf = malloc(len ? len : 1);
972 imagex_error(T("Failed to allocate buffer of %zu bytes to hold "
973 "contents of file \"%"TS"\""), len, filename);
976 if (fread(buf, 1, len, fp) != len) {
977 imagex_error_with_errno(T("Failed to read %zu bytes from the "
978 "file \"%"TS"\""), len, filename);
992 /* Read standard input until EOF and return the full contents in a malloc()ed
993 * buffer and the number of bytes of data in @len_ret. Returns NULL on read
996 stdin_get_contents(size_t *len_ret)
998 /* stdin can, of course, be a pipe or other non-seekable file, so the
999 * total length of the data cannot be pre-determined */
1001 size_t newlen = 1024;
1005 char *p = realloc(buf, newlen);
1006 size_t bytes_read, bytes_to_read;
1008 imagex_error(T("out of memory while reading stdin"));
1012 bytes_to_read = newlen - pos;
1013 bytes_read = fread(&buf[pos], 1, bytes_to_read, stdin);
1015 if (bytes_read != bytes_to_read) {
1020 imagex_error_with_errno(T("error reading stdin"));
1034 translate_text_to_tstr(char *text, size_t num_bytes, size_t *num_tchars_ret)
1037 /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
1039 *num_tchars_ret = num_bytes;
1041 #else /* !__WIN32__ */
1042 /* On Windows, translate the text to UTF-16LE */
1046 if (num_bytes >= 2 &&
1047 (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
1048 ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
1050 /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
1051 * with something that looks like an ASCII character encoded as
1052 * a UTF-16LE code unit. Assume the file is encoded as
1053 * UTF-16LE. This is not a 100% reliable check. */
1054 num_wchars = num_bytes / 2;
1055 text_wstr = (wchar_t*)text;
1057 /* File does not look like UTF-16LE. Assume it is encoded in
1058 * the current Windows code page. I think these are always
1059 * ASCII-compatible, so any so-called "plain-text" (ASCII) files
1060 * should work as expected. */
1061 text_wstr = win32_mbs_to_wcs(text,
1066 *num_tchars_ret = num_wchars;
1068 #endif /* __WIN32__ */
1072 file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
1077 contents = file_get_contents(filename, &num_bytes);
1080 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1084 stdin_get_text_contents(size_t *num_tchars_ret)
1089 contents = stdin_get_contents(&num_bytes);
1092 return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
1095 #define TO_PERCENT(numerator, denominator) \
1096 (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
1098 #define GIBIBYTE_MIN_NBYTES 10000000000ULL
1099 #define MEBIBYTE_MIN_NBYTES 10000000ULL
1100 #define KIBIBYTE_MIN_NBYTES 10000ULL
1103 get_unit(uint64_t total_bytes, const tchar **name_ret)
1105 if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
1106 *name_ret = T("GiB");
1108 } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
1109 *name_ret = T("MiB");
1111 } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
1112 *name_ret = T("KiB");
1115 *name_ret = T("bytes");
1120 static struct wimlib_progress_info_scan last_scan_progress;
1123 report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
1125 uint64_t prev_count, cur_count;
1127 prev_count = last_scan_progress.num_nondirs_scanned +
1128 last_scan_progress.num_dirs_scanned;
1129 cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
1131 if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
1132 cur_count % 128 == 0)
1134 unsigned unit_shift;
1135 const tchar *unit_name;
1137 unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
1138 imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
1139 "%"PRIu64" directories) "),
1140 scan->num_bytes_scanned >> unit_shift,
1142 scan->num_nondirs_scanned,
1143 scan->num_dirs_scanned);
1144 last_scan_progress = *scan;
1147 /* Progress callback function passed to various wimlib functions. */
1148 static enum wimlib_progress_status
1149 imagex_progress_func(enum wimlib_progress_msg msg,
1150 union wimlib_progress_info *info,
1151 void *_ignored_context)
1153 unsigned percent_done;
1154 unsigned unit_shift;
1155 const tchar *unit_name;
1157 if (imagex_be_quiet)
1158 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1160 case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
1162 static bool first = true;
1164 imagex_printf(T("Writing %"TS"-compressed data "
1165 "using %u thread%"TS"\n"),
1166 wimlib_get_compression_type_string(
1167 info->write_streams.compression_type),
1168 info->write_streams.num_threads,
1169 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1173 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1174 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1175 info->write_streams.total_bytes);
1177 imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
1178 "written (%u%% done)"),
1179 info->write_streams.completed_bytes >> unit_shift,
1181 info->write_streams.total_bytes >> unit_shift,
1184 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1185 imagex_printf(T("\n"));
1187 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1188 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1189 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1190 imagex_printf(T("\n"));
1192 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1193 info->scan.wim_target_path);
1195 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1197 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1198 switch (info->scan.status) {
1199 case WIMLIB_SCAN_DENTRY_OK:
1200 report_scan_progress(&info->scan, false);
1202 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1203 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1205 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1206 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1207 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1209 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1210 /* Symlink fixups are enabled by default. This is
1211 * mainly intended for Windows, which for some reason
1212 * uses absolute junctions (with drive letters!) in the
1213 * default installation. On UNIX-like systems, warn the
1214 * user when fixing the target of an absolute symbolic
1215 * link, so they know to disable this if they want. */
1217 imagex_printf(T("\nWARNING: Adjusted target of "
1218 "absolute symbolic link \"%"TS"\"\n"
1219 " (Use --norpfix to capture "
1220 "absolute symbolic links as-is)\n"),
1221 info->scan.cur_path);
1228 case WIMLIB_PROGRESS_MSG_SCAN_END:
1229 report_scan_progress(&info->scan, true);
1230 imagex_printf(T("\n"));
1232 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1233 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1234 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1235 info->integrity.total_bytes);
1236 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1237 "of %"PRIu64" %"TS" (%u%%) done"),
1238 info->integrity.filename,
1239 info->integrity.completed_bytes >> unit_shift,
1241 info->integrity.total_bytes >> unit_shift,
1244 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1245 imagex_printf(T("\n"));
1247 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1248 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1249 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1250 info->integrity.total_bytes);
1251 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1252 "of %"PRIu64" %"TS" (%u%%) done"),
1253 info->integrity.completed_bytes >> unit_shift,
1255 info->integrity.total_bytes >> unit_shift,
1258 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1259 imagex_printf(T("\n"));
1261 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1262 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1263 "to %"TS" \"%"TS"\"\n"),
1264 info->extract.image,
1265 info->extract.image_name,
1266 info->extract.wimfile_name,
1267 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1268 T("NTFS volume") : T("directory")),
1269 info->extract.target);
1271 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1272 if (info->extract.end_file_count >= 2000) {
1273 percent_done = TO_PERCENT(info->extract.current_file_count,
1274 info->extract.end_file_count);
1275 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1276 info->extract.current_file_count,
1277 info->extract.end_file_count, percent_done);
1278 if (info->extract.current_file_count == info->extract.end_file_count)
1279 imagex_printf(T("\n"));
1282 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1283 percent_done = TO_PERCENT(info->extract.completed_bytes,
1284 info->extract.total_bytes);
1285 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1286 imagex_printf(T("\rExtracting file data: "
1287 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1288 info->extract.completed_bytes >> unit_shift,
1290 info->extract.total_bytes >> unit_shift,
1293 if (info->extract.completed_bytes >= info->extract.total_bytes)
1294 imagex_printf(T("\n"));
1296 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1297 if (info->extract.end_file_count >= 2000) {
1298 percent_done = TO_PERCENT(info->extract.current_file_count,
1299 info->extract.end_file_count);
1300 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1301 info->extract.current_file_count,
1302 info->extract.end_file_count, percent_done);
1303 if (info->extract.current_file_count == info->extract.end_file_count)
1304 imagex_printf(T("\n"));
1307 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1308 if (info->extract.total_parts != 1) {
1309 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1310 info->extract.part_number,
1311 info->extract.total_parts);
1314 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1315 percent_done = TO_PERCENT(info->split.completed_bytes,
1316 info->split.total_bytes);
1317 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1318 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1319 "%"PRIu64" %"TS" (%u%%) written\n"),
1320 info->split.part_name,
1321 info->split.cur_part_number,
1322 info->split.total_parts,
1323 info->split.completed_bytes >> unit_shift,
1325 info->split.total_bytes >> unit_shift,
1329 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1330 if (info->split.completed_bytes == info->split.total_bytes) {
1331 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1332 info->split.cur_part_number,
1333 info->split.total_parts);
1336 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1337 switch (info->update.command->op) {
1338 case WIMLIB_UPDATE_OP_DELETE:
1339 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1340 info->update.command->delete_.wim_path);
1342 case WIMLIB_UPDATE_OP_RENAME:
1343 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1344 info->update.command->rename.wim_source_path,
1345 info->update.command->rename.wim_target_path);
1347 case WIMLIB_UPDATE_OP_ADD:
1352 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1353 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1354 info->replace.path_in_wim);
1356 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1357 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1358 info->wimboot_exclude.path_in_wim);
1360 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1361 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1362 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1363 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1364 info->unmount.mounted_wim,
1365 info->unmount.mounted_image);
1367 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1368 info->unmount.mounted_wim,
1369 info->unmount.mounted_image);
1370 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1374 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1375 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1376 info->verify_image.current_image,
1377 info->verify_image.total_images);
1379 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1380 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1381 info->verify_streams.total_bytes);
1382 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1383 imagex_printf(T("\rVerifying file data: "
1384 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1385 info->verify_streams.completed_bytes >> unit_shift,
1387 info->verify_streams.total_bytes >> unit_shift,
1390 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1391 imagex_printf(T("\n"));
1396 fflush(imagex_info_file);
1397 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1401 parse_num_threads(const tchar *optarg)
1404 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1405 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1406 imagex_error(T("Number of threads must be a non-negative integer!"));
1414 parse_chunk_size(const tchar *optarg)
1417 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1418 if (chunk_size == 0) {
1419 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1420 " with optional K, M, or G suffix"));
1424 if (*tmp == T('k') || *tmp == T('K')) {
1427 } else if (*tmp == T('m') || *tmp == T('M')) {
1430 } else if (*tmp == T('g') || *tmp == T('G')) {
1434 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1435 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1439 if (chunk_size >= UINT32_MAX) {
1440 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1448 * Parse an option passed to an update command.
1450 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1453 * @option: Text string for the option (beginning with --)
1455 * @cmd: `struct wimlib_update_command' that is being constructed for
1458 * Returns true if the option was recognized; false if not.
1461 update_command_add_option(int op, const tchar *option,
1462 struct wimlib_update_command *cmd)
1464 bool recognized = true;
1466 case WIMLIB_UPDATE_OP_ADD:
1467 if (!tstrcmp(option, T("--verbose")))
1468 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1469 else if (!tstrcmp(option, T("--unix-data")))
1470 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1471 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1472 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1473 else if (!tstrcmp(option, T("--strict-acls")))
1474 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1475 else if (!tstrcmp(option, T("--dereference")))
1476 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1477 else if (!tstrcmp(option, T("--no-replace")))
1478 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1482 case WIMLIB_UPDATE_OP_DELETE:
1483 if (!tstrcmp(option, T("--force")))
1484 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1485 else if (!tstrcmp(option, T("--recursive")))
1486 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1497 /* How many nonoption arguments each `imagex update' command expects */
1498 static const unsigned update_command_num_nonoptions[] = {
1499 [WIMLIB_UPDATE_OP_ADD] = 2,
1500 [WIMLIB_UPDATE_OP_DELETE] = 1,
1501 [WIMLIB_UPDATE_OP_RENAME] = 2,
1505 update_command_add_nonoption(int op, const tchar *nonoption,
1506 struct wimlib_update_command *cmd,
1507 unsigned num_nonoptions)
1510 case WIMLIB_UPDATE_OP_ADD:
1511 if (num_nonoptions == 0)
1512 cmd->add.fs_source_path = (tchar*)nonoption;
1514 cmd->add.wim_target_path = (tchar*)nonoption;
1516 case WIMLIB_UPDATE_OP_DELETE:
1517 cmd->delete_.wim_path = (tchar*)nonoption;
1519 case WIMLIB_UPDATE_OP_RENAME:
1520 if (num_nonoptions == 0)
1521 cmd->rename.wim_source_path = (tchar*)nonoption;
1523 cmd->rename.wim_target_path = (tchar*)nonoption;
1529 * Parse a command passed on stdin to `imagex update'.
1531 * @line: Text of the command.
1532 * @len: Length of the line, including a null terminator
1535 * @command: A `struct wimlib_update_command' to fill in from the parsed
1538 * @line_number: Line number of the command, for diagnostics.
1540 * Returns true on success; returns false on parse error.
1543 parse_update_command(tchar *line, size_t len,
1544 struct wimlib_update_command *command,
1548 tchar *command_name;
1550 size_t num_nonoptions;
1552 /* Get the command name ("add", "delete", "rename") */
1553 ret = parse_string(&line, &len, &command_name);
1554 if (ret != PARSE_STRING_SUCCESS)
1557 if (!tstrcasecmp(command_name, T("add"))) {
1558 op = WIMLIB_UPDATE_OP_ADD;
1559 } else if (!tstrcasecmp(command_name, T("delete"))) {
1560 op = WIMLIB_UPDATE_OP_DELETE;
1561 } else if (!tstrcasecmp(command_name, T("rename"))) {
1562 op = WIMLIB_UPDATE_OP_RENAME;
1564 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1565 command_name, line_number);
1570 /* Parse additional options and non-options as needed */
1575 ret = parse_string(&line, &len, &next_string);
1576 if (ret == PARSE_STRING_NONE) /* End of line */
1578 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1580 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1582 if (!update_command_add_option(op, next_string, command))
1584 imagex_error(T("Unrecognized option \"%"TS"\" to "
1585 "update command \"%"TS"\" on line %zu"),
1586 next_string, command_name, line_number);
1592 if (num_nonoptions == update_command_num_nonoptions[op])
1594 imagex_error(T("Unexpected argument \"%"TS"\" in "
1595 "update command on line %zu\n"
1596 " (The \"%"TS"\" command only "
1597 "takes %zu nonoption arguments!)\n"),
1598 next_string, line_number,
1599 command_name, num_nonoptions);
1602 update_command_add_nonoption(op, next_string,
1603 command, num_nonoptions);
1608 if (num_nonoptions != update_command_num_nonoptions[op]) {
1609 imagex_error(T("Not enough arguments to update command "
1610 "\"%"TS"\" on line %zu"), command_name, line_number);
1616 static struct wimlib_update_command *
1617 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1618 size_t *num_cmds_ret)
1622 struct wimlib_update_command *cmds;
1625 nlines = text_file_count_lines(cmd_file_contents_p,
1630 /* Always allocate at least 1 slot, just in case the implementation of
1631 * calloc() returns NULL if 0 bytes are requested. */
1632 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1634 imagex_error(T("out of memory"));
1637 p = *cmd_file_contents_p;
1639 for (i = 0; i < nlines; i++) {
1640 /* XXX: Could use rawmemchr() here instead, but it may not be
1641 * available on all platforms. */
1642 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1643 size_t len = endp - p + 1;
1645 if (!is_comment_line(p, len)) {
1646 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1657 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1658 * one image from a WIM file to an NTFS volume. */
1660 imagex_apply(int argc, tchar **argv, int cmd)
1664 int image = WIMLIB_NO_IMAGE;
1666 struct wimlib_wim_info info;
1668 const tchar *wimfile;
1669 const tchar *target;
1670 const tchar *image_num_or_name = NULL;
1671 int extract_flags = 0;
1673 STRING_SET(refglobs);
1675 for_opt(c, apply_options) {
1677 case IMAGEX_CHECK_OPTION:
1678 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1680 case IMAGEX_VERBOSE_OPTION:
1681 /* No longer does anything. */
1683 case IMAGEX_REF_OPTION:
1684 ret = string_set_append(&refglobs, optarg);
1686 goto out_free_refglobs;
1688 case IMAGEX_UNIX_DATA_OPTION:
1689 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1691 case IMAGEX_NO_ACLS_OPTION:
1692 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1694 case IMAGEX_STRICT_ACLS_OPTION:
1695 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1697 case IMAGEX_NO_ATTRIBUTES_OPTION:
1698 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1700 case IMAGEX_NORPFIX_OPTION:
1701 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1703 case IMAGEX_RPFIX_OPTION:
1704 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1706 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1707 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1708 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1710 case IMAGEX_RESUME_OPTION:
1711 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1713 case IMAGEX_WIMBOOT_OPTION:
1714 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1716 case IMAGEX_COMPACT_OPTION:
1717 ret = set_compact_mode(optarg, &extract_flags);
1719 goto out_free_refglobs;
1727 if (argc != 2 && argc != 3)
1732 if (!tstrcmp(wimfile, T("-"))) {
1733 /* Attempt to apply pipable WIM from standard input. */
1735 image_num_or_name = NULL;
1738 image_num_or_name = argv[1];
1743 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1744 imagex_progress_func, NULL);
1746 goto out_free_refglobs;
1748 wimlib_get_wim_info(wim, &info);
1751 /* Image explicitly specified. */
1752 image_num_or_name = argv[1];
1753 image = wimlib_resolve_image(wim, image_num_or_name);
1754 ret = verify_image_exists(image, image_num_or_name, wimfile);
1756 goto out_wimlib_free;
1759 /* No image specified; default to image 1, but only if the WIM
1760 * contains exactly one image. */
1762 if (info.image_count != 1) {
1763 imagex_error(T("\"%"TS"\" contains %d images; "
1764 "Please select one (or all)."),
1765 wimfile, info.image_count);
1774 if (refglobs.num_strings) {
1776 imagex_error(T("Can't specify --ref when applying from stdin!"));
1778 goto out_wimlib_free;
1780 ret = wim_reference_globs(wim, &refglobs, open_flags);
1782 goto out_wimlib_free;
1787 /* Interpret a regular file or block device target as an NTFS
1791 if (tstat(target, &stbuf)) {
1792 if (errno != ENOENT) {
1793 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1796 goto out_wimlib_free;
1799 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1800 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1806 ret = wimlib_extract_image(wim, image, target, extract_flags);
1808 set_fd_to_binary_mode(STDIN_FILENO);
1809 ret = wimlib_extract_image_from_pipe_with_progress(
1814 imagex_progress_func,
1818 imagex_printf(T("Done applying WIM image.\n"));
1819 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1821 do_resource_not_found_warning(wimfile, &info, &refglobs);
1823 imagex_error(T( "If you are applying an image "
1824 "from a split pipable WIM,\n"
1825 " make sure you have "
1826 "concatenated together all parts."));
1828 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1829 do_metadata_not_found_warning(wimfile, &info);
1834 string_set_destroy(&refglobs);
1838 usage(CMD_APPLY, stderr);
1840 goto out_free_refglobs;
1843 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1844 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1845 * the desired image. 'wimlib-imagex append': add a new image to an existing
1848 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1852 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1853 WIMLIB_ADD_FLAG_WINCONFIG |
1854 WIMLIB_ADD_FLAG_VERBOSE;
1855 int write_flags = 0;
1856 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1857 uint32_t chunk_size = UINT32_MAX;
1858 uint32_t solid_chunk_size = UINT32_MAX;
1859 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1860 const tchar *wimfile;
1863 STRING_SET(image_properties);
1866 STRING_SET(base_wimfiles);
1867 WIMStruct **base_wims;
1869 WIMStruct *template_wim;
1870 const tchar *template_wimfile = NULL;
1871 const tchar *template_image_name_or_num = NULL;
1872 int template_image = WIMLIB_NO_IMAGE;
1875 unsigned num_threads = 0;
1880 tchar *config_file = NULL;
1882 bool source_list = false;
1883 size_t source_list_nchars = 0;
1884 tchar *source_list_contents;
1885 bool capture_sources_malloced;
1886 struct wimlib_capture_source *capture_sources;
1888 bool name_defaulted;
1890 for_opt(c, capture_or_append_options) {
1892 case IMAGEX_BOOT_OPTION:
1893 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1895 case IMAGEX_CHECK_OPTION:
1896 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1897 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1899 case IMAGEX_NOCHECK_OPTION:
1900 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1902 case IMAGEX_CONFIG_OPTION:
1903 config_file = optarg;
1904 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1906 case IMAGEX_COMPRESS_OPTION:
1907 compression_type = get_compression_type(optarg);
1908 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1911 case IMAGEX_COMPRESS_SLOW_OPTION:
1912 set_compress_slow();
1914 case IMAGEX_CHUNK_SIZE_OPTION:
1915 chunk_size = parse_chunk_size(optarg);
1916 if (chunk_size == UINT32_MAX)
1919 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1920 solid_chunk_size = parse_chunk_size(optarg);
1921 if (solid_chunk_size == UINT32_MAX)
1924 case IMAGEX_SOLID_COMPRESS_OPTION:
1925 solid_ctype = get_compression_type(optarg);
1926 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1929 case IMAGEX_SOLID_OPTION:
1930 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1932 case IMAGEX_NO_SOLID_SORT_OPTION:
1933 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1935 case IMAGEX_FLAGS_OPTION: {
1936 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1937 tsprintf(p, T("FLAGS=%"TS), optarg);
1938 ret = string_set_append(&image_properties, p);
1943 case IMAGEX_IMAGE_PROPERTY_OPTION:
1944 ret = append_image_property_argument(&image_properties);
1948 case IMAGEX_DEREFERENCE_OPTION:
1949 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1951 case IMAGEX_VERBOSE_OPTION:
1952 /* No longer does anything. */
1954 case IMAGEX_THREADS_OPTION:
1955 num_threads = parse_num_threads(optarg);
1956 if (num_threads == UINT_MAX)
1959 case IMAGEX_REBUILD_OPTION:
1960 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1962 case IMAGEX_UNIX_DATA_OPTION:
1963 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1965 case IMAGEX_SOURCE_LIST_OPTION:
1968 case IMAGEX_NO_ACLS_OPTION:
1969 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1971 case IMAGEX_STRICT_ACLS_OPTION:
1972 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1974 case IMAGEX_RPFIX_OPTION:
1975 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1977 case IMAGEX_NORPFIX_OPTION:
1978 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1980 case IMAGEX_PIPABLE_OPTION:
1981 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1983 case IMAGEX_NOT_PIPABLE_OPTION:
1984 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1986 case IMAGEX_UPDATE_OF_OPTION:
1987 if (template_image_name_or_num) {
1988 imagex_error(T("'--update-of' can only be "
1989 "specified one time!"));
1993 colon = tstrrchr(optarg, T(':'));
1996 template_wimfile = optarg;
1998 template_image_name_or_num = colon + 1;
2000 template_wimfile = NULL;
2001 template_image_name_or_num = optarg;
2005 case IMAGEX_DELTA_FROM_OPTION:
2006 if (cmd != CMD_CAPTURE) {
2007 imagex_error(T("'--delta-from' is only "
2008 "valid for capture!"));
2011 ret = string_set_append(&base_wimfiles, optarg);
2014 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2016 case IMAGEX_WIMBOOT_OPTION:
2017 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2019 case IMAGEX_UNSAFE_COMPACT_OPTION:
2020 if (cmd != CMD_APPEND) {
2021 imagex_error(T("'--unsafe-compact' is only "
2022 "valid for append!"));
2025 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2027 case IMAGEX_SNAPSHOT_OPTION:
2028 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2037 if (argc < 2 || argc > 4)
2043 /* Set default compression type and parameters. */
2046 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2047 /* No compression type specified. Use the default. */
2049 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2050 /* With --wimboot, default to XPRESS compression. */
2051 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2052 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2053 /* With --solid, default to LZMS compression. (However,
2054 * this will not affect solid resources!) */
2055 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2057 /* Otherwise, default to LZX compression. */
2058 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2062 if (!tstrcmp(wimfile, T("-"))) {
2063 /* Writing captured WIM to standard output. */
2065 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2066 imagex_error("Can't write a non-pipable WIM to "
2067 "standard output! Specify --pipable\n"
2068 " if you want to create a pipable WIM "
2069 "(but read the docs first).");
2073 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2075 if (cmd == CMD_APPEND) {
2076 imagex_error(T("Using standard output for append does "
2077 "not make sense."));
2080 wim_fd = STDOUT_FILENO;
2082 imagex_info_file = stderr;
2083 set_fd_to_binary_mode(wim_fd);
2086 /* If template image was specified using --update-of=IMAGE rather
2087 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2088 if (template_image_name_or_num && !template_wimfile) {
2089 if (base_wimfiles.num_strings == 1) {
2090 /* Capturing delta WIM based on single WIM: default to
2092 template_wimfile = base_wimfiles.strings[0];
2093 } else if (cmd == CMD_APPEND) {
2094 /* Appending to WIM: default to WIM being appended to.
2096 template_wimfile = wimfile;
2098 /* Capturing a normal (non-delta) WIM, so the WIM file
2099 * *must* be explicitly specified. */
2100 if (base_wimfiles.num_strings > 1) {
2101 imagex_error(T("For capture of delta WIM "
2102 "based on multiple existing "
2104 " '--update-of' must "
2105 "specify WIMFILE:IMAGE!"));
2107 imagex_error(T("For capture of non-delta WIM, "
2108 "'--update-of' must specify "
2117 name_defaulted = false;
2119 /* Set default name to SOURCE argument, omitting any directory
2120 * prefixes and trailing slashes. This requires making a copy
2121 * of @source. Leave some free characters at the end in case we
2122 * append a number to keep the name unique. */
2123 size_t source_name_len;
2125 source_name_len = tstrlen(source);
2126 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2127 name = tbasename(tstrcpy(source_copy, source));
2128 name_defaulted = true;
2131 /* Image description (if given). */
2133 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2134 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2135 ret = string_set_append(&image_properties, p);
2141 /* Set up capture sources in source list mode */
2142 if (source[0] == T('-') && source[1] == T('\0')) {
2143 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2145 source_list_contents = file_get_text_contents(source,
2146 &source_list_nchars);
2148 if (!source_list_contents)
2151 capture_sources = parse_source_list(&source_list_contents,
2154 if (!capture_sources) {
2156 goto out_free_source_list_contents;
2158 capture_sources_malloced = true;
2160 /* Set up capture source in non-source-list mode. */
2161 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2162 capture_sources[0].fs_source_path = source;
2163 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2164 capture_sources[0].reserved = 0;
2166 capture_sources_malloced = false;
2167 source_list_contents = NULL;
2170 /* Open the existing WIM, or create a new one. */
2171 if (cmd == CMD_APPEND) {
2172 ret = wimlib_open_wim_with_progress(wimfile,
2173 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2175 imagex_progress_func,
2178 goto out_free_capture_sources;
2180 ret = wimlib_create_new_wim(compression_type, &wim);
2182 goto out_free_capture_sources;
2183 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2186 /* Set chunk size if non-default. */
2187 if (chunk_size != UINT32_MAX) {
2188 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2191 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2193 int ctype = compression_type;
2195 if (cmd == CMD_APPEND) {
2196 struct wimlib_wim_info info;
2197 wimlib_get_wim_info(wim, &info);
2198 ctype = info.compression_type;
2201 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2202 ret = wimlib_set_output_chunk_size(wim, 4096);
2207 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2208 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2212 if (solid_chunk_size != UINT32_MAX) {
2213 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2219 /* Detect if source is regular file or block device and set NTFS volume
2224 if (tstat(source, &stbuf) == 0) {
2225 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2226 imagex_printf(T("Capturing WIM image from NTFS "
2227 "filesystem on \"%"TS"\"\n"), source);
2228 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2231 if (errno != ENOENT) {
2232 imagex_error_with_errno(T("Failed to stat "
2233 "\"%"TS"\""), source);
2241 /* If the user did not specify an image name, and the basename of the
2242 * source already exists as an image name in the WIM file, append a
2243 * suffix to make it unique. */
2244 if (cmd == CMD_APPEND && name_defaulted) {
2245 unsigned long conflict_idx;
2246 tchar *name_end = tstrchr(name, T('\0'));
2247 for (conflict_idx = 1;
2248 wimlib_image_name_in_use(wim, name);
2251 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2255 /* If capturing a delta WIM, reference resources from the base WIMs
2256 * before adding the new image. */
2257 if (base_wimfiles.num_strings) {
2258 base_wims = calloc(base_wimfiles.num_strings,
2259 sizeof(base_wims[0]));
2260 if (base_wims == NULL) {
2261 imagex_error(T("Out of memory!"));
2266 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2267 ret = wimlib_open_wim_with_progress(
2268 base_wimfiles.strings[i], open_flags,
2269 &base_wims[i], imagex_progress_func, NULL);
2271 goto out_free_base_wims;
2275 ret = wimlib_reference_resources(wim, base_wims,
2276 base_wimfiles.num_strings, 0);
2278 goto out_free_base_wims;
2280 if (base_wimfiles.num_strings == 1) {
2281 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2282 base_wimfiles.strings[0]);
2284 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2285 base_wimfiles.num_strings);
2292 /* If capturing or appending as an update of an existing (template) image,
2293 * open the WIM if needed and parse the image index. */
2294 if (template_image_name_or_num) {
2297 if (base_wimfiles.num_strings == 1 &&
2298 template_wimfile == base_wimfiles.strings[0]) {
2299 template_wim = base_wims[0];
2300 } else if (template_wimfile == wimfile) {
2303 ret = wimlib_open_wim_with_progress(template_wimfile,
2306 imagex_progress_func,
2309 goto out_free_base_wims;
2312 template_image = wimlib_resolve_image(template_wim,
2313 template_image_name_or_num);
2315 if (template_image_name_or_num[0] == T('-')) {
2318 struct wimlib_wim_info info;
2320 wimlib_get_wim_info(template_wim, &info);
2321 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2322 if (n >= 1 && n <= info.image_count &&
2324 tmp != template_image_name_or_num + 1)
2326 template_image = info.image_count - (n - 1);
2329 ret = verify_image_exists_and_is_single(template_image,
2330 template_image_name_or_num,
2333 goto out_free_template_wim;
2335 template_wim = NULL;
2338 ret = wimlib_add_image_multisource(wim,
2345 goto out_free_template_wim;
2347 if (image_properties.num_strings || template_image_name_or_num) {
2348 /* User asked to set additional image properties, or an image on
2349 * which the added one is to be based has been specified with
2351 struct wimlib_wim_info info;
2353 wimlib_get_wim_info(wim, &info);
2355 ret = apply_image_properties(&image_properties, wim,
2356 info.image_count, NULL);
2358 goto out_free_template_wim;
2360 /* Reference template image if the user provided one. */
2361 if (template_image_name_or_num) {
2362 imagex_printf(T("Using image %d "
2363 "from \"%"TS"\" as template\n"),
2364 template_image, template_wimfile);
2365 ret = wimlib_reference_template_image(wim,
2371 goto out_free_template_wim;
2375 /* Write the new WIM or overwrite the existing WIM with the new image
2377 if (cmd == CMD_APPEND) {
2378 ret = wimlib_overwrite(wim, write_flags, num_threads);
2379 } else if (wimfile) {
2380 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2381 write_flags, num_threads);
2383 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2384 write_flags, num_threads);
2386 out_free_template_wim:
2387 /* template_wim may alias base_wims[0] or wim. */
2388 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2389 template_wim != wim)
2390 wimlib_free(template_wim);
2392 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2393 wimlib_free(base_wims[i]);
2397 out_free_capture_sources:
2398 if (capture_sources_malloced)
2399 free(capture_sources);
2400 out_free_source_list_contents:
2401 free(source_list_contents);
2403 string_set_destroy(&image_properties);
2404 string_set_destroy(&base_wimfiles);
2414 /* Remove image(s) from a WIM. */
2416 imagex_delete(int argc, tchar **argv, int cmd)
2419 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2420 int write_flags = 0;
2421 const tchar *wimfile;
2422 const tchar *image_num_or_name;
2427 for_opt(c, delete_options) {
2429 case IMAGEX_CHECK_OPTION:
2430 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2431 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2433 case IMAGEX_SOFT_OPTION:
2434 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2436 case IMAGEX_UNSAFE_COMPACT_OPTION:
2437 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2448 imagex_error(T("Must specify a WIM file"));
2450 imagex_error(T("Must specify an image"));
2454 image_num_or_name = argv[1];
2456 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2457 imagex_progress_func, NULL);
2461 image = wimlib_resolve_image(wim, image_num_or_name);
2463 ret = verify_image_exists(image, image_num_or_name, wimfile);
2465 goto out_wimlib_free;
2467 ret = wimlib_delete_image(wim, image);
2469 imagex_error(T("Failed to delete image from \"%"TS"\""),
2471 goto out_wimlib_free;
2474 ret = wimlib_overwrite(wim, write_flags, 0);
2476 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2477 "deleted"), wimfile);
2485 usage(CMD_DELETE, stderr);
2490 struct print_dentry_options {
2495 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2497 tprintf(T("%"TS"\n"), dentry->full_path);
2500 static const struct {
2503 } file_attr_flags[] = {
2504 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2505 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2506 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2507 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2508 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2509 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2510 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2511 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2512 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2513 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2514 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2515 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2516 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2517 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2518 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2521 #define TIMESTR_MAX 100
2524 timespec_to_string(const struct timespec *spec, tchar *buf)
2526 time_t t = spec->tv_sec;
2529 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2530 buf[TIMESTR_MAX - 1] = '\0';
2534 print_time(const tchar *type, const struct timespec *spec)
2536 tchar timestr[TIMESTR_MAX];
2538 timespec_to_string(spec, timestr);
2540 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2543 static void print_byte_field(const uint8_t field[], size_t len)
2546 tprintf(T("%02hhx"), *field++);
2550 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2552 tchar attr_string[256];
2555 tputs(T("WIM Information:"));
2556 tputs(T("----------------"));
2557 tprintf(T("Path: %"TS"\n"), wimfile);
2558 tprintf(T("GUID: 0x"));
2559 print_byte_field(info->guid, sizeof(info->guid));
2561 tprintf(T("Version: %u\n"), info->wim_version);
2562 tprintf(T("Image Count: %d\n"), info->image_count);
2563 tprintf(T("Compression: %"TS"\n"),
2564 wimlib_get_compression_type_string(info->compression_type));
2565 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2567 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2568 tprintf(T("Boot Index: %d\n"), info->boot_index);
2569 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2571 attr_string[0] = T('\0');
2574 tstrcat(attr_string, T("Pipable, "));
2576 if (info->has_integrity_table)
2577 tstrcat(attr_string, T("Integrity info, "));
2579 if (info->has_rpfix)
2580 tstrcat(attr_string, T("Relative path junction, "));
2582 if (info->resource_only)
2583 tstrcat(attr_string, T("Resource only, "));
2585 if (info->metadata_only)
2586 tstrcat(attr_string, T("Metadata only, "));
2588 if (info->is_marked_readonly)
2589 tstrcat(attr_string, T("Readonly, "));
2591 p = tstrchr(attr_string, T('\0'));
2592 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2595 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2599 print_resource(const struct wimlib_resource_entry *resource,
2602 tprintf(T("Hash = 0x"));
2603 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2606 if (!resource->is_missing) {
2607 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2608 resource->uncompressed_size);
2609 if (resource->packed) {
2610 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2611 "bytes @ offset %"PRIu64"\n"),
2612 resource->raw_resource_uncompressed_size,
2613 resource->raw_resource_compressed_size,
2614 resource->raw_resource_offset_in_wim);
2616 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2619 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2620 resource->compressed_size);
2622 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2626 tprintf(T("Part Number = %u\n"), resource->part_number);
2627 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2629 tprintf(T("Flags = "));
2630 if (resource->is_compressed)
2631 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2632 if (resource->is_metadata)
2633 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2634 if (resource->is_free)
2635 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2636 if (resource->is_spanned)
2637 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2638 if (resource->packed)
2639 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2647 print_blobs(WIMStruct *wim)
2649 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2653 default_print_security_descriptor(const uint8_t *sd, size_t size)
2655 tprintf(T("Security Descriptor = "));
2656 print_byte_field(sd, size);
2661 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2665 "----------------------------------------------------------------------------\n"));
2666 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2667 if (dentry->dos_name)
2668 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2669 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2670 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2671 if (file_attr_flags[i].flag & dentry->attributes)
2672 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2673 file_attr_flags[i].name);
2675 if (dentry->security_descriptor) {
2676 print_security_descriptor(dentry->security_descriptor,
2677 dentry->security_descriptor_size);
2680 print_time(T("Creation Time"), &dentry->creation_time);
2681 print_time(T("Last Write Time"), &dentry->last_write_time);
2682 print_time(T("Last Access Time"), &dentry->last_access_time);
2685 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2686 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2688 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2689 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2691 if (dentry->unix_mode != 0) {
2692 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2693 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2694 dentry->unix_uid, dentry->unix_gid,
2695 dentry->unix_mode, dentry->unix_rdev);
2698 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2699 if (dentry->streams[i].stream_name) {
2700 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2701 dentry->streams[i].stream_name);
2702 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2703 tprintf(T("\tRaw encrypted data stream:\n"));
2704 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2705 tprintf(T("\tReparse point stream:\n"));
2707 tprintf(T("\tUnnamed data stream:\n"));
2709 print_resource(&dentry->streams[i].resource, NULL);
2714 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2716 const struct print_dentry_options *options = _options;
2717 if (!options->detailed)
2718 print_dentry_full_path(dentry);
2720 print_dentry_detailed(dentry);
2724 /* Print the files contained in an image(s) in a WIM file. */
2726 imagex_dir(int argc, tchar **argv, int cmd)
2728 const tchar *wimfile;
2729 WIMStruct *wim = NULL;
2732 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2734 struct print_dentry_options options = {
2737 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2739 STRING_SET(refglobs);
2741 for_opt(c, dir_options) {
2743 case IMAGEX_PATH_OPTION:
2746 case IMAGEX_DETAILED_OPTION:
2747 options.detailed = true;
2749 case IMAGEX_ONE_FILE_ONLY_OPTION:
2750 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2752 case IMAGEX_REF_OPTION:
2753 ret = string_set_append(&refglobs, optarg);
2755 goto out_free_refglobs;
2765 imagex_error(T("Must specify a WIM file"));
2769 imagex_error(T("Too many arguments"));
2774 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2775 imagex_progress_func, NULL);
2777 goto out_free_refglobs;
2780 image = wimlib_resolve_image(wim, argv[1]);
2781 ret = verify_image_exists(image, argv[1], wimfile);
2783 goto out_wimlib_free;
2785 /* No image specified; default to image 1, but only if the WIM
2786 * contains exactly one image. */
2788 struct wimlib_wim_info info;
2790 wimlib_get_wim_info(wim, &info);
2791 if (info.image_count != 1) {
2792 imagex_error(T("\"%"TS"\" contains %d images; Please "
2793 "select one (or all)."),
2794 wimfile, info.image_count);
2801 if (refglobs.num_strings) {
2802 ret = wim_reference_globs(wim, &refglobs, 0);
2804 goto out_wimlib_free;
2807 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2808 print_dentry, &options);
2809 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2810 struct wimlib_wim_info info;
2812 wimlib_get_wim_info(wim, &info);
2813 do_metadata_not_found_warning(wimfile, &info);
2818 string_set_destroy(&refglobs);
2822 usage(CMD_DIR, stderr);
2824 goto out_free_refglobs;
2827 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2830 imagex_export(int argc, tchar **argv, int cmd)
2834 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2835 int write_flags = 0;
2836 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2837 const tchar *src_wimfile;
2838 const tchar *src_image_num_or_name;
2839 const tchar *dest_wimfile;
2841 const tchar *dest_name;
2842 const tchar *dest_desc;
2844 struct wimlib_wim_info src_info;
2845 WIMStruct *dest_wim;
2850 STRING_SET(refglobs);
2851 unsigned num_threads = 0;
2852 uint32_t chunk_size = UINT32_MAX;
2853 uint32_t solid_chunk_size = UINT32_MAX;
2854 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2856 for_opt(c, export_options) {
2858 case IMAGEX_BOOT_OPTION:
2859 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2861 case IMAGEX_CHECK_OPTION:
2862 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2863 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2865 case IMAGEX_NOCHECK_OPTION:
2866 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2868 case IMAGEX_COMPRESS_OPTION:
2869 compression_type = get_compression_type(optarg);
2870 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2873 case IMAGEX_COMPRESS_SLOW_OPTION:
2874 set_compress_slow();
2875 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2877 case IMAGEX_RECOMPRESS_OPTION:
2878 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2880 case IMAGEX_SOLID_OPTION:
2881 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2883 case IMAGEX_NO_SOLID_SORT_OPTION:
2884 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2886 case IMAGEX_CHUNK_SIZE_OPTION:
2887 chunk_size = parse_chunk_size(optarg);
2888 if (chunk_size == UINT32_MAX)
2891 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2892 solid_chunk_size = parse_chunk_size(optarg);
2893 if (solid_chunk_size == UINT32_MAX)
2896 case IMAGEX_SOLID_COMPRESS_OPTION:
2897 solid_ctype = get_compression_type(optarg);
2898 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2901 case IMAGEX_REF_OPTION:
2902 ret = string_set_append(&refglobs, optarg);
2904 goto out_free_refglobs;
2906 case IMAGEX_THREADS_OPTION:
2907 num_threads = parse_num_threads(optarg);
2908 if (num_threads == UINT_MAX)
2911 case IMAGEX_REBUILD_OPTION:
2912 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2914 case IMAGEX_PIPABLE_OPTION:
2915 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2917 case IMAGEX_NOT_PIPABLE_OPTION:
2918 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2920 case IMAGEX_WIMBOOT_OPTION:
2921 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2923 case IMAGEX_UNSAFE_COMPACT_OPTION:
2924 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2932 if (argc < 3 || argc > 5)
2934 src_wimfile = argv[0];
2935 src_image_num_or_name = argv[1];
2936 dest_wimfile = argv[2];
2937 dest_name = (argc >= 4) ? argv[3] : NULL;
2938 dest_desc = (argc >= 5) ? argv[4] : NULL;
2939 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2940 imagex_progress_func, NULL);
2942 goto out_free_refglobs;
2944 wimlib_get_wim_info(src_wim, &src_info);
2946 /* Determine if the destination is an existing file or not. If so, we
2947 * try to append the exported image(s) to it; otherwise, we create a new
2948 * WIM containing the exported image(s). Furthermore, determine if we
2949 * need to write a pipable WIM directly to standard output. */
2951 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2953 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2954 imagex_error("Can't write a non-pipable WIM to "
2955 "standard output! Specify --pipable\n"
2956 " if you want to create a pipable WIM "
2957 "(but read the docs first).");
2959 goto out_free_src_wim;
2962 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2964 dest_wimfile = NULL;
2965 dest_wim_fd = STDOUT_FILENO;
2966 imagex_info_file = stderr;
2967 set_fd_to_binary_mode(dest_wim_fd);
2970 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2972 /* Destination file exists. */
2974 if (!S_ISREG(stbuf.st_mode)) {
2975 imagex_error(T("\"%"TS"\" is not a regular file"),
2978 goto out_free_src_wim;
2980 ret = wimlib_open_wim_with_progress(dest_wimfile,
2982 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2984 imagex_progress_func,
2987 goto out_free_src_wim;
2989 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2990 /* The user specified a compression type, but we're
2991 * exporting to an existing WIM. Make sure the
2992 * specified compression type is the same as the
2993 * compression type of the existing destination WIM. */
2994 struct wimlib_wim_info dest_info;
2996 wimlib_get_wim_info(dest_wim, &dest_info);
2997 if (compression_type != dest_info.compression_type) {
2998 imagex_error(T("Cannot specify a compression type that is "
2999 "not the same as that used in the "
3000 "destination WIM"));
3002 goto out_free_dest_wim;
3008 if (errno != ENOENT) {
3009 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3012 goto out_free_src_wim;
3015 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3016 imagex_error(T("'--unsafe-compact' is only valid when "
3017 "exporting to an existing WIM file!"));
3019 goto out_free_src_wim;
3022 /* dest_wimfile is not an existing file, so create a new WIM. */
3024 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3025 /* The user did not specify a compression type; default
3026 * to that of the source WIM, unless --solid or
3027 * --wimboot was specified. */
3029 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3030 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3031 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3032 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3034 compression_type = src_info.compression_type;
3036 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3038 goto out_free_src_wim;
3040 wimlib_register_progress_function(dest_wim,
3041 imagex_progress_func, NULL);
3043 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3044 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3046 /* For --wimboot export, use small XPRESS chunks. */
3047 wimlib_set_output_chunk_size(dest_wim, 4096);
3048 } else if (compression_type == src_info.compression_type &&
3049 chunk_size == UINT32_MAX)
3051 /* Use same chunk size if compression type is the same. */
3052 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3056 if (chunk_size != UINT32_MAX) {
3057 /* Set destination chunk size. */
3058 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3060 goto out_free_dest_wim;
3062 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3063 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3065 goto out_free_dest_wim;
3067 if (solid_chunk_size != UINT32_MAX) {
3068 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3070 goto out_free_dest_wim;
3073 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3074 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3076 goto out_free_dest_wim;
3078 if (refglobs.num_strings) {
3079 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3081 goto out_free_dest_wim;
3084 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3085 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3087 imagex_error(T("--boot specified for all-images export, but source WIM "
3088 "has no bootable image."));
3090 goto out_free_dest_wim;
3093 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3094 dest_desc, export_flags);
3096 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3097 do_resource_not_found_warning(src_wimfile,
3098 &src_info, &refglobs);
3099 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3100 do_metadata_not_found_warning(src_wimfile, &src_info);
3102 goto out_free_dest_wim;
3106 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3107 else if (dest_wimfile)
3108 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3109 write_flags, num_threads);
3111 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3112 WIMLIB_ALL_IMAGES, write_flags,
3115 wimlib_free(dest_wim);
3117 wimlib_free(src_wim);
3119 string_set_destroy(&refglobs);
3123 usage(CMD_EXPORT, stderr);
3126 goto out_free_refglobs;
3129 /* Extract files or directories from a WIM image */
3131 imagex_extract(int argc, tchar **argv, int cmd)
3138 const tchar *wimfile;
3139 const tchar *image_num_or_name;
3140 tchar *dest_dir = T(".");
3141 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3142 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3143 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3144 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3146 STRING_SET(refglobs);
3148 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3150 for_opt(c, extract_options) {
3152 case IMAGEX_CHECK_OPTION:
3153 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3155 case IMAGEX_VERBOSE_OPTION:
3156 /* No longer does anything. */
3158 case IMAGEX_REF_OPTION:
3159 ret = string_set_append(&refglobs, optarg);
3161 goto out_free_refglobs;
3163 case IMAGEX_UNIX_DATA_OPTION:
3164 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3166 case IMAGEX_NO_ACLS_OPTION:
3167 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3169 case IMAGEX_STRICT_ACLS_OPTION:
3170 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3172 case IMAGEX_NO_ATTRIBUTES_OPTION:
3173 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3175 case IMAGEX_DEST_DIR_OPTION:
3178 case IMAGEX_TO_STDOUT_OPTION:
3179 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3180 imagex_info_file = stderr;
3181 imagex_be_quiet = true;
3182 set_fd_to_binary_mode(STDOUT_FILENO);
3184 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3185 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3186 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3188 case IMAGEX_NO_GLOBS_OPTION:
3189 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3191 case IMAGEX_NULLGLOB_OPTION:
3192 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3194 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3195 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3197 case IMAGEX_WIMBOOT_OPTION:
3198 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3200 case IMAGEX_COMPACT_OPTION:
3201 ret = set_compact_mode(optarg, &extract_flags);
3203 goto out_free_refglobs;
3215 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3216 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3218 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3223 image_num_or_name = argv[1];
3228 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3229 imagex_progress_func, NULL);
3231 goto out_free_refglobs;
3233 image = wimlib_resolve_image(wim, image_num_or_name);
3234 ret = verify_image_exists_and_is_single(image,
3238 goto out_wimlib_free;
3240 if (refglobs.num_strings) {
3241 ret = wim_reference_globs(wim, &refglobs, open_flags);
3243 goto out_wimlib_free;
3249 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3252 while (argc != 0 && ret == 0) {
3256 num_paths < argc && argv[num_paths][0] != T('@');
3261 ret = wimlib_extract_paths(wim, image, dest_dir,
3262 (const tchar **)argv,
3264 extract_flags | notlist_extract_flags);
3268 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3277 if (!imagex_be_quiet)
3278 imagex_printf(T("Done extracting files.\n"));
3279 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3280 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3281 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3282 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3283 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3286 T("Note: You can use the '--nullglob' "
3287 "option to ignore missing files.\n"));
3289 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3290 "files and directories\n"
3291 " are in the WIM image.\n"),
3292 get_cmd_string(CMD_DIR, false));
3293 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3294 struct wimlib_wim_info info;
3296 wimlib_get_wim_info(wim, &info);
3297 do_resource_not_found_warning(wimfile, &info, &refglobs);
3298 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3299 struct wimlib_wim_info info;
3301 wimlib_get_wim_info(wim, &info);
3302 do_metadata_not_found_warning(wimfile, &info);
3307 string_set_destroy(&refglobs);
3311 usage(CMD_EXTRACT, stderr);
3314 goto out_free_refglobs;
3317 /* Prints information about a WIM file; also can mark an image as bootable,
3318 * change the name of an image, or change the description of an image. */
3320 imagex_info(int argc, tchar **argv, int cmd)
3325 bool nocheck = false;
3326 bool header = false;
3329 bool short_header = true;
3330 const tchar *xml_out_file = NULL;
3331 const tchar *wimfile;
3332 const tchar *image_num_or_name;
3333 STRING_SET(image_properties);
3338 struct wimlib_wim_info info;
3340 for_opt(c, info_options) {
3342 case IMAGEX_BOOT_OPTION:
3345 case IMAGEX_CHECK_OPTION:
3348 case IMAGEX_NOCHECK_OPTION:
3351 case IMAGEX_HEADER_OPTION:
3353 short_header = false;
3355 case IMAGEX_BLOBS_OPTION:
3357 short_header = false;
3359 case IMAGEX_XML_OPTION:
3361 short_header = false;
3363 case IMAGEX_EXTRACT_XML_OPTION:
3364 xml_out_file = optarg;
3365 short_header = false;
3367 case IMAGEX_METADATA_OPTION:
3368 imagex_error(T("The --metadata option has been removed. "
3369 "Use 'wimdir --detail' instead."));
3371 case IMAGEX_IMAGE_PROPERTY_OPTION:
3372 ret = append_image_property_argument(&image_properties);
3383 if (argc < 1 || argc > 4)
3387 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3391 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3392 tsprintf(p, T("NAME=%"TS), argv[2]);
3393 ret = string_set_append(&image_properties, p);
3400 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3401 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3402 ret = string_set_append(&image_properties, p);
3407 if (check && nocheck) {
3408 imagex_error(T("Can't specify both --check and --nocheck"));
3413 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3415 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3416 imagex_progress_func, NULL);
3420 wimlib_get_wim_info(wim, &info);
3422 image = wimlib_resolve_image(wim, image_num_or_name);
3423 ret = WIMLIB_ERR_INVALID_IMAGE;
3424 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3425 verify_image_exists(image, image_num_or_name, wimfile);
3427 imagex_error(T("If you would like to set the boot "
3428 "index to 0, specify image \"0\" with "
3429 "the --boot flag."));
3431 goto out_wimlib_free;
3434 if (boot && info.image_count == 0) {
3435 imagex_error(T("--boot is meaningless on a WIM with no images"));
3436 goto out_wimlib_free;
3439 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3441 imagex_error(T("Cannot specify the --boot flag "
3442 "without specifying a specific "
3443 "image in a multi-image WIM"));
3444 goto out_wimlib_free;
3446 if (image_properties.num_strings) {
3447 imagex_error(T("Can't change image properties without "
3448 "specifying a specific image in a "
3449 "multi-image WIM"));
3450 goto out_wimlib_free;
3454 /* Operations that print information are separated from operations that
3455 * recreate the WIM file. */
3456 if (!image_properties.num_strings && !boot) {
3458 /* Read-only operations */
3460 if (image == WIMLIB_NO_IMAGE) {
3461 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3462 image_num_or_name, wimfile);
3463 goto out_wimlib_free;
3466 if (image == WIMLIB_ALL_IMAGES && short_header)
3467 print_wim_information(wimfile, &info);
3470 wimlib_print_header(wim);
3473 if (info.total_parts != 1) {
3474 tfprintf(stderr, T("Warning: Only showing the blobs "
3475 "for part %d of a %d-part WIM.\n"),
3476 info.part_number, info.total_parts);
3482 ret = wimlib_extract_xml_data(wim, stdout);
3484 goto out_wimlib_free;
3490 fp = tfopen(xml_out_file, T("wb"));
3492 imagex_error_with_errno(T("Failed to open the "
3493 "file \"%"TS"\" for "
3497 goto out_wimlib_free;
3499 ret = wimlib_extract_xml_data(wim, fp);
3501 imagex_error(T("Failed to close the file "
3507 goto out_wimlib_free;
3511 wimlib_print_available_images(wim, image);
3515 /* Modification operations */
3516 bool any_property_changes;
3518 if (image == WIMLIB_ALL_IMAGES)
3521 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3522 imagex_error(T("Cannot change image properties "
3523 "when using image 0"));
3525 goto out_wimlib_free;
3529 if (image == info.boot_index) {
3530 imagex_printf(T("Image %d is already marked as "
3531 "bootable.\n"), image);
3534 imagex_printf(T("Marking image %d as bootable.\n"),
3536 info.boot_index = image;
3537 ret = wimlib_set_wim_info(wim, &info,
3538 WIMLIB_CHANGE_BOOT_INDEX);
3540 goto out_wimlib_free;
3544 ret = apply_image_properties(&image_properties, wim, image,
3545 &any_property_changes);
3547 goto out_wimlib_free;
3549 /* Only call wimlib_overwrite() if something actually needs to
3551 if (boot || any_property_changes ||
3552 (check && !info.has_integrity_table) ||
3553 (nocheck && info.has_integrity_table))
3555 int write_flags = 0;
3558 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3560 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3561 ret = wimlib_overwrite(wim, write_flags, 1);
3563 imagex_printf(T("The file \"%"TS"\" was not modified "
3564 "because nothing needed to be done.\n"),
3572 string_set_destroy(&image_properties);
3576 usage(CMD_INFO, stderr);
3582 /* Join split WIMs into one part WIM */
3584 imagex_join(int argc, tchar **argv, int cmd)
3587 int swm_open_flags = 0;
3588 int wim_write_flags = 0;
3589 const tchar *output_path;
3592 for_opt(c, join_options) {
3594 case IMAGEX_CHECK_OPTION:
3595 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3596 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3606 imagex_error(T("Must specify one or more split WIM (.swm) "
3610 output_path = argv[0];
3611 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3616 imagex_progress_func,
3622 usage(CMD_JOIN, stderr);
3627 #if WIM_MOUNTING_SUPPORTED
3629 /* Mounts a WIM image. */
3631 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3634 int mount_flags = 0;
3636 const tchar *staging_dir = NULL;
3637 const tchar *wimfile;
3640 struct wimlib_wim_info info;
3644 STRING_SET(refglobs);
3646 if (cmd == CMD_MOUNTRW) {
3647 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3648 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3651 for_opt(c, mount_options) {
3653 case IMAGEX_ALLOW_OTHER_OPTION:
3654 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3656 case IMAGEX_CHECK_OPTION:
3657 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3659 case IMAGEX_DEBUG_OPTION:
3660 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3662 case IMAGEX_STREAMS_INTERFACE_OPTION:
3663 if (!tstrcasecmp(optarg, T("none")))
3664 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3665 else if (!tstrcasecmp(optarg, T("xattr")))
3666 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3667 else if (!tstrcasecmp(optarg, T("windows")))
3668 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3670 imagex_error(T("Unknown stream interface \"%"TS"\""),
3675 case IMAGEX_REF_OPTION:
3676 ret = string_set_append(&refglobs, optarg);
3678 goto out_free_refglobs;
3680 case IMAGEX_STAGING_DIR_OPTION:
3681 staging_dir = optarg;
3683 case IMAGEX_UNIX_DATA_OPTION:
3684 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3692 if (argc != 2 && argc != 3)
3697 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3698 imagex_progress_func, NULL);
3700 goto out_free_refglobs;
3702 wimlib_get_wim_info(wim, &info);
3705 /* Image explicitly specified. */
3706 image = wimlib_resolve_image(wim, argv[1]);
3708 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3712 /* No image specified; default to image 1, but only if the WIM
3713 * contains exactly one image. */
3715 if (info.image_count != 1) {
3716 imagex_error(T("\"%"TS"\" contains %d images; Please "
3717 "select one."), wimfile, info.image_count);
3725 if (refglobs.num_strings) {
3726 ret = wim_reference_globs(wim, &refglobs, open_flags);
3731 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3733 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3734 do_metadata_not_found_warning(wimfile, &info);
3736 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3738 image, wimfile, dir);
3744 string_set_destroy(&refglobs);
3750 goto out_free_refglobs;
3752 #endif /* WIM_MOUNTING_SUPPORTED */
3754 /* Rebuild a WIM file */
3756 imagex_optimize(int argc, tchar **argv, int cmd)
3759 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3760 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3761 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3762 uint32_t chunk_size = UINT32_MAX;
3763 uint32_t solid_chunk_size = UINT32_MAX;
3764 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3767 const tchar *wimfile;
3770 unsigned num_threads = 0;
3772 for_opt(c, optimize_options) {
3774 case IMAGEX_CHECK_OPTION:
3775 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3776 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3778 case IMAGEX_NOCHECK_OPTION:
3779 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3781 case IMAGEX_COMPRESS_OPTION:
3782 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3783 compression_type = get_compression_type(optarg);
3784 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3787 case IMAGEX_COMPRESS_SLOW_OPTION:
3788 set_compress_slow();
3789 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3791 case IMAGEX_RECOMPRESS_OPTION:
3792 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3794 case IMAGEX_CHUNK_SIZE_OPTION:
3795 chunk_size = parse_chunk_size(optarg);
3796 if (chunk_size == UINT32_MAX)
3799 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3800 solid_chunk_size = parse_chunk_size(optarg);
3801 if (solid_chunk_size == UINT32_MAX)
3804 case IMAGEX_SOLID_COMPRESS_OPTION:
3805 solid_ctype = get_compression_type(optarg);
3806 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3809 case IMAGEX_SOLID_OPTION:
3810 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3811 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3813 case IMAGEX_NO_SOLID_SORT_OPTION:
3814 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3816 case IMAGEX_THREADS_OPTION:
3817 num_threads = parse_num_threads(optarg);
3818 if (num_threads == UINT_MAX)
3821 case IMAGEX_PIPABLE_OPTION:
3822 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3824 case IMAGEX_NOT_PIPABLE_OPTION:
3825 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3827 case IMAGEX_UNSAFE_COMPACT_OPTION:
3828 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3842 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3843 imagex_progress_func, NULL);
3847 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3848 /* Change compression type. */
3849 ret = wimlib_set_output_compression_type(wim, compression_type);
3851 goto out_wimlib_free;
3854 if (chunk_size != UINT32_MAX) {
3855 /* Change chunk size. */
3856 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3858 goto out_wimlib_free;
3860 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3861 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3863 goto out_wimlib_free;
3865 if (solid_chunk_size != UINT32_MAX) {
3866 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3868 goto out_wimlib_free;
3871 old_size = file_get_size(wimfile);
3872 tprintf(T("\"%"TS"\" original size: "), wimfile);
3874 tputs(T("Unknown"));
3876 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3878 ret = wimlib_overwrite(wim, write_flags, num_threads);
3880 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3881 goto out_wimlib_free;
3884 new_size = file_get_size(wimfile);
3885 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3887 tputs(T("Unknown"));
3889 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3891 tfputs(T("Space saved: "), stdout);
3892 if (new_size != -1 && old_size != -1) {
3893 tprintf(T("%lld KiB\n"),
3894 ((long long)old_size - (long long)new_size) >> 10);
3896 tputs(T("Unknown"));
3905 usage(CMD_OPTIMIZE, stderr);
3911 /* Split a WIM into a spanned set */
3913 imagex_split(int argc, tchar **argv, int cmd)
3917 int write_flags = 0;
3918 unsigned long part_size;
3923 for_opt(c, split_options) {
3925 case IMAGEX_CHECK_OPTION:
3926 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3927 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3939 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3940 if (tmp == argv[2] || *tmp) {
3941 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3942 imagex_error(T("The part size must be an integer or "
3943 "floating-point number of megabytes."));
3946 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3947 imagex_progress_func, NULL);
3951 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3957 usage(CMD_SPLIT, stderr);
3963 #if WIM_MOUNTING_SUPPORTED
3964 /* Unmounts a mounted WIM image. */
3966 imagex_unmount(int argc, tchar **argv, int cmd)
3969 int unmount_flags = 0;
3972 for_opt(c, unmount_options) {
3974 case IMAGEX_COMMIT_OPTION:
3975 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3977 case IMAGEX_CHECK_OPTION:
3978 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3980 case IMAGEX_REBUILD_OPTION:
3981 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3983 case IMAGEX_LAZY_OPTION:
3984 case IMAGEX_FORCE_OPTION:
3985 /* Now, unmount is lazy by default. However, committing
3986 * the image will fail with
3987 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3988 * file descriptors on the WIM image. The
3989 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3990 * descriptors to be closed. */
3991 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3993 case IMAGEX_NEW_IMAGE_OPTION:
3994 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4005 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4006 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4007 imagex_error(T("--new-image is meaningless "
4008 "without --commit also specified!"));
4013 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4014 imagex_progress_func, NULL);
4016 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4017 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4019 "\tNote: Use --commit --force to force changes "
4020 "to be committed, regardless\n"
4021 "\t of open files.\n"));
4028 usage(CMD_UNMOUNT, stderr);
4033 #endif /* WIM_MOUNTING_SUPPORTED */
4036 * Add, delete, or rename files in a WIM image.
4039 imagex_update(int argc, tchar **argv, int cmd)
4041 const tchar *wimfile;
4045 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4046 int write_flags = 0;
4047 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4048 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4049 WIMLIB_ADD_FLAG_VERBOSE |
4050 WIMLIB_ADD_FLAG_WINCONFIG;
4051 int default_delete_flags = 0;
4052 unsigned num_threads = 0;
4054 tchar *cmd_file_contents;
4055 size_t cmd_file_nchars;
4056 struct wimlib_update_command *cmds;
4058 tchar *command_str = NULL;
4059 tchar *config_file = NULL;
4060 tchar *wimboot_config = NULL;
4062 for_opt(c, update_options) {
4064 /* Generic or write options */
4065 case IMAGEX_THREADS_OPTION:
4066 num_threads = parse_num_threads(optarg);
4067 if (num_threads == UINT_MAX)
4070 case IMAGEX_CHECK_OPTION:
4071 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4072 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4074 case IMAGEX_REBUILD_OPTION:
4075 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4077 case IMAGEX_COMMAND_OPTION:
4079 imagex_error(T("--command may only be specified "
4080 "one time. Please provide\n"
4081 " the update commands "
4082 "on standard input instead."));
4085 command_str = tstrdup(optarg);
4087 imagex_error(T("Out of memory!"));
4091 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4092 wimboot_config = optarg;
4094 /* Default delete options */
4095 case IMAGEX_FORCE_OPTION:
4096 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4098 case IMAGEX_RECURSIVE_OPTION:
4099 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4102 /* Global add option */
4103 case IMAGEX_CONFIG_OPTION:
4104 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4105 config_file = optarg;
4108 /* Default add options */
4109 case IMAGEX_VERBOSE_OPTION:
4110 /* No longer does anything. */
4112 case IMAGEX_DEREFERENCE_OPTION:
4113 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4115 case IMAGEX_UNIX_DATA_OPTION:
4116 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4118 case IMAGEX_NO_ACLS_OPTION:
4119 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4121 case IMAGEX_STRICT_ACLS_OPTION:
4122 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4124 case IMAGEX_NO_REPLACE_OPTION:
4125 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4127 case IMAGEX_UNSAFE_COMPACT_OPTION:
4128 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4137 if (argc != 1 && argc != 2)
4141 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4142 imagex_progress_func, NULL);
4144 goto out_free_command_str;
4147 /* Image explicitly specified. */
4148 image = wimlib_resolve_image(wim, argv[1]);
4149 ret = verify_image_exists_and_is_single(image, argv[1],
4152 goto out_wimlib_free;
4154 /* No image specified; default to image 1, but only if the WIM
4155 * contains exactly one image. */
4156 struct wimlib_wim_info info;
4158 wimlib_get_wim_info(wim, &info);
4159 if (info.image_count != 1) {
4160 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4161 wimfile, info.image_count);
4168 /* Read update commands from standard input, or the command string if
4171 cmd_file_contents = NULL;
4172 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4176 goto out_free_cmd_file_contents;
4178 } else if (!wimboot_config) {
4179 if (isatty(STDIN_FILENO)) {
4180 tputs(T("Reading update commands from standard input..."));
4181 recommend_man_page(CMD_UPDATE, stdout);
4183 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4184 if (!cmd_file_contents) {
4186 goto out_wimlib_free;
4189 /* Parse the update commands */
4190 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4194 goto out_free_cmd_file_contents;
4197 cmd_file_contents = NULL;
4202 /* Set default flags and capture config on the update commands */
4203 for (size_t i = 0; i < num_cmds; i++) {
4204 switch (cmds[i].op) {
4205 case WIMLIB_UPDATE_OP_ADD:
4206 cmds[i].add.add_flags |= default_add_flags;
4207 cmds[i].add.config_file = config_file;
4209 case WIMLIB_UPDATE_OP_DELETE:
4210 cmds[i].delete_.delete_flags |= default_delete_flags;
4217 /* Execute the update commands */
4218 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4222 if (wimboot_config) {
4223 /* --wimboot-config=FILE is short for an
4224 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4226 struct wimlib_update_command cmd;
4228 cmd.op = WIMLIB_UPDATE_OP_ADD;
4229 cmd.add.fs_source_path = wimboot_config;
4230 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4231 cmd.add.config_file = NULL;
4232 cmd.add.add_flags = 0;
4234 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4239 /* Overwrite the updated WIM */
4240 ret = wimlib_overwrite(wim, write_flags, num_threads);
4243 out_free_cmd_file_contents:
4244 free(cmd_file_contents);
4247 out_free_command_str:
4252 usage(CMD_UPDATE, stderr);
4255 goto out_free_command_str;
4258 /* Verify a WIM file. */
4260 imagex_verify(int argc, tchar **argv, int cmd)
4263 const tchar *wimfile;
4265 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4266 int verify_flags = 0;
4267 STRING_SET(refglobs);
4270 for_opt(c, verify_options) {
4272 case IMAGEX_REF_OPTION:
4273 ret = string_set_append(&refglobs, optarg);
4275 goto out_free_refglobs;
4277 case IMAGEX_NOCHECK_OPTION:
4278 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4290 imagex_error(T("Must specify a WIM file!"));
4292 imagex_error(T("At most one WIM file can be specified!"));
4298 ret = wimlib_open_wim_with_progress(wimfile,
4301 imagex_progress_func,
4304 goto out_free_refglobs;
4306 ret = wim_reference_globs(wim, &refglobs, open_flags);
4308 goto out_wimlib_free;
4310 ret = wimlib_verify_wim(wim, verify_flags);
4312 tputc(T('\n'), stderr);
4313 imagex_error(T("\"%"TS"\" failed verification!"),
4315 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4316 refglobs.num_strings == 0)
4318 imagex_printf(T("Note: if this WIM file is not standalone, "
4319 "use the --ref option to specify the other parts.\n"));
4322 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4329 string_set_destroy(&refglobs);
4333 usage(CMD_VERIFY, stderr);
4335 goto out_free_refglobs;
4338 struct imagex_command {
4340 int (*func)(int argc, tchar **argv, int cmd);
4343 static const struct imagex_command imagex_commands[] = {
4344 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4345 [CMD_APPLY] = {T("apply"), imagex_apply},
4346 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4347 [CMD_DELETE] = {T("delete"), imagex_delete},
4348 [CMD_DIR ] = {T("dir"), imagex_dir},
4349 [CMD_EXPORT] = {T("export"), imagex_export},
4350 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4351 [CMD_INFO] = {T("info"), imagex_info},
4352 [CMD_JOIN] = {T("join"), imagex_join},
4353 #if WIM_MOUNTING_SUPPORTED
4354 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4355 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4357 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4358 [CMD_SPLIT] = {T("split"), imagex_split},
4359 #if WIM_MOUNTING_SUPPORTED
4360 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4362 [CMD_UPDATE] = {T("update"), imagex_update},
4363 [CMD_VERIFY] = {T("verify"), imagex_verify},
4368 /* Can be a directory or source list file. But source list file is probably
4369 * a rare use case, so just say directory. */
4370 # define SOURCE_STR T("DIRECTORY")
4372 /* Can only be a directory */
4373 # define TARGET_STR T("DIRECTORY")
4376 /* Can be a directory, NTFS volume, or source list file. */
4377 # define SOURCE_STR T("SOURCE")
4379 /* Can be a directory or NTFS volume. */
4380 # define TARGET_STR T("TARGET")
4384 static const tchar *usage_strings[] = {
4387 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4388 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4389 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4390 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4391 " [--wimboot] [--unix-data] [--dereference] [--snapshot]\n"
4395 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4396 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4397 " [--no-attributes] [--rpfix] [--norpfix]\n"
4398 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4399 " [--compact=FORMAT]\n"
4403 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4404 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4405 " [--config=FILE] [--threads=NUM_THREADS]\n"
4406 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4407 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4408 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4413 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4417 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4421 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4422 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4423 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4424 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4425 " [--wimboot] [--solid]\n"
4429 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4430 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4431 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4432 " [--no-attributes] [--include-invalid-names]\n"
4433 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4437 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4438 " [--boot] [--check] [--nocheck] [--xml]\n"
4439 " [--extract-xml FILE] [--header] [--blobs]\n"
4440 " [--image-property NAME=VALUE]\n"
4444 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4446 #if WIM_MOUNTING_SUPPORTED
4449 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4450 " [--check] [--streams-interface=INTERFACE]\n"
4451 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4455 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4456 " [--check] [--streams-interface=INTERFACE]\n"
4457 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4463 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4464 " [--check] [--nocheck] [--solid]\n"
4469 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4471 #if WIM_MOUNTING_SUPPORTED
4474 " %"TS" DIRECTORY\n"
4475 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4480 " %"TS" WIMFILE [IMAGE]\n"
4481 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4482 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4483 " [--command=STRING] [--wimboot-config=FILE]\n"
4488 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4492 static const tchar *invocation_name;
4493 static int invocation_cmd = CMD_NONE;
4495 static const tchar *get_cmd_string(int cmd, bool nospace)
4497 static tchar buf[50];
4498 if (cmd == CMD_NONE) {
4499 return T("wimlib-imagex");
4500 } else if (invocation_cmd != CMD_NONE) {
4501 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4503 const tchar *format;
4506 format = T("%"TS"-%"TS"");
4508 format = T("%"TS" %"TS"");
4509 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4517 static const tchar *s =
4519 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4520 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4521 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4522 "This is free software: you are free to change and redistribute it.\n"
4523 "There is NO WARRANTY, to the extent permitted by law.\n"
4525 "Report bugs to "PACKAGE_BUGREPORT".\n"
4532 help_or_version(int argc, tchar **argv, int cmd)
4537 for (i = 1; i < argc; i++) {
4539 if (p[0] == T('-') && p[1] == T('-')) {
4541 if (!tstrcmp(p, T("help"))) {
4542 if (cmd == CMD_NONE)
4547 } else if (!tstrcmp(p, T("version"))) {
4556 print_usage_string(int cmd, FILE *fp)
4558 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4562 recommend_man_page(int cmd, FILE *fp)
4564 const tchar *format_str;
4566 format_str = T("Some uncommon options are not listed;\n"
4567 "See %"TS".pdf in the doc directory for more details.\n");
4569 format_str = T("Some uncommon options are not listed;\n"
4570 "Try `man %"TS"' for more details.\n");
4572 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4576 usage(int cmd, FILE *fp)
4578 tfprintf(fp, T("Usage:\n"));
4579 print_usage_string(cmd, fp);
4580 tfprintf(fp, T("\n"));
4581 recommend_man_page(cmd, fp);
4587 tfprintf(fp, T("Usage:\n"));
4588 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4589 print_usage_string(cmd, fp);
4590 tfprintf(fp, T("\n"));
4592 static const tchar *extra =
4595 " %"TS" --version\n"
4598 tfprintf(fp, extra, invocation_name, invocation_name);
4600 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4601 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4602 "For some commands IMAGE may be \"all\".\n"
4604 recommend_man_page(CMD_NONE, fp);
4607 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4608 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4609 * something else), while on Windows the command arguments will be UTF-16LE
4610 * encoded 'wchar_t' strings. */
4613 wmain(int argc, wchar_t **argv, wchar_t **envp)
4615 main(int argc, char **argv)
4622 imagex_info_file = stdout;
4623 invocation_name = tbasename(argv[0]);
4626 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4627 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4631 setlocale(LC_ALL, "");
4632 codeset = nl_langinfo(CODESET);
4633 if (!strstr(codeset, "UTF-8") &&
4634 !strstr(codeset, "UTF8") &&
4635 !strstr(codeset, "utf-8") &&
4636 !strstr(codeset, "utf8"))
4639 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4640 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4641 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4642 " to any value to force wimlib to use UTF-8.\n",
4648 #endif /* !__WIN32__ */
4651 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4652 if (igcase != NULL) {
4653 if (!tstrcmp(igcase, T("no")) ||
4654 !tstrcmp(igcase, T("0")))
4655 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4656 else if (!tstrcmp(igcase, T("yes")) ||
4657 !tstrcmp(igcase, T("1")))
4658 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4661 "WARNING: Ignoring unknown setting of "
4662 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4667 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4669 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4670 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4671 for (int i = 0; i < CMD_MAX; i++) {
4672 if (!tstrcmp(invocation_name + 3,
4673 imagex_commands[i].name))
4682 /* Unless already known from the invocation name, determine which
4683 * command was specified. */
4684 if (cmd == CMD_NONE) {
4686 imagex_error(T("No command specified!\n"));
4690 for (int i = 0; i < CMD_MAX; i++) {
4691 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4696 if (cmd != CMD_NONE) {
4702 /* Handle --help and --version. --help can be either for the program as
4703 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4704 * CMD_NONE). Note: help_or_version() will not return if a --help or
4705 * --version argument was found. */
4706 help_or_version(argc, argv, cmd);
4708 /* Bail if a valid command was not specified. */
4709 if (cmd == CMD_NONE) {
4710 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4715 /* Enable warning and error messages in wimlib to be more user-friendly.
4717 wimlib_set_print_errors(true);
4719 /* Initialize wimlib. */
4720 ret = wimlib_global_init(init_flags);
4722 goto out_check_status;
4724 /* Call the command handler function. */
4725 ret = imagex_commands[cmd].func(argc, argv, cmd);
4727 /* Check for error writing to standard output, especially since for some
4728 * commands, writing to standard output is part of the program's actual
4729 * behavior and not just for informational purposes. */
4730 if (ferror(stdout) || fclose(stdout)) {
4731 imagex_error_with_errno(T("error writing to standard output"));
4736 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4737 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4738 * error code from which an error message can be printed. */
4740 imagex_error(T("Exiting with error code %d:\n"
4742 wimlib_get_error_string(ret));
4743 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4744 imagex_error_with_errno(T("errno"));
4746 /* Make wimlib free any resources it's holding (although this is not
4747 * strictly necessary because the process is ending anyway). */
4748 wimlib_global_cleanup();