+ size_t num_raw_path_components;
+ const struct wim_dentry *d;
+ size_t num_target_path_components;
+ tchar *p;
+ const tchar *p_old;
+ int ret;
+
+ num_raw_path_components = 0;
+ for (d = dentry; d != ctx->extract_root; d = d->parent)
+ num_raw_path_components++;
+
+ if (ctx->ops->requires_realtarget_in_paths)
+ num_target_path_components = get_num_path_components(ctx->realtarget,
+ ctx->ops->path_separator);
+ else if (ctx->ops->requires_target_in_paths)
+ num_target_path_components = get_num_path_components(ctx->target,
+ ctx->ops->path_separator);
+ else
+ num_target_path_components = 0;
+
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) {
+ wimlib_assert(num_target_path_components > 0);
+ num_raw_path_components++;
+ num_target_path_components--;
+ }
+
+ p_old = oldpath + ctx->ops->path_prefix_nchars;
+#ifdef __WIN32__
+ if (p_old[0] != L'\0' && p_old[1] == ':')
+ p_old += 2;
+#endif
+ while (*p_old == ctx->ops->path_separator)
+ p_old++;
+ while (--num_target_path_components) {
+ while (*p_old != ctx->ops->path_separator)
+ p_old++;
+ while (*p_old == ctx->ops->path_separator)
+ p_old++;
+ }
+
+ tchar symlink_target[tstrlen(p_old) + 3 * num_raw_path_components + 1];
+
+ p = &symlink_target[0];
+ while (num_raw_path_components--) {
+ *p++ = '.';
+ *p++ = '.';
+ *p++ = ctx->ops->path_separator;
+ }
+ tstrcpy(p, p_old);
+ DEBUG("Creating symlink \"%"TS"\" => \"%"TS"\"",
+ newpath, symlink_target);
+ ret = ctx->ops->create_symlink(symlink_target, newpath, ctx);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to create symlink "
+ "\"%"TS"\" => \"%"TS"\"",
+ newpath, symlink_target);
+ }
+ return ret;
+}
+
+/* Create the "skeleton" of an extracted file or directory. Don't yet extract
+ * data streams, reparse data (including symbolic links), timestamps, and
+ * security descriptors. Basically, everything that doesn't require reading
+ * non-metadata resources from the WIM file and isn't delayed until the final
+ * pass. */
+static int
+do_dentry_extract_skeleton(tchar path[], struct wim_dentry *dentry,
+ struct apply_ctx *ctx)
+{
+ struct wim_inode *inode = dentry->d_inode;
+ int ret;
+ const tchar *oldpath;
+
+ if (unlikely(is_linked_extraction(ctx))) {
+ struct wim_lookup_table_entry *unnamed_lte;
+
+ unnamed_lte = inode_unnamed_lte_resolved(dentry->d_inode);
+ if (unnamed_lte && unnamed_lte->extracted_file) {
+ oldpath = unnamed_lte->extracted_file;
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK)
+ goto hardlink;
+ else
+ goto symlink;
+ }
+ }
+
+ /* Create hard link if this dentry corresponds to an already-extracted
+ * inode. */
+ if (inode->i_extracted_file) {
+ oldpath = inode->i_extracted_file;
+ goto hardlink;
+ }
+
+ /* Skip symlinks unless they can be extracted as reparse points rather
+ * than created directly. */
+ if (inode_is_symlink(inode) && !ctx->supported_features.reparse_points)
+ return 0;
+
+ /* Create this file or directory unless it's the extraction root, which
+ * was already created if necessary. */
+ if (dentry != ctx->extract_root) {
+ ret = extract_inode(path, ctx, inode);
+ if (ret)
+ return ret;
+ }
+
+ /* Create empty named data streams. */
+ if (can_extract_named_data_streams(ctx)) {
+ for (u16 i = 0; i < inode->i_num_ads; i++) {
+ file_spec_t file_spec;
+ struct wim_ads_entry *entry = &inode->i_ads_entries[i];
+
+ if (!ads_entry_is_named_stream(entry))
+ continue;
+ if (entry->lte)
+ continue;
+ if (ctx->ops->uses_cookies)
+ file_spec.cookie = inode->extract_cookie;
+ else
+ file_spec.path = path;
+ ret = ctx->ops->extract_named_stream(file_spec,
+ entry->stream_name,
+ entry->stream_name_nbytes / 2,
+ entry->lte, ctx);
+ if (ret) {
+ ERROR_WITH_ERRNO("\"%"TS"\": failed to create "
+ "empty named data stream",
+ path);
+ return ret;
+ }
+ }
+ }
+
+ /* Set file attributes (if supported). */
+ ret = extract_file_attributes(path, ctx, dentry, 0);
+ if (ret)
+ return ret;
+
+ /* Set or remove file short name (if supported). */
+ ret = extract_short_name(path, ctx, dentry);
+ if (ret)
+ return ret;
+
+ /* If inode has multiple links and hard links are supported in this
+ * extraction mode and volume, save the path to the extracted file in
+ * case it's needed to create a hard link. */
+ if (unlikely(is_linked_extraction(ctx))) {
+ struct wim_lookup_table_entry *unnamed_lte;
+
+ unnamed_lte = inode_unnamed_lte_resolved(dentry->d_inode);
+ if (unnamed_lte) {
+ unnamed_lte->extracted_file = TSTRDUP(path);
+ if (!unnamed_lte->extracted_file)
+ return WIMLIB_ERR_NOMEM;
+ }
+ } else if (inode->i_nlink > 1 && ctx->supported_features.hard_links) {
+ inode->i_extracted_file = TSTRDUP(path);
+ if (!inode->i_extracted_file)
+ return WIMLIB_ERR_NOMEM;
+ }
+ return 0;
+
+symlink:
+ ret = extract_multiimage_symlink(oldpath, path, ctx, dentry);
+ if (ret)
+ return ret;
+ dentry->was_hardlinked = 1;
+ return 0;
+
+hardlink:
+ ret = extract_hardlink(oldpath, path, ctx);
+ if (ret)
+ return ret;
+ dentry->was_hardlinked = 1;
+ return 0;
+}
+
+/* This is a wrapper around do_dentry_extract_skeleton() that handles building
+ * the path, doing short name reordering. This is also idempotent; dentries
+ * already processed have skeleton_extracted set and no action is taken. See
+ * apply_operations.requires_short_name_reordering for more details about short
+ * name reordering. */
+static int
+dentry_extract_skeleton(struct wim_dentry *dentry, void *_ctx)
+{
+ struct apply_ctx *ctx = _ctx;
+ tchar path[ctx->ops->path_max];
+ struct wim_dentry *orig_dentry;
+ struct wim_dentry *other_dentry;
+ int ret;
+
+ if (dentry->skeleton_extracted)
+ return 0;
+
+ orig_dentry = NULL;
+ if (ctx->supported_features.short_names
+ && ctx->ops->requires_short_name_reordering
+ && !dentry_has_short_name(dentry)
+ && !dentry->d_inode->i_dos_name_extracted)
+ {
+ inode_for_each_dentry(other_dentry, dentry->d_inode) {
+ if (dentry_has_short_name(other_dentry)
+ && !other_dentry->skeleton_extracted
+ && other_dentry->in_extraction_tree
+ && !other_dentry->extraction_skipped)
+ {
+ DEBUG("Creating %"TS" before %"TS" "
+ "to guarantee correct DOS name extraction",
+ dentry_full_path(other_dentry),
+ dentry_full_path(dentry));
+ orig_dentry = dentry;
+ dentry = other_dentry;
+ break;
+ }
+ }
+ }
+again:
+ if (!build_extraction_path(path, dentry, ctx))
+ return 0;
+ ret = do_dentry_extract_skeleton(path, dentry, ctx);
+ if (ret)
+ return ret;
+
+ dentry->skeleton_extracted = 1;
+
+ if (orig_dentry) {
+ dentry = orig_dentry;
+ orig_dentry = NULL;
+ goto again;
+ }
+ dentry->d_inode->i_dos_name_extracted = 1;
+ return 0;
+}
+
+static int
+dentry_extract_dir_skeleton(struct wim_dentry *dentry, void *_ctx)
+{
+ if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+ return dentry_extract_skeleton(dentry, _ctx);
+ return 0;
+}
+
+/* Create a file or directory, then immediately extract all streams. The WIM
+ * may not be read sequentially by this function. */
+static int
+dentry_extract(struct wim_dentry *dentry, void *_ctx)
+{
+ struct apply_ctx *ctx = _ctx;
+ tchar path[ctx->ops->path_max];
+ int ret;
+
+ ret = dentry_extract_skeleton(dentry, ctx);
+ if (ret)
+ return ret;
+
+ if (!build_extraction_path(path, dentry, ctx))
+ return 0;
+
+ return extract_streams(path, ctx, dentry, NULL, NULL);
+}
+
+/* Creates a temporary file opened for writing. The open file descriptor is
+ * returned in @fd_ret and its name is returned in @name_ret (dynamically
+ * allocated). */
+static int
+create_temporary_file(struct filedes *fd_ret, tchar **name_ret)
+{
+ tchar *name;
+ int raw_fd;
+
+retry:
+ name = ttempnam(NULL, T("wimlib"));
+ if (name == NULL) {
+ ERROR_WITH_ERRNO("Failed to create temporary filename");
+ return WIMLIB_ERR_NOMEM;
+ }
+
+ raw_fd = topen(name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600);
+
+ if (raw_fd < 0) {
+ if (errno == EEXIST) {
+ FREE(name);
+ goto retry;
+ }
+ ERROR_WITH_ERRNO("Failed to open temporary file \"%"TS"\"", name);
+ FREE(name);
+ return WIMLIB_ERR_OPEN;
+ }
+
+ filedes_init(fd_ret, raw_fd);
+ *name_ret = name;
+ return 0;
+}
+
+/* Extract all instances of the stream @lte that are being extracted in this
+ * call of extract_tree(), but actually read the stream data from @lte_override.
+ */
+static int
+extract_stream_instances(struct wim_lookup_table_entry *lte,
+ struct wim_lookup_table_entry *lte_override,
+ struct apply_ctx *ctx)
+{
+ struct wim_dentry **lte_dentries;
+ tchar path[ctx->ops->path_max];
+ size_t i;
+ int ret;
+
+ if (lte->out_refcnt <= ARRAY_LEN(lte->inline_lte_dentries))
+ lte_dentries = lte->inline_lte_dentries;
+ else
+ lte_dentries = lte->lte_dentries;
+
+ for (i = 0; i < lte->out_refcnt; i++) {
+ struct wim_dentry *dentry = lte_dentries[i];
+
+ if (dentry->tmp_flag)
+ continue;
+ if (!build_extraction_path(path, dentry, ctx))
+ continue;
+ ret = extract_streams(path, ctx, dentry, lte, lte_override);
+ if (ret)
+ goto out_clear_tmp_flags;
+ dentry->tmp_flag = 1;
+ }
+ ret = 0;
+out_clear_tmp_flags:
+ for (i = 0; i < lte->out_refcnt; i++)
+ lte_dentries[i]->tmp_flag = 0;
+ return ret;
+}
+
+/* Determine whether the specified stream needs to be extracted to a temporary
+ * file or not.
+ *
+ * @lte->out_refcnt specifies the number of instances of this stream that must
+ * be extracted.
+ *
+ * @is_partial_res is %true if this stream is just one of multiple in a single
+ * WIM resource being extracted. */
+static bool
+need_tmpfile_to_extract(struct wim_lookup_table_entry *lte,
+ bool is_partial_res)
+{
+ /* Temporary file is always required when reading a partial resource,
+ * since in that case we retrieve all the contained streams in one pass.
+ * */
+ if (is_partial_res)
+ return true;
+
+ /* Otherwise we don't need a temporary file if only a single instance of
+ * the stream is needed. */
+ if (lte->out_refcnt == 1)
+ return false;
+
+ wimlib_assert(lte->out_refcnt >= 2);
+
+ /* We also don't need a temporary file if random access to the stream is
+ * allowed. */
+ if (lte->resource_location != RESOURCE_IN_WIM ||
+ filedes_is_seekable(<e->rspec->wim->in_fd))
+ return false;
+
+ return true;
+}
+
+static int
+begin_extract_stream_to_tmpfile(struct wim_lookup_table_entry *lte,
+ bool is_partial_res,
+ void *_ctx)
+{
+ struct apply_ctx *ctx = _ctx;
+ int ret;
+
+ if (!need_tmpfile_to_extract(lte, is_partial_res)) {
+ DEBUG("Temporary file not needed "
+ "for stream (size=%"PRIu64")", lte->size);
+ ret = extract_stream_instances(lte, lte, ctx);
+ if (ret)
+ return ret;
+
+ return BEGIN_STREAM_STATUS_SKIP_STREAM;
+ }
+
+ DEBUG("Temporary file needed for stream (size=%"PRIu64")", lte->size);
+ return create_temporary_file(&ctx->tmpfile_fd, &ctx->tmpfile_name);
+}
+
+static int
+end_extract_stream_to_tmpfile(struct wim_lookup_table_entry *lte,
+ int status, void *_ctx)
+{
+ struct apply_ctx *ctx = _ctx;
+ struct wim_lookup_table_entry lte_override;
+ int ret;
+ int errno_save = errno;
+
+ ret = filedes_close(&ctx->tmpfile_fd);
+
+ if (status) {
+ ret = status;
+ errno = errno_save;
+ goto out_delete_tmpfile;
+ }
+
+ if (ret) {
+ ERROR_WITH_ERRNO("Error writing temporary file %"TS, ctx->tmpfile_name);
+ ret = WIMLIB_ERR_WRITE;
+ goto out_delete_tmpfile;
+ }
+
+ /* Now that a full stream has been extracted to a temporary file,
+ * extract all instances of it to the actual target. */
+
+ memcpy(<e_override, lte, sizeof(struct wim_lookup_table_entry));
+ lte_override.resource_location = RESOURCE_IN_FILE_ON_DISK;
+ lte_override.file_on_disk = ctx->tmpfile_name;
+
+ ret = extract_stream_instances(lte, <e_override, ctx);
+
+out_delete_tmpfile:
+ errno_save = errno;
+ tunlink(ctx->tmpfile_name);
+ FREE(ctx->tmpfile_name);
+ errno = errno_save;
+ return ret;
+}
+
+/* Extracts a list of streams (ctx.stream_list), assuming that the directory
+ * structure and empty files were already created. This relies on the
+ * per-`struct wim_lookup_table_entry' list of dentries that reference each
+ * stream that was constructed earlier. */
+static int
+extract_stream_list(struct apply_ctx *ctx)
+{
+ if (!(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER)) {
+ /* Sequential extraction: read the streams in the order in which
+ * they appear in the WIM file. */
+ struct read_stream_list_callbacks cbs = {
+ .begin_stream = begin_extract_stream_to_tmpfile,
+ .begin_stream_ctx = ctx,
+ .consume_chunk = extract_chunk_to_fd,
+ .consume_chunk_ctx = &ctx->tmpfile_fd,
+ .end_stream = end_extract_stream_to_tmpfile,
+ .end_stream_ctx = ctx,
+ };
+ return read_stream_list(&ctx->stream_list,
+ offsetof(struct wim_lookup_table_entry, extraction_list),
+ &cbs, VERIFY_STREAM_HASHES);
+ } else {
+ /* Extract the streams in unsorted order. */
+ struct wim_lookup_table_entry *lte;
+ int ret;
+
+ list_for_each_entry(lte, &ctx->stream_list, extraction_list) {
+ ret = extract_stream_instances(lte, lte, ctx);
+ if (ret)
+ return ret;
+ }
+ return 0;
+ }
+}
+
+#define PWM_ALLOW_WIM_HDR 0x00001
+#define PWM_SILENT_EOF 0x00002
+
+/* Read the header from a stream in a pipable WIM. */
+static int
+read_pwm_stream_header(WIMStruct *pwm, struct wim_lookup_table_entry *lte,
+ struct wim_resource_spec *rspec,
+ int flags, struct wim_header_disk *hdr_ret)
+{
+ union {
+ struct pwm_stream_hdr stream_hdr;
+ struct wim_header_disk pwm_hdr;
+ } buf;
+ struct wim_reshdr reshdr;
+ int ret;
+
+ ret = full_read(&pwm->in_fd, &buf.stream_hdr, sizeof(buf.stream_hdr));
+ if (ret)
+ goto read_error;
+
+ if ((flags & PWM_ALLOW_WIM_HDR) && buf.stream_hdr.magic == PWM_MAGIC) {
+ BUILD_BUG_ON(sizeof(buf.pwm_hdr) < sizeof(buf.stream_hdr));
+ ret = full_read(&pwm->in_fd, &buf.stream_hdr + 1,
+ sizeof(buf.pwm_hdr) - sizeof(buf.stream_hdr));
+
+ if (ret)
+ goto read_error;
+ lte->resource_location = RESOURCE_NONEXISTENT;
+ memcpy(hdr_ret, &buf.pwm_hdr, sizeof(buf.pwm_hdr));
+ return 0;
+ }
+
+ if (le64_to_cpu(buf.stream_hdr.magic) != PWM_STREAM_MAGIC) {
+ ERROR("Data read on pipe is invalid (expected stream header).");
+ return WIMLIB_ERR_INVALID_PIPABLE_WIM;
+ }
+
+ copy_hash(lte->hash, buf.stream_hdr.hash);
+
+ reshdr.size_in_wim = 0;
+ reshdr.flags = le32_to_cpu(buf.stream_hdr.flags);
+ reshdr.offset_in_wim = pwm->in_fd.offset;
+ reshdr.uncompressed_size = le64_to_cpu(buf.stream_hdr.uncompressed_size);
+ wim_res_hdr_to_spec(&reshdr, pwm, rspec);
+ lte_bind_wim_resource_spec(lte, rspec);
+ lte->flags = rspec->flags;
+ lte->size = rspec->uncompressed_size;
+ lte->offset_in_res = 0;
+ return 0;
+
+read_error:
+ if (ret != WIMLIB_ERR_UNEXPECTED_END_OF_FILE || !(flags & PWM_SILENT_EOF))
+ ERROR_WITH_ERRNO("Error reading pipable WIM from pipe");
+ return ret;
+}
+
+static int
+extract_streams_from_pipe(struct apply_ctx *ctx)
+{
+ struct wim_lookup_table_entry *found_lte;
+ struct wim_resource_spec *rspec;
+ struct wim_lookup_table_entry *needed_lte;
+ struct wim_lookup_table *lookup_table;
+ struct wim_header_disk pwm_hdr;