+ 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 int
+imagex_progress_func(enum wimlib_progress_msg msg,
+ const union wimlib_progress_info *info)
+{
+ unsigned percent_done;
+ unsigned unit_shift;
+ const tchar *unit_name;
+
+ if (imagex_be_quiet)
+ return 0;
+ switch (msg) {
+ case WIMLIB_PROGRESS_MSG_WRITE_STREAMS:
+ 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);
+
+ if (info->write_streams.completed_streams == 0) {
+ 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"));
+ }
+ if (info->write_streams.total_parts <= 1) {
+ 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);
+ } else {
+ imagex_printf(T("\rWriting resources from part %u of %u: "
+ "%"PRIu64 " %"TS" of %"PRIu64" %"TS" (%u%%) written"),
+ (info->write_streams.completed_parts ==
+ info->write_streams.total_parts) ?
+ info->write_streams.completed_parts :
+ info->write_streams.completed_parts + 1,
+ info->write_streams.total_parts,
+ 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 (*info->scan.wim_target_path) {
+ imagex_printf(T(" (loading as WIM path: "
+ "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\")...\n"),
+ info->scan.wim_target_path);
+ } else {
+ imagex_printf(T("\n"));
+ }
+ 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;
+ }
+ 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_TREE_BEGIN:
+ imagex_printf(T("Extracting "
+ "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\" from image %d (\"%"TS"\") "
+ "in \"%"TS"\" to \"%"TS"\"\n"),
+ info->extract.extract_root_wim_source_path,
+ info->extract.image,
+ info->extract.image_name,
+ info->extract.wimfile_name,
+ info->extract.target);
+ 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 files: "
+ "%"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_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_APPLY_TIMESTAMPS:
+ if (info->extract.extract_root_wim_source_path[0] == T('\0'))
+ imagex_printf(T("Setting timestamps on all extracted files...\n"));
+ break;
+ case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END:
+ if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
+ imagex_printf(T("Unmounting NTFS volume \"%"TS"\"...\n"),
+ info->extract.target);
+ }
+ 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 "
+ "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"),
+ info->update.command->delete_.wim_path);
+ break;
+ case WIMLIB_UPDATE_OP_RENAME:
+ imagex_printf(T("Renamed WIM path "
+ "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\" => "
+ "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"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;
+ default:
+ break;
+ }
+ fflush(imagex_info_file);
+ return 0;
+}
+
+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;
+ }
+}
+
+static uint32_t parse_chunk_size(const tchar *optarg)
+{
+ tchar *tmp;
+ unsigned long chunk_size = tstrtoul(optarg, &tmp, 10);
+ if (chunk_size >= UINT32_MAX || *tmp || tmp == optarg) {
+ imagex_error(T("Chunk size must be a non-negative integer!"));
+ return UINT32_MAX;
+ } else {
+ return chunk_size;
+ }
+}
+
+
+/*
+ * Parse an option passed to an update command.
+ *
+ * @op: One of WIMLIB_UPDATE_OP_* that indicates the command being
+ * parsed.
+ *
+ * @option: Text string for the option (beginning with --)
+ *
+ * @cmd: `struct wimlib_update_command' that is being constructed for
+ * this command.
+ *
+ * Returns true if the option was recognized; false if not.
+ */
+static bool
+update_command_add_option(int op, const tchar *option,
+ struct wimlib_update_command *cmd)
+{
+ bool recognized = true;
+ switch (op) {
+ case WIMLIB_UPDATE_OP_ADD:
+ if (!tstrcmp(option, T("--verbose")))
+ cmd->add.add_flags |= WIMLIB_ADD_FLAG_VERBOSE;
+ else if (!tstrcmp(option, T("--unix-data")))
+ cmd->add.add_flags |= WIMLIB_ADD_FLAG_UNIX_DATA;
+ else if (!tstrcmp(option, T("--no-acls")) || !tstrcmp(option, T("--noacls")))
+ cmd->add.add_flags |= WIMLIB_ADD_FLAG_NO_ACLS;
+ else if (!tstrcmp(option, T("--strict-acls")))
+ cmd->add.add_flags |= WIMLIB_ADD_FLAG_STRICT_ACLS;
+ else if (!tstrcmp(option, T("--dereference")))
+ cmd->add.add_flags |= WIMLIB_ADD_FLAG_DEREFERENCE;
+ else
+ recognized = false;
+ break;
+ case WIMLIB_UPDATE_OP_DELETE:
+ if (!tstrcmp(option, T("--force")))
+ cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_FORCE;
+ else if (!tstrcmp(option, T("--recursive")))
+ cmd->delete_.delete_flags |= WIMLIB_DELETE_FLAG_RECURSIVE;
+ else
+ recognized = false;
+ break;
+ default:
+ recognized = false;
+ break;
+ }
+ return recognized;
+}
+
+/* How many nonoption arguments each `imagex update' command expects */
+static const unsigned update_command_num_nonoptions[] = {
+ [WIMLIB_UPDATE_OP_ADD] = 2,
+ [WIMLIB_UPDATE_OP_DELETE] = 1,
+ [WIMLIB_UPDATE_OP_RENAME] = 2,
+};
+
+static void
+update_command_add_nonoption(int op, const tchar *nonoption,
+ struct wimlib_update_command *cmd,
+ unsigned num_nonoptions)
+{
+ switch (op) {
+ case WIMLIB_UPDATE_OP_ADD:
+ if (num_nonoptions == 0)
+ cmd->add.fs_source_path = (tchar*)nonoption;
+ else
+ cmd->add.wim_target_path = (tchar*)nonoption;
+ break;
+ case WIMLIB_UPDATE_OP_DELETE:
+ cmd->delete_.wim_path = (tchar*)nonoption;
+ break;
+ case WIMLIB_UPDATE_OP_RENAME:
+ if (num_nonoptions == 0)
+ cmd->rename.wim_source_path = (tchar*)nonoption;
+ else
+ cmd->rename.wim_target_path = (tchar*)nonoption;
+ break;
+ }
+}
+
+/*
+ * Parse a command passed on stdin to `imagex update'.
+ *
+ * @line: Text of the command.
+ * @len: Length of the line, including a null terminator
+ * at line[len - 1].
+ *
+ * @command: A `struct wimlib_update_command' to fill in from the parsed
+ * line.
+ *
+ * @line_number: Line number of the command, for diagnostics.
+ *
+ * Returns true on success; returns false on parse error.
+ */
+static bool
+parse_update_command(tchar *line, size_t len,
+ struct wimlib_update_command *command,
+ size_t line_number)
+{
+ int ret;
+ tchar *command_name;
+ int op;
+ size_t num_nonoptions;
+
+ /* Get the command name ("add", "delete", "rename") */
+ ret = parse_string(&line, &len, &command_name);
+ if (ret != PARSE_STRING_SUCCESS)
+ return false;
+
+ if (!tstrcasecmp(command_name, T("add"))) {
+ op = WIMLIB_UPDATE_OP_ADD;
+ } else if (!tstrcasecmp(command_name, T("delete"))) {
+ op = WIMLIB_UPDATE_OP_DELETE;
+ } else if (!tstrcasecmp(command_name, T("rename"))) {
+ op = WIMLIB_UPDATE_OP_RENAME;
+ } else {
+ imagex_error(T("Unknown update command \"%"TS"\" on line %zu"),
+ command_name, line_number);
+ return false;
+ }
+ command->op = op;
+
+ /* Parse additional options and non-options as needed */
+ num_nonoptions = 0;
+ for (;;) {
+ tchar *next_string;
+
+ ret = parse_string(&line, &len, &next_string);
+ if (ret == PARSE_STRING_NONE) /* End of line */
+ break;
+ else if (ret != PARSE_STRING_SUCCESS) /* Parse failure */
+ return false;
+ if (next_string[0] == T('-') && next_string[1] == T('-')) {
+ /* Option */
+ if (!update_command_add_option(op, next_string, command))
+ {
+ imagex_error(T("Unrecognized option \"%"TS"\" to "
+ "update command \"%"TS"\" on line %zu"),
+ next_string, command_name, line_number);
+
+ return false;
+ }
+ } else {
+ /* Nonoption */
+ if (num_nonoptions == update_command_num_nonoptions[op])
+ {
+ imagex_error(T("Unexpected argument \"%"TS"\" in "
+ "update command on line %zu\n"
+ " (The \"%"TS"\" command only "
+ "takes %zu nonoption arguments!)\n"),
+ next_string, line_number,
+ command_name, num_nonoptions);
+ return false;
+ }
+ update_command_add_nonoption(op, next_string,
+ command, num_nonoptions);
+ num_nonoptions++;
+ }
+ }
+
+ if (num_nonoptions != update_command_num_nonoptions[op]) {
+ imagex_error(T("Not enough arguments to update command "
+ "\"%"TS"\" on line %zu"), command_name, line_number);
+ return false;
+ }
+ return true;
+}
+
+static struct wimlib_update_command *
+parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
+ size_t *num_cmds_ret)
+{
+ ssize_t nlines;
+ tchar *p;
+ struct wimlib_update_command *cmds;
+ size_t i, j;
+
+ nlines = text_file_count_lines(cmd_file_contents_p,
+ &cmd_file_nchars);
+ if (nlines < 0)
+ return NULL;
+
+ /* Always allocate at least 1 slot, just in case the implementation of
+ * calloc() returns NULL if 0 bytes are requested. */
+ cmds = calloc(nlines ?: 1, sizeof(struct wimlib_update_command));
+ if (!cmds) {
+ imagex_error(T("out of memory"));
+ return NULL;
+ }
+ p = *cmd_file_contents_p;
+ j = 0;
+ for (i = 0; i < nlines; i++) {
+ /* XXX: Could use rawmemchr() here instead, but it may not be
+ * available on all platforms. */
+ tchar *endp = tmemchr(p, T('\n'), cmd_file_nchars);
+ size_t len = endp - p + 1;
+ *endp = T('\0');
+ if (!is_comment_line(p, len)) {
+ if (!parse_update_command(p, len, &cmds[j++], i + 1)) {
+ free(cmds);
+ return NULL;
+ }
+ }
+ p = endp + 1;
+ }
+ *num_cmds_ret = j;
+ return cmds;
+}
+
+/* Apply one image, or all images, from a WIM file into a directory, OR apply
+ * one image from a WIM file to a NTFS volume. */
+static int
+imagex_apply(int argc, tchar **argv, int cmd)
+{
+ int c;
+ int open_flags = 0;
+ int image = WIMLIB_NO_IMAGE;
+ WIMStruct *wim;
+ struct wimlib_wim_info info;
+ int ret;
+ const tchar *wimfile;
+ const tchar *target;
+ const tchar *image_num_or_name = NULL;
+ int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
+
+ STRING_SET(refglobs);
+
+ for_opt(c, apply_options) {
+ switch (c) {
+ case IMAGEX_CHECK_OPTION:
+ open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
+ break;
+ case IMAGEX_HARDLINK_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_HARDLINK;
+ break;
+ case IMAGEX_SYMLINK_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_SYMLINK;
+ break;
+ case IMAGEX_VERBOSE_OPTION:
+ /* No longer does anything. */
+ break;
+ case IMAGEX_REF_OPTION:
+ ret = string_set_append(&refglobs, optarg);
+ if (ret)
+ goto out_free_refglobs;
+ break;
+ case IMAGEX_UNIX_DATA_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_UNIX_DATA;
+ break;
+ case IMAGEX_NO_ACLS_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_NO_ACLS;
+ break;
+ case IMAGEX_STRICT_ACLS_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
+ break;
+ case IMAGEX_NORPFIX_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
+ break;
+ case IMAGEX_RPFIX_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
+ break;
+ case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES;
+ extract_flags |= WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS;
+ break;
+ case IMAGEX_RESUME_OPTION:
+ extract_flags |= WIMLIB_EXTRACT_FLAG_RESUME;