+ return extract_streams(path, ctx, dentry, NULL, NULL);
+}
+
+/* Extract all instances of the stream @lte that are being extracted in this
+ * call of extract_tree(). @can_seek specifies whether the WIM file descriptor
+ * is seekable or not (e.g. is a pipe). If not and the stream needs to be
+ * extracted multiple times, it is extracted to a temporary file first.
+ *
+ * This is intended for use with sequential extraction of a WIM image
+ * (WIMLIB_EXTRACT_FLAG_SEQUENTIAL specified). */
+static int
+extract_stream_instances(struct wim_lookup_table_entry *lte,
+ struct apply_ctx *ctx, bool can_seek)
+{
+ struct wim_dentry **lte_dentries;
+ struct wim_lookup_table_entry *lte_tmp = NULL;
+ struct wim_lookup_table_entry *lte_override;
+ tchar *stream_tmp_filename = NULL;
+ tchar path[ctx->ops->path_max];
+ unsigned 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;
+
+ if (likely(can_seek || lte->out_refcnt < 2)) {
+ lte_override = lte;
+ } else {
+ /* Need to extract stream to temporary file. */
+ struct filedes fd;
+ int raw_fd;
+
+ stream_tmp_filename = ttempnam(NULL, T("wimlib"));
+ if (!stream_tmp_filename) {
+ ERROR_WITH_ERRNO("Failed to create temporary filename");
+ ret = WIMLIB_ERR_OPEN;
+ goto out;
+ }
+
+ lte_tmp = memdup(lte, sizeof(struct wim_lookup_table_entry));
+ if (!lte_tmp) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out_free_stream_tmp_filename;
+ }
+ lte_tmp->resource_location = RESOURCE_IN_FILE_ON_DISK;
+ lte_tmp->file_on_disk = stream_tmp_filename;
+ lte_override = lte_tmp;
+
+ raw_fd = topen(stream_tmp_filename,
+ O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600);
+ if (raw_fd < 0) {
+ ERROR_WITH_ERRNO("Failed to open temporary file");
+ ret = WIMLIB_ERR_OPEN;
+ goto out_free_lte_tmp;
+ }
+ filedes_init(&fd, raw_fd);
+ ret = extract_wim_resource_to_fd(lte, &fd,
+ wim_resource_size(lte));
+ if (filedes_close(&fd) && !ret)
+ ret = WIMLIB_ERR_WRITE;
+ if (ret)
+ goto out_unlink_stream_tmp_file;
+ }
+
+ /* Extract all instances of the stream, reading either from the stream
+ * in the WIM file or from the temporary file containing the stream.
+ * dentry->tmp_flag is used to ensure that each dentry is processed only
+ * once regardless of how many times this stream appears in the streams
+ * of the corresponding inode. */
+ 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;
+out_unlink_stream_tmp_file:
+ if (stream_tmp_filename)
+ tunlink(stream_tmp_filename);
+out_free_lte_tmp:
+ FREE(lte_tmp);
+out_free_stream_tmp_filename:
+ FREE(stream_tmp_filename);
+out:
+ 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. Streams are extracted exactly in the
+ * order of the stream list; however, unless the WIM's file descriptor is
+ * detected to be non-seekable, streams may be read from the WIM file more than
+ * one time if multiple copies need to be extracted. */
+static int
+extract_stream_list(struct apply_ctx *ctx)
+{
+ struct wim_lookup_table_entry *lte;
+ bool can_seek;
+ int ret;
+
+ can_seek = (lseek(ctx->wim->in_fd.fd, 0, SEEK_CUR) != -1);
+ list_for_each_entry(lte, &ctx->stream_list, extraction_list) {
+ ret = extract_stream_instances(lte, ctx, can_seek);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* Read the header from a stream in a pipable WIM. */
+static int
+read_pwm_stream_header(WIMStruct *pwm, struct wim_lookup_table_entry *lte,
+ bool allow_header)
+{
+ struct pwm_stream_hdr stream_hdr;
+ int ret;
+
+ ret = full_read(&pwm->in_fd, &stream_hdr, sizeof(stream_hdr));
+ if (ret)
+ goto read_error;
+
+ if (allow_header && stream_hdr.magic == PWM_MAGIC) {
+ u8 buf[WIM_HEADER_DISK_SIZE - sizeof(stream_hdr)];
+ ret = full_read(&pwm->in_fd, buf, sizeof(buf));
+ if (ret)
+ goto read_error;
+ lte->resource_location = RESOURCE_NONEXISTENT;
+ return 0;
+ }
+
+ if (stream_hdr.magic != PWM_STREAM_MAGIC) {
+ ERROR("Data read on pipe is invalid (expected stream header).");
+ return WIMLIB_ERR_INVALID_PIPABLE_WIM;
+ }
+
+ lte->resource_entry.original_size = le64_to_cpu(stream_hdr.uncompressed_size);
+ copy_hash(lte->hash, stream_hdr.hash);
+ lte->resource_entry.flags = le32_to_cpu(stream_hdr.flags);
+ lte->resource_entry.offset = pwm->in_fd.offset;
+ lte->resource_location = RESOURCE_IN_WIM;
+ lte->wim = pwm;
+ if (lte->resource_entry.flags & WIM_RESHDR_FLAG_COMPRESSED) {
+ lte->compression_type = pwm->compression_type;
+ lte->resource_entry.size = 0;
+ } else {
+ lte->compression_type = WIMLIB_COMPRESSION_TYPE_NONE;
+ lte->resource_entry.size = lte->resource_entry.original_size;
+ }
+ lte->is_pipable = 1;
+ return 0;
+
+read_error:
+ ERROR_WITH_ERRNO("Error reading pipable WIM from pipe");
+ return ret;
+}
+
+/* Skip over an unneeded stream in a pipable WIM being read from a pipe. */
+static int
+skip_pwm_stream(struct wim_lookup_table_entry *lte)
+{
+ return read_partial_wim_resource(lte, wim_resource_size(lte),
+ NULL, NULL,
+ WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY,
+ 0);
+}
+
+static int
+extract_streams_from_pipe(struct apply_ctx *ctx)
+{
+ struct wim_lookup_table_entry *found_lte;
+ struct wim_lookup_table_entry *needed_lte;
+ struct wim_lookup_table *lookup_table;
+ int ret;
+
+ ret = WIMLIB_ERR_NOMEM;
+ found_lte = new_lookup_table_entry();
+ if (!found_lte)
+ goto out;
+
+ lookup_table = ctx->wim->lookup_table;
+
+ while (ctx->num_streams_remaining) {
+ ret = read_pwm_stream_header(ctx->wim, found_lte, true);
+ if (ret)
+ goto out_free_found_lte;
+
+ if ((found_lte->resource_location != RESOURCE_NONEXISTENT)
+ && !(found_lte->resource_entry.flags & WIM_RESHDR_FLAG_METADATA)
+ && (needed_lte = __lookup_resource(lookup_table, found_lte->hash))
+ && (needed_lte->out_refcnt))
+ {
+ copy_resource_entry(&needed_lte->resource_entry,
+ &found_lte->resource_entry);
+ needed_lte->resource_location = found_lte->resource_location;
+ needed_lte->wim = found_lte->wim;
+ needed_lte->compression_type = found_lte->compression_type;
+ needed_lte->is_pipable = found_lte->is_pipable;
+
+ ret = extract_stream_instances(needed_lte, ctx, false);
+ if (ret)
+ goto out_free_found_lte;
+ ctx->num_streams_remaining--;
+ } else if (found_lte->resource_location != RESOURCE_NONEXISTENT) {
+ ret = skip_pwm_stream(found_lte);
+ if (ret)
+ goto out_free_found_lte;
+ }
+ }
+ ret = 0;
+out_free_found_lte:
+ free_lookup_table_entry(found_lte);
+out:
+ return ret;
+}
+
+/* Finish extracting a file, directory, or symbolic link by setting file
+ * security and timestamps. */
+static int
+dentry_extract_final(struct wim_dentry *dentry, void *_ctx)
+{
+ struct apply_ctx *ctx = _ctx;
+ int ret;
+ tchar path[ctx->ops->path_max];
+
+ if (!build_extraction_path(path, dentry, ctx))
+ return 0;
+
+ ret = extract_security(path, ctx, dentry);
+ if (ret)
+ return ret;
+
+ return extract_timestamps(path, ctx, dentry);
+}
+
+/*
+ * 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)
+{
+ int ret = 0;
+ if (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_DIRECTORY))
+ {
+ ERROR("\"%"TS"\" is not a regular file and therefore cannot be "
+ "extracted to standard output", dentry_full_path(dentry));
+ ret = WIMLIB_ERR_NOT_A_REGULAR_FILE;
+ } else {
+ struct wim_lookup_table_entry *lte;
+
+ lte = inode_unnamed_lte_resolved(dentry->d_inode);
+ if (lte) {
+ struct filedes _stdout;
+ filedes_init(&_stdout, STDOUT_FILENO);
+ ret = extract_wim_resource_to_fd(lte, &_stdout,
+ wim_resource_size(lte));
+ }
+ }
+ return ret;
+}
+
+#ifdef __WIN32__
+static const utf16lechar replacement_char = cpu_to_le16(0xfffd);
+#else
+static const utf16lechar replacement_char = cpu_to_le16('?');
+#endif
+
+static bool
+file_name_valid(utf16lechar *name, size_t num_chars, bool fix)
+{
+ size_t i;
+
+ if (num_chars == 0)
+ return true;
+ for (i = 0; i < num_chars; i++) {
+ switch (name[i]) {
+ #ifdef __WIN32__
+ case cpu_to_le16('\\'):
+ case cpu_to_le16(':'):
+ case cpu_to_le16('*'):
+ case cpu_to_le16('?'):
+ case cpu_to_le16('"'):
+ case cpu_to_le16('<'):
+ case cpu_to_le16('>'):
+ case cpu_to_le16('|'):
+ #endif
+ case cpu_to_le16('/'):
+ case cpu_to_le16('\0'):
+ if (fix)
+ name[i] = replacement_char;
+ else
+ return false;
+ }
+ }
+
+#ifdef __WIN32__
+ if (name[num_chars - 1] == cpu_to_le16(' ') ||
+ name[num_chars - 1] == cpu_to_le16('.'))
+ {
+ if (fix)
+ name[num_chars - 1] = replacement_char;
+ else
+ return false;
+ }
+#endif
+ return true;
+}
+
+static bool
+dentry_is_dot_or_dotdot(const struct wim_dentry *dentry)
+{
+ const utf16lechar *file_name = dentry->file_name;
+ return file_name != NULL &&
+ file_name[0] == cpu_to_le16('.') &&
+ (file_name[1] == cpu_to_le16('\0') ||
+ (file_name[1] == cpu_to_le16('.') &&
+ file_name[2] == cpu_to_le16('\0')));
+}
+
+static int
+dentry_mark_skipped(struct wim_dentry *dentry, void *_ignore)
+{
+ dentry->extraction_skipped = 1;
+ return 0;
+}
+
+/*
+ * dentry_calculate_extraction_path-
+ *
+ * Calculate the actual filename component at which a WIM dentry will be
+ * extracted, handling invalid filenames "properly".
+ *
+ * dentry->extraction_name usually will be set the same as dentry->file_name (on
+ * UNIX, converted into the platform's multibyte encoding). However, if the
+ * file name contains characters that are not valid on the current platform or
+ * has some other format that is not valid, leave dentry->extraction_name as
+ * NULL and set dentry->extraction_skipped to indicate that this dentry should
+ * not be extracted, unless the appropriate flag
+ * WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES is set in the extract flags, in
+ * which case a substitute filename will be created and set instead.
+ *
+ * Conflicts with case-insensitive names on Windows are handled similarly; see
+ * below.
+ */
+static int
+dentry_calculate_extraction_path(struct wim_dentry *dentry, void *_args)
+{
+ struct apply_ctx *ctx = _args;
+ int ret;
+
+ if (dentry == ctx->extract_root || dentry->extraction_skipped)
+ return 0;
+
+ if (!dentry_is_supported(dentry, &ctx->supported_features))
+ goto skip_dentry;
+
+ if (dentry_is_dot_or_dotdot(dentry)) {
+ /* WIM files shouldn't contain . or .. entries. But if they are
+ * there, don't attempt to extract them. */
+ WARNING("Skipping extraction of unexpected . or .. file "
+ "\"%"TS"\"", dentry_full_path(dentry));
+ goto skip_dentry;
+ }
+
+#ifdef __WIN32__
+ if (!ctx->ops->supports_case_sensitive_filenames)
+ {
+ struct wim_dentry *other;
+ list_for_each_entry(other, &dentry->case_insensitive_conflict_list,
+ case_insensitive_conflict_list)
+ {
+ if (ctx->extract_flags &
+ WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS) {
+ WARNING("\"%"TS"\" has the same "
+ "case-insensitive name as "
+ "\"%"TS"\"; extracting "
+ "dummy name instead",
+ dentry_full_path(dentry),
+ dentry_full_path(other));
+ goto out_replace;
+ } else {
+ WARNING("Not extracting \"%"TS"\": "
+ "has same case-insensitive "
+ "name as \"%"TS"\"",
+ dentry_full_path(dentry),
+ dentry_full_path(other));
+ goto skip_dentry;
+ }
+ }
+ }
+#else /* __WIN32__ */
+ wimlib_assert(ctx->ops->supports_case_sensitive_filenames);
+#endif /* !__WIN32__ */
+
+ if (file_name_valid(dentry->file_name, dentry->file_name_nbytes / 2, false)) {
+#ifdef __WIN32__
+ dentry->extraction_name = dentry->file_name;
+ dentry->extraction_name_nchars = dentry->file_name_nbytes / 2;
+ return 0;
+#else
+ return utf16le_to_tstr(dentry->file_name,
+ dentry->file_name_nbytes,
+ &dentry->extraction_name,
+ &dentry->extraction_name_nchars);
+#endif
+ } else {
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES)
+ {
+ WARNING("\"%"TS"\" has an invalid filename "
+ "that is not supported on this platform; "
+ "extracting dummy name instead",
+ dentry_full_path(dentry));
+ goto out_replace;