+#include "wimlib/wildcard.h"
+#include "wimlib/wim.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define WIMLIB_EXTRACT_FLAG_MULTI_IMAGE 0x80000000
+#define WIMLIB_EXTRACT_FLAG_FROM_PIPE 0x40000000
+#define WIMLIB_EXTRACT_FLAG_FILEMODE 0x20000000
+#define WIMLIB_EXTRACT_FLAG_IMAGEMODE 0x10000000
+
+/* Keep in sync with wimlib.h */
+#define WIMLIB_EXTRACT_MASK_PUBLIC \
+ (WIMLIB_EXTRACT_FLAG_NTFS | \
+ WIMLIB_EXTRACT_FLAG_HARDLINK | \
+ WIMLIB_EXTRACT_FLAG_SYMLINK | \
+ WIMLIB_EXTRACT_FLAG_VERBOSE | \
+ WIMLIB_EXTRACT_FLAG_SEQUENTIAL | \
+ WIMLIB_EXTRACT_FLAG_UNIX_DATA | \
+ WIMLIB_EXTRACT_FLAG_NO_ACLS | \
+ WIMLIB_EXTRACT_FLAG_STRICT_ACLS | \
+ WIMLIB_EXTRACT_FLAG_RPFIX | \
+ WIMLIB_EXTRACT_FLAG_NORPFIX | \
+ WIMLIB_EXTRACT_FLAG_TO_STDOUT | \
+ WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES | \
+ WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS | \
+ WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS | \
+ WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES | \
+ WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS | \
+ WIMLIB_EXTRACT_FLAG_RESUME | \
+ WIMLIB_EXTRACT_FLAG_FILE_ORDER | \
+ WIMLIB_EXTRACT_FLAG_GLOB_PATHS | \
+ WIMLIB_EXTRACT_FLAG_STRICT_GLOB | \
+ WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES | \
+ WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE)
+
+static bool
+dentry_in_list(const struct wim_dentry *dentry)
+{
+ return dentry->extraction_list.next != NULL;
+}
+
+static inline bool
+is_linked_extraction(const struct apply_ctx *ctx)
+{
+ return 0 != (ctx->extract_flags & (WIMLIB_EXTRACT_FLAG_HARDLINK |
+ WIMLIB_EXTRACT_FLAG_SYMLINK));
+}
+
+static inline bool
+can_extract_named_data_streams(const struct apply_ctx *ctx)
+{
+ return ctx->supported_features.named_data_streams &&
+ !is_linked_extraction(ctx);
+}
+/* Inform library user of progress of stream extraction following the successful
+ * extraction of a copy of the stream specified by @lte. */
+static void
+update_extract_progress(struct apply_ctx *ctx,
+ const struct wim_lookup_table_entry *lte)
+{
+ wimlib_progress_func_t progress_func = ctx->progress_func;
+ union wimlib_progress_info *progress = &ctx->progress;
+
+ progress->extract.completed_bytes += lte->size;
+ if (progress_func &&
+ progress->extract.completed_bytes >= ctx->next_progress)
+ {
+ progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS, progress);
+ if (progress->extract.completed_bytes >=
+ progress->extract.total_bytes)
+ {
+ ctx->next_progress = ~0ULL;
+ } else {
+ ctx->next_progress += progress->extract.total_bytes / 128;
+ if (ctx->next_progress > progress->extract.total_bytes)
+ ctx->next_progress = progress->extract.total_bytes;
+ }
+ }
+}
+
+#ifndef __WIN32__
+/* Extract a symbolic link (not directly as reparse data), handling fixing up
+ * the target of absolute symbolic links and updating the extract progress.
+ *
+ * @inode must specify the WIM inode for a symbolic link or junction reparse
+ * point.
+ *
+ * @lte_override overrides the resource used as the reparse data for the
+ * symbolic link. */
+static int
+extract_symlink(const tchar *path, struct apply_ctx *ctx,
+ struct wim_inode *inode,
+ struct wim_lookup_table_entry *lte_override)
+{
+ ssize_t bufsize = ctx->ops->path_max;
+ tchar target[bufsize];
+ tchar *buf = target;
+ tchar *fixed_target;
+ ssize_t sret;
+ int ret;
+
+ /* If absolute symbolic link fixups requested, reserve space in the link
+ * target buffer for the absolute path of the target directory. */
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
+ {
+ buf += ctx->realtarget_nchars;
+ bufsize -= ctx->realtarget_nchars;
+ }
+
+ /* Translate the WIM inode's reparse data into the link target. */
+ sret = wim_inode_readlink(inode, buf, bufsize - 1, lte_override);
+ if (sret < 0) {
+ errno = -sret;
+ return WIMLIB_ERR_READLINK;
+ }
+ buf[sret] = '\0';
+
+ if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
+ buf[0] == '/')
+ {
+ /* Fix absolute symbolic link target to point into the
+ * actual extraction destination. */
+ tmemcpy(target, ctx->realtarget, ctx->realtarget_nchars);
+ fixed_target = target;
+ } else {
+ /* Keep same link target. */
+ fixed_target = buf;
+ }
+
+ /* Call into the apply_operations to create the symbolic link. */
+ DEBUG("Creating symlink \"%"TS"\" => \"%"TS"\"",
+ path, fixed_target);
+ ret = ctx->ops->create_symlink(fixed_target, path, ctx);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to create symlink "
+ "\"%"TS"\" => \"%"TS"\"", path, fixed_target);
+ return ret;
+ }
+
+ /* Account for reparse data consumed. */
+ update_extract_progress(ctx,
+ (lte_override ? lte_override :
+ inode_unnamed_lte_resolved(inode)));
+ return 0;
+}
+#endif /* !__WIN32__ */
+
+/* Create a file, directory, or symbolic link. */
+static int
+extract_inode(const tchar *path, struct apply_ctx *ctx, struct wim_inode *inode)
+{
+ int ret;
+
+#ifndef __WIN32__
+ if (ctx->supported_features.symlink_reparse_points &&
+ !ctx->supported_features.reparse_points &&
+ inode_is_symlink(inode))
+ {
+ ret = extract_symlink(path, ctx, inode, NULL);
+ } else
+#endif /* !__WIN32__ */
+ if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
+ ret = ctx->ops->create_directory(path, ctx, &inode->extract_cookie);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to create the directory "
+ "\"%"TS"\"", path);
+ }
+ } else if ((inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) &&
+ ctx->ops->extract_encrypted_stream_creates_file &&
+ ctx->supported_features.encrypted_files) {
+ ret = ctx->ops->extract_encrypted_stream(
+ path, inode_unnamed_lte_resolved(inode), ctx);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to create and extract "
+ "encrypted file \"%"TS"\"", path);
+ }
+ } else {
+ ret = ctx->ops->create_file(path, ctx, &inode->extract_cookie);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to create the file "
+ "\"%"TS"\"", path);
+ }
+ }
+ return ret;
+}
+
+static int
+extract_hardlink(const tchar *oldpath, const tchar *newpath,
+ struct apply_ctx *ctx)
+{
+ int ret;
+
+ DEBUG("Creating hardlink \"%"TS"\" => \"%"TS"\"", newpath, oldpath);
+ ret = ctx->ops->create_hardlink(oldpath, newpath, ctx);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to create hardlink "
+ "\"%"TS"\" => \"%"TS"\"",
+ newpath, oldpath);
+ }
+ return ret;
+}
+
+#ifdef __WIN32__
+static int
+try_extract_rpfix(u8 *rpbuf,
+ u16 *rpbuflen_p,
+ const wchar_t *extract_root_realpath,
+ unsigned extract_root_realpath_nchars)
+{
+ struct reparse_data rpdata;
+ wchar_t *target;
+ size_t target_nchars;
+ size_t stripped_nchars;
+ wchar_t *stripped_target;
+ wchar_t stripped_target_nchars;
+ int ret;
+
+ utf16lechar *new_target;
+ utf16lechar *new_print_name;
+ size_t new_target_nchars;
+ size_t new_print_name_nchars;
+ utf16lechar *p;
+
+ ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata);
+ if (ret)
+ return ret;
+
+ if (extract_root_realpath[0] == L'\0' ||
+ extract_root_realpath[1] != L':' ||
+ extract_root_realpath[2] != L'\\')
+ return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
+
+ ret = parse_substitute_name(rpdata.substitute_name,
+ rpdata.substitute_name_nbytes,
+ rpdata.rptag);
+ if (ret < 0)
+ return 0;
+ stripped_nchars = ret;
+ target = rpdata.substitute_name;
+ target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
+ stripped_target = target + stripped_nchars;
+ stripped_target_nchars = target_nchars - stripped_nchars;
+
+ new_target = alloca((6 + extract_root_realpath_nchars +
+ stripped_target_nchars) * sizeof(utf16lechar));
+
+ p = new_target;
+ if (stripped_nchars == 6) {
+ /* Include \??\ prefix if it was present before */
+ p = wmempcpy(p, L"\\??\\", 4);
+ }
+
+ /* Print name excludes the \??\ if present. */
+ new_print_name = p;
+ if (stripped_nchars != 0) {
+ /* Get drive letter from real path to extract root, if a drive
+ * letter was present before. */
+ *p++ = extract_root_realpath[0];
+ *p++ = extract_root_realpath[1];
+ }
+ /* Copy the rest of the extract root */
+ p = wmempcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
+
+ /* Append the stripped target */
+ p = wmempcpy(p, stripped_target, stripped_target_nchars);
+ new_target_nchars = p - new_target;
+ new_print_name_nchars = p - new_print_name;
+
+ if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
+ new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
+ return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
+
+ rpdata.substitute_name = new_target;
+ rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
+ rpdata.print_name = new_print_name;
+ rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
+ return make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
+}
+#endif /* __WIN32__ */
+
+/* Set reparse data on extracted file or directory that has
+ * FILE_ATTRIBUTE_REPARSE_POINT set. */
+static int
+extract_reparse_data(const tchar *path, struct apply_ctx *ctx,
+ struct wim_inode *inode,
+ struct wim_lookup_table_entry *lte_override)
+{
+ int ret;
+ u8 rpbuf[REPARSE_POINT_MAX_SIZE];
+ u16 rpbuflen;
+
+ ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen, lte_override);
+ if (ret)
+ goto error;
+
+#ifdef __WIN32__
+ /* Fix up target of absolute symbolic link or junction points so
+ * that they point into the actual extraction target. */
+ if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
+ (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
+ inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
+ !inode->i_not_rpfixed)
+ {
+ ret = try_extract_rpfix(rpbuf, &rpbuflen, ctx->realtarget,
+ ctx->realtarget_nchars);
+ if (ret && !(ctx->extract_flags &
+ WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS))
+ {
+ WARNING("Reparse point fixup of \"%"TS"\" "
+ "failed", path);
+ ret = 0;
+ }
+ if (ret)
+ goto error;
+ }
+#endif
+
+ ret = ctx->ops->set_reparse_data(path, rpbuf, rpbuflen, ctx);
+
+ /* On Windows, the SeCreateSymbolicLink privilege is required to create
+ * symbolic links. To be more friendly towards non-Administrator users,
+ * we merely warn the user if symbolic links cannot be created due to
+ * insufficient permissions or privileges, unless
+ * WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS was provided. */
+#ifdef __WIN32__
+ if (ret && inode_is_symlink(inode) &&
+ (errno == EACCES || errno == EPERM) &&
+ !(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS))
+ {
+ WARNING("Can't set reparse data on \"%"TS"\": "
+ "Access denied!\n"
+ " You may be trying to "
+ "extract a symbolic link without the\n"
+ " SeCreateSymbolicLink privilege, "
+ "which by default non-Administrator\n"
+ " accounts do not have.",
+ path);
+ ret = 0;
+ }
+#endif
+ if (ret)
+ goto error;
+
+ /* Account for reparse data consumed. */
+ update_extract_progress(ctx,
+ (lte_override ? lte_override :
+ inode_unnamed_lte_resolved(inode)));
+ return 0;
+
+error:
+ ERROR_WITH_ERRNO("Failed to set reparse data on \"%"TS"\"", path);
+ return ret;
+}
+
+/*
+ * Extract zero or more streams to a file.
+ *
+ * This function operates slightly differently depending on whether @lte_spec is
+ * NULL or not. When @lte_spec is NULL, the behavior is to extract the default
+ * file contents (unnamed stream), and, if named data streams are supported in
+ * the extract mode and volume, any named data streams. When @lte_spec is not
+ * NULL, the behavior is to extract only all copies of the stream @lte_spec, and
+ * in addition use @lte_spec to set the reparse data or create the symbolic link
+ * if appropriate.
+ *
+ * @path
+ * Path to file to extract (as can be passed to apply_operations
+ * functions).
+ * @ctx
+ * Apply context.
+ * @dentry
+ * WIM dentry that corresponds to the file being extracted.
+ * @lte_spec
+ * If non-NULL, specifies the lookup table entry for a stream to extract,
+ * and only that stream will be extracted (although there may be more than
+ * one instance of it).
+ * @lte_override
+ * Used only if @lte_spec != NULL; it is passed to the extraction functions
+ * rather than @lte_spec, allowing the location of the stream to be
+ * overridden. (This is used when the WIM is being read from a nonseekable
+ * file, such as a pipe, when streams need to be used more than once; each
+ * such stream is extracted to a temporary file.)
+ */
+static int
+extract_streams(const tchar *path, struct apply_ctx *ctx,
+ struct wim_dentry *dentry,
+ struct wim_lookup_table_entry *lte_spec,
+ struct wim_lookup_table_entry *lte_override)
+{
+ struct wim_inode *inode = dentry->d_inode;
+ struct wim_lookup_table_entry *lte;
+ file_spec_t file_spec;
+ int ret;
+
+ if (dentry->was_linked)
+ return 0;
+
+#ifdef ENABLE_DEBUG
+ if (lte_spec) {
+ char sha1_str[100];
+ char *p = sha1_str;
+ for (unsigned i = 0; i < SHA1_HASH_SIZE; i++)
+ p += sprintf(p, "%02x", lte_override->hash[i]);
+ DEBUG("Extracting stream SHA1=%s to \"%"TS"\"",
+ sha1_str, path, inode->i_ino);
+ } else {
+ DEBUG("Extracting streams to \"%"TS"\"", path, inode->i_ino);
+ }
+#endif
+
+ if (ctx->ops->uses_cookies)
+ file_spec.cookie = inode->extract_cookie;
+ else
+ file_spec.path = path;
+
+ /* Unnamed data stream. */
+ lte = inode_unnamed_lte_resolved(inode);
+ if (lte && (!lte_spec || lte == lte_spec)) {
+ if (lte_spec)
+ lte = lte_override;
+ if (!(inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_REPARSE_POINT)))
+ {
+ if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
+ ctx->supported_features.encrypted_files) {
+ if (!ctx->ops->extract_encrypted_stream_creates_file) {
+ ret = ctx->ops->extract_encrypted_stream(
+ path, lte, ctx);
+ if (ret)
+ goto error;
+ }
+ } else {
+ ret = ctx->ops->extract_unnamed_stream(
+ file_spec, lte, ctx);
+ if (ret)
+ goto error;
+ }
+ update_extract_progress(ctx, lte);
+ }
+ else if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ ret = 0;
+ if (ctx->supported_features.reparse_points)
+ ret = extract_reparse_data(path, ctx, inode, lte);
+ #ifndef __WIN32__
+ else if ((inode_is_symlink(inode) &&
+ ctx->supported_features.symlink_reparse_points))
+ ret = extract_symlink(path, ctx, inode, lte);
+ #endif
+ if (ret)
+ return ret;
+ }
+ }
+
+ /* Named data streams. */
+ if (can_extract_named_data_streams(ctx)) {
+ for (u16 i = 0; i < inode->i_num_ads; i++) {
+ struct wim_ads_entry *entry = &inode->i_ads_entries[i];
+
+ if (!ads_entry_is_named_stream(entry))
+ continue;
+ lte = entry->lte;
+ if (!lte)
+ continue;
+ if (lte_spec && lte_spec != lte)
+ continue;
+ if (lte_spec)
+ lte = lte_override;
+ ret = ctx->ops->extract_named_stream(file_spec, entry->stream_name,
+ entry->stream_name_nbytes / 2,
+ lte, ctx);
+ if (ret)
+ goto error;
+ update_extract_progress(ctx, lte);
+ }
+ }
+ return 0;
+
+error:
+ ERROR_WITH_ERRNO("Failed to extract data of \"%"TS"\"", path);
+ return ret;
+}
+
+/* Set attributes on an extracted file or directory if supported by the
+ * extraction mode. */
+static int
+extract_file_attributes(const tchar *path, struct apply_ctx *ctx,
+ struct wim_dentry *dentry, unsigned pass)
+{
+ int ret;
+
+ if (ctx->ops->set_file_attributes &&
+ !(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) &&
+ !(dentry == ctx->target_dentry && ctx->root_dentry_is_special)) {
+ u32 attributes = dentry->d_inode->i_attributes;
+
+ /* Clear unsupported attributes. */
+ attributes &= ctx->supported_attributes_mask;
+
+ if ((attributes & FILE_ATTRIBUTE_DIRECTORY &&
+ !ctx->supported_features.encrypted_directories) ||
+ (!(attributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !ctx->supported_features.encrypted_files))
+ {
+ attributes &= ~FILE_ATTRIBUTE_ENCRYPTED;
+ }
+
+ if (attributes == 0)
+ attributes = FILE_ATTRIBUTE_NORMAL;
+
+ ret = ctx->ops->set_file_attributes(path, attributes, ctx, pass);
+ if (ret) {
+ ERROR_WITH_ERRNO("Failed to set attributes on "
+ "\"%"TS"\"", path);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+
+/* Set or remove the short (DOS) name on an extracted file or directory if
+ * supported by the extraction mode. Since DOS names are unimportant and it's
+ * easy to run into problems setting them on Windows (SetFileShortName()
+ * requires SE_RESTORE privilege, which only the Administrator can request, and
+ * also requires DELETE access to the file), failure is ignored unless
+ * WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES is set. */
+static int
+extract_short_name(const tchar *path, struct apply_ctx *ctx,
+ struct wim_dentry *dentry)
+{
+ int ret;
+
+ /* The root of the dentry tree being extracted may not be extracted to
+ * its original name, so its short name should be ignored. */
+ if (dentry == ctx->target_dentry)
+ return 0;
+
+ if (ctx->supported_features.short_names) {
+ ret = ctx->ops->set_short_name(path,
+ dentry->short_name,
+ dentry->short_name_nbytes / 2,
+ ctx);
+ if (ret && (ctx->extract_flags &
+ WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES))
+ {
+ ERROR_WITH_ERRNO("Failed to set short name of "
+ "\"%"TS"\"", path);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/* Set security descriptor, UNIX data, or neither on an extracted file, taking
+ * into account the current extraction mode and flags. */
+static int
+extract_security(const tchar *path, struct apply_ctx *ctx,
+ struct wim_dentry *dentry)
+{
+ int ret;
+ struct wim_inode *inode = dentry->d_inode;
+
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
+ return 0;
+
+ if ((ctx->target_dentry == dentry) && ctx->root_dentry_is_special)
+ return 0;
+
+#ifndef __WIN32__
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
+ struct wimlib_unix_data data;
+
+ ret = inode_get_unix_data(inode, &data, NULL);
+ if (ret < 0)
+ ret = 0;
+ else if (ret == 0)
+ ret = ctx->ops->set_unix_data(path, &data, ctx);
+ if (ret) {
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
+ ERROR_WITH_ERRNO("Failed to set UNIX owner, "
+ "group, and/or mode on "
+ "\"%"TS"\"", path);
+ return ret;
+ } else {
+ WARNING_WITH_ERRNO("Failed to set UNIX owner, "
+ "group, and/or/mode on "
+ "\"%"TS"\"", path);
+ }
+ }
+ }
+ else
+#endif /* __WIN32__ */
+ if (ctx->supported_features.security_descriptors &&
+ inode->i_security_id != -1)
+ {
+ const struct wim_security_data *sd;
+ const u8 *desc;
+ size_t desc_size;
+
+ sd = wim_const_security_data(ctx->wim);
+ desc = sd->descriptors[inode->i_security_id];
+ desc_size = sd->sizes[inode->i_security_id];
+
+ ret = ctx->ops->set_security_descriptor(path, desc,
+ desc_size, ctx);
+ if (ret) {
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
+ ERROR_WITH_ERRNO("Failed to set security "
+ "descriptor on \"%"TS"\"", path);
+ return ret;
+ } else {
+ #if 0
+ if (errno != EACCES) {
+ WARNING_WITH_ERRNO("Failed to set "
+ "security descriptor "
+ "on \"%"TS"\"", path);
+ }
+ #endif
+ ctx->no_security_descriptors++;
+ }
+ }
+ }
+ return 0;
+}
+
+/* Set timestamps on an extracted file. Failure is warning-only unless
+ * WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS is set. */
+static int
+extract_timestamps(const tchar *path, struct apply_ctx *ctx,
+ struct wim_dentry *dentry)
+{
+ struct wim_inode *inode = dentry->d_inode;
+ int ret;
+
+ if ((ctx->target_dentry == dentry) && ctx->root_dentry_is_special)
+ return 0;
+
+ if (ctx->ops->set_timestamps) {
+ ret = ctx->ops->set_timestamps(path,
+ inode->i_creation_time,
+ inode->i_last_write_time,
+ inode->i_last_access_time,
+ ctx);
+ if (ret) {
+ if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS) {
+ ERROR_WITH_ERRNO("Failed to set timestamps "
+ "on \"%"TS"\"", path);
+ return ret;
+ } else {
+ WARNING_WITH_ERRNO("Failed to set timestamps "
+ "on \"%"TS"\"", path);
+ }
+ }
+ }
+ return 0;
+}
+
+/* Check whether the extraction of a dentry should be skipped completely. */
+static bool
+dentry_is_supported(struct wim_dentry *dentry,
+ const struct wim_features *supported_features)
+{
+ struct wim_inode *inode = dentry->d_inode;
+
+ if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ return supported_features->reparse_points ||
+ (inode_is_symlink(inode) &&
+ supported_features->symlink_reparse_points);
+ }
+ if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
+ if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+ return supported_features->encrypted_directories != 0;
+ else
+ return supported_features->encrypted_files != 0;
+ }
+ return true;
+}
+
+/* Given a WIM dentry to extract, build the path to which to extract it, in the
+ * format understood by the callbacks in the apply_operations being used.
+ *
+ * Write the resulting path into @path, which must have room for at least
+ * ctx->ops->path_max characters.
+ *
+ * Return %true if successful; %false if this WIM dentry doesn't actually need
+ * to be extracted or if the calculated path exceeds ctx->ops->max_path
+ * characters.
+ *
+ * This function clobbers the tmp_list member of @dentry and its ancestors up
+ * until the extraction root. */
+static bool
+build_extraction_path(tchar path[], struct wim_dentry *dentry,
+ const struct apply_ctx *ctx)
+{
+ size_t path_nchars;
+ LIST_HEAD(ancestor_list);
+ tchar *p = path;
+ const tchar *target_prefix;
+ size_t target_prefix_nchars;
+ struct wim_dentry *d;
+
+ path_nchars = ctx->ops->path_prefix_nchars;
+
+ if (ctx->ops->requires_realtarget_in_paths) {
+ target_prefix = ctx->realtarget;
+ target_prefix_nchars = ctx->realtarget_nchars;
+ } else if (ctx->ops->requires_target_in_paths) {
+ target_prefix = ctx->target;
+ target_prefix_nchars = ctx->target_nchars;
+ } else {
+ target_prefix = NULL;
+ target_prefix_nchars = 0;
+ }
+ path_nchars += target_prefix_nchars;
+
+ for (d = dentry; d != ctx->target_dentry; d = d->parent) {
+ if (!dentry_in_list(d))
+ break;
+
+ path_nchars += d->extraction_name_nchars + 1;
+ list_add(&d->tmp_list, &ancestor_list);
+ }
+
+ path_nchars++; /* null terminator */
+
+ if (path_nchars > ctx->ops->path_max) {
+ WARNING("\"%"TS"\": Path too long to extract",
+ dentry_full_path(dentry));
+ return false;
+ }
+
+ p = tmempcpy(p, ctx->ops->path_prefix, ctx->ops->path_prefix_nchars);
+ p = tmempcpy(p, target_prefix, target_prefix_nchars);
+ list_for_each_entry(d, &ancestor_list, tmp_list) {
+ *p++ = ctx->ops->path_separator;
+ p = tmempcpy(p, d->extraction_name, d->extraction_name_nchars);
+ }
+ *p++ = T('\0');
+ wimlib_assert(p - path == path_nchars);
+ return true;
+}
+
+static unsigned
+get_num_path_components(const tchar *path, tchar path_separator)
+{
+ unsigned num_components = 0;
+#ifdef __WIN32__
+ /* Ignore drive letter. */
+ if (path[0] != L'\0' && path[1] == L':')
+ path += 2;
+#endif
+
+ while (*path) {
+ while (*path == path_separator)
+ path++;
+ if (*path)
+ num_components++;
+ while (*path && *path != path_separator)
+ path++;
+ }
+ return num_components;
+}
+
+static int
+extract_multiimage_symlink(const tchar *oldpath, const tchar *newpath,
+ struct apply_ctx *ctx, struct wim_dentry *dentry)
+{
+ 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->target_dentry; 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->target_dentry) {
+ 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_linked = 1;
+ return 0;
+
+hardlink:
+ ret = extract_hardlink(oldpath, path, ctx);
+ if (ret)
+ return ret;
+ dentry->was_linked = 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, struct apply_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
+ && dentry_in_list(other_dentry))
+ {
+ 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;
+}
+
+/* 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, struct apply_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);
+}
+
+/* Finish extracting a file, directory, or symbolic link by setting file
+ * security and timestamps. */
+static int
+dentry_extract_final(struct wim_dentry *dentry, struct apply_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;
+
+ if (ctx->ops->requires_final_set_attributes_pass) {
+ /* Set file attributes (if supported). */
+ ret = extract_file_attributes(path, ctx, dentry, 1);
+ if (ret)
+ return ret;
+ }