X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Funix_apply.c;h=ae9b8cea55e7ea7fde64de03e5cfbcedb703a4fe;hp=97c1c83b2e3c0ac593184f0aa436441bfe98f3e8;hb=564aacbad3801ec37c2455e15b9296ecaef4eb93;hpb=5218b1d7c83cf9e98ed6276e099844ae0d80abc2 diff --git a/src/unix_apply.c b/src/unix_apply.c index 97c1c83b..ae9b8cea 100644 --- a/src/unix_apply.c +++ b/src/unix_apply.c @@ -1,470 +1,807 @@ -#include "config.h" - -#ifdef HAVE_UTIME_H -# include +/* + * 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 + #include #include -#include +#include #include #include +#include #include -#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), +};