+ struct apply_ctx *ctx = _ctx;
+ union wimlib_progress_info *progress = &ctx->progress;
+ int ret;
+
+ ctx->cur_blob_offset += size;
+
+ if (likely(ctx->supported_features.hard_links)) {
+ progress->extract.completed_bytes +=
+ (u64)size * ctx->cur_blob->out_refcnt;
+ if (ctx->cur_blob_offset == ctx->cur_blob->size)
+ progress->extract.completed_streams += ctx->cur_blob->out_refcnt;
+ } else {
+ const struct blob_extraction_target *targets =
+ blob_extraction_targets(ctx->cur_blob);
+ for (u32 i = 0; i < ctx->cur_blob->out_refcnt; i++) {
+ const struct wim_inode *inode = targets[i].inode;
+ const struct wim_dentry *dentry;
+
+ inode_for_each_extraction_alias(dentry, inode) {
+ progress->extract.completed_bytes += size;
+ if (ctx->cur_blob_offset == ctx->cur_blob->size)
+ progress->extract.completed_streams++;
+ }
+ }
+ }
+ if (progress->extract.completed_bytes >= ctx->next_progress) {
+
+ ret = extract_progress(ctx, WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS);
+ if (ret)
+ return ret;
+
+ set_next_progress(progress->extract.completed_bytes,
+ progress->extract.total_bytes,
+ &ctx->next_progress);
+ }
+
+ if (unlikely(filedes_valid(&ctx->tmpfile_fd))) {
+ /* Just extracting to temporary file for now. */
+ ret = full_write(&ctx->tmpfile_fd, chunk, size);
+ if (ret) {
+ ERROR_WITH_ERRNO("Error writing data to "
+ "temporary file \"%"TS"\"",
+ ctx->tmpfile_name);
+ }
+ return ret;
+ }
+
+ return call_consume_chunk(chunk, size, ctx->saved_cbs);
+}
+
+/* Copy the blob's data from the temporary file to each of its targets.
+ *
+ * This is executed only in the very uncommon case that a blob is being
+ * extracted to more than MAX_OPEN_FILES targets! */
+static int
+extract_from_tmpfile(const tchar *tmpfile_name,
+ const struct blob_descriptor *orig_blob,
+ const struct read_blob_callbacks *cbs)
+{
+ struct blob_descriptor tmpfile_blob;
+ const struct blob_extraction_target *targets = blob_extraction_targets(orig_blob);
+ int ret;
+
+ memcpy(&tmpfile_blob, orig_blob, sizeof(struct blob_descriptor));
+ tmpfile_blob.blob_location = BLOB_IN_FILE_ON_DISK;
+ tmpfile_blob.file_on_disk = (tchar *)tmpfile_name;
+ tmpfile_blob.out_refcnt = 1;
+
+ for (u32 i = 0; i < orig_blob->out_refcnt; i++) {
+ tmpfile_blob.inline_blob_extraction_targets[0] = targets[i];
+ ret = read_blob_with_cbs(&tmpfile_blob, cbs);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int
+end_extract_blob_wrapper(struct blob_descriptor *blob, int status, void *_ctx)
+{
+ struct apply_ctx *ctx = _ctx;
+
+ if (unlikely(filedes_valid(&ctx->tmpfile_fd))) {
+ filedes_close(&ctx->tmpfile_fd);
+ if (!status)
+ status = extract_from_tmpfile(ctx->tmpfile_name, blob,
+ ctx->saved_cbs);
+ filedes_invalidate(&ctx->tmpfile_fd);
+ tunlink(ctx->tmpfile_name);
+ FREE(ctx->tmpfile_name);
+ return status;
+ }
+
+ return call_end_blob(blob, status, ctx->saved_cbs);
+}
+
+/*
+ * Read the list of blobs to extract and feed their data into the specified
+ * callback functions.
+ *
+ * This handles checksumming each blob.
+ *
+ * This also handles sending WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS.
+ *
+ * This also works if the WIM is being read from a pipe.
+ *
+ * This also will split up blobs that will need to be extracted to more than
+ * MAX_OPEN_FILES locations, as measured by the 'out_refcnt' of each blob.
+ * Therefore, the apply_operations implementation need not worry about running
+ * out of file descriptors, unless it might open more than one file descriptor
+ * per 'blob_extraction_target' (e.g. Win32 currently might because the
+ * destination file system might not support hard links).
+ */
+int
+extract_blob_list(struct apply_ctx *ctx, const struct read_blob_callbacks *cbs)
+{
+ struct read_blob_callbacks wrapper_cbs = {
+ .begin_blob = begin_extract_blob_wrapper,
+ .consume_chunk = extract_chunk_wrapper,
+ .end_blob = end_extract_blob_wrapper,
+ .ctx = ctx,
+ };
+ ctx->saved_cbs = cbs;
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
+ return read_blobs_from_pipe(ctx, &wrapper_cbs);
+ } else {
+ return read_blob_list(&ctx->blob_list,
+ offsetof(struct blob_descriptor,
+ extraction_list),
+ &wrapper_cbs, VERIFY_BLOB_HASHES);
+ }
+}
+
+/* Extract a WIM dentry to standard output.
+ *
+ * This obviously doesn't make sense in all cases. We return an error if the
+ * dentry does not correspond to a regular file. Otherwise we extract the
+ * unnamed data stream only. */
+static int
+extract_dentry_to_stdout(struct wim_dentry *dentry,
+ const struct blob_table *blob_table)
+{
+ struct wim_inode *inode = dentry->d_inode;
+ struct blob_descriptor *blob;
+ struct filedes _stdout;
+
+ if (inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_ENCRYPTED))
+ {
+ ERROR("\"%"TS"\" is not a regular file and therefore cannot be "
+ "extracted to standard output", dentry_full_path(dentry));
+ return WIMLIB_ERR_NOT_A_REGULAR_FILE;
+ }
+
+ blob = inode_get_blob_for_unnamed_data_stream(inode, blob_table);
+ if (!blob) {
+ const u8 *hash = inode_get_hash_of_unnamed_data_stream(inode);
+ if (!is_zero_hash(hash))
+ return blob_not_found_error(inode, hash);
+ return 0;
+ }
+
+ filedes_init(&_stdout, STDOUT_FILENO);
+ return extract_blob_to_fd(blob, &_stdout);
+}
+
+static int
+extract_dentries_to_stdout(struct wim_dentry **dentries, size_t num_dentries,
+ const struct blob_table *blob_table)
+{
+ for (size_t i = 0; i < num_dentries; i++) {
+ int ret = extract_dentry_to_stdout(dentries[i], blob_table);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/**********************************************************************/
+
+/*
+ * Removes duplicate dentries from the array.
+ *
+ * Returns the new number of dentries, packed at the front of the array.
+ */
+static size_t
+remove_duplicate_trees(struct wim_dentry **trees, size_t num_trees)
+{
+ size_t i, j = 0;
+ for (i = 0; i < num_trees; i++) {
+ if (!trees[i]->d_tmp_flag) {
+ /* Found distinct dentry. */
+ trees[i]->d_tmp_flag = 1;
+ trees[j++] = trees[i];
+ }
+ }
+ for (i = 0; i < j; i++)
+ trees[i]->d_tmp_flag = 0;
+ return j;
+}
+
+/*
+ * Remove dentries that are descendants of other dentries in the array.
+ *
+ * Returns the new number of dentries, packed at the front of the array.
+ */
+static size_t
+remove_contained_trees(struct wim_dentry **trees, size_t num_trees)
+{
+ size_t i, j = 0;
+ for (i = 0; i < num_trees; i++)
+ trees[i]->d_tmp_flag = 1;
+ for (i = 0; i < num_trees; i++) {
+ struct wim_dentry *d = trees[i];
+ while (!dentry_is_root(d)) {
+ d = d->d_parent;
+ if (d->d_tmp_flag)
+ goto tree_contained;
+ }
+ trees[j++] = trees[i];
+ continue;
+
+ tree_contained:
+ trees[i]->d_tmp_flag = 0;
+ }
+
+ for (i = 0; i < j; i++)
+ trees[i]->d_tmp_flag = 0;
+ return j;
+}
+
+static int
+dentry_append_to_list(struct wim_dentry *dentry, void *_dentry_list)
+{
+ struct list_head *dentry_list = _dentry_list;
+ list_add_tail(&dentry->d_extraction_list_node, dentry_list);
+ return 0;
+}
+
+static void
+dentry_reset_extraction_list_node(struct wim_dentry *dentry)
+{
+ dentry->d_extraction_list_node = (struct list_head){NULL, NULL};
+}
+
+static int
+dentry_delete_from_list(struct wim_dentry *dentry, void *_ignore)
+{
+ if (will_extract_dentry(dentry)) {
+ list_del(&dentry->d_extraction_list_node);
+ dentry_reset_extraction_list_node(dentry);
+ }
+ return 0;
+}
+
+/*
+ * Build the preliminary list of dentries to be extracted.
+ *
+ * The list maintains the invariant that if d1 and d2 are in the list and d1 is
+ * an ancestor of d2, then d1 appears before d2 in the list.
+ */
+static void
+build_dentry_list(struct list_head *dentry_list, struct wim_dentry **trees,
+ size_t num_trees, bool add_ancestors)
+{
+ INIT_LIST_HEAD(dentry_list);
+
+ /* Add the trees recursively. */
+ for (size_t i = 0; i < num_trees; i++)
+ for_dentry_in_tree(trees[i], dentry_append_to_list, dentry_list);
+
+ /* If requested, add ancestors of the trees. */
+ if (add_ancestors) {
+ for (size_t i = 0; i < num_trees; i++) {
+ struct wim_dentry *dentry = trees[i];
+ struct wim_dentry *ancestor;
+ struct list_head *place_after;
+
+ if (dentry_is_root(dentry))
+ continue;
+
+ place_after = dentry_list;
+ ancestor = dentry;
+ do {
+ ancestor = ancestor->d_parent;
+ if (will_extract_dentry(ancestor)) {
+ place_after = &ancestor->d_extraction_list_node;
+ break;
+ }
+ } while (!dentry_is_root(ancestor));
+
+ ancestor = dentry;
+ do {
+ ancestor = ancestor->d_parent;
+ if (will_extract_dentry(ancestor))
+ break;
+ list_add(&ancestor->d_extraction_list_node, place_after);
+ } while (!dentry_is_root(ancestor));
+ }
+ }
+}
+
+static void
+destroy_dentry_list(struct list_head *dentry_list)
+{
+ struct wim_dentry *dentry, *tmp;
+ struct wim_inode *inode;
+
+ list_for_each_entry_safe(dentry, tmp, dentry_list, d_extraction_list_node) {
+ inode = dentry->d_inode;
+ dentry_reset_extraction_list_node(dentry);
+ inode->i_visited = 0;
+ inode->i_can_externally_back = 0;
+ if ((void *)dentry->d_extraction_name != (void *)dentry->d_name)
+ FREE(dentry->d_extraction_name);
+ dentry->d_extraction_name = NULL;
+ dentry->d_extraction_name_nchars = 0;
+ }
+}
+
+static void
+destroy_blob_list(struct list_head *blob_list)
+{
+ struct blob_descriptor *blob;
+
+ list_for_each_entry(blob, blob_list, extraction_list)
+ if (blob->out_refcnt > ARRAY_LEN(blob->inline_blob_extraction_targets))
+ FREE(blob->blob_extraction_targets);
+}
+