+
+ 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;