+#ifndef __WIN32__
+ /* On non-Windows, assume an ASCII-compatible encoding, such as UTF-8.
+ * */
+ *num_tchars_ret = num_bytes;
+ return text;
+#else /* !__WIN32__ */
+ /* On Windows, translate the text to UTF-16LE */
+ wchar_t *text_wstr;
+ size_t num_wchars;
+
+ if (num_bytes >= 2 &&
+ (((unsigned char)text[0] == 0xff && (unsigned char)text[1] == 0xfe) ||
+ ((unsigned char)text[0] <= 0x7f && (unsigned char)text[1] == 0x00)))
+ {
+ /* File begins with 0xfeff, the BOM for UTF-16LE, or it begins
+ * with something that looks like an ASCII character encoded as
+ * a UTF-16LE code unit. Assume the file is encoded as
+ * UTF-16LE. This is not a 100% reliable check. */
+ num_wchars = num_bytes / 2;
+ text_wstr = (wchar_t*)text;
+ } else {
+ /* File does not look like UTF-16LE. Assume it is encoded in
+ * the current Windows code page. I think these are always
+ * ASCII-compatible, so any so-called "plain-text" (ASCII) files
+ * should work as expected. */
+ text_wstr = win32_mbs_to_wcs(text,
+ num_bytes,
+ &num_wchars);
+ free(text);
+ }
+ *num_tchars_ret = num_wchars;
+ return text_wstr;
+#endif /* __WIN32__ */
+}
+
+static tchar *
+file_get_text_contents(const tchar *filename, size_t *num_tchars_ret)
+{
+ char *contents;
+ size_t num_bytes;
+
+ contents = file_get_contents(filename, &num_bytes);
+ if (!contents)
+ return NULL;
+ return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
+}
+
+static tchar *
+stdin_get_text_contents(size_t *num_tchars_ret)
+{
+ char *contents;
+ size_t num_bytes;
+
+ contents = stdin_get_contents(&num_bytes);
+ if (!contents)
+ return NULL;
+ return translate_text_to_tstr(contents, num_bytes, num_tchars_ret);
+}
+
+#define TO_PERCENT(numerator, denominator) \
+ (((denominator) == 0) ? 0 : ((numerator) * 100 / (denominator)))
+
+#define GIBIBYTE_MIN_NBYTES 10000000000ULL
+#define MEBIBYTE_MIN_NBYTES 10000000ULL
+#define KIBIBYTE_MIN_NBYTES 10000ULL
+
+static unsigned
+get_unit(uint64_t total_bytes, const tchar **name_ret)
+{
+ if (total_bytes >= GIBIBYTE_MIN_NBYTES) {
+ *name_ret = T("GiB");
+ return 30;
+ } else if (total_bytes >= MEBIBYTE_MIN_NBYTES) {
+ *name_ret = T("MiB");
+ return 20;
+ } else if (total_bytes >= KIBIBYTE_MIN_NBYTES) {
+ *name_ret = T("KiB");
+ return 10;
+ } else {
+ *name_ret = T("bytes");
+ return 0;
+ }
+}
+
+static struct wimlib_progress_info_scan last_scan_progress;
+
+static void
+report_scan_progress(const struct wimlib_progress_info_scan *scan, bool done)
+{
+ uint64_t prev_count, cur_count;
+
+ prev_count = last_scan_progress.num_nondirs_scanned +
+ last_scan_progress.num_dirs_scanned;
+ cur_count = scan->num_nondirs_scanned + scan->num_dirs_scanned;
+
+ if (done || prev_count == 0 || cur_count >= prev_count + 100 ||
+ cur_count % 128 == 0)
+ {
+ unsigned unit_shift;
+ const tchar *unit_name;
+
+ unit_shift = get_unit(scan->num_bytes_scanned, &unit_name);
+ imagex_printf(T("\r%"PRIu64" %"TS" scanned (%"PRIu64" files, "
+ "%"PRIu64" directories) "),
+ scan->num_bytes_scanned >> unit_shift,
+ unit_name,
+ scan->num_nondirs_scanned,
+ scan->num_dirs_scanned);
+ last_scan_progress = *scan;
+ }
+}
+/* Progress callback function passed to various wimlib functions. */
+static enum wimlib_progress_status
+imagex_progress_func(enum wimlib_progress_msg msg,
+ union wimlib_progress_info *info,
+ void *_ignored_context)
+{
+ unsigned percent_done;
+ unsigned unit_shift;
+ const tchar *unit_name;
+
+ if (imagex_be_quiet)
+ return WIMLIB_PROGRESS_STATUS_CONTINUE;
+ switch (msg) {
+ case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
+ {
+ static bool first = true;
+ if (first) {
+ imagex_printf(T("Writing %"TS"-compressed data "
+ "using %u thread%"TS"\n"),
+ wimlib_get_compression_type_string(
+ info->write_streams.compression_type),
+ info->write_streams.num_threads,
+ (info->write_streams.num_threads == 1) ? T("") : T("s"));
+ first = false;
+ }
+ }
+ unit_shift = get_unit(info->write_streams.total_bytes, &unit_name);
+ percent_done = TO_PERCENT(info->write_streams.completed_bytes,
+ info->write_streams.total_bytes);
+
+ imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
+ "written (%u%% done)"),
+ info->write_streams.completed_bytes >> unit_shift,
+ unit_name,
+ info->write_streams.total_bytes >> unit_shift,
+ unit_name,
+ percent_done);
+ if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
+ imagex_printf(T("\n"));
+ break;
+ case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
+ imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
+ if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) {
+ imagex_printf(T("\n"));
+ } else {
+ imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"),
+ info->scan.wim_target_path);
+ }
+ memset(&last_scan_progress, 0, sizeof(last_scan_progress));
+ break;
+ case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
+ switch (info->scan.status) {
+ case WIMLIB_SCAN_DENTRY_OK:
+ report_scan_progress(&info->scan, false);
+ break;
+ case WIMLIB_SCAN_DENTRY_EXCLUDED:
+ imagex_printf(T("\nExcluding \"%"TS"\" from capture\n"), info->scan.cur_path);
+ break;
+ case WIMLIB_SCAN_DENTRY_UNSUPPORTED:
+ imagex_printf(T("\nWARNING: Excluding unsupported file or directory\n"
+ " \"%"TS"\" from capture\n"), info->scan.cur_path);
+ break;
+ case WIMLIB_SCAN_DENTRY_FIXED_SYMLINK:
+ /* Symlink fixups are enabled by default. This is
+ * mainly intended for Windows, which for some reason
+ * uses absolute junctions (with drive letters!) in the
+ * default installation. On UNIX-like systems, warn the
+ * user when fixing the target of an absolute symbolic
+ * link, so they know to disable this if they want. */
+ #ifndef __WIN32__
+ imagex_printf(T("\nWARNING: Adjusted target of "
+ "absolute symbolic link \"%"TS"\"\n"
+ " (Use --norpfix to capture "
+ "absolute symbolic links as-is)\n"),
+ info->scan.cur_path);
+ #endif
+ break;
+ default:
+ break;
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_SCAN_END:
+ report_scan_progress(&info->scan, true);
+ imagex_printf(T("\n"));
+ break;
+ case WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY:
+ unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
+ percent_done = TO_PERCENT(info->integrity.completed_bytes,
+ info->integrity.total_bytes);
+ imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
+ "of %"PRIu64" %"TS" (%u%%) done"),
+ info->integrity.filename,
+ info->integrity.completed_bytes >> unit_shift,
+ unit_name,
+ info->integrity.total_bytes >> unit_shift,
+ unit_name,
+ percent_done);
+ if (info->integrity.completed_bytes == info->integrity.total_bytes)
+ imagex_printf(T("\n"));
+ break;
+ case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
+ unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
+ percent_done = TO_PERCENT(info->integrity.completed_bytes,
+ info->integrity.total_bytes);
+ imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
+ "of %"PRIu64" %"TS" (%u%%) done"),
+ info->integrity.completed_bytes >> unit_shift,
+ unit_name,
+ info->integrity.total_bytes >> unit_shift,
+ unit_name,
+ percent_done);
+ if (info->integrity.completed_bytes == info->integrity.total_bytes)
+ imagex_printf(T("\n"));
+ break;
+ case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
+ imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
+ "to %"TS" \"%"TS"\"\n"),
+ info->extract.image,
+ info->extract.image_name,
+ info->extract.wimfile_name,
+ ((info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) ?
+ T("NTFS volume") : T("directory")),
+ info->extract.target);
+ break;
+ case WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE:
+ if (info->extract.end_file_count >= 2000) {
+ percent_done = TO_PERCENT(info->extract.current_file_count,
+ info->extract.end_file_count);
+ imagex_printf(T("\rCreating files: %"PRIu64" of %"PRIu64" (%u%%) done"),
+ info->extract.current_file_count,
+ info->extract.end_file_count, percent_done);
+ if (info->extract.current_file_count == info->extract.end_file_count)
+ imagex_printf(T("\n"));
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
+ percent_done = TO_PERCENT(info->extract.completed_bytes,
+ info->extract.total_bytes);
+ unit_shift = get_unit(info->extract.total_bytes, &unit_name);
+ imagex_printf(T("\rExtracting file data: "
+ "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
+ info->extract.completed_bytes >> unit_shift,
+ unit_name,
+ info->extract.total_bytes >> unit_shift,
+ unit_name,
+ percent_done);
+ if (info->extract.completed_bytes >= info->extract.total_bytes)
+ imagex_printf(T("\n"));
+ break;
+ case WIMLIB_PROGRESS_MSG_EXTRACT_METADATA:
+ if (info->extract.end_file_count >= 2000) {
+ percent_done = TO_PERCENT(info->extract.current_file_count,
+ info->extract.end_file_count);
+ imagex_printf(T("\rApplying metadata to files: %"PRIu64" of %"PRIu64" (%u%%) done"),
+ info->extract.current_file_count,
+ info->extract.end_file_count, percent_done);
+ if (info->extract.current_file_count == info->extract.end_file_count)
+ imagex_printf(T("\n"));
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN:
+ if (info->extract.total_parts != 1) {
+ imagex_printf(T("\nReading split pipable WIM part %u of %u\n"),
+ info->extract.part_number,
+ info->extract.total_parts);
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
+ percent_done = TO_PERCENT(info->split.completed_bytes,
+ info->split.total_bytes);
+ unit_shift = get_unit(info->split.total_bytes, &unit_name);
+ imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
+ "%"PRIu64" %"TS" (%u%%) written\n"),
+ info->split.part_name,
+ info->split.cur_part_number,
+ info->split.total_parts,
+ info->split.completed_bytes >> unit_shift,
+ unit_name,
+ info->split.total_bytes >> unit_shift,
+ unit_name,
+ percent_done);
+ break;
+ case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
+ if (info->split.completed_bytes == info->split.total_bytes) {
+ imagex_printf(T("Finished writing split WIM part %u of %u\n"),
+ info->split.cur_part_number,
+ info->split.total_parts);
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
+ switch (info->update.command->op) {
+ case WIMLIB_UPDATE_OP_DELETE:
+ imagex_printf(T("Deleted WIM path \"%"TS"\"\n"),
+ info->update.command->delete_.wim_path);
+ break;
+ case WIMLIB_UPDATE_OP_RENAME:
+ imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"),
+ info->update.command->rename.wim_source_path,
+ info->update.command->rename.wim_target_path);
+ break;
+ case WIMLIB_UPDATE_OP_ADD:
+ default:
+ break;
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM:
+ imagex_printf(T("Updating \"%"TS"\" in WIM image\n"),
+ info->replace.path_in_wim);
+ break;
+ case WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE:
+ imagex_printf(T("\nExtracting \"%"TS"\" as normal file (not WIMBoot pointer)\n"),
+ info->wimboot_exclude.path_in_wim);
+ break;
+ case WIMLIB_PROGRESS_MSG_UNMOUNT_BEGIN:
+ if (info->unmount.mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
+ if (info->unmount.unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) {
+ imagex_printf(T("Committing changes to %"TS" (image %d)\n"),
+ info->unmount.mounted_wim,
+ info->unmount.mounted_image);
+ } else {
+ imagex_printf(T("Discarding changes to %"TS" (image %d)\n"),
+ info->unmount.mounted_wim,
+ info->unmount.mounted_image);
+ imagex_printf(T("\t(Use --commit to keep changes.)\n"));
+ }
+ }
+ break;
+ case WIMLIB_PROGRESS_MSG_BEGIN_VERIFY_IMAGE:
+ imagex_printf(T("Verifying metadata for image %"PRIu32" of %"PRIu32"\n"),
+ info->verify_image.current_image,
+ info->verify_image.total_images);
+ break;
+ case WIMLIB_PROGRESS_MSG_VERIFY_STREAMS:
+ percent_done = TO_PERCENT(info->verify_streams.completed_bytes,
+ info->verify_streams.total_bytes);
+ unit_shift = get_unit(info->verify_streams.total_bytes, &unit_name);
+ imagex_printf(T("\rVerifying file data: "
+ "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
+ info->verify_streams.completed_bytes >> unit_shift,
+ unit_name,
+ info->verify_streams.total_bytes >> unit_shift,
+ unit_name,
+ percent_done);
+ if (info->verify_streams.completed_bytes == info->verify_streams.total_bytes)
+ imagex_printf(T("\n"));
+ break;
+ default:
+ break;
+ }
+ fflush(imagex_info_file);
+ return WIMLIB_PROGRESS_STATUS_CONTINUE;
+}
+
+static unsigned
+parse_num_threads(const tchar *optarg)
+{
+ tchar *tmp;
+ unsigned long ul_nthreads = tstrtoul(optarg, &tmp, 10);
+ if (ul_nthreads >= UINT_MAX || *tmp || tmp == optarg) {
+ imagex_error(T("Number of threads must be a non-negative integer!"));
+ return UINT_MAX;
+ } else {
+ return ul_nthreads;
+ }