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 started;
1164 if (info->write_streams.compression_type != WIMLIB_COMPRESSION_TYPE_NONE) {
1165 imagex_printf(T("Using %"TS" compression "
1166 "with %u thread%"TS"\n"),
1167 wimlib_get_compression_type_string(
1168 info->write_streams.compression_type),
1169 info->write_streams.num_threads,
1170 (info->write_streams.num_threads == 1) ? T("") : T("s"));
1175 unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
1176 percent_done = TO_PERCENT(info->write_streams.completed_bytes,
1177 info->write_streams.total_bytes);
1179 imagex_printf(T("\rArchiving file data: %"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1180 info->write_streams.completed_bytes >> unit_shift,
1182 info->write_streams.total_bytes >> unit_shift,
1185 if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
1186 imagex_printf(T("\n"));
1188 case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
1189 imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
1190 if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
1191 imagex_printf(T("\n"));
1193 imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
1194 info->scan.wim_target_path);
1196 memset(&last_scan_progress, 0, sizeof(last_scan_progress));
1198 case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
1199 switch (info->scan.status) {
1200 case WIMLIB_SCAN_DENTRY_OK:
1201 report_scan_progress(&info->scan, false);
1203 case WIMLIB_SCAN_DENTRY_EXCLUDED:
1204 imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
1206 case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
1207 imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
1208 " \"%"TS"\" from capture\n"), info->scan.cur_path);
1210 case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
1211 /* Symlink fixups are enabled by default. This is
1212 * mainly intended for Windows, which for some reason
1213 * uses absolute junctions (with drive letters!) in the
1214 * default installation. On UNIX-like systems, warn the
1215 * user when fixing the target of an absolute symbolic
1216 * link, so they know to disable this if they want. */
1218 imagex_printf(T("\nWARNING: Adjusted target of "
1219 "absolute symbolic link \"%"TS"\"\n"
1220 " (Use --norpfix to capture "
1221 "absolute symbolic links as-is)\n"),
1222 info->scan.cur_path);
1229 case WIMLIB_PROGRESS_MSG_SCAN_END:
1230 report_scan_progress(&info->scan, true);
1231 imagex_printf(T("\n"));
1233 case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
1234 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1235 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1236 info->integrity.total_bytes);
1237 imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
1238 "of %"PRIu64" %"TS" (%u%%) done"),
1239 info->integrity.filename,
1240 info->integrity.completed_bytes >> unit_shift,
1242 info->integrity.total_bytes >> unit_shift,
1245 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1246 imagex_printf(T("\n"));
1248 case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
1249 unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
1250 percent_done = TO_PERCENT(info->integrity.completed_bytes,
1251 info->integrity.total_bytes);
1252 imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
1253 "of %"PRIu64" %"TS" (%u%%) done"),
1254 info->integrity.completed_bytes >> unit_shift,
1256 info->integrity.total_bytes >> unit_shift,
1259 if (info->integrity.completed_bytes == info->integrity.total_bytes)
1260 imagex_printf(T("\n"));
1262 case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
1263 imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
1264 "to %"TS" \"%"TS"\"\n"),
1265 info->extract.image,
1266 info->extract.image_name,
1267 info->extract.wimfile_name,
1268 ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
1269 T("NTFS volume") : T("directory")),
1270 info->extract.target);
1272 case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
1273 if (info->extract.end_file_count >= 2000) {
1274 percent_done = TO_PERCENT(info->extract.current_file_count,
1275 info->extract.end_file_count);
1276 imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1277 info->extract.current_file_count,
1278 info->extract.end_file_count, percent_done);
1279 if (info->extract.current_file_count == info->extract.end_file_count)
1280 imagex_printf(T("\n"));
1283 case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
1284 percent_done = TO_PERCENT(info->extract.completed_bytes,
1285 info->extract.total_bytes);
1286 unit_shift = get_unit(info->extract.total_bytes, &unit_name);
1287 imagex_printf(T("\rExtracting file data: "
1288 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1289 info->extract.completed_bytes >> unit_shift,
1291 info->extract.total_bytes >> unit_shift,
1294 if (info->extract.completed_bytes >= info->extract.total_bytes)
1295 imagex_printf(T("\n"));
1297 case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
1298 if (info->extract.end_file_count >= 2000) {
1299 percent_done = TO_PERCENT(info->extract.current_file_count,
1300 info->extract.end_file_count);
1301 imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
1302 info->extract.current_file_count,
1303 info->extract.end_file_count, percent_done);
1304 if (info->extract.current_file_count == info->extract.end_file_count)
1305 imagex_printf(T("\n"));
1308 case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
1309 if (info->extract.total_parts != 1) {
1310 imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
1311 info->extract.part_number,
1312 info->extract.total_parts);
1315 case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
1316 percent_done = TO_PERCENT(info->split.completed_bytes,
1317 info->split.total_bytes);
1318 unit_shift = get_unit(info->split.total_bytes, &unit_name);
1319 imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
1320 "%"PRIu64" %"TS" (%u%%) written\n"),
1321 info->split.part_name,
1322 info->split.cur_part_number,
1323 info->split.total_parts,
1324 info->split.completed_bytes >> unit_shift,
1326 info->split.total_bytes >> unit_shift,
1330 case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
1331 if (info->split.completed_bytes == info->split.total_bytes) {
1332 imagex_printf(T("Finished writing split WIM part %u of %u\n"),
1333 info->split.cur_part_number,
1334 info->split.total_parts);
1337 case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
1338 switch (info->update.command->op) {
1339 case WIMLIB_UPDATE_OP_DELETE:
1340 imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
1341 info->update.command->delete_.wim_path);
1343 case WIMLIB_UPDATE_OP_RENAME:
1344 imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
1345 info->update.command->rename.wim_source_path,
1346 info->update.command->rename.wim_target_path);
1348 case WIMLIB_UPDATE_OP_ADD:
1353 case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
1354 imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
1355 info->replace.path_in_wim);
1357 case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
1358 imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
1359 info->wimboot_exclude.path_in_wim);
1361 case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
1362 if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
1363 if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
1364 imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
1365 info->unmount.mounted_wim,
1366 info->unmount.mounted_image);
1368 imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
1369 info->unmount.mounted_wim,
1370 info->unmount.mounted_image);
1371 imagex_printf(T("\t(Use --commit to keep changes.)\n"));
1375 case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
1376 imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
1377 info->verify_image.current_image,
1378 info->verify_image.total_images);
1380 case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
1381 percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
1382 info->verify_streams.total_bytes);
1383 unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
1384 imagex_printf(T("\rVerifying file data: "
1385 "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
1386 info->verify_streams.completed_bytes >> unit_shift,
1388 info->verify_streams.total_bytes >> unit_shift,
1391 if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
1392 imagex_printf(T("\n"));
1397 fflush(imagex_info_file);
1398 return WIMLIB_PROGRESS_STATUS_CONTINUE;
1402 parse_num_threads(const tchar *optarg)
1405 unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
1406 if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
1407 imagex_error(T("Number of threads must be a non-negative integer!"));
1415 parse_chunk_size(const tchar *optarg)
1418 uint64_t chunk_size = tstrtoul(optarg, &tmp, 10);
1419 if (chunk_size == 0) {
1420 imagex_error(T("Invalid chunk size specification; must be a positive integer\n"
1421 " with optional K, M, or G suffix"));
1425 if (*tmp == T('k') || *tmp == T('K')) {
1428 } else if (*tmp == T('m') || *tmp == T('M')) {
1431 } else if (*tmp == T('g') || *tmp == T('G')) {
1435 if (*tmp && !(*tmp == T('i') && *(tmp + 1) == T('B'))) {
1436 imagex_error(T("Invalid chunk size specification; suffix must be K, M, or G"));
1440 if (chunk_size >= UINT32_MAX) {
1441 imagex_error(T("Invalid chunk size specification; the value is too large!"));
1449 * Parse an option passed to an update command.
1451 * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
1454 * @option: Text string for the option (beginning with --)
1456 * @cmd: `struct wimlib_update_command' that is being constructed for
1459 * Returns true if the option was recognized; false if not.
1462 update_command_add_option(int op, const tchar *option,
1463 struct wimlib_update_command *cmd)
1465 bool recognized = true;
1467 case WIMLIB_UPDATE_OP_ADD:
1468 if (!tstrcmp(option, T("--verbose")))
1469 cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
1470 else if (!tstrcmp(option, T("--unix-data")))
1471 cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1472 else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
1473 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1474 else if (!tstrcmp(option, T("--strict-acls")))
1475 cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1476 else if (!tstrcmp(option, T("--dereference")))
1477 cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1478 else if (!tstrcmp(option, T("--no-replace")))
1479 cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
1483 case WIMLIB_UPDATE_OP_DELETE:
1484 if (!tstrcmp(option, T("--force")))
1485 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
1486 else if (!tstrcmp(option, T("--recursive")))
1487 cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
1498 /* How many nonoption arguments each `imagex update' command expects */
1499 static const unsigned update_command_num_nonoptions[] = {
1500 [WIMLIB_UPDATE_OP_ADD] = 2,
1501 [WIMLIB_UPDATE_OP_DELETE] = 1,
1502 [WIMLIB_UPDATE_OP_RENAME] = 2,
1506 update_command_add_nonoption(int op, const tchar *nonoption,
1507 struct wimlib_update_command *cmd,
1508 unsigned num_nonoptions)
1511 case WIMLIB_UPDATE_OP_ADD:
1512 if (num_nonoptions == 0)
1513 cmd->add.fs_source_path = (tchar*)nonoption;
1515 cmd->add.wim_target_path = (tchar*)nonoption;
1517 case WIMLIB_UPDATE_OP_DELETE:
1518 cmd->delete_.wim_path = (tchar*)nonoption;
1520 case WIMLIB_UPDATE_OP_RENAME:
1521 if (num_nonoptions == 0)
1522 cmd->rename.wim_source_path = (tchar*)nonoption;
1524 cmd->rename.wim_target_path = (tchar*)nonoption;
1530 * Parse a command passed on stdin to `imagex update'.
1532 * @line: Text of the command.
1533 * @len: Length of the line, including a null terminator
1536 * @command: A `struct wimlib_update_command' to fill in from the parsed
1539 * @line_number: Line number of the command, for diagnostics.
1541 * Returns true on success; returns false on parse error.
1544 parse_update_command(tchar *line, size_t len,
1545 struct wimlib_update_command *command,
1549 tchar *command_name;
1551 size_t num_nonoptions;
1553 /* Get the command name ("add", "delete", "rename") */
1554 ret = parse_string(&line, &len, &command_name);
1555 if (ret != PARSE_STRING_SUCCESS)
1558 if (!tstrcasecmp(command_name, T("add"))) {
1559 op = WIMLIB_UPDATE_OP_ADD;
1560 } else if (!tstrcasecmp(command_name, T("delete"))) {
1561 op = WIMLIB_UPDATE_OP_DELETE;
1562 } else if (!tstrcasecmp(command_name, T("rename"))) {
1563 op = WIMLIB_UPDATE_OP_RENAME;
1565 imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
1566 command_name, line_number);
1571 /* Parse additional options and non-options as needed */
1576 ret = parse_string(&line, &len, &next_string);
1577 if (ret == PARSE_STRING_NONE) /* End of line */
1579 else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
1581 if (next_string[0] == T('-') && next_string[1] == T('-')) {
1583 if (!update_command_add_option(op, next_string, command))
1585 imagex_error(T("Unrecognized option \"%"TS"\" to "
1586 "update command \"%"TS"\" on line %zu"),
1587 next_string, command_name, line_number);
1593 if (num_nonoptions == update_command_num_nonoptions[op])
1595 imagex_error(T("Unexpected argument \"%"TS"\" in "
1596 "update command on line %zu\n"
1597 " (The \"%"TS"\" command only "
1598 "takes %zu nonoption arguments!)\n"),
1599 next_string, line_number,
1600 command_name, num_nonoptions);
1603 update_command_add_nonoption(op, next_string,
1604 command, num_nonoptions);
1609 if (num_nonoptions != update_command_num_nonoptions[op]) {
1610 imagex_error(T("Not enough arguments to update command "
1611 "\"%"TS"\" on line %zu"), command_name, line_number);
1617 static struct wimlib_update_command *
1618 parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
1619 size_t *num_cmds_ret)
1623 struct wimlib_update_command *cmds;
1626 nlines = text_file_count_lines(cmd_file_contents_p,
1631 /* Always allocate at least 1 slot, just in case the implementation of
1632 * calloc() returns NULL if 0 bytes are requested. */
1633 cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
1635 imagex_error(T("out of memory"));
1638 p = *cmd_file_contents_p;
1640 for (i = 0; i < nlines; i++) {
1641 /* XXX: Could use rawmemchr() here instead, but it may not be
1642 * available on all platforms. */
1643 tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
1644 size_t len = endp - p + 1;
1646 if (!is_comment_line(p, len)) {
1647 if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
1658 /* Apply one image, or all images, from a WIM file to a directory, OR apply
1659 * one image from a WIM file to an NTFS volume. */
1661 imagex_apply(int argc, tchar **argv, int cmd)
1665 int image = WIMLIB_NO_IMAGE;
1667 struct wimlib_wim_info info;
1669 const tchar *wimfile;
1670 const tchar *target;
1671 const tchar *image_num_or_name = NULL;
1672 int extract_flags = 0;
1674 STRING_SET(refglobs);
1676 for_opt(c, apply_options) {
1678 case IMAGEX_CHECK_OPTION:
1679 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1681 case IMAGEX_VERBOSE_OPTION:
1682 /* No longer does anything. */
1684 case IMAGEX_REF_OPTION:
1685 ret = string_set_append(&refglobs, optarg);
1687 goto out_free_refglobs;
1689 case IMAGEX_UNIX_DATA_OPTION:
1690 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
1692 case IMAGEX_NO_ACLS_OPTION:
1693 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
1695 case IMAGEX_STRICT_ACLS_OPTION:
1696 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
1698 case IMAGEX_NO_ATTRIBUTES_OPTION:
1699 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
1701 case IMAGEX_NORPFIX_OPTION:
1702 extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
1704 case IMAGEX_RPFIX_OPTION:
1705 extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
1707 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
1708 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
1709 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
1711 case IMAGEX_RESUME_OPTION:
1712 extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;
1714 case IMAGEX_WIMBOOT_OPTION:
1715 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
1717 case IMAGEX_COMPACT_OPTION:
1718 ret = set_compact_mode(optarg, &extract_flags);
1720 goto out_free_refglobs;
1728 if (argc != 2 && argc != 3)
1733 if (!tstrcmp(wimfile, T("-"))) {
1734 /* Attempt to apply pipable WIM from standard input. */
1736 image_num_or_name = NULL;
1739 image_num_or_name = argv[1];
1744 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
1745 imagex_progress_func, NULL);
1747 goto out_free_refglobs;
1749 wimlib_get_wim_info(wim, &info);
1752 /* Image explicitly specified. */
1753 image_num_or_name = argv[1];
1754 image = wimlib_resolve_image(wim, image_num_or_name);
1755 ret = verify_image_exists(image, image_num_or_name, wimfile);
1757 goto out_wimlib_free;
1760 /* No image specified; default to image 1, but only if the WIM
1761 * contains exactly one image. */
1763 if (info.image_count != 1) {
1764 imagex_error(T("\"%"TS"\" contains %d images; "
1765 "Please select one (or all)."),
1766 wimfile, info.image_count);
1775 if (refglobs.num_strings) {
1777 imagex_error(T("Can't specify --ref when applying from stdin!"));
1779 goto out_wimlib_free;
1781 ret = wim_reference_globs(wim, &refglobs, open_flags);
1783 goto out_wimlib_free;
1788 /* Interpret a regular file or block device target as an NTFS
1792 if (tstat(target, &stbuf)) {
1793 if (errno != ENOENT) {
1794 imagex_error_with_errno(T("Failed to stat \"%"TS"\""),
1797 goto out_wimlib_free;
1800 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode))
1801 extract_flags |= WIMLIB_EXTRACT_FLAG_NTFS;
1807 ret = wimlib_extract_image(wim, image, target, extract_flags);
1809 set_fd_to_binary_mode(STDIN_FILENO);
1810 ret = wimlib_extract_image_from_pipe_with_progress(
1815 imagex_progress_func,
1819 imagex_printf(T("Done applying WIM image.\n"));
1820 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
1822 do_resource_not_found_warning(wimfile, &info, &refglobs);
1824 imagex_error(T( "If you are applying an image "
1825 "from a split pipable WIM,\n"
1826 " make sure you have "
1827 "concatenated together all parts."));
1829 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND && wim) {
1830 do_metadata_not_found_warning(wimfile, &info);
1835 string_set_destroy(&refglobs);
1839 usage(CMD_APPLY, stderr);
1841 goto out_free_refglobs;
1844 /* Create a WIM image from a directory tree, NTFS volume, or multiple files or
1845 * directory trees. 'wimlib-imagex capture': create a new WIM file containing
1846 * the desired image. 'wimlib-imagex append': add a new image to an existing
1849 imagex_capture_or_append(int argc, tchar **argv, int cmd)
1853 int add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
1854 WIMLIB_ADD_FLAG_WINCONFIG |
1855 WIMLIB_ADD_FLAG_VERBOSE;
1856 int write_flags = 0;
1857 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
1858 uint32_t chunk_size = UINT32_MAX;
1859 uint32_t solid_chunk_size = UINT32_MAX;
1860 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
1861 const tchar *wimfile;
1864 STRING_SET(image_properties);
1867 STRING_SET(base_wimfiles);
1868 WIMStruct **base_wims;
1870 WIMStruct *template_wim;
1871 const tchar *template_wimfile = NULL;
1872 const tchar *template_image_name_or_num = NULL;
1873 int template_image = WIMLIB_NO_IMAGE;
1876 unsigned num_threads = 0;
1881 tchar *config_file = NULL;
1883 bool source_list = false;
1884 size_t source_list_nchars = 0;
1885 tchar *source_list_contents;
1886 bool capture_sources_malloced;
1887 struct wimlib_capture_source *capture_sources;
1889 bool name_defaulted;
1891 for_opt(c, capture_or_append_options) {
1893 case IMAGEX_BOOT_OPTION:
1894 add_flags |= WIMLIB_ADD_FLAG_BOOT;
1896 case IMAGEX_CHECK_OPTION:
1897 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
1898 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
1900 case IMAGEX_NOCHECK_OPTION:
1901 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
1903 case IMAGEX_CONFIG_OPTION:
1904 config_file = optarg;
1905 add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
1907 case IMAGEX_COMPRESS_OPTION:
1908 compression_type = get_compression_type(optarg);
1909 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
1912 case IMAGEX_COMPRESS_SLOW_OPTION:
1913 set_compress_slow();
1915 case IMAGEX_CHUNK_SIZE_OPTION:
1916 chunk_size = parse_chunk_size(optarg);
1917 if (chunk_size == UINT32_MAX)
1920 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
1921 solid_chunk_size = parse_chunk_size(optarg);
1922 if (solid_chunk_size == UINT32_MAX)
1925 case IMAGEX_SOLID_COMPRESS_OPTION:
1926 solid_ctype = get_compression_type(optarg);
1927 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
1930 case IMAGEX_SOLID_OPTION:
1931 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
1933 case IMAGEX_NO_SOLID_SORT_OPTION:
1934 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
1936 case IMAGEX_FLAGS_OPTION: {
1937 tchar *p = alloca((6 + tstrlen(optarg) + 1) * sizeof(tchar));
1938 tsprintf(p, T("FLAGS=%"TS), optarg);
1939 ret = string_set_append(&image_properties, p);
1944 case IMAGEX_IMAGE_PROPERTY_OPTION:
1945 ret = append_image_property_argument(&image_properties);
1949 case IMAGEX_DEREFERENCE_OPTION:
1950 add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
1952 case IMAGEX_VERBOSE_OPTION:
1953 /* No longer does anything. */
1955 case IMAGEX_THREADS_OPTION:
1956 num_threads = parse_num_threads(optarg);
1957 if (num_threads == UINT_MAX)
1960 case IMAGEX_REBUILD_OPTION:
1961 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
1963 case IMAGEX_UNIX_DATA_OPTION:
1964 add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
1966 case IMAGEX_SOURCE_LIST_OPTION:
1969 case IMAGEX_NO_ACLS_OPTION:
1970 add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
1972 case IMAGEX_STRICT_ACLS_OPTION:
1973 add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
1975 case IMAGEX_RPFIX_OPTION:
1976 add_flags |= WIMLIB_ADD_FLAG_RPFIX;
1978 case IMAGEX_NORPFIX_OPTION:
1979 add_flags |= WIMLIB_ADD_FLAG_NORPFIX;
1981 case IMAGEX_PIPABLE_OPTION:
1982 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
1984 case IMAGEX_NOT_PIPABLE_OPTION:
1985 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
1987 case IMAGEX_UPDATE_OF_OPTION:
1988 if (template_image_name_or_num) {
1989 imagex_error(T("'--update-of' can only be "
1990 "specified one time!"));
1994 colon = tstrrchr(optarg, T(':'));
1997 template_wimfile = optarg;
1999 template_image_name_or_num = colon + 1;
2001 template_wimfile = NULL;
2002 template_image_name_or_num = optarg;
2006 case IMAGEX_DELTA_FROM_OPTION:
2007 if (cmd != CMD_CAPTURE) {
2008 imagex_error(T("'--delta-from' is only "
2009 "valid for capture!"));
2012 ret = string_set_append(&base_wimfiles, optarg);
2015 write_flags |= WIMLIB_WRITE_FLAG_SKIP_EXTERNAL_WIMS;
2017 case IMAGEX_WIMBOOT_OPTION:
2018 add_flags |= WIMLIB_ADD_FLAG_WIMBOOT;
2020 case IMAGEX_UNSAFE_COMPACT_OPTION:
2021 if (cmd != CMD_APPEND) {
2022 imagex_error(T("'--unsafe-compact' is only "
2023 "valid for append!"));
2026 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2028 case IMAGEX_SNAPSHOT_OPTION:
2029 add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT;
2038 if (argc < 2 || argc > 4)
2044 /* Set default compression type and parameters. */
2047 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
2048 /* No compression type specified. Use the default. */
2050 if (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) {
2051 /* With --wimboot, default to XPRESS compression. */
2052 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
2053 } else if (write_flags & WIMLIB_WRITE_FLAG_SOLID) {
2054 /* With --solid, default to LZMS compression. (However,
2055 * this will not affect solid resources!) */
2056 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
2058 /* Otherwise, default to LZX compression. */
2059 compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
2063 if (!tstrcmp(wimfile, T("-"))) {
2064 /* Writing captured WIM to standard output. */
2066 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2067 imagex_error("Can't write a non-pipable WIM to "
2068 "standard output! Specify --pipable\n"
2069 " if you want to create a pipable WIM "
2070 "(but read the docs first).");
2074 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2076 if (cmd == CMD_APPEND) {
2077 imagex_error(T("Using standard output for append does "
2078 "not make sense."));
2081 wim_fd = STDOUT_FILENO;
2083 imagex_info_file = stderr;
2084 set_fd_to_binary_mode(wim_fd);
2087 /* If template image was specified using --update-of=IMAGE rather
2088 * than --update-of=WIMFILE:IMAGE, set the default WIMFILE. */
2089 if (template_image_name_or_num && !template_wimfile) {
2090 if (base_wimfiles.num_strings == 1) {
2091 /* Capturing delta WIM based on single WIM: default to
2093 template_wimfile = base_wimfiles.strings[0];
2094 } else if (cmd == CMD_APPEND) {
2095 /* Appending to WIM: default to WIM being appended to.
2097 template_wimfile = wimfile;
2099 /* Capturing a normal (non-delta) WIM, so the WIM file
2100 * *must* be explicitly specified. */
2101 if (base_wimfiles.num_strings > 1) {
2102 imagex_error(T("For capture of delta WIM "
2103 "based on multiple existing "
2105 " '--update-of' must "
2106 "specify WIMFILE:IMAGE!"));
2108 imagex_error(T("For capture of non-delta WIM, "
2109 "'--update-of' must specify "
2118 name_defaulted = false;
2120 /* Set default name to SOURCE argument, omitting any directory
2121 * prefixes and trailing slashes. This requires making a copy
2122 * of @source. Leave some free characters at the end in case we
2123 * append a number to keep the name unique. */
2124 size_t source_name_len;
2126 source_name_len = tstrlen(source);
2127 source_copy = alloca((source_name_len + 1 + 25) * sizeof(tchar));
2128 name = tbasename(tstrcpy(source_copy, source));
2129 name_defaulted = true;
2132 /* Image description (if given). */
2134 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
2135 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
2136 ret = string_set_append(&image_properties, p);
2142 /* Set up capture sources in source list mode */
2143 if (source[0] == T('-') && source[1] == T('\0')) {
2144 source_list_contents = stdin_get_text_contents(&source_list_nchars);
2146 source_list_contents = file_get_text_contents(source,
2147 &source_list_nchars);
2149 if (!source_list_contents)
2152 capture_sources = parse_source_list(&source_list_contents,
2155 if (!capture_sources) {
2157 goto out_free_source_list_contents;
2159 capture_sources_malloced = true;
2161 /* Set up capture source in non-source-list mode. */
2162 capture_sources = alloca(sizeof(struct wimlib_capture_source));
2163 capture_sources[0].fs_source_path = source;
2164 capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH;
2165 capture_sources[0].reserved = 0;
2167 capture_sources_malloced = false;
2168 source_list_contents = NULL;
2171 /* Open the existing WIM, or create a new one. */
2172 if (cmd == CMD_APPEND) {
2173 ret = wimlib_open_wim_with_progress(wimfile,
2174 open_flags | WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2176 imagex_progress_func,
2179 goto out_free_capture_sources;
2181 ret = wimlib_create_new_wim(compression_type, &wim);
2183 goto out_free_capture_sources;
2184 wimlib_register_progress_function(wim, imagex_progress_func, NULL);
2187 /* Set chunk size if non-default. */
2188 if (chunk_size != UINT32_MAX) {
2189 ret = wimlib_set_output_chunk_size(wim, chunk_size);
2192 } else if ((add_flags & WIMLIB_ADD_FLAG_WIMBOOT)) {
2194 int ctype = compression_type;
2196 if (cmd == CMD_APPEND) {
2197 struct wimlib_wim_info info;
2198 wimlib_get_wim_info(wim, &info);
2199 ctype = info.compression_type;
2202 if (ctype == WIMLIB_COMPRESSION_TYPE_XPRESS) {
2203 ret = wimlib_set_output_chunk_size(wim, 4096);
2208 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
2209 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
2213 if (solid_chunk_size != UINT32_MAX) {
2214 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
2220 /* Detect if source is regular file or block device and set NTFS volume
2225 if (tstat(source, &stbuf) == 0) {
2226 if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
2227 imagex_printf(T("Capturing WIM image from NTFS "
2228 "filesystem on \"%"TS"\"\n"), source);
2229 add_flags |= WIMLIB_ADD_FLAG_NTFS;
2232 if (errno != ENOENT) {
2233 imagex_error_with_errno(T("Failed to stat "
2234 "\"%"TS"\""), source);
2242 /* If the user did not specify an image name, and the basename of the
2243 * source already exists as an image name in the WIM file, append a
2244 * suffix to make it unique. */
2245 if (cmd == CMD_APPEND && name_defaulted) {
2246 unsigned long conflict_idx;
2247 tchar *name_end = tstrchr(name, T('\0'));
2248 for (conflict_idx = 1;
2249 wimlib_image_name_in_use(wim, name);
2252 tsprintf(name_end, T(" (%lu)"), conflict_idx);
2256 /* If capturing a delta WIM, reference resources from the base WIMs
2257 * before adding the new image. */
2258 if (base_wimfiles.num_strings) {
2259 base_wims = calloc(base_wimfiles.num_strings,
2260 sizeof(base_wims[0]));
2261 if (base_wims == NULL) {
2262 imagex_error(T("Out of memory!"));
2267 for (size_t i = 0; i < base_wimfiles.num_strings; i++) {
2268 ret = wimlib_open_wim_with_progress(
2269 base_wimfiles.strings[i], open_flags,
2270 &base_wims[i], imagex_progress_func, NULL);
2272 goto out_free_base_wims;
2276 ret = wimlib_reference_resources(wim, base_wims,
2277 base_wimfiles.num_strings, 0);
2279 goto out_free_base_wims;
2281 if (base_wimfiles.num_strings == 1) {
2282 imagex_printf(T("Capturing delta WIM based on \"%"TS"\"\n"),
2283 base_wimfiles.strings[0]);
2285 imagex_printf(T("Capturing delta WIM based on %u WIMs\n"),
2286 base_wimfiles.num_strings);
2293 /* If capturing or appending as an update of an existing (template) image,
2294 * open the WIM if needed and parse the image index. */
2295 if (template_image_name_or_num) {
2298 if (base_wimfiles.num_strings == 1 &&
2299 template_wimfile == base_wimfiles.strings[0]) {
2300 template_wim = base_wims[0];
2301 } else if (template_wimfile == wimfile) {
2304 ret = wimlib_open_wim_with_progress(template_wimfile,
2307 imagex_progress_func,
2310 goto out_free_base_wims;
2313 template_image = wimlib_resolve_image(template_wim,
2314 template_image_name_or_num);
2316 if (template_image_name_or_num[0] == T('-')) {
2319 struct wimlib_wim_info info;
2321 wimlib_get_wim_info(template_wim, &info);
2322 n = tstrtoul(template_image_name_or_num + 1, &tmp, 10);
2323 if (n >= 1 && n <= info.image_count &&
2325 tmp != template_image_name_or_num + 1)
2327 template_image = info.image_count - (n - 1);
2330 ret = verify_image_exists_and_is_single(template_image,
2331 template_image_name_or_num,
2334 goto out_free_template_wim;
2336 template_wim = NULL;
2339 ret = wimlib_add_image_multisource(wim,
2346 goto out_free_template_wim;
2348 if (image_properties.num_strings || template_image_name_or_num) {
2349 /* User asked to set additional image properties, or an image on
2350 * which the added one is to be based has been specified with
2352 struct wimlib_wim_info info;
2354 wimlib_get_wim_info(wim, &info);
2356 ret = apply_image_properties(&image_properties, wim,
2357 info.image_count, NULL);
2359 goto out_free_template_wim;
2361 /* Reference template image if the user provided one. */
2362 if (template_image_name_or_num) {
2363 imagex_printf(T("Using image %d "
2364 "from \"%"TS"\" as template\n"),
2365 template_image, template_wimfile);
2366 ret = wimlib_reference_template_image(wim,
2372 goto out_free_template_wim;
2376 /* Write the new WIM or overwrite the existing WIM with the new image
2378 if (cmd == CMD_APPEND) {
2379 ret = wimlib_overwrite(wim, write_flags, num_threads);
2380 } else if (wimfile) {
2381 ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
2382 write_flags, num_threads);
2384 ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
2385 write_flags, num_threads);
2387 out_free_template_wim:
2388 /* template_wim may alias base_wims[0] or wim. */
2389 if ((base_wimfiles.num_strings != 1 || template_wim != base_wims[0]) &&
2390 template_wim != wim)
2391 wimlib_free(template_wim);
2393 for (size_t i = 0; i < base_wimfiles.num_strings; i++)
2394 wimlib_free(base_wims[i]);
2398 out_free_capture_sources:
2399 if (capture_sources_malloced)
2400 free(capture_sources);
2401 out_free_source_list_contents:
2402 free(source_list_contents);
2404 string_set_destroy(&image_properties);
2405 string_set_destroy(&base_wimfiles);
2415 /* Remove image(s) from a WIM. */
2417 imagex_delete(int argc, tchar **argv, int cmd)
2420 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
2421 int write_flags = 0;
2422 const tchar *wimfile;
2423 const tchar *image_num_or_name;
2428 for_opt(c, delete_options) {
2430 case IMAGEX_CHECK_OPTION:
2431 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2432 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2434 case IMAGEX_SOFT_OPTION:
2435 write_flags |= WIMLIB_WRITE_FLAG_SOFT_DELETE;
2437 case IMAGEX_UNSAFE_COMPACT_OPTION:
2438 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2449 imagex_error(T("Must specify a WIM file"));
2451 imagex_error(T("Must specify an image"));
2455 image_num_or_name = argv[1];
2457 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
2458 imagex_progress_func, NULL);
2462 image = wimlib_resolve_image(wim, image_num_or_name);
2464 ret = verify_image_exists(image, image_num_or_name, wimfile);
2466 goto out_wimlib_free;
2468 ret = wimlib_delete_image(wim, image);
2470 imagex_error(T("Failed to delete image from \"%"TS"\""),
2472 goto out_wimlib_free;
2475 ret = wimlib_overwrite(wim, write_flags, 0);
2477 imagex_error(T("Failed to write the file \"%"TS"\" with image "
2478 "deleted"), wimfile);
2486 usage(CMD_DELETE, stderr);
2491 struct print_dentry_options {
2496 print_dentry_full_path(const struct wimlib_dir_entry *dentry)
2498 tprintf(T("%"TS"\n"), dentry->full_path);
2501 static const struct {
2504 } file_attr_flags[] = {
2505 {WIMLIB_FILE_ATTRIBUTE_READONLY, T("READONLY")},
2506 {WIMLIB_FILE_ATTRIBUTE_HIDDEN, T("HIDDEN")},
2507 {WIMLIB_FILE_ATTRIBUTE_SYSTEM, T("SYSTEM")},
2508 {WIMLIB_FILE_ATTRIBUTE_DIRECTORY, T("DIRECTORY")},
2509 {WIMLIB_FILE_ATTRIBUTE_ARCHIVE, T("ARCHIVE")},
2510 {WIMLIB_FILE_ATTRIBUTE_DEVICE, T("DEVICE")},
2511 {WIMLIB_FILE_ATTRIBUTE_NORMAL, T("NORMAL")},
2512 {WIMLIB_FILE_ATTRIBUTE_TEMPORARY, T("TEMPORARY")},
2513 {WIMLIB_FILE_ATTRIBUTE_SPARSE_FILE, T("SPARSE_FILE")},
2514 {WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT, T("REPARSE_POINT")},
2515 {WIMLIB_FILE_ATTRIBUTE_COMPRESSED, T("COMPRESSED")},
2516 {WIMLIB_FILE_ATTRIBUTE_OFFLINE, T("OFFLINE")},
2517 {WIMLIB_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, T("NOT_CONTENT_INDEXED")},
2518 {WIMLIB_FILE_ATTRIBUTE_ENCRYPTED, T("ENCRYPTED")},
2519 {WIMLIB_FILE_ATTRIBUTE_VIRTUAL, T("VIRTUAL")},
2522 #define TIMESTR_MAX 100
2525 timespec_to_string(const struct timespec *spec, tchar *buf)
2527 time_t t = spec->tv_sec;
2530 tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
2531 buf[TIMESTR_MAX - 1] = '\0';
2535 print_time(const tchar *type, const struct timespec *spec)
2537 tchar timestr[TIMESTR_MAX];
2539 timespec_to_string(spec, timestr);
2541 tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
2544 static void print_byte_field(const uint8_t field[], size_t len)
2547 tprintf(T("%02hhx"), *field++);
2551 print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
2553 tchar attr_string[256];
2556 tputs(T("WIM Information:"));
2557 tputs(T("----------------"));
2558 tprintf(T("Path: %"TS"\n"), wimfile);
2559 tprintf(T("GUID: 0x"));
2560 print_byte_field(info->guid, sizeof(info->guid));
2562 tprintf(T("Version: %u\n"), info->wim_version);
2563 tprintf(T("Image Count: %d\n"), info->image_count);
2564 tprintf(T("Compression: %"TS"\n"),
2565 wimlib_get_compression_type_string(info->compression_type));
2566 tprintf(T("Chunk Size: %"PRIu32" bytes\n"),
2568 tprintf(T("Part Number: %d/%d\n"), info->part_number, info->total_parts);
2569 tprintf(T("Boot Index: %d\n"), info->boot_index);
2570 tprintf(T("Size: %"PRIu64" bytes\n"), info->total_bytes);
2572 attr_string[0] = T('\0');
2575 tstrcat(attr_string, T("Pipable, "));
2577 if (info->has_integrity_table)
2578 tstrcat(attr_string, T("Integrity info, "));
2580 if (info->has_rpfix)
2581 tstrcat(attr_string, T("Relative path junction, "));
2583 if (info->resource_only)
2584 tstrcat(attr_string, T("Resource only, "));
2586 if (info->metadata_only)
2587 tstrcat(attr_string, T("Metadata only, "));
2589 if (info->is_marked_readonly)
2590 tstrcat(attr_string, T("Readonly, "));
2592 p = tstrchr(attr_string, T('\0'));
2593 if (p >= &attr_string[2] && p[-1] == T(' ') && p[-2] == T(','))
2596 tprintf(T("Attributes: %"TS"\n\n"), attr_string);
2600 print_resource(const struct wimlib_resource_entry *resource,
2603 tprintf(T("Hash = 0x"));
2604 print_byte_field(resource->sha1_hash, sizeof(resource->sha1_hash));
2607 if (!resource->is_missing) {
2608 tprintf(T("Uncompressed size = %"PRIu64" bytes\n"),
2609 resource->uncompressed_size);
2610 if (resource->packed) {
2611 tprintf(T("Solid resource = %"PRIu64" => %"PRIu64" "
2612 "bytes @ offset %"PRIu64"\n"),
2613 resource->raw_resource_uncompressed_size,
2614 resource->raw_resource_compressed_size,
2615 resource->raw_resource_offset_in_wim);
2617 tprintf(T("Solid offset = %"PRIu64" bytes\n"),
2620 tprintf(T("Compressed size = %"PRIu64" bytes\n"),
2621 resource->compressed_size);
2623 tprintf(T("Offset in WIM = %"PRIu64" bytes\n"),
2627 tprintf(T("Part Number = %u\n"), resource->part_number);
2628 tprintf(T("Reference Count = %u\n"), resource->reference_count);
2630 tprintf(T("Flags = "));
2631 if (resource->is_compressed)
2632 tprintf(T("WIM_RESHDR_FLAG_COMPRESSED "));
2633 if (resource->is_metadata)
2634 tprintf(T("WIM_RESHDR_FLAG_METADATA "));
2635 if (resource->is_free)
2636 tprintf(T("WIM_RESHDR_FLAG_FREE "));
2637 if (resource->is_spanned)
2638 tprintf(T("WIM_RESHDR_FLAG_SPANNED "));
2639 if (resource->packed)
2640 tprintf(T("WIM_RESHDR_FLAG_SOLID "));
2648 print_blobs(WIMStruct *wim)
2650 wimlib_iterate_lookup_table(wim, 0, print_resource, NULL);
2654 default_print_security_descriptor(const uint8_t *sd, size_t size)
2656 tprintf(T("Security Descriptor = "));
2657 print_byte_field(sd, size);
2662 print_dentry_detailed(const struct wimlib_dir_entry *dentry)
2666 "----------------------------------------------------------------------------\n"));
2667 tprintf(T("Full Path = \"%"TS"\"\n"), dentry->full_path);
2668 if (dentry->dos_name)
2669 tprintf(T("Short Name = \"%"TS"\"\n"), dentry->dos_name);
2670 tprintf(T("Attributes = 0x%08x\n"), dentry->attributes);
2671 for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++)
2672 if (file_attr_flags[i].flag & dentry->attributes)
2673 tprintf(T(" FILE_ATTRIBUTE_%"TS" is set\n"),
2674 file_attr_flags[i].name);
2676 if (dentry->security_descriptor) {
2677 print_security_descriptor(dentry->security_descriptor,
2678 dentry->security_descriptor_size);
2681 print_time(T("Creation Time"), &dentry->creation_time);
2682 print_time(T("Last Write Time"), &dentry->last_write_time);
2683 print_time(T("Last Access Time"), &dentry->last_access_time);
2686 if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
2687 tprintf(T("Reparse Tag = 0x%"PRIx32"\n"), dentry->reparse_tag);
2689 tprintf(T("Link Group ID = 0x%016"PRIx64"\n"), dentry->hard_link_group_id);
2690 tprintf(T("Link Count = %"PRIu32"\n"), dentry->num_links);
2692 if (dentry->unix_mode != 0) {
2693 tprintf(T("UNIX Data = uid:%"PRIu32" gid:%"PRIu32" "
2694 "mode:0%"PRIo32" rdev:0x%"PRIx32"\n"),
2695 dentry->unix_uid, dentry->unix_gid,
2696 dentry->unix_mode, dentry->unix_rdev);
2699 for (uint32_t i = 0; i <= dentry->num_named_streams; i++) {
2700 if (dentry->streams[i].stream_name) {
2701 tprintf(T("\tNamed data stream \"%"TS"\":\n"),
2702 dentry->streams[i].stream_name);
2703 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_ENCRYPTED) {
2704 tprintf(T("\tRaw encrypted data stream:\n"));
2705 } else if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT) {
2706 tprintf(T("\tReparse point stream:\n"));
2708 tprintf(T("\tUnnamed data stream:\n"));
2710 print_resource(&dentry->streams[i].resource, NULL);
2715 print_dentry(const struct wimlib_dir_entry *dentry, void *_options)
2717 const struct print_dentry_options *options = _options;
2718 if (!options->detailed)
2719 print_dentry_full_path(dentry);
2721 print_dentry_detailed(dentry);
2725 /* Print the files contained in an image(s) in a WIM file. */
2727 imagex_dir(int argc, tchar **argv, int cmd)
2729 const tchar *wimfile;
2730 WIMStruct *wim = NULL;
2733 const tchar *path = WIMLIB_WIM_ROOT_PATH;
2735 struct print_dentry_options options = {
2738 int iterate_flags = WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2740 STRING_SET(refglobs);
2742 for_opt(c, dir_options) {
2744 case IMAGEX_PATH_OPTION:
2747 case IMAGEX_DETAILED_OPTION:
2748 options.detailed = true;
2750 case IMAGEX_ONE_FILE_ONLY_OPTION:
2751 iterate_flags &= ~WIMLIB_ITERATE_DIR_TREE_FLAG_RECURSIVE;
2753 case IMAGEX_REF_OPTION:
2754 ret = string_set_append(&refglobs, optarg);
2756 goto out_free_refglobs;
2766 imagex_error(T("Must specify a WIM file"));
2770 imagex_error(T("Too many arguments"));
2775 ret = wimlib_open_wim_with_progress(wimfile, 0, &wim,
2776 imagex_progress_func, NULL);
2778 goto out_free_refglobs;
2781 image = wimlib_resolve_image(wim, argv[1]);
2782 ret = verify_image_exists(image, argv[1], wimfile);
2784 goto out_wimlib_free;
2786 /* No image specified; default to image 1, but only if the WIM
2787 * contains exactly one image. */
2789 struct wimlib_wim_info info;
2791 wimlib_get_wim_info(wim, &info);
2792 if (info.image_count != 1) {
2793 imagex_error(T("\"%"TS"\" contains %d images; Please "
2794 "select one (or all)."),
2795 wimfile, info.image_count);
2802 if (refglobs.num_strings) {
2803 ret = wim_reference_globs(wim, &refglobs, 0);
2805 goto out_wimlib_free;
2808 ret = wimlib_iterate_dir_tree(wim, image, path, iterate_flags,
2809 print_dentry, &options);
2810 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
2811 struct wimlib_wim_info info;
2813 wimlib_get_wim_info(wim, &info);
2814 do_metadata_not_found_warning(wimfile, &info);
2819 string_set_destroy(&refglobs);
2823 usage(CMD_DIR, stderr);
2825 goto out_free_refglobs;
2828 /* Exports one, or all, images from a WIM file to a new WIM file or an existing
2831 imagex_export(int argc, tchar **argv, int cmd)
2835 int export_flags = WIMLIB_EXPORT_FLAG_GIFT;
2836 int write_flags = 0;
2837 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
2838 const tchar *src_wimfile;
2839 const tchar *src_image_num_or_name;
2840 const tchar *dest_wimfile;
2842 const tchar *dest_name;
2843 const tchar *dest_desc;
2845 struct wimlib_wim_info src_info;
2846 WIMStruct *dest_wim;
2851 STRING_SET(refglobs);
2852 unsigned num_threads = 0;
2853 uint32_t chunk_size = UINT32_MAX;
2854 uint32_t solid_chunk_size = UINT32_MAX;
2855 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
2857 for_opt(c, export_options) {
2859 case IMAGEX_BOOT_OPTION:
2860 export_flags |= WIMLIB_EXPORT_FLAG_BOOT;
2862 case IMAGEX_CHECK_OPTION:
2863 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
2864 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
2866 case IMAGEX_NOCHECK_OPTION:
2867 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
2869 case IMAGEX_COMPRESS_OPTION:
2870 compression_type = get_compression_type(optarg);
2871 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
2874 case IMAGEX_COMPRESS_SLOW_OPTION:
2875 set_compress_slow();
2876 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2878 case IMAGEX_RECOMPRESS_OPTION:
2879 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
2881 case IMAGEX_SOLID_OPTION:
2882 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
2884 case IMAGEX_NO_SOLID_SORT_OPTION:
2885 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
2887 case IMAGEX_CHUNK_SIZE_OPTION:
2888 chunk_size = parse_chunk_size(optarg);
2889 if (chunk_size == UINT32_MAX)
2892 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
2893 solid_chunk_size = parse_chunk_size(optarg);
2894 if (solid_chunk_size == UINT32_MAX)
2897 case IMAGEX_SOLID_COMPRESS_OPTION:
2898 solid_ctype = get_compression_type(optarg);
2899 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
2902 case IMAGEX_REF_OPTION:
2903 ret = string_set_append(&refglobs, optarg);
2905 goto out_free_refglobs;
2907 case IMAGEX_THREADS_OPTION:
2908 num_threads = parse_num_threads(optarg);
2909 if (num_threads == UINT_MAX)
2912 case IMAGEX_REBUILD_OPTION:
2913 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
2915 case IMAGEX_PIPABLE_OPTION:
2916 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2918 case IMAGEX_NOT_PIPABLE_OPTION:
2919 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
2921 case IMAGEX_WIMBOOT_OPTION:
2922 export_flags |= WIMLIB_EXPORT_FLAG_WIMBOOT;
2924 case IMAGEX_UNSAFE_COMPACT_OPTION:
2925 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
2933 if (argc < 3 || argc > 5)
2935 src_wimfile = argv[0];
2936 src_image_num_or_name = argv[1];
2937 dest_wimfile = argv[2];
2938 dest_name = (argc >= 4) ? argv[3] : NULL;
2939 dest_desc = (argc >= 5) ? argv[4] : NULL;
2940 ret = wimlib_open_wim_with_progress(src_wimfile, open_flags, &src_wim,
2941 imagex_progress_func, NULL);
2943 goto out_free_refglobs;
2945 wimlib_get_wim_info(src_wim, &src_info);
2947 /* Determine if the destination is an existing file or not. If so, we
2948 * try to append the exported image(s) to it; otherwise, we create a new
2949 * WIM containing the exported image(s). Furthermore, determine if we
2950 * need to write a pipable WIM directly to standard output. */
2952 if (tstrcmp(dest_wimfile, T("-")) == 0) {
2954 if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
2955 imagex_error("Can't write a non-pipable WIM to "
2956 "standard output! Specify --pipable\n"
2957 " if you want to create a pipable WIM "
2958 "(but read the docs first).");
2960 goto out_free_src_wim;
2963 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
2965 dest_wimfile = NULL;
2966 dest_wim_fd = STDOUT_FILENO;
2967 imagex_info_file = stderr;
2968 set_fd_to_binary_mode(dest_wim_fd);
2971 if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
2973 /* Destination file exists. */
2975 if (!S_ISREG(stbuf.st_mode)) {
2976 imagex_error(T("\"%"TS"\" is not a regular file"),
2979 goto out_free_src_wim;
2981 ret = wimlib_open_wim_with_progress(dest_wimfile,
2983 WIMLIB_OPEN_FLAG_WRITE_ACCESS,
2985 imagex_progress_func,
2988 goto out_free_src_wim;
2990 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
2991 /* The user specified a compression type, but we're
2992 * exporting to an existing WIM. Make sure the
2993 * specified compression type is the same as the
2994 * compression type of the existing destination WIM. */
2995 struct wimlib_wim_info dest_info;
2997 wimlib_get_wim_info(dest_wim, &dest_info);
2998 if (compression_type != dest_info.compression_type) {
2999 imagex_error(T("Cannot specify a compression type that is "
3000 "not the same as that used in the "
3001 "destination WIM"));
3003 goto out_free_dest_wim;
3009 if (errno != ENOENT) {
3010 imagex_error_with_errno(T("Cannot stat file \"%"TS"\""),
3013 goto out_free_src_wim;
3016 if (write_flags & WIMLIB_WRITE_FLAG_UNSAFE_COMPACT) {
3017 imagex_error(T("'--unsafe-compact' is only valid when "
3018 "exporting to an existing WIM file!"));
3020 goto out_free_src_wim;
3023 /* dest_wimfile is not an existing file, so create a new WIM. */
3025 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID) {
3026 /* The user did not specify a compression type; default
3027 * to that of the source WIM, unless --solid or
3028 * --wimboot was specified. */
3030 if (write_flags & WIMLIB_WRITE_FLAG_SOLID)
3031 compression_type = WIMLIB_COMPRESSION_TYPE_LZMS;
3032 else if (export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3033 compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
3035 compression_type = src_info.compression_type;
3037 ret = wimlib_create_new_wim(compression_type, &dest_wim);
3039 goto out_free_src_wim;
3041 wimlib_register_progress_function(dest_wim,
3042 imagex_progress_func, NULL);
3044 if ((export_flags & WIMLIB_EXPORT_FLAG_WIMBOOT)
3045 && compression_type == WIMLIB_COMPRESSION_TYPE_XPRESS)
3047 /* For --wimboot export, use small XPRESS chunks. */
3048 wimlib_set_output_chunk_size(dest_wim, 4096);
3049 } else if (compression_type == src_info.compression_type &&
3050 chunk_size == UINT32_MAX)
3052 /* Use same chunk size if compression type is the same. */
3053 wimlib_set_output_chunk_size(dest_wim, src_info.chunk_size);
3057 if (chunk_size != UINT32_MAX) {
3058 /* Set destination chunk size. */
3059 ret = wimlib_set_output_chunk_size(dest_wim, chunk_size);
3061 goto out_free_dest_wim;
3063 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3064 ret = wimlib_set_output_pack_compression_type(dest_wim, solid_ctype);
3066 goto out_free_dest_wim;
3068 if (solid_chunk_size != UINT32_MAX) {
3069 ret = wimlib_set_output_pack_chunk_size(dest_wim, solid_chunk_size);
3071 goto out_free_dest_wim;
3074 image = wimlib_resolve_image(src_wim, src_image_num_or_name);
3075 ret = verify_image_exists(image, src_image_num_or_name, src_wimfile);
3077 goto out_free_dest_wim;
3079 if (refglobs.num_strings) {
3080 ret = wim_reference_globs(src_wim, &refglobs, open_flags);
3082 goto out_free_dest_wim;
3085 if ((export_flags & WIMLIB_EXPORT_FLAG_BOOT) &&
3086 image == WIMLIB_ALL_IMAGES && src_info.boot_index == 0)
3088 imagex_error(T("--boot specified for all-images export, but source WIM "
3089 "has no bootable image."));
3091 goto out_free_dest_wim;
3094 ret = wimlib_export_image(src_wim, image, dest_wim, dest_name,
3095 dest_desc, export_flags);
3097 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3098 do_resource_not_found_warning(src_wimfile,
3099 &src_info, &refglobs);
3100 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3101 do_metadata_not_found_warning(src_wimfile, &src_info);
3103 goto out_free_dest_wim;
3107 ret = wimlib_overwrite(dest_wim, write_flags, num_threads);
3108 else if (dest_wimfile)
3109 ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
3110 write_flags, num_threads);
3112 ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
3113 WIMLIB_ALL_IMAGES, write_flags,
3116 wimlib_free(dest_wim);
3118 wimlib_free(src_wim);
3120 string_set_destroy(&refglobs);
3124 usage(CMD_EXPORT, stderr);
3127 goto out_free_refglobs;
3130 /* Extract files or directories from a WIM image */
3132 imagex_extract(int argc, tchar **argv, int cmd)
3139 const tchar *wimfile;
3140 const tchar *image_num_or_name;
3141 tchar *dest_dir = T(".");
3142 int extract_flags = WIMLIB_EXTRACT_FLAG_NORPFIX |
3143 WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3144 WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3145 int notlist_extract_flags = WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3147 STRING_SET(refglobs);
3149 tchar *root_path = WIMLIB_WIM_ROOT_PATH;
3151 for_opt(c, extract_options) {
3153 case IMAGEX_CHECK_OPTION:
3154 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3156 case IMAGEX_VERBOSE_OPTION:
3157 /* No longer does anything. */
3159 case IMAGEX_REF_OPTION:
3160 ret = string_set_append(&refglobs, optarg);
3162 goto out_free_refglobs;
3164 case IMAGEX_UNIX_DATA_OPTION:
3165 extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
3167 case IMAGEX_NO_ACLS_OPTION:
3168 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
3170 case IMAGEX_STRICT_ACLS_OPTION:
3171 extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
3173 case IMAGEX_NO_ATTRIBUTES_OPTION:
3174 extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES;
3176 case IMAGEX_DEST_DIR_OPTION:
3179 case IMAGEX_TO_STDOUT_OPTION:
3180 extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
3181 imagex_info_file = stderr;
3182 imagex_be_quiet = true;
3183 set_fd_to_binary_mode(STDOUT_FILENO);
3185 case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
3186 extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
3187 extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
3189 case IMAGEX_NO_GLOBS_OPTION:
3190 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3192 case IMAGEX_NULLGLOB_OPTION:
3193 extract_flags &= ~WIMLIB_EXTRACT_FLAG_STRICT_GLOB;
3195 case IMAGEX_PRESERVE_DIR_STRUCTURE_OPTION:
3196 notlist_extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE;
3198 case IMAGEX_WIMBOOT_OPTION:
3199 extract_flags |= WIMLIB_EXTRACT_FLAG_WIMBOOT;
3201 case IMAGEX_COMPACT_OPTION:
3202 ret = set_compact_mode(optarg, &extract_flags);
3204 goto out_free_refglobs;
3216 if (!(extract_flags & (WIMLIB_EXTRACT_FLAG_GLOB_PATHS |
3217 WIMLIB_EXTRACT_FLAG_STRICT_GLOB)))
3219 imagex_error(T("Can't combine --no-globs and --nullglob!"));
3224 image_num_or_name = argv[1];
3229 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3230 imagex_progress_func, NULL);
3232 goto out_free_refglobs;
3234 image = wimlib_resolve_image(wim, image_num_or_name);
3235 ret = verify_image_exists_and_is_single(image,
3239 goto out_wimlib_free;
3241 if (refglobs.num_strings) {
3242 ret = wim_reference_globs(wim, &refglobs, open_flags);
3244 goto out_wimlib_free;
3250 extract_flags &= ~WIMLIB_EXTRACT_FLAG_GLOB_PATHS;
3253 while (argc != 0 && ret == 0) {
3257 num_paths < argc && argv[num_paths][0] != T('@');
3262 ret = wimlib_extract_paths(wim, image, dest_dir,
3263 (const tchar **)argv,
3265 extract_flags | notlist_extract_flags);
3269 ret = wimlib_extract_pathlist(wim, image, dest_dir,
3278 if (!imagex_be_quiet)
3279 imagex_printf(T("Done extracting files.\n"));
3280 } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
3281 if ((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3282 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3283 == (WIMLIB_EXTRACT_FLAG_STRICT_GLOB |
3284 WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
3287 T("Note: You can use the '--nullglob' "
3288 "option to ignore missing files.\n"));
3290 tfprintf(stderr, T("Note: You can use `%"TS"' to see what "
3291 "files and directories\n"
3292 " are in the WIM image.\n"),
3293 get_cmd_string(CMD_DIR, false));
3294 } else if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND) {
3295 struct wimlib_wim_info info;
3297 wimlib_get_wim_info(wim, &info);
3298 do_resource_not_found_warning(wimfile, &info, &refglobs);
3299 } else if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3300 struct wimlib_wim_info info;
3302 wimlib_get_wim_info(wim, &info);
3303 do_metadata_not_found_warning(wimfile, &info);
3308 string_set_destroy(&refglobs);
3312 usage(CMD_EXTRACT, stderr);
3315 goto out_free_refglobs;
3318 /* Prints information about a WIM file; also can mark an image as bootable,
3319 * change the name of an image, or change the description of an image. */
3321 imagex_info(int argc, tchar **argv, int cmd)
3326 bool nocheck = false;
3327 bool header = false;
3330 bool short_header = true;
3331 const tchar *xml_out_file = NULL;
3332 const tchar *wimfile;
3333 const tchar *image_num_or_name;
3334 STRING_SET(image_properties);
3339 struct wimlib_wim_info info;
3341 for_opt(c, info_options) {
3343 case IMAGEX_BOOT_OPTION:
3346 case IMAGEX_CHECK_OPTION:
3349 case IMAGEX_NOCHECK_OPTION:
3352 case IMAGEX_HEADER_OPTION:
3354 short_header = false;
3356 case IMAGEX_BLOBS_OPTION:
3358 short_header = false;
3360 case IMAGEX_XML_OPTION:
3362 short_header = false;
3364 case IMAGEX_EXTRACT_XML_OPTION:
3365 xml_out_file = optarg;
3366 short_header = false;
3368 case IMAGEX_METADATA_OPTION:
3369 imagex_error(T("The --metadata option has been removed. "
3370 "Use 'wimdir --detail' instead."));
3372 case IMAGEX_IMAGE_PROPERTY_OPTION:
3373 ret = append_image_property_argument(&image_properties);
3384 if (argc < 1 || argc > 4)
3388 image_num_or_name = (argc >= 2) ? argv[1] : T("all");
3392 tchar *p = alloca((5 + tstrlen(argv[2]) + 1) * sizeof(tchar));
3393 tsprintf(p, T("NAME=%"TS), argv[2]);
3394 ret = string_set_append(&image_properties, p);
3401 tchar *p = alloca((12 + tstrlen(argv[3]) + 1) * sizeof(tchar));
3402 tsprintf(p, T("DESCRIPTION=%"TS), argv[3]);
3403 ret = string_set_append(&image_properties, p);
3408 if (check && nocheck) {
3409 imagex_error(T("Can't specify both --check and --nocheck"));
3414 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3416 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3417 imagex_progress_func, NULL);
3421 wimlib_get_wim_info(wim, &info);
3423 image = wimlib_resolve_image(wim, image_num_or_name);
3424 ret = WIMLIB_ERR_INVALID_IMAGE;
3425 if (image == WIMLIB_NO_IMAGE && tstrcmp(image_num_or_name, T("0"))) {
3426 verify_image_exists(image, image_num_or_name, wimfile);
3428 imagex_error(T("If you would like to set the boot "
3429 "index to 0, specify image \"0\" with "
3430 "the --boot flag."));
3432 goto out_wimlib_free;
3435 if (boot && info.image_count == 0) {
3436 imagex_error(T("--boot is meaningless on a WIM with no images"));
3437 goto out_wimlib_free;
3440 if (image == WIMLIB_ALL_IMAGES && info.image_count > 1) {
3442 imagex_error(T("Cannot specify the --boot flag "
3443 "without specifying a specific "
3444 "image in a multi-image WIM"));
3445 goto out_wimlib_free;
3447 if (image_properties.num_strings) {
3448 imagex_error(T("Can't change image properties without "
3449 "specifying a specific image in a "
3450 "multi-image WIM"));
3451 goto out_wimlib_free;
3455 /* Operations that print information are separated from operations that
3456 * recreate the WIM file. */
3457 if (!image_properties.num_strings && !boot) {
3459 /* Read-only operations */
3461 if (image == WIMLIB_NO_IMAGE) {
3462 imagex_error(T("\"%"TS"\" is not a valid image in \"%"TS"\""),
3463 image_num_or_name, wimfile);
3464 goto out_wimlib_free;
3467 if (image == WIMLIB_ALL_IMAGES && short_header)
3468 print_wim_information(wimfile, &info);
3471 wimlib_print_header(wim);
3474 if (info.total_parts != 1) {
3475 tfprintf(stderr, T("Warning: Only showing the blobs "
3476 "for part %d of a %d-part WIM.\n"),
3477 info.part_number, info.total_parts);
3483 ret = wimlib_extract_xml_data(wim, stdout);
3485 goto out_wimlib_free;
3491 fp = tfopen(xml_out_file, T("wb"));
3493 imagex_error_with_errno(T("Failed to open the "
3494 "file \"%"TS"\" for "
3498 goto out_wimlib_free;
3500 ret = wimlib_extract_xml_data(wim, fp);
3502 imagex_error(T("Failed to close the file "
3508 goto out_wimlib_free;
3512 wimlib_print_available_images(wim, image);
3516 /* Modification operations */
3517 bool any_property_changes;
3519 if (image == WIMLIB_ALL_IMAGES)
3522 if (image == WIMLIB_NO_IMAGE && image_properties.num_strings) {
3523 imagex_error(T("Cannot change image properties "
3524 "when using image 0"));
3526 goto out_wimlib_free;
3530 if (image == info.boot_index) {
3531 imagex_printf(T("Image %d is already marked as "
3532 "bootable.\n"), image);
3535 imagex_printf(T("Marking image %d as bootable.\n"),
3537 info.boot_index = image;
3538 ret = wimlib_set_wim_info(wim, &info,
3539 WIMLIB_CHANGE_BOOT_INDEX);
3541 goto out_wimlib_free;
3545 ret = apply_image_properties(&image_properties, wim, image,
3546 &any_property_changes);
3548 goto out_wimlib_free;
3550 /* Only call wimlib_overwrite() if something actually needs to
3552 if (boot || any_property_changes ||
3553 (check && !info.has_integrity_table) ||
3554 (nocheck && info.has_integrity_table))
3556 int write_flags = 0;
3559 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3561 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3562 ret = wimlib_overwrite(wim, write_flags, 1);
3564 imagex_printf(T("The file \"%"TS"\" was not modified "
3565 "because nothing needed to be done.\n"),
3573 string_set_destroy(&image_properties);
3577 usage(CMD_INFO, stderr);
3583 /* Join split WIMs into one part WIM */
3585 imagex_join(int argc, tchar **argv, int cmd)
3588 int swm_open_flags = 0;
3589 int wim_write_flags = 0;
3590 const tchar *output_path;
3593 for_opt(c, join_options) {
3595 case IMAGEX_CHECK_OPTION:
3596 swm_open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3597 wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3607 imagex_error(T("Must specify one or more split WIM (.swm) "
3611 output_path = argv[0];
3612 ret = wimlib_join_with_progress((const tchar * const *)++argv,
3617 imagex_progress_func,
3623 usage(CMD_JOIN, stderr);
3628 #if WIM_MOUNTING_SUPPORTED
3630 /* Mounts a WIM image. */
3632 imagex_mount_rw_or_ro(int argc, tchar **argv, int cmd)
3635 int mount_flags = 0;
3637 const tchar *staging_dir = NULL;
3638 const tchar *wimfile;
3641 struct wimlib_wim_info info;
3645 STRING_SET(refglobs);
3647 if (cmd == CMD_MOUNTRW) {
3648 mount_flags |= WIMLIB_MOUNT_FLAG_READWRITE;
3649 open_flags |= WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3652 for_opt(c, mount_options) {
3654 case IMAGEX_ALLOW_OTHER_OPTION:
3655 mount_flags |= WIMLIB_MOUNT_FLAG_ALLOW_OTHER;
3657 case IMAGEX_CHECK_OPTION:
3658 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3660 case IMAGEX_DEBUG_OPTION:
3661 mount_flags |= WIMLIB_MOUNT_FLAG_DEBUG;
3663 case IMAGEX_STREAMS_INTERFACE_OPTION:
3664 if (!tstrcasecmp(optarg, T("none")))
3665 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE;
3666 else if (!tstrcasecmp(optarg, T("xattr")))
3667 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR;
3668 else if (!tstrcasecmp(optarg, T("windows")))
3669 mount_flags |= WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS;
3671 imagex_error(T("Unknown stream interface \"%"TS"\""),
3676 case IMAGEX_REF_OPTION:
3677 ret = string_set_append(&refglobs, optarg);
3679 goto out_free_refglobs;
3681 case IMAGEX_STAGING_DIR_OPTION:
3682 staging_dir = optarg;
3684 case IMAGEX_UNIX_DATA_OPTION:
3685 mount_flags |= WIMLIB_MOUNT_FLAG_UNIX_DATA;
3693 if (argc != 2 && argc != 3)
3698 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3699 imagex_progress_func, NULL);
3701 goto out_free_refglobs;
3703 wimlib_get_wim_info(wim, &info);
3706 /* Image explicitly specified. */
3707 image = wimlib_resolve_image(wim, argv[1]);
3709 ret = verify_image_exists_and_is_single(image, argv[1], wimfile);
3713 /* No image specified; default to image 1, but only if the WIM
3714 * contains exactly one image. */
3716 if (info.image_count != 1) {
3717 imagex_error(T("\"%"TS"\" contains %d images; Please "
3718 "select one."), wimfile, info.image_count);
3726 if (refglobs.num_strings) {
3727 ret = wim_reference_globs(wim, &refglobs, open_flags);
3732 ret = wimlib_mount_image(wim, image, dir, mount_flags, staging_dir);
3734 if (ret == WIMLIB_ERR_METADATA_NOT_FOUND) {
3735 do_metadata_not_found_warning(wimfile, &info);
3737 imagex_error(T("Failed to mount image %d from \"%"TS"\" "
3739 image, wimfile, dir);
3745 string_set_destroy(&refglobs);
3751 goto out_free_refglobs;
3753 #endif /* WIM_MOUNTING_SUPPORTED */
3755 /* Rebuild a WIM file */
3757 imagex_optimize(int argc, tchar **argv, int cmd)
3760 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
3761 int write_flags = WIMLIB_WRITE_FLAG_REBUILD;
3762 int compression_type = WIMLIB_COMPRESSION_TYPE_INVALID;
3763 uint32_t chunk_size = UINT32_MAX;
3764 uint32_t solid_chunk_size = UINT32_MAX;
3765 int solid_ctype = WIMLIB_COMPRESSION_TYPE_INVALID;
3768 const tchar *wimfile;
3771 unsigned num_threads = 0;
3773 for_opt(c, optimize_options) {
3775 case IMAGEX_CHECK_OPTION:
3776 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3777 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3779 case IMAGEX_NOCHECK_OPTION:
3780 write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
3782 case IMAGEX_COMPRESS_OPTION:
3783 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3784 compression_type = get_compression_type(optarg);
3785 if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
3788 case IMAGEX_COMPRESS_SLOW_OPTION:
3789 set_compress_slow();
3790 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3792 case IMAGEX_RECOMPRESS_OPTION:
3793 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3795 case IMAGEX_CHUNK_SIZE_OPTION:
3796 chunk_size = parse_chunk_size(optarg);
3797 if (chunk_size == UINT32_MAX)
3800 case IMAGEX_SOLID_CHUNK_SIZE_OPTION:
3801 solid_chunk_size = parse_chunk_size(optarg);
3802 if (solid_chunk_size == UINT32_MAX)
3805 case IMAGEX_SOLID_COMPRESS_OPTION:
3806 solid_ctype = get_compression_type(optarg);
3807 if (solid_ctype == WIMLIB_COMPRESSION_TYPE_INVALID)
3810 case IMAGEX_SOLID_OPTION:
3811 write_flags |= WIMLIB_WRITE_FLAG_SOLID;
3812 write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
3814 case IMAGEX_NO_SOLID_SORT_OPTION:
3815 write_flags |= WIMLIB_WRITE_FLAG_NO_SOLID_SORT;
3817 case IMAGEX_THREADS_OPTION:
3818 num_threads = parse_num_threads(optarg);
3819 if (num_threads == UINT_MAX)
3822 case IMAGEX_PIPABLE_OPTION:
3823 write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
3825 case IMAGEX_NOT_PIPABLE_OPTION:
3826 write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
3828 case IMAGEX_UNSAFE_COMPACT_OPTION:
3829 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
3843 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
3844 imagex_progress_func, NULL);
3848 if (compression_type != WIMLIB_COMPRESSION_TYPE_INVALID) {
3849 /* Change compression type. */
3850 ret = wimlib_set_output_compression_type(wim, compression_type);
3852 goto out_wimlib_free;
3855 if (chunk_size != UINT32_MAX) {
3856 /* Change chunk size. */
3857 ret = wimlib_set_output_chunk_size(wim, chunk_size);
3859 goto out_wimlib_free;
3861 if (solid_ctype != WIMLIB_COMPRESSION_TYPE_INVALID) {
3862 ret = wimlib_set_output_pack_compression_type(wim, solid_ctype);
3864 goto out_wimlib_free;
3866 if (solid_chunk_size != UINT32_MAX) {
3867 ret = wimlib_set_output_pack_chunk_size(wim, solid_chunk_size);
3869 goto out_wimlib_free;
3872 old_size = file_get_size(wimfile);
3873 tprintf(T("\"%"TS"\" original size: "), wimfile);
3875 tputs(T("Unknown"));
3877 tprintf(T("%"PRIu64" KiB\n"), old_size >> 10);
3879 ret = wimlib_overwrite(wim, write_flags, num_threads);
3881 imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
3882 goto out_wimlib_free;
3885 new_size = file_get_size(wimfile);
3886 tprintf(T("\"%"TS"\" optimized size: "), wimfile);
3888 tputs(T("Unknown"));
3890 tprintf(T("%"PRIu64" KiB\n"), new_size >> 10);
3892 tfputs(T("Space saved: "), stdout);
3893 if (new_size != -1 && old_size != -1) {
3894 tprintf(T("%lld KiB\n"),
3895 ((long long)old_size - (long long)new_size) >> 10);
3897 tputs(T("Unknown"));
3906 usage(CMD_OPTIMIZE, stderr);
3912 /* Split a WIM into a spanned set */
3914 imagex_split(int argc, tchar **argv, int cmd)
3918 int write_flags = 0;
3919 unsigned long part_size;
3924 for_opt(c, split_options) {
3926 case IMAGEX_CHECK_OPTION:
3927 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
3928 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
3940 part_size = tstrtod(argv[2], &tmp) * (1 << 20);
3941 if (tmp == argv[2] || *tmp) {
3942 imagex_error(T("Invalid part size \"%"TS"\""), argv[2]);
3943 imagex_error(T("The part size must be an integer or "
3944 "floating-point number of megabytes."));
3947 ret = wimlib_open_wim_with_progress(argv[0], open_flags, &wim,
3948 imagex_progress_func, NULL);
3952 ret = wimlib_split(wim, argv[1], part_size, write_flags);
3958 usage(CMD_SPLIT, stderr);
3964 #if WIM_MOUNTING_SUPPORTED
3965 /* Unmounts a mounted WIM image. */
3967 imagex_unmount(int argc, tchar **argv, int cmd)
3970 int unmount_flags = 0;
3973 for_opt(c, unmount_options) {
3975 case IMAGEX_COMMIT_OPTION:
3976 unmount_flags |= WIMLIB_UNMOUNT_FLAG_COMMIT;
3978 case IMAGEX_CHECK_OPTION:
3979 unmount_flags |= WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY;
3981 case IMAGEX_REBUILD_OPTION:
3982 unmount_flags |= WIMLIB_UNMOUNT_FLAG_REBUILD;
3984 case IMAGEX_LAZY_OPTION:
3985 case IMAGEX_FORCE_OPTION:
3986 /* Now, unmount is lazy by default. However, committing
3987 * the image will fail with
3988 * WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY if there are open
3989 * file descriptors on the WIM image. The
3990 * WIMLIB_UNMOUNT_FLAG_FORCE option forces these file
3991 * descriptors to be closed. */
3992 unmount_flags |= WIMLIB_UNMOUNT_FLAG_FORCE;
3994 case IMAGEX_NEW_IMAGE_OPTION:
3995 unmount_flags |= WIMLIB_UNMOUNT_FLAG_NEW_IMAGE;
4006 if (unmount_flags & WIMLIB_UNMOUNT_FLAG_NEW_IMAGE) {
4007 if (!(unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT)) {
4008 imagex_error(T("--new-image is meaningless "
4009 "without --commit also specified!"));
4014 ret = wimlib_unmount_image_with_progress(argv[0], unmount_flags,
4015 imagex_progress_func, NULL);
4017 imagex_error(T("Failed to unmount \"%"TS"\""), argv[0]);
4018 if (ret == WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY) {
4020 "\tNote: Use --commit --force to force changes "
4021 "to be committed, regardless\n"
4022 "\t of open files.\n"));
4029 usage(CMD_UNMOUNT, stderr);
4034 #endif /* WIM_MOUNTING_SUPPORTED */
4037 * Add, delete, or rename files in a WIM image.
4040 imagex_update(int argc, tchar **argv, int cmd)
4042 const tchar *wimfile;
4046 int open_flags = WIMLIB_OPEN_FLAG_WRITE_ACCESS;
4047 int write_flags = 0;
4048 int update_flags = WIMLIB_UPDATE_FLAG_SEND_PROGRESS;
4049 int default_add_flags = WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE |
4050 WIMLIB_ADD_FLAG_VERBOSE |
4051 WIMLIB_ADD_FLAG_WINCONFIG;
4052 int default_delete_flags = 0;
4053 unsigned num_threads = 0;
4055 tchar *cmd_file_contents;
4056 size_t cmd_file_nchars;
4057 struct wimlib_update_command *cmds;
4059 tchar *command_str = NULL;
4060 tchar *config_file = NULL;
4061 tchar *wimboot_config = NULL;
4063 for_opt(c, update_options) {
4065 /* Generic or write options */
4066 case IMAGEX_THREADS_OPTION:
4067 num_threads = parse_num_threads(optarg);
4068 if (num_threads == UINT_MAX)
4071 case IMAGEX_CHECK_OPTION:
4072 open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4073 write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
4075 case IMAGEX_REBUILD_OPTION:
4076 write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
4078 case IMAGEX_COMMAND_OPTION:
4080 imagex_error(T("--command may only be specified "
4081 "one time. Please provide\n"
4082 " the update commands "
4083 "on standard input instead."));
4086 command_str = tstrdup(optarg);
4088 imagex_error(T("Out of memory!"));
4092 case IMAGEX_WIMBOOT_CONFIG_OPTION:
4093 wimboot_config = optarg;
4095 /* Default delete options */
4096 case IMAGEX_FORCE_OPTION:
4097 default_delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
4099 case IMAGEX_RECURSIVE_OPTION:
4100 default_delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
4103 /* Global add option */
4104 case IMAGEX_CONFIG_OPTION:
4105 default_add_flags &= ~WIMLIB_ADD_FLAG_WINCONFIG;
4106 config_file = optarg;
4109 /* Default add options */
4110 case IMAGEX_VERBOSE_OPTION:
4111 /* No longer does anything. */
4113 case IMAGEX_DEREFERENCE_OPTION:
4114 default_add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
4116 case IMAGEX_UNIX_DATA_OPTION:
4117 default_add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
4119 case IMAGEX_NO_ACLS_OPTION:
4120 default_add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
4122 case IMAGEX_STRICT_ACLS_OPTION:
4123 default_add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
4125 case IMAGEX_NO_REPLACE_OPTION:
4126 default_add_flags |= WIMLIB_ADD_FLAG_NO_REPLACE;
4128 case IMAGEX_UNSAFE_COMPACT_OPTION:
4129 write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT;
4138 if (argc != 1 && argc != 2)
4142 ret = wimlib_open_wim_with_progress(wimfile, open_flags, &wim,
4143 imagex_progress_func, NULL);
4145 goto out_free_command_str;
4148 /* Image explicitly specified. */
4149 image = wimlib_resolve_image(wim, argv[1]);
4150 ret = verify_image_exists_and_is_single(image, argv[1],
4153 goto out_wimlib_free;
4155 /* No image specified; default to image 1, but only if the WIM
4156 * contains exactly one image. */
4157 struct wimlib_wim_info info;
4159 wimlib_get_wim_info(wim, &info);
4160 if (info.image_count != 1) {
4161 imagex_error(T("\"%"TS"\" contains %d images; Please select one."),
4162 wimfile, info.image_count);
4169 /* Read update commands from standard input, or the command string if
4172 cmd_file_contents = NULL;
4173 cmds = parse_update_command_file(&command_str, tstrlen(command_str),
4177 goto out_free_cmd_file_contents;
4179 } else if (!wimboot_config) {
4180 if (isatty(STDIN_FILENO)) {
4181 tputs(T("Reading update commands from standard input..."));
4182 recommend_man_page(CMD_UPDATE, stdout);
4184 cmd_file_contents = stdin_get_text_contents(&cmd_file_nchars);
4185 if (!cmd_file_contents) {
4187 goto out_wimlib_free;
4190 /* Parse the update commands */
4191 cmds = parse_update_command_file(&cmd_file_contents, cmd_file_nchars,
4195 goto out_free_cmd_file_contents;
4198 cmd_file_contents = NULL;
4203 /* Set default flags and capture config on the update commands */
4204 for (size_t i = 0; i < num_cmds; i++) {
4205 switch (cmds[i].op) {
4206 case WIMLIB_UPDATE_OP_ADD:
4207 cmds[i].add.add_flags |= default_add_flags;
4208 cmds[i].add.config_file = config_file;
4210 case WIMLIB_UPDATE_OP_DELETE:
4211 cmds[i].delete_.delete_flags |= default_delete_flags;
4218 /* Execute the update commands */
4219 ret = wimlib_update_image(wim, image, cmds, num_cmds, update_flags);
4223 if (wimboot_config) {
4224 /* --wimboot-config=FILE is short for an
4225 * "add FILE /Windows/System32/WimBootCompress.ini" command.
4227 struct wimlib_update_command cmd;
4229 cmd.op = WIMLIB_UPDATE_OP_ADD;
4230 cmd.add.fs_source_path = wimboot_config;
4231 cmd.add.wim_target_path = T("/Windows/System32/WimBootCompress.ini");
4232 cmd.add.config_file = NULL;
4233 cmd.add.add_flags = 0;
4235 ret = wimlib_update_image(wim, image, &cmd, 1, update_flags);
4240 /* Overwrite the updated WIM */
4241 ret = wimlib_overwrite(wim, write_flags, num_threads);
4244 out_free_cmd_file_contents:
4245 free(cmd_file_contents);
4248 out_free_command_str:
4253 usage(CMD_UPDATE, stderr);
4256 goto out_free_command_str;
4259 /* Verify a WIM file. */
4261 imagex_verify(int argc, tchar **argv, int cmd)
4264 const tchar *wimfile;
4266 int open_flags = WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4267 int verify_flags = 0;
4268 STRING_SET(refglobs);
4271 for_opt(c, verify_options) {
4273 case IMAGEX_REF_OPTION:
4274 ret = string_set_append(&refglobs, optarg);
4276 goto out_free_refglobs;
4278 case IMAGEX_NOCHECK_OPTION:
4279 open_flags &= ~WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
4291 imagex_error(T("Must specify a WIM file!"));
4293 imagex_error(T("At most one WIM file can be specified!"));
4299 ret = wimlib_open_wim_with_progress(wimfile,
4302 imagex_progress_func,
4305 goto out_free_refglobs;
4307 ret = wim_reference_globs(wim, &refglobs, open_flags);
4309 goto out_wimlib_free;
4311 ret = wimlib_verify_wim(wim, verify_flags);
4313 tputc(T('\n'), stderr);
4314 imagex_error(T("\"%"TS"\" failed verification!"),
4316 if (ret == WIMLIB_ERR_RESOURCE_NOT_FOUND &&
4317 refglobs.num_strings == 0)
4319 imagex_printf(T("Note: if this WIM file is not standalone, "
4320 "use the --ref option to specify the other parts.\n"));
4323 imagex_printf(T("\n\"%"TS"\" was successfully verified.\n"),
4330 string_set_destroy(&refglobs);
4334 usage(CMD_VERIFY, stderr);
4336 goto out_free_refglobs;
4339 struct imagex_command {
4341 int (*func)(int argc, tchar **argv, int cmd);
4344 static const struct imagex_command imagex_commands[] = {
4345 [CMD_APPEND] = {T("append"), imagex_capture_or_append},
4346 [CMD_APPLY] = {T("apply"), imagex_apply},
4347 [CMD_CAPTURE] = {T("capture"), imagex_capture_or_append},
4348 [CMD_DELETE] = {T("delete"), imagex_delete},
4349 [CMD_DIR ] = {T("dir"), imagex_dir},
4350 [CMD_EXPORT] = {T("export"), imagex_export},
4351 [CMD_EXTRACT] = {T("extract"), imagex_extract},
4352 [CMD_INFO] = {T("info"), imagex_info},
4353 [CMD_JOIN] = {T("join"), imagex_join},
4354 #if WIM_MOUNTING_SUPPORTED
4355 [CMD_MOUNT] = {T("mount"), imagex_mount_rw_or_ro},
4356 [CMD_MOUNTRW] = {T("mountrw"), imagex_mount_rw_or_ro},
4358 [CMD_OPTIMIZE] = {T("optimize"), imagex_optimize},
4359 [CMD_SPLIT] = {T("split"), imagex_split},
4360 #if WIM_MOUNTING_SUPPORTED
4361 [CMD_UNMOUNT] = {T("unmount"), imagex_unmount},
4363 [CMD_UPDATE] = {T("update"), imagex_update},
4364 [CMD_VERIFY] = {T("verify"), imagex_verify},
4369 /* Can be a directory or source list file. But source list file is probably
4370 * a rare use case, so just say directory. */
4371 # define SOURCE_STR T("DIRECTORY")
4373 /* Can only be a directory */
4374 # define TARGET_STR T("DIRECTORY")
4377 /* Can be a directory, NTFS volume, or source list file. */
4378 # define SOURCE_STR T("SOURCE")
4380 /* Can be a directory or NTFS volume. */
4381 # define TARGET_STR T("TARGET")
4385 static const tchar *usage_strings[] = {
4388 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4389 " [--boot] [--check] [--nocheck] [--config=FILE]\n"
4390 " [--threads=NUM_THREADS] [--no-acls] [--strict-acls]\n"
4391 " [--rpfix] [--norpfix] [--update-of=[WIMFILE:]IMAGE]\n"
4392 " [--wimboot] [--unix-data] [--dereference] [--snapshot]\n"
4396 " %"TS" WIMFILE [IMAGE] " TARGET_STR "\n"
4397 " [--check] [--ref=\"GLOB\"] [--no-acls] [--strict-acls]\n"
4398 " [--no-attributes] [--rpfix] [--norpfix]\n"
4399 " [--include-invalid-names] [--wimboot] [--unix-data]\n"
4400 " [--compact=FORMAT]\n"
4404 " %"TS" " SOURCE_STR " WIMFILE [IMAGE_NAME [IMAGE_DESC]]\n"
4405 " [--compress=TYPE] [--boot] [--check] [--nocheck]\n"
4406 " [--config=FILE] [--threads=NUM_THREADS]\n"
4407 " [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
4408 " [--update-of=[WIMFILE:]IMAGE] [--delta-from=WIMFILE]\n"
4409 " [--wimboot] [--unix-data] [--dereference] [--solid]\n"
4414 " %"TS" WIMFILE IMAGE [--check] [--soft]\n"
4418 " %"TS" WIMFILE IMAGE [--path=PATH] [--detailed]\n"
4422 " %"TS" SRC_WIMFILE SRC_IMAGE DEST_WIMFILE\n"
4423 " [DEST_IMAGE_NAME [DEST_IMAGE_DESC]]\n"
4424 " [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
4425 " [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
4426 " [--wimboot] [--solid]\n"
4430 " %"TS" WIMFILE IMAGE [(PATH | @LISTFILE)...]\n"
4431 " [--check] [--ref=\"GLOB\"] [--dest-dir=CMD_DIR]\n"
4432 " [--to-stdout] [--no-acls] [--strict-acls]\n"
4433 " [--no-attributes] [--include-invalid-names]\n"
4434 " [--no-globs] [--nullglob] [--preserve-dir-structure]\n"
4438 " %"TS" WIMFILE [IMAGE [NEW_NAME [NEW_DESC]]]\n"
4439 " [--boot] [--check] [--nocheck] [--xml]\n"
4440 " [--extract-xml FILE] [--header] [--blobs]\n"
4441 " [--image-property NAME=VALUE]\n"
4445 " %"TS" OUT_WIMFILE SPLIT_WIM_PART... [--check]\n"
4447 #if WIM_MOUNTING_SUPPORTED
4450 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4451 " [--check] [--streams-interface=INTERFACE]\n"
4452 " [--ref=\"GLOB\"] [--allow-other] [--unix-data]\n"
4456 " %"TS" WIMFILE [IMAGE] DIRECTORY\n"
4457 " [--check] [--streams-interface=INTERFACE]\n"
4458 " [--staging-dir=CMD_DIR] [--allow-other] [--unix-data]\n"
4464 " [--recompress] [--compress=TYPE] [--threads=NUM_THREADS]\n"
4465 " [--check] [--nocheck] [--solid]\n"
4470 " %"TS" WIMFILE SPLIT_WIM_PART_1 PART_SIZE_MB [--check]\n"
4472 #if WIM_MOUNTING_SUPPORTED
4475 " %"TS" DIRECTORY\n"
4476 " [--commit] [--force] [--new-image] [--check] [--rebuild]\n"
4481 " %"TS" WIMFILE [IMAGE]\n"
4482 " [--check] [--rebuild] [--threads=NUM_THREADS]\n"
4483 " [DEFAULT_ADD_OPTIONS] [DEFAULT_DELETE_OPTIONS]\n"
4484 " [--command=STRING] [--wimboot-config=FILE]\n"
4489 " %"TS" WIMFILE [--ref=\"GLOB\"]\n"
4493 static const tchar *invocation_name;
4494 static int invocation_cmd = CMD_NONE;
4496 static const tchar *get_cmd_string(int cmd, bool nospace)
4498 static tchar buf[50];
4499 if (cmd == CMD_NONE) {
4500 return T("wimlib-imagex");
4501 } else if (invocation_cmd != CMD_NONE) {
4502 tsprintf(buf, T("wim%"TS), imagex_commands[cmd].name);
4504 const tchar *format;
4507 format = T("%"TS"-%"TS"");
4509 format = T("%"TS" %"TS"");
4510 tsprintf(buf, format, invocation_name, imagex_commands[cmd].name);
4518 static const tchar *s =
4520 "wimlib-imagex (distributed with " PACKAGE " " PACKAGE_VERSION ")\n"
4521 "Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers\n"
4522 "License GPLv3+; GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
4523 "This is free software: you are free to change and redistribute it.\n"
4524 "There is NO WARRANTY, to the extent permitted by law.\n"
4526 "Report bugs to "PACKAGE_BUGREPORT".\n"
4533 help_or_version(int argc, tchar **argv, int cmd)
4538 for (i = 1; i < argc; i++) {
4540 if (p[0] == T('-') && p[1] == T('-')) {
4542 if (!tstrcmp(p, T("help"))) {
4543 if (cmd == CMD_NONE)
4548 } else if (!tstrcmp(p, T("version"))) {
4557 print_usage_string(int cmd, FILE *fp)
4559 tfprintf(fp, usage_strings[cmd], get_cmd_string(cmd, false));
4563 recommend_man_page(int cmd, FILE *fp)
4565 const tchar *format_str;
4567 format_str = T("Some uncommon options are not listed;\n"
4568 "See %"TS".pdf in the doc directory for more details.\n");
4570 format_str = T("Some uncommon options are not listed;\n"
4571 "Try `man %"TS"' for more details.\n");
4573 tfprintf(fp, format_str, get_cmd_string(cmd, true));
4577 usage(int cmd, FILE *fp)
4579 tfprintf(fp, T("Usage:\n"));
4580 print_usage_string(cmd, fp);
4581 tfprintf(fp, T("\n"));
4582 recommend_man_page(cmd, fp);
4588 tfprintf(fp, T("Usage:\n"));
4589 for (int cmd = 0; cmd < CMD_MAX; cmd++) {
4590 print_usage_string(cmd, fp);
4591 tfprintf(fp, T("\n"));
4593 static const tchar *extra =
4596 " %"TS" --version\n"
4599 tfprintf(fp, extra, invocation_name, invocation_name);
4601 T("IMAGE can be the 1-based index or name of an image in the WIM file.\n"
4602 "For some commands IMAGE is optional if the WIM file only contains one image.\n"
4603 "For some commands IMAGE may be \"all\".\n"
4605 recommend_man_page(CMD_NONE, fp);
4608 /* Entry point for wimlib's ImageX implementation. On UNIX the command
4609 * arguments will just be 'char' strings (ideally UTF-8 encoded, but could be
4610 * something else), while on Windows the command arguments will be UTF-16LE
4611 * encoded 'wchar_t' strings. */
4614 wmain(int argc, wchar_t **argv, wchar_t **envp)
4616 main(int argc, char **argv)
4623 imagex_info_file = stdout;
4624 invocation_name = tbasename(argv[0]);
4627 if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
4628 init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
4632 setlocale(LC_ALL, "");
4633 codeset = nl_langinfo(CODESET);
4634 if (!strstr(codeset, "UTF-8") &&
4635 !strstr(codeset, "UTF8") &&
4636 !strstr(codeset, "utf-8") &&
4637 !strstr(codeset, "utf8"))
4640 "WARNING: Running %"TS" in a UTF-8 locale is recommended!\n"
4641 " Maybe try: `export LANG=en_US.UTF-8'?\n"
4642 " Alternatively, set the environmental variable WIMLIB_IMAGEX_USE_UTF8\n"
4643 " to any value to force wimlib to use UTF-8.\n",
4649 #endif /* !__WIN32__ */
4652 tchar *igcase = tgetenv(T("WIMLIB_IMAGEX_IGNORE_CASE"));
4653 if (igcase != NULL) {
4654 if (!tstrcmp(igcase, T("no")) ||
4655 !tstrcmp(igcase, T("0")))
4656 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_SENSITIVE;
4657 else if (!tstrcmp(igcase, T("yes")) ||
4658 !tstrcmp(igcase, T("1")))
4659 init_flags |= WIMLIB_INIT_FLAG_DEFAULT_CASE_INSENSITIVE;
4662 "WARNING: Ignoring unknown setting of "
4663 "WIMLIB_IMAGEX_IGNORE_CASE\n");
4668 /* Allow being invoked as wimCOMMAND (e.g. wimapply). */
4670 if (!tstrncmp(invocation_name, T("wim"), 3) &&
4671 tstrcmp(invocation_name, T("wimlib-imagex"))) {
4672 for (int i = 0; i < CMD_MAX; i++) {
4673 if (!tstrcmp(invocation_name + 3,
4674 imagex_commands[i].name))
4683 /* Unless already known from the invocation name, determine which
4684 * command was specified. */
4685 if (cmd == CMD_NONE) {
4687 imagex_error(T("No command specified!\n"));
4691 for (int i = 0; i < CMD_MAX; i++) {
4692 if (!tstrcmp(argv[1], imagex_commands[i].name)) {
4697 if (cmd != CMD_NONE) {
4703 /* Handle --help and --version. --help can be either for the program as
4704 * a whole (cmd == CMD_NONE) or just for a specific command (cmd !=
4705 * CMD_NONE). Note: help_or_version() will not return if a --help or
4706 * --version argument was found. */
4707 help_or_version(argc, argv, cmd);
4709 /* Bail if a valid command was not specified. */
4710 if (cmd == CMD_NONE) {
4711 imagex_error(T("Unrecognized command: `%"TS"'\n"), argv[1]);
4716 /* Enable warning and error messages in wimlib to be more user-friendly.
4718 wimlib_set_print_errors(true);
4720 /* Initialize wimlib. */
4721 ret = wimlib_global_init(init_flags);
4723 goto out_check_status;
4725 /* Call the command handler function. */
4726 ret = imagex_commands[cmd].func(argc, argv, cmd);
4728 /* Check for error writing to standard output, especially since for some
4729 * commands, writing to standard output is part of the program's actual
4730 * behavior and not just for informational purposes. */
4731 if (ferror(stdout) || fclose(stdout)) {
4732 imagex_error_with_errno(T("error writing to standard output"));
4737 /* Exit status (ret): -1 indicates an error found by 'wimlib-imagex'
4738 * itself (not by wimlib). 0 indicates success. > 0 indicates a wimlib
4739 * error code from which an error message can be printed. */
4741 imagex_error(T("Exiting with error code %d:\n"
4743 wimlib_get_error_string(ret));
4744 if (ret == WIMLIB_ERR_NTFS_3G && errno != 0)
4745 imagex_error_with_errno(T("errno"));
4747 /* Make wimlib free any resources it's holding (although this is not
4748 * strictly necessary because the process is ending anyway). */
4749 wimlib_global_cleanup();