-#include "config.h"
-
-#ifdef HAVE_UTIME_H
-# include <utime.h>
+/*
+ * unix_apply.c - Code to apply files from a WIM image on UNIX.
+ */
+
+/*
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option) any
+ * later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this file; if not, see http://www.gnu.org/licenses/.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
#endif
-#include <dirent.h>
+
#include <errno.h>
#include <fcntl.h>
-#include <string.h>
+#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
+#include <sys/types.h>
#include <unistd.h>
-#include "timestamp.h"
-#include "wimlib_internal.h"
-#include "lookup_table.h"
+#include "wimlib/apply.h"
+#include "wimlib/assert.h"
+#include "wimlib/blob_table.h"
+#include "wimlib/dentry.h"
+#include "wimlib/error.h"
+#include "wimlib/file_io.h"
+#include "wimlib/reparse.h"
+#include "wimlib/timestamp.h"
+#include "wimlib/unix_data.h"
+
+/* We don't require O_NOFOLLOW, but the advantage of having it is that if we
+ * need to extract a file to a location at which there exists a symbolic link,
+ * open(..., O_NOFOLLOW | ...) recognizes the symbolic link rather than
+ * following it and creating the file somewhere else. (Equivalent to
+ * FILE_OPEN_REPARSE_POINT on Windows.) */
+#ifndef O_NOFOLLOW
+# define O_NOFOLLOW 0
+#endif
+
+static int
+unix_get_supported_features(const char *target,
+ struct wim_features *supported_features)
+{
+ supported_features->hard_links = 1;
+ supported_features->symlink_reparse_points = 1;
+ supported_features->unix_data = 1;
+ supported_features->timestamps = 1;
+ supported_features->case_sensitive_filenames = 1;
+ return 0;
+}
+
+#define NUM_PATHBUFS 2 /* We need 2 when creating hard links */
+
+struct unix_apply_ctx {
+ /* Extract flags, the pointer to the WIMStruct, etc. */
+ struct apply_ctx common;
+
+ /* Buffers for building extraction paths (allocated). */
+ char *pathbufs[NUM_PATHBUFS];
+
+ /* Index of next pathbuf to use */
+ unsigned which_pathbuf;
+
+ /* Currently open file descriptors for extraction */
+ struct filedes open_fds[MAX_OPEN_FILES];
+
+ /* Number of currently open file descriptors in open_fds, starting from
+ * the beginning of the array. */
+ unsigned num_open_fds;
-/* Returns the number of components of @path. */
-static unsigned
-get_num_path_components(const char *path)
+ /* Buffer for reading reparse point data into memory */
+ u8 reparse_data[REPARSE_DATA_MAX_SIZE];
+
+ /* Pointer to the next byte in @reparse_data to fill */
+ u8 *reparse_ptr;
+
+ /* Absolute path to the target directory (allocated buffer). Only set
+ * if needed for absolute symbolic link fixups. */
+ char *target_abspath;
+
+ /* Number of characters in target_abspath. */
+ size_t target_abspath_nchars;
+
+ /* Number of special files we couldn't create due to EPERM */
+ unsigned long num_special_files_ignored;
+};
+
+/* Returns the number of characters needed to represent the path to the
+ * specified @dentry when extracted, not including the null terminator or the
+ * path to the target directory itself. */
+static size_t
+unix_dentry_path_length(const struct wim_dentry *dentry)
+{
+ size_t len = 0;
+ const struct wim_dentry *d;
+
+ d = dentry;
+ do {
+ len += d->d_extraction_name_nchars + 1;
+ d = d->d_parent;
+ } while (!dentry_is_root(d) && will_extract_dentry(d));
+
+ return len;
+}
+
+/* Returns the maximum number of characters needed to represent the path to any
+ * dentry in @dentry_list when extracted, including the null terminator and the
+ * path to the target directory itself. */
+static size_t
+unix_compute_path_max(const struct list_head *dentry_list,
+ const struct unix_apply_ctx *ctx)
{
- unsigned num_components = 0;
- while (*path) {
- while (*path == '/')
- path++;
- if (*path)
- num_components++;
- while (*path && *path != '/')
- path++;
+ size_t max = 0;
+ size_t len;
+ const struct wim_dentry *dentry;
+
+ list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+ len = unix_dentry_path_length(dentry);
+ if (len > max)
+ max = len;
}
- return num_components;
+
+ /* Account for target and null terminator. */
+ return ctx->common.target_nchars + max + 1;
+}
+
+/* Builds and returns the filesystem path to which to extract @dentry.
+ * This cycles through NUM_PATHBUFS different buffers. */
+static const char *
+unix_build_extraction_path(const struct wim_dentry *dentry,
+ struct unix_apply_ctx *ctx)
+{
+ char *pathbuf;
+ char *p;
+ const struct wim_dentry *d;
+
+ pathbuf = ctx->pathbufs[ctx->which_pathbuf];
+ ctx->which_pathbuf = (ctx->which_pathbuf + 1) % NUM_PATHBUFS;
+
+ p = &pathbuf[ctx->common.target_nchars +
+ unix_dentry_path_length(dentry)];
+ *p = '\0';
+ d = dentry;
+ do {
+ p -= d->d_extraction_name_nchars;
+ if (d->d_extraction_name_nchars)
+ memcpy(p, d->d_extraction_name,
+ d->d_extraction_name_nchars);
+ *--p = '/';
+ d = d->d_parent;
+ } while (!dentry_is_root(d) && will_extract_dentry(d));
+
+ return pathbuf;
+}
+
+/* This causes the next call to unix_build_extraction_path() to use the same
+ * path buffer as the previous call. */
+static void
+unix_reuse_pathbuf(struct unix_apply_ctx *ctx)
+{
+ ctx->which_pathbuf = (ctx->which_pathbuf - 1) % NUM_PATHBUFS;
}
+/* Builds and returns the filesystem path to which to extract an unspecified
+ * alias of the @inode. This cycles through NUM_PATHBUFS different buffers. */
static const char *
-path_next_part(const char *path)
+unix_build_inode_extraction_path(const struct wim_inode *inode,
+ struct unix_apply_ctx *ctx)
+{
+ return unix_build_extraction_path(inode_first_extraction_dentry(inode), ctx);
+}
+
+/* Should the specified file be extracted as a directory on UNIX? We extract
+ * the file as a directory if FILE_ATTRIBUTE_DIRECTORY is set and the file does
+ * not have a symlink or junction reparse point. It *may* have a different type
+ * of reparse point. */
+static inline bool
+should_extract_as_directory(const struct wim_inode *inode)
{
- while (*path && *path != '/')
- path++;
- while (*path && *path == '/')
- path++;
- return path;
+ return (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !inode_is_symlink(inode);
}
+/* Sets the timestamps on a file being extracted.
+ *
+ * Either @fd or @path must be specified (not -1 and not NULL, respectively).
+ */
static int
-extract_regular_file_linked(struct wim_dentry *dentry,
- const char *output_path,
- struct apply_args *args,
- struct wim_lookup_table_entry *lte)
+unix_set_timestamps(int fd, const char *path, u64 atime, u64 mtime)
{
- /* This mode overrides the normal hard-link extraction and
- * instead either symlinks or hardlinks *all* identical files in
- * the WIM, even if they are in a different image (in the case
- * of a multi-image extraction) */
-
- if (args->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) {
- if (link(lte->extracted_file, output_path) != 0) {
- ERROR_WITH_ERRNO("Failed to hard link "
- "`%s' to `%s'",
- output_path, lte->extracted_file);
- return WIMLIB_ERR_LINK;
- }
- } else {
- int num_path_components;
- int num_output_dir_path_components;
- size_t extracted_file_len;
- char *p;
- const char *p2;
- size_t i;
-
- num_path_components = get_num_path_components(dentry->_full_path) - 1;
- num_output_dir_path_components = get_num_path_components(args->target);
-
- if (args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) {
- num_path_components++;
- num_output_dir_path_components--;
- }
- extracted_file_len = strlen(lte->extracted_file);
+ {
+ struct timespec times[2];
- char buf[extracted_file_len + 3 * num_path_components + 1];
- p = &buf[0];
+ times[0] = wim_timestamp_to_timespec(atime);
+ times[1] = wim_timestamp_to_timespec(mtime);
- for (i = 0; i < num_path_components; i++) {
- *p++ = '.';
- *p++ = '.';
- *p++ = '/';
- }
- p2 = lte->extracted_file;
- while (*p2 == '/')
- p2++;
- while (num_output_dir_path_components > 0) {
- p2 = path_next_part(p2);
- num_output_dir_path_components--;
- }
- strcpy(p, p2);
- if (symlink(buf, output_path) != 0) {
- ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
- buf, lte->extracted_file);
- return WIMLIB_ERR_LINK;
- }
+ errno = ENOSYS;
+#ifdef HAVE_FUTIMENS
+ if (fd >= 0 && !futimens(fd, times))
+ return 0;
+#endif
+#ifdef HAVE_UTIMENSAT
+ if (fd < 0 && !utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW))
+ return 0;
+#endif
+ if (errno != ENOSYS)
+ return WIMLIB_ERR_SET_TIMESTAMPS;
+ }
+ {
+ struct timeval times[2];
+
+ times[0] = wim_timestamp_to_timeval(atime);
+ times[1] = wim_timestamp_to_timeval(mtime);
+
+ if (fd >= 0 && !futimes(fd, times))
+ return 0;
+ if (fd < 0 && !lutimes(path, times))
+ return 0;
+ return WIMLIB_ERR_SET_TIMESTAMPS;
}
- return 0;
}
static int
-symlink_apply_unix_data(const char *link,
- const struct wimlib_unix_data *unix_data)
+unix_set_owner_and_group(int fd, const char *path, uid_t uid, gid_t gid)
{
- if (lchown(link, unix_data->uid, unix_data->gid)) {
- if (errno == EPERM) {
- /* Ignore */
- WARNING_WITH_ERRNO("failed to set symlink UNIX "
- "owner/group on \"%s\"", link);
- } else {
- ERROR_WITH_ERRNO("failed to set symlink UNIX "
- "owner/group on \"%s\"", link);
- return WIMLIB_ERR_INVALID_DENTRY;
- }
- }
- return 0;
+ if (fd >= 0 && !fchown(fd, uid, gid))
+ return 0;
+ if (fd < 0 && !lchown(path, uid, gid))
+ return 0;
+ return WIMLIB_ERR_SET_SECURITY;
}
static int
-fd_apply_unix_data(int fd, const char *path,
- const struct wimlib_unix_data *unix_data,
- int extract_flags)
+unix_set_mode(int fd, const char *path, mode_t mode)
{
- if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
+ if (fd >= 0 && !fchmod(fd, mode))
return 0;
+ if (fd < 0 && !chmod(path, mode))
+ return 0;
+ return WIMLIB_ERR_SET_SECURITY;
+}
- if (fchown(fd, unix_data->uid, unix_data->gid)) {
- if (errno == EPERM &&
- !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
- {
- WARNING_WITH_ERRNO("failed to set file UNIX "
- "owner/group on \"%s\"", path);
- } else {
- ERROR_WITH_ERRNO("failed to set file UNIX "
- "owner/group on \"%s\"", path);
- return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT :
- WIMLIB_ERR_WRITE;
+/*
+ * Set metadata on an extracted file.
+ *
+ * @fd is an open file descriptor to the extracted file, or -1. @path is the
+ * path to the extracted file, or NULL. If valid, this function uses @fd.
+ * Otherwise, if valid, it uses @path. Otherwise, it calculates the path to one
+ * alias of the extracted file and uses it.
+ */
+static int
+unix_set_metadata(int fd, const struct wim_inode *inode,
+ const char *path, struct unix_apply_ctx *ctx)
+{
+ int ret;
+ struct wimlib_unix_data unix_data;
+
+ if (fd < 0 && !path)
+ path = unix_build_inode_extraction_path(inode, ctx);
+
+ if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA)
+ && inode_get_unix_data(inode, &unix_data))
+ {
+ u32 uid = unix_data.uid;
+ u32 gid = unix_data.gid;
+ u32 mode = unix_data.mode;
+
+ ret = unix_set_owner_and_group(fd, path, uid, gid);
+ if (ret) {
+ if (!path)
+ path = unix_build_inode_extraction_path(inode, ctx);
+ if (ctx->common.extract_flags &
+ WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
+ {
+ ERROR_WITH_ERRNO("Can't set uid=%"PRIu32" and "
+ "gid=%"PRIu32" on \"%s\"",
+ uid, gid, path);
+ return ret;
+ } else {
+ WARNING_WITH_ERRNO("Can't set uid=%"PRIu32" and "
+ "gid=%"PRIu32" on \"%s\"",
+ uid, gid, path);
+ }
+ }
+
+ ret = 0;
+ if (!inode_is_symlink(inode))
+ ret = unix_set_mode(fd, path, mode);
+ if (ret) {
+ if (!path)
+ path = unix_build_inode_extraction_path(inode, ctx);
+ if (ctx->common.extract_flags &
+ WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
+ {
+ ERROR_WITH_ERRNO("Can't set mode=0%"PRIo32" "
+ "on \"%s\"", mode, path);
+ return ret;
+ } else {
+ WARNING_WITH_ERRNO("Can't set mode=0%"PRIo32" "
+ "on \"%s\"", mode, path);
+ }
}
}
- if (fchmod(fd, unix_data->mode)) {
- if (errno == EPERM &&
- !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
+ ret = unix_set_timestamps(fd, path,
+ inode->i_last_access_time,
+ inode->i_last_write_time);
+ if (ret) {
+ if (!path)
+ path = unix_build_inode_extraction_path(inode, ctx);
+ if (ctx->common.extract_flags &
+ WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS)
{
- WARNING_WITH_ERRNO("failed to set UNIX file mode "
- "on \"%s\"", path);
+ ERROR_WITH_ERRNO("Can't set timestamps on \"%s\"", path);
+ return ret;
} else {
- ERROR_WITH_ERRNO("failed to set UNIX file mode "
- "on \"%s\"", path);
- return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT :
- WIMLIB_ERR_WRITE;
+ WARNING_WITH_ERRNO("Can't set timestamps on \"%s\"", path);
}
}
return 0;
}
+/* Extract all needed aliases of the @inode, where one alias, corresponding to
+ * @first_dentry, has already been extracted to @first_path. */
static int
-dir_apply_unix_data(const char *dir, const struct wimlib_unix_data *unix_data,
- int extract_flags)
+unix_create_hardlinks(const struct wim_inode *inode,
+ const struct wim_dentry *first_dentry,
+ const char *first_path, struct unix_apply_ctx *ctx)
{
- int dfd = open(dir, O_RDONLY);
- int ret;
- if (dfd >= 0) {
- ret = fd_apply_unix_data(dfd, dir, unix_data, extract_flags);
- if (close(dfd) && ret == 0) {
- ERROR_WITH_ERRNO("can't close directory `%s'", dir);
- ret = WIMLIB_ERR_WRITE;
+ const struct wim_dentry *dentry;
+ const char *newpath;
+
+ inode_for_each_extraction_alias(dentry, inode) {
+ if (dentry == first_dentry)
+ continue;
+
+ newpath = unix_build_extraction_path(dentry, ctx);
+ retry_link:
+ if (link(first_path, newpath)) {
+ if (errno == EEXIST && !unlink(newpath))
+ goto retry_link;
+ ERROR_WITH_ERRNO("Can't create hard link "
+ "\"%s\" => \"%s\"", newpath, first_path);
+ return WIMLIB_ERR_LINK;
}
- } else {
- ERROR_WITH_ERRNO("can't open directory `%s'", dir);
- ret = WIMLIB_ERR_OPENDIR;
+ unix_reuse_pathbuf(ctx);
}
- return ret;
+ return 0;
}
+/* If @dentry represents a directory, create it. */
static int
-extract_regular_file_unlinked(struct wim_dentry *dentry,
- struct apply_args *args,
- const char *output_path,
- struct wim_lookup_table_entry *lte)
+unix_create_if_directory(const struct wim_dentry *dentry,
+ struct unix_apply_ctx *ctx)
{
- /* Normal mode of extraction. Regular files and hard links are
- * extracted in the way that they appear in the WIM. */
+ const char *path;
+ struct stat stbuf;
+
+ if (!should_extract_as_directory(dentry->d_inode))
+ return 0;
- int out_fd;
+ path = unix_build_extraction_path(dentry, ctx);
+ if (mkdir(path, 0755) &&
+ /* It's okay if the path already exists, as long as it's a
+ * directory. */
+ !(errno == EEXIST && !lstat(path, &stbuf) && S_ISDIR(stbuf.st_mode)))
+ {
+ ERROR_WITH_ERRNO("Can't create directory \"%s\"", path);
+ return WIMLIB_ERR_MKDIR;
+ }
+
+ return report_file_created(&ctx->common);
+}
+
+/* If @dentry represents an empty regular file or a special file, create it, set
+ * its metadata, and create any needed hard links. */
+static int
+unix_extract_if_empty_file(const struct wim_dentry *dentry,
+ struct unix_apply_ctx *ctx)
+{
+ const struct wim_inode *inode;
+ struct wimlib_unix_data unix_data;
+ const char *path;
int ret;
- struct wim_inode *inode = dentry->d_inode;
- if (!((args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE)
- && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
- WIMLIB_EXTRACT_FLAG_HARDLINK))))
+ inode = dentry->d_inode;
+
+ /* Extract all aliases only when the "first" comes up. */
+ if (dentry != inode_first_extraction_dentry(inode))
+ return 0;
+
+ /* Is this a directory, a symbolic link, or any type of nonempty file?
+ */
+ if (should_extract_as_directory(inode) || inode_is_symlink(inode) ||
+ inode_get_blob_for_unnamed_data_stream_resolved(inode))
+ return 0;
+
+ /* Recognize special files in UNIX_DATA mode */
+ if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) &&
+ inode_get_unix_data(inode, &unix_data) &&
+ !S_ISREG(unix_data.mode))
{
- /* If the dentry is part of a hard link set of at least 2
- * dentries and one of the other dentries has already been
- * extracted, make a hard link to the file corresponding to this
- * already-extracted directory. Otherwise, extract the file and
- * set the inode->i_extracted_file field so that other dentries
- * in the hard link group can link to it. */
- if (inode->i_nlink > 1) {
- if (inode->i_extracted_file) {
- DEBUG("Extracting hard link `%s' => `%s'",
- output_path, inode->i_extracted_file);
- if (link(inode->i_extracted_file, output_path) != 0) {
- ERROR_WITH_ERRNO("Failed to hard link "
- "`%s' to `%s'",
- output_path,
- inode->i_extracted_file);
- return WIMLIB_ERR_LINK;
- }
+ path = unix_build_extraction_path(dentry, ctx);
+ retry_mknod:
+ if (mknod(path, unix_data.mode, unix_data.rdev)) {
+ if (errno == EPERM) {
+ WARNING_WITH_ERRNO("Can't create special "
+ "file \"%s\"", path);
+ ctx->num_special_files_ignored++;
return 0;
}
- FREE(inode->i_extracted_file);
- inode->i_extracted_file = STRDUP(output_path);
- if (!inode->i_extracted_file) {
- ERROR("Failed to allocate memory for filename");
- return WIMLIB_ERR_NOMEM;
- }
+ if (errno == EEXIST && !unlink(path))
+ goto retry_mknod;
+ ERROR_WITH_ERRNO("Can't create special file \"%s\"",
+ path);
+ return WIMLIB_ERR_MKNOD;
+ }
+ /* On special files, we can set timestamps immediately because
+ * we don't need to write any data to them. */
+ ret = unix_set_metadata(-1, inode, path, ctx);
+ } else {
+ int fd;
+
+ path = unix_build_extraction_path(dentry, ctx);
+ retry_create:
+ fd = open(path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
+ if (fd < 0) {
+ if (errno == EEXIST && !unlink(path))
+ goto retry_create;
+ ERROR_WITH_ERRNO("Can't create regular file \"%s\"", path);
+ return WIMLIB_ERR_OPEN;
+ }
+ /* On empty files, we can set timestamps immediately because we
+ * don't need to write any data to them. */
+ ret = unix_set_metadata(fd, inode, path, ctx);
+ if (close(fd) && !ret) {
+ ERROR_WITH_ERRNO("Error closing \"%s\"", path);
+ ret = WIMLIB_ERR_WRITE;
}
}
+ if (ret)
+ return ret;
- /* Extract the contents of the file to @output_path. */
+ ret = unix_create_hardlinks(inode, dentry, path, ctx);
+ if (ret)
+ return ret;
- out_fd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
- if (out_fd == -1) {
- ERROR_WITH_ERRNO("Failed to open the file `%s' for writing",
- output_path);
- return WIMLIB_ERR_OPEN;
- }
+ return report_file_created(&ctx->common);
+}
- if (!lte) {
- /* Empty file with no lookup table entry */
- DEBUG("Empty file `%s'.", output_path);
- ret = 0;
- goto out_extract_unix_data;
- }
+static int
+unix_create_dirs_and_empty_files(const struct list_head *dentry_list,
+ struct unix_apply_ctx *ctx)
+{
+ const struct wim_dentry *dentry;
+ int ret;
- ret = extract_wim_resource_to_fd(lte, out_fd, wim_resource_size(lte));
- if (ret) {
- ERROR("Failed to extract resource to `%s'", output_path);
- goto out;
+ list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+ ret = unix_create_if_directory(dentry, ctx);
+ if (ret)
+ return ret;
}
-
-out_extract_unix_data:
- if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
- struct wimlib_unix_data unix_data;
- ret = inode_get_unix_data(inode, &unix_data, NULL);
- if (ret > 0)
- ;
- else if (ret < 0)
- ret = 0;
- else
- ret = fd_apply_unix_data(out_fd, output_path, &unix_data,
- args->extract_flags);
+ list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+ ret = unix_extract_if_empty_file(dentry, ctx);
if (ret)
- goto out;
+ return ret;
}
- if (lte)
- args->progress.extract.completed_bytes += wim_resource_size(lte);
-out:
- if (close(out_fd) != 0) {
- ERROR_WITH_ERRNO("Failed to close file `%s'", output_path);
- if (ret == 0)
- ret = WIMLIB_ERR_WRITE;
+ return 0;
+}
+
+static void
+unix_count_dentries(const struct list_head *dentry_list,
+ u64 *dir_count_ret, u64 *empty_file_count_ret)
+{
+ const struct wim_dentry *dentry;
+ u64 dir_count = 0;
+ u64 empty_file_count = 0;
+
+ list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+
+ const struct wim_inode *inode = dentry->d_inode;
+
+ if (should_extract_as_directory(inode))
+ dir_count++;
+ else if ((dentry == inode_first_extraction_dentry(inode)) &&
+ !inode_is_symlink(inode) &&
+ !inode_get_blob_for_unnamed_data_stream_resolved(inode))
+ empty_file_count++;
}
- return ret;
+
+ *dir_count_ret = dir_count;
+ *empty_file_count_ret = empty_file_count;
}
static int
-extract_regular_file(struct wim_dentry *dentry,
- struct apply_args *args,
- const char *output_path)
+unix_create_symlink(const struct wim_inode *inode, const char *path,
+ size_t rpdatalen, struct unix_apply_ctx *ctx)
{
- struct wim_lookup_table_entry *lte;
- const struct wim_inode *inode = dentry->d_inode;
+ char target[REPARSE_POINT_MAX_SIZE];
+ struct blob_descriptor blob_override;
+ int ret;
- lte = inode_unnamed_lte_resolved(inode);
+ blob_set_is_located_in_attached_buffer(&blob_override,
+ ctx->reparse_data, rpdatalen);
- if (lte && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
- WIMLIB_EXTRACT_FLAG_HARDLINK)))
- {
- if (lte->extracted_file) {
- return extract_regular_file_linked(dentry, output_path, args, lte);
- } else {
- lte->extracted_file = STRDUP(output_path);
- if (!lte->extracted_file)
- return WIMLIB_ERR_NOMEM;
- }
+ ret = wim_inode_readlink(inode, target, sizeof(target) - 1,
+ &blob_override,
+ ctx->target_abspath,
+ ctx->target_abspath_nchars);
+ if (unlikely(ret < 0)) {
+ errno = -ret;
+ return WIMLIB_ERR_READLINK;
+ }
+ target[ret] = '\0';
+
+retry_symlink:
+ if (symlink(target, path)) {
+ if (errno == EEXIST && !unlink(path))
+ goto retry_symlink;
+ return WIMLIB_ERR_LINK;
}
- return extract_regular_file_unlinked(dentry, args, output_path, lte);
+ return 0;
+}
+
+static void
+unix_cleanup_open_fds(struct unix_apply_ctx *ctx, unsigned offset)
+{
+ for (unsigned i = offset; i < ctx->num_open_fds; i++)
+ filedes_close(&ctx->open_fds[i]);
+ ctx->num_open_fds = 0;
}
static int
-extract_symlink(struct wim_dentry *dentry,
- struct apply_args *args,
- const char *output_path)
+unix_begin_extract_blob_instance(const struct blob_descriptor *blob,
+ const struct wim_inode *inode,
+ const struct wim_inode_stream *strm,
+ struct unix_apply_ctx *ctx)
{
- char target[4096 + args->target_realpath_len];
- char *fixed_target;
- const struct wim_inode *inode = dentry->d_inode;
-
- ssize_t ret = wim_inode_readlink(inode,
- target + args->target_realpath_len,
- sizeof(target) - args->target_realpath_len - 1);
- struct wim_lookup_table_entry *lte;
-
- if (ret <= 0) {
- ERROR("Could not read the symbolic link from dentry `%s'",
- dentry->_full_path);
- return WIMLIB_ERR_INVALID_DENTRY;
- }
- target[args->target_realpath_len + ret] = '\0';
- if (target[args->target_realpath_len] == '/' &&
- args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
- {
- /* Fix absolute symbolic link target to point into the actual
- * extraction destination */
- memcpy(target, args->target_realpath,
- args->target_realpath_len);
- fixed_target = target;
- } else {
- /* Keep same link target */
- fixed_target = target + args->target_realpath_len;
+ const struct wim_dentry *first_dentry;
+ const char *first_path;
+ int fd;
+
+ if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) {
+ /* On UNIX, symbolic links must be created with symlink(), which
+ * requires that the full link target be available. */
+ if (blob->size > REPARSE_DATA_MAX_SIZE) {
+ ERROR_WITH_ERRNO("Reparse data of \"%s\" has size "
+ "%"PRIu64" bytes (exceeds %u bytes)",
+ inode_any_full_path(inode),
+ blob->size, REPARSE_DATA_MAX_SIZE);
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+ }
+ ctx->reparse_ptr = ctx->reparse_data;
+ return 0;
}
- ret = symlink(fixed_target, output_path);
- if (ret) {
- ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
- output_path, fixed_target);
- return WIMLIB_ERR_LINK;
+
+ wimlib_assert(stream_is_unnamed_data_stream(strm));
+
+ /* Unnamed data stream of "regular" file */
+
+ /* This should be ensured by extract_blob_list() */
+ wimlib_assert(ctx->num_open_fds < MAX_OPEN_FILES);
+
+ first_dentry = inode_first_extraction_dentry(inode);
+ first_path = unix_build_extraction_path(first_dentry, ctx);
+retry_create:
+ fd = open(first_path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
+ if (fd < 0) {
+ if (errno == EEXIST && !unlink(first_path))
+ goto retry_create;
+ ERROR_WITH_ERRNO("Can't create regular file \"%s\"", first_path);
+ return WIMLIB_ERR_OPEN;
}
- if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
- struct wimlib_unix_data unix_data;
- ret = inode_get_unix_data(inode, &unix_data, NULL);
- if (ret > 0)
- ;
- else if (ret < 0)
- ret = 0;
- else
- ret = symlink_apply_unix_data(output_path, &unix_data);
- if (ret)
+ filedes_init(&ctx->open_fds[ctx->num_open_fds++], fd);
+ return unix_create_hardlinks(inode, first_dentry, first_path, ctx);
+}
+
+/* Called when starting to read a blob for extraction */
+static int
+unix_begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
+{
+ struct unix_apply_ctx *ctx = _ctx;
+ const struct blob_extraction_target *targets = blob_extraction_targets(blob);
+
+ for (u32 i = 0; i < blob->out_refcnt; i++) {
+ int ret = unix_begin_extract_blob_instance(blob,
+ targets[i].inode,
+ targets[i].stream,
+ ctx);
+ if (ret) {
+ ctx->reparse_ptr = NULL;
+ unix_cleanup_open_fds(ctx, 0);
return ret;
+ }
}
- lte = inode_unnamed_lte_resolved(inode);
- wimlib_assert(lte != NULL);
- args->progress.extract.completed_bytes += wim_resource_size(lte);
return 0;
}
+/* Called when the next chunk of a blob has been read for extraction */
static int
-extract_directory(struct wim_dentry *dentry, const tchar *output_path,
- int extract_flags)
+unix_extract_chunk(const void *chunk, size_t size, void *_ctx)
{
+ struct unix_apply_ctx *ctx = _ctx;
int ret;
- struct stat stbuf;
- ret = tstat(output_path, &stbuf);
- if (ret == 0) {
- if (S_ISDIR(stbuf.st_mode)) {
- /*if (!is_root)*/
- /*WARNING("`%s' already exists", output_path);*/
- goto dir_exists;
- } else {
- ERROR("`%"TS"' is not a directory", output_path);
- return WIMLIB_ERR_MKDIR;
- }
- } else {
- if (errno != ENOENT) {
- ERROR_WITH_ERRNO("Failed to stat `%"TS"'", output_path);
- return WIMLIB_ERR_STAT;
+ for (unsigned i = 0; i < ctx->num_open_fds; i++) {
+ ret = full_write(&ctx->open_fds[i], chunk, size);
+ if (ret) {
+ ERROR_WITH_ERRNO("Error writing data to filesystem");
+ return ret;
}
}
+ if (ctx->reparse_ptr)
+ ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
+ return 0;
+}
- if (tmkdir(output_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
- {
- ERROR_WITH_ERRNO("Cannot create directory `%"TS"'", output_path);
- return WIMLIB_ERR_MKDIR;
+/* Called when a blob has been fully read for extraction */
+static int
+unix_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
+{
+ struct unix_apply_ctx *ctx = _ctx;
+ int ret;
+ unsigned j;
+ const struct blob_extraction_target *targets = blob_extraction_targets(blob);
+
+ ctx->reparse_ptr = NULL;
+
+ if (status) {
+ unix_cleanup_open_fds(ctx, 0);
+ return status;
}
-dir_exists:
+
+ j = 0;
ret = 0;
-#ifndef __WIN32__
- if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
- struct wimlib_unix_data unix_data;
- ret = inode_get_unix_data(dentry->d_inode, &unix_data, NULL);
- if (ret > 0)
- ;
- else if (ret < 0)
- ret = 0;
- else
- ret = dir_apply_unix_data(output_path, &unix_data,
- extract_flags);
+ for (u32 i = 0; i < blob->out_refcnt; i++) {
+ struct wim_inode *inode = targets[i].inode;
+
+ if (inode_is_symlink(inode)) {
+ /* We finally have the symlink data, so we can create
+ * the symlink. */
+ const char *path;
+
+ path = unix_build_inode_extraction_path(inode, ctx);
+ ret = unix_create_symlink(inode, path, blob->size, ctx);
+ if (ret) {
+ ERROR_WITH_ERRNO("Can't create symbolic link "
+ "\"%s\"", path);
+ break;
+ }
+ ret = unix_set_metadata(-1, inode, path, ctx);
+ if (ret)
+ break;
+ } else {
+ /* Set metadata on regular file just before closing it.
+ */
+ struct filedes *fd = &ctx->open_fds[j];
+
+ ret = unix_set_metadata(fd->fd, inode, NULL, ctx);
+ if (ret)
+ break;
+
+ if (filedes_close(fd)) {
+ ERROR_WITH_ERRNO("Error closing \"%s\"",
+ unix_build_inode_extraction_path(inode, ctx));
+ ret = WIMLIB_ERR_WRITE;
+ break;
+ }
+ j++;
+ }
}
-#endif
+ unix_cleanup_open_fds(ctx, j);
return ret;
}
-int
-unix_do_apply_dentry(const char *output_path, size_t output_path_len,
- struct wim_dentry *dentry, struct apply_args *args)
+static int
+unix_set_dir_metadata(struct list_head *dentry_list, struct unix_apply_ctx *ctx)
{
- const struct wim_inode *inode = dentry->d_inode;
-
- if (inode_is_symlink(inode))
- return extract_symlink(dentry, args, output_path);
- else if (inode_is_directory(inode))
- return extract_directory(dentry, output_path, args->extract_flags);
- else
- return extract_regular_file(dentry, args, output_path);
+ const struct wim_dentry *dentry;
+ int ret;
+
+ list_for_each_entry_reverse(dentry, dentry_list, d_extraction_list_node) {
+ if (should_extract_as_directory(dentry->d_inode)) {
+ ret = unix_set_metadata(-1, dentry->d_inode, NULL, ctx);
+ if (ret)
+ return ret;
+ ret = report_file_metadata_applied(&ctx->common);
+ if (ret)
+ return ret;
+ }
+ }
+ return 0;
}
-int
-unix_do_apply_dentry_timestamps(const char *output_path,
- size_t output_path_len,
- struct wim_dentry *dentry,
- struct apply_args *args)
+static int
+unix_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
{
int ret;
- const struct wim_inode *inode = dentry->d_inode;
+ struct unix_apply_ctx *ctx = (struct unix_apply_ctx *)_ctx;
+ size_t path_max;
+ u64 dir_count;
+ u64 empty_file_count;
+
+ /* Compute the maximum path length that will be needed, then allocate
+ * some path buffers. */
+ path_max = unix_compute_path_max(dentry_list, ctx);
+
+ for (unsigned i = 0; i < NUM_PATHBUFS; i++) {
+ ctx->pathbufs[i] = MALLOC(path_max);
+ if (!ctx->pathbufs[i]) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out;
+ }
+ /* Pre-fill the target in each path buffer. We'll just append
+ * the rest of the paths after this. */
+ memcpy(ctx->pathbufs[i],
+ ctx->common.target, ctx->common.target_nchars);
+ }
-#ifdef HAVE_UTIMENSAT
- /* Convert the WIM timestamps, which are accurate to 100 nanoseconds,
- * into `struct timespec's for passing to utimensat(), which is accurate
- * to 1 nanosecond. */
-
- struct timespec ts[2];
- ts[0] = wim_timestamp_to_timespec(inode->i_last_access_time);
- ts[1] = wim_timestamp_to_timespec(inode->i_last_write_time);
- ret = utimensat(AT_FDCWD, output_path, ts, AT_SYMLINK_NOFOLLOW);
+ /* Extract directories and empty regular files. Directories are needed
+ * because we can't extract any other files until their directories
+ * exist. Empty files are needed because they don't have
+ * representatives in the blob list. */
+
+ unix_count_dentries(dentry_list, &dir_count, &empty_file_count);
+
+ ret = start_file_structure_phase(&ctx->common, dir_count + empty_file_count);
if (ret)
- ret = errno;
-#else
- ret = ENOSYS;
-#endif
+ goto out;
- if (ret == ENOSYS) {
- /* utimensat() not implemented or not available */
- #ifdef HAVE_LUTIMES
- /* Convert the WIM timestamps, which are accurate to 100
- * nanoseconds, into `struct timeval's for passing to lutimes(),
- * which is accurate to 1 microsecond. */
- struct timeval tv[2];
- tv[0] = wim_timestamp_to_timeval(inode->i_last_access_time);
- tv[1] = wim_timestamp_to_timeval(inode->i_last_write_time);
- ret = lutimes(output_path, tv);
- if (ret)
- ret = errno;
- #endif
- }
+ ret = unix_create_dirs_and_empty_files(dentry_list, ctx);
+ if (ret)
+ goto out;
- if (ret == ENOSYS) {
- /* utimensat() and lutimes() both not implemented or not
- * available */
- #ifdef HAVE_UTIME
- /* Convert the WIM timestamps, which are accurate to 100
- * nanoseconds, into a `struct utimbuf's for passing to
- * utime(), which is accurate to 1 second. */
- struct utimbuf buf;
- buf.actime = wim_timestamp_to_unix(inode->i_last_access_time);
- buf.modtime = wim_timestamp_to_unix(inode->i_last_write_time);
- ret = utime(output_path, &buf);
- #endif
+ ret = end_file_structure_phase(&ctx->common);
+ if (ret)
+ goto out;
+
+ /* Get full path to target if needed for absolute symlink fixups. */
+ if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
+ ctx->common.required_features.symlink_reparse_points)
+ {
+ ctx->target_abspath = realpath(ctx->common.target, NULL);
+ if (!ctx->target_abspath) {
+ ret = WIMLIB_ERR_NOMEM;
+ goto out;
+ }
+ ctx->target_abspath_nchars = strlen(ctx->target_abspath);
}
- if (ret && args->num_utime_warnings < 10) {
- WARNING_WITH_ERRNO("Failed to set timestamp on file `%s'",
- output_path);
- args->num_utime_warnings++;
+
+ /* Extract nonempty regular files and symbolic links. */
+
+ struct read_blob_callbacks cbs = {
+ .begin_blob = unix_begin_extract_blob,
+ .consume_chunk = unix_extract_chunk,
+ .end_blob = unix_end_extract_blob,
+ .ctx = ctx,
+ };
+ ret = extract_blob_list(&ctx->common, &cbs);
+ if (ret)
+ goto out;
+
+
+ /* Set directory metadata. We do this last so that we get the right
+ * directory timestamps. */
+ ret = start_file_metadata_phase(&ctx->common, dir_count);
+ if (ret)
+ goto out;
+
+ ret = unix_set_dir_metadata(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ ret = end_file_metadata_phase(&ctx->common);
+ if (ret)
+ goto out;
+
+ if (ctx->num_special_files_ignored) {
+ WARNING("%lu special files were not extracted due to EPERM!",
+ ctx->num_special_files_ignored);
}
- return 0;
+out:
+ for (unsigned i = 0; i < NUM_PATHBUFS; i++)
+ FREE(ctx->pathbufs[i]);
+ FREE(ctx->target_abspath);
+ return ret;
}
+
+const struct apply_operations unix_apply_ops = {
+ .name = "UNIX",
+ .get_supported_features = unix_get_supported_features,
+ .extract = unix_extract,
+ .context_size = sizeof(struct unix_apply_ctx),
+};