X-Git-Url: https://wimlib.net/git/?a=blobdiff_plain;f=src%2Fmount_image.c;h=4702d60d8d806d393dcc8b825bcc76cbcb557706;hb=ae44786dcd57c6668233c51fa1c6e3a60fd459cd;hp=9d7ddd326f7fc0f9fb222c62b34684a1106ed5fb;hpb=d8af927e5d02cbcfc45cd1a3568b31c9814ec6f5;p=wimlib diff --git a/src/mount_image.c b/src/mount_image.c index 9d7ddd32..4702d60d 100644 --- a/src/mount_image.c +++ b/src/mount_image.c @@ -2,13 +2,13 @@ * mount_image.c * * This file implements mounting of WIM images using FUSE - * (Filesystem in Userspace). See http://fuse.sourceforge.net/. + * (Filesystem in Userspace). See https://github.com/libfuse/libfuse * * Currently it is only expected to work on Linux. */ /* - * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers + * Copyright 2012-2023 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 @@ -21,7 +21,7 @@ * 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/. + * along with this file; if not, see https://www.gnu.org/licenses/. */ #ifdef HAVE_CONFIG_H @@ -33,24 +33,23 @@ #ifdef WITH_FUSE -#ifdef __WIN32__ +#ifdef _WIN32 # error "FUSE mount not supported on Windows! Please configure --without-fuse" #endif -#define FUSE_USE_VERSION 26 +#define FUSE_USE_VERSION 30 -#include +#include /* sometimes required before */ +#include #include #include #include #include #include -#include #include #include #include #include -#include #include #include @@ -61,6 +60,7 @@ #include "wimlib/paths.h" #include "wimlib/progress.h" #include "wimlib/reparse.h" +#include "wimlib/threads.h" #include "wimlib/timestamp.h" #include "wimlib/unix_data.h" #include "wimlib/write.h" @@ -70,6 +70,10 @@ # define O_NOFOLLOW 0 /* Security only... */ #endif +#ifndef ENOATTR +# define ENOATTR ENODATA +#endif + #define WIMFS_MQUEUE_NAME_LEN 32 #define WIMLIB_UNMOUNT_FLAG_SEND_PROGRESS 0x80000000 @@ -144,6 +148,11 @@ struct wimfs_context { uid_t owner_uid; gid_t owner_gid; + /* Absolute path to the mountpoint directory (may be needed for absolute + * symbolic link fixups) */ + char *mountpoint_abspath; + size_t mountpoint_abspath_nchars; + /* Information about the staging directory for a read-write mount. */ int parent_dir_fd; int staging_dir_fd; @@ -158,9 +167,9 @@ struct wimfs_context { /* Number of file descriptors open to the mounted WIM image. */ unsigned long num_open_fds; - /* Original list of blobs in the mounted image, linked by - * 'struct blob_descriptor'.orig_blob_list. */ - struct list_head orig_blob_list; + /* For read-write mounts, the original metadata resource of the mounted + * image. */ + struct blob_descriptor *metadata_resource; /* Parameters for unmounting the image (can be set via extended * attribute "wimfs.unmount_info"). */ @@ -210,10 +219,7 @@ flags_writable(int open_flags) static mode_t fuse_mask_mode(mode_t mode, const struct fuse_context *fuse_ctx) { -#if FUSE_MAJOR_VERSION > 2 || (FUSE_MAJOR_VERSION == 2 && FUSE_MINOR_VERSION >= 8) - mode &= ~fuse_ctx->umask; -#endif - return mode; + return mode & ~fuse_ctx->umask; } /* @@ -357,7 +363,7 @@ inode_get_data_stream_tstr(const struct wim_inode *inode, struct wim_inode_stream *strm; if (!stream_name || !*stream_name) { - strm = inode_get_unnamed_stream(inode, STREAM_TYPE_DATA); + strm = inode_get_unnamed_data_stream(inode); } else { const utf16lechar *uname; @@ -442,14 +448,12 @@ wim_pathname_to_stream(const struct wimfs_context *ctx, * The path at which to create the first link to the new file. If a file * already exists at this path, -EEXIST is returned. * @mode - * The UNIX mode for the new file. This is only honored if + * The UNIX mode for the new file. This is only fully honored if * WIMLIB_MOUNT_FLAG_UNIX_DATA was passed to wimlib_mount_image(). * @rdev * The device ID for the new file, encoding the major and minor device * numbers. This is only honored if WIMLIB_MOUNT_FLAG_UNIX_DATA was passed * to wimlib_mount_image(). - * @attributes - * Windows file attributes to use for the new file. * @dentry_ret * On success, a pointer to the new dentry is returned here. Its d_inode * member will point to the new inode that was created for it and added to @@ -459,14 +463,13 @@ wim_pathname_to_stream(const struct wimfs_context *ctx, */ static int create_file(struct fuse_context *fuse_ctx, const char *path, - mode_t mode, dev_t rdev, u32 attributes, - struct wim_dentry **dentry_ret) + mode_t mode, dev_t rdev, struct wim_dentry **dentry_ret) { struct wimfs_context *wimfs_ctx = WIMFS_CTX(fuse_ctx); struct wim_dentry *parent; const char *basename; - struct wim_dentry *new_dentry; - struct wim_inode *new_inode; + struct wim_dentry *dentry; + struct wim_inode *inode; parent = get_parent_dentry(wimfs_ctx->wim, path, WIMLIB_CASE_SENSITIVE); if (!parent) @@ -480,13 +483,19 @@ create_file(struct fuse_context *fuse_ctx, const char *path, if (get_dentry_child_with_name(parent, basename, WIMLIB_CASE_SENSITIVE)) return -EEXIST; - if (new_dentry_with_new_inode(basename, true, &new_dentry)) + if (new_dentry_with_new_inode(basename, true, &dentry)) return -ENOMEM; - new_inode = new_dentry->d_inode; + inode = dentry->d_inode; + + inode->i_ino = wimfs_ctx->next_ino++; - new_inode->i_ino = wimfs_ctx->next_ino++; - new_inode->i_attributes = attributes; + /* Note: we still use FILE_ATTRIBUTE_NORMAL for device nodes, named + * pipes, and sockets. The real mode is in the UNIX metadata. */ + if (S_ISDIR(mode)) + inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY; + else + inode->i_attributes = FILE_ATTRIBUTE_NORMAL; if (wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA) { struct wimlib_unix_data unix_data; @@ -495,19 +504,19 @@ create_file(struct fuse_context *fuse_ctx, const char *path, unix_data.gid = fuse_ctx->gid; unix_data.mode = fuse_mask_mode(mode, fuse_ctx); unix_data.rdev = rdev; - if (!inode_set_unix_data(new_inode, &unix_data, UNIX_DATA_ALL)) + if (!inode_set_unix_data(inode, &unix_data, UNIX_DATA_ALL)) { - free_dentry(new_dentry); + free_dentry(dentry); return -ENOMEM; } } - list_add_tail(&new_inode->i_list, - &wim_get_current_image_metadata(wimfs_ctx->wim)->inode_list); + hlist_add_head(&inode->i_hlist_node, + &wim_get_current_image_metadata(wimfs_ctx->wim)->inode_list); - dentry_add_child(parent, new_dentry); + dentry_add_child(parent, dentry); - *dentry_ret = new_dentry; + *dentry_ret = dentry; return 0; } @@ -549,6 +558,25 @@ inode_default_unix_mode(const struct wim_inode *inode) return inode_unix_file_type(inode) | 0777; } +static u64 +blob_size(const struct blob_descriptor *blob) +{ + if (!blob) + return 0; + return blob->size; +} + +static u64 +blob_stored_size(const struct blob_descriptor *blob) +{ + if (!blob) + return 0; + if (blob->blob_location == BLOB_IN_WIM && + blob->size == blob->rdesc->uncompressed_size) + return blob->rdesc->size_in_wim; + return blob->size; +} + /* * Retrieve standard UNIX metadata ('struct stat') for a WIM inode. * @@ -586,8 +614,7 @@ inode_to_stbuf(const struct wim_inode *inode, } stbuf->st_ino = inode->i_ino; stbuf->st_nlink = inode->i_nlink; - if (blob) - stbuf->st_size = blob->size; + stbuf->st_size = blob_size(blob); #ifdef HAVE_STAT_NANOSECOND_PRECISION stbuf->st_atim = wim_timestamp_to_timespec(inode->i_last_access_time); stbuf->st_mtim = wim_timestamp_to_timespec(inode->i_last_write_time); @@ -597,7 +624,7 @@ inode_to_stbuf(const struct wim_inode *inode, stbuf->st_mtime = wim_timestamp_to_time_t(inode->i_last_write_time); stbuf->st_ctime = stbuf->st_mtime; #endif - stbuf->st_blocks = DIV_ROUND_UP(stbuf->st_size, 512); + stbuf->st_blocks = DIV_ROUND_UP(blob_stored_size(blob), 512); return 0; } @@ -616,6 +643,31 @@ touch_parent(struct wim_dentry *dentry) touch_inode(dentry->d_parent->d_inode); } +/* + * Update inode metadata after a regular file's contents have changed: + * + * - Update the timestamps + * - Clear the setuid and setgid bits + */ +static void +file_contents_changed(struct wim_inode *inode) +{ + struct wimlib_unix_data unix_data; + bool ok; + + touch_inode(inode); + + if (inode_get_unix_data(inode, &unix_data)) { + unix_data.mode &= ~(S_ISUID | S_ISGID); + ok = inode_set_unix_data(inode, &unix_data, UNIX_DATA_MODE); + /* + * This cannot fail because no memory allocation should have + * been required, as the UNIX data already exists. + */ + wimlib_assert(ok); + } /* Else, set[ug]id can't be set, so there's nothing to do. */ +} + /* * Create a new file in the staging directory for a read-write mounted image. * @@ -639,7 +691,7 @@ create_staging_file(const struct wimfs_context *ctx, char **name_ret) name[STAGING_FILE_NAME_LEN] = '\0'; retry: - randomize_char_array_with_alnum(name, STAGING_FILE_NAME_LEN); + get_random_alnum_chars(name, STAGING_FILE_NAME_LEN); fd = openat(ctx->staging_dir_fd, name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); if (unlikely(fd < 0)) { @@ -698,7 +750,7 @@ extract_blob_to_staging_dir(struct wim_inode *inode, filedes_init(&fd, staging_fd); errno = 0; extract_size = min(old_blob->size, size); - result = extract_blob_to_fd(old_blob, &fd, extract_size); + result = extract_blob_prefix_to_fd(old_blob, extract_size, &fd); } else { extract_size = 0; result = 0; @@ -768,6 +820,8 @@ extract_blob_to_staging_dir(struct wim_inode *inode, prepare_unhashed_blob(new_blob, inode, strm->stream_id, &wim_get_current_image_metadata(ctx->wim)->unhashed_blobs); inode_replace_stream_blob(inode, strm, new_blob, ctx->wim->blob_table); + if (size != blob_size(old_blob)) + file_contents_changed(inode); return 0; out_revert_fd_changes: @@ -823,7 +877,7 @@ make_staging_dir_at(int parent_dir_fd, const char *wim_basename, p = staging_dir_name; p = mempcpy(p, wim_basename, wim_basename_len); p = mempcpy(p, common_suffix, sizeof(common_suffix)); - randomize_char_array_with_alnum(p, random_suffix_len); + get_random_alnum_chars(p, random_suffix_len); p += random_suffix_len; *p = '\0'; @@ -921,28 +975,20 @@ delete_staging_dir(struct wimfs_context *ctx) close(ctx->parent_dir_fd); } -/* Number the inodes in the mounted image sequentially. */ static void -reassign_inode_numbers(struct wimfs_context *ctx) +prepare_inodes(struct wimfs_context *ctx) { struct wim_image_metadata *imd; struct wim_inode *inode; ctx->next_ino = 1; imd = wim_get_current_image_metadata(ctx->wim); - image_for_each_inode(inode, imd) + image_for_each_inode(inode, imd) { inode->i_ino = ctx->next_ino++; -} - -static void -release_extra_refcnts(struct wimfs_context *ctx) -{ - struct list_head *list = &ctx->orig_blob_list; - struct blob_table *blob_table = ctx->wim->blob_table; - struct blob_descriptor *blob, *tmp; - - list_for_each_entry_safe(blob, tmp, list, orig_blob_list) - blob_subtract_refcnt(blob, blob_table, blob->out_refcnt); + inode->i_num_opened_fds = 0; + inode->i_num_allocated_fds = 0; + inode->i_fds = NULL; + } } /* Delete the 'struct blob_descriptor' for any stream that was modified @@ -983,12 +1029,13 @@ inode_close_fds(struct wim_inode *inode) static void close_all_fds(struct wimfs_context *ctx) { - struct wim_inode *inode, *tmp; + struct wim_inode *inode; + struct hlist_node *tmp; struct wim_image_metadata *imd; imd = wim_get_current_image_metadata(ctx->wim); - list_for_each_entry_safe(inode, tmp, &imd->inode_list, i_list) + image_for_each_inode_safe(inode, tmp, imd) inode_close_fds(inode); } @@ -999,53 +1046,56 @@ static int renew_current_image(struct wimfs_context *ctx) { WIMStruct *wim = ctx->wim; - int idx = wim->current_image - 1; - struct wim_image_metadata *imd = wim->image_metadata[idx]; - struct wim_image_metadata *replace_imd; - struct blob_descriptor *new_blob; + int image = wim->current_image; + struct wim_image_metadata *imd; + struct wim_inode *inode; int ret; - /* Create 'replace_imd' structure to use for the reset original, - * unmodified image. */ ret = WIMLIB_ERR_NOMEM; - replace_imd = new_image_metadata(); - if (!replace_imd) + imd = new_unloaded_image_metadata(ctx->metadata_resource); + if (!imd) goto err; - /* Create new blob descriptor for the modified image's metadata - * resource, which doesn't exist yet. */ - ret = WIMLIB_ERR_NOMEM; - new_blob = new_blob_descriptor(); - if (!new_blob) - goto err_put_replace_imd; - - new_blob->refcnt = 1; - new_blob->unhashed = 1; - new_blob->is_metadata = 1; - - /* Make the image being moved available at a new index. Increments the - * WIM's image count, but does not increment the reference count of the - * 'struct image_metadata'. */ - ret = append_image_metadata(wim, imd); + ret = append_image_metadata(wim, wim->image_metadata[image - 1]); if (ret) - goto err_free_new_blob; + goto err_put_imd; - ret = xml_add_image(wim, ""); + ret = xml_export_image(wim->xml_info, image, + wim->xml_info, NULL, NULL, false); if (ret) goto err_undo_append; - replace_imd->metadata_blob = imd->metadata_blob; - imd->metadata_blob = new_blob; - wim->image_metadata[idx] = replace_imd; + wim->image_metadata[image - 1] = imd; wim->current_image = wim->hdr.image_count; + + ret = select_wim_image(wim, image); + if (ret) + goto err_undo_export; + + image_for_each_inode(inode, imd) { + for (unsigned i = 0; i < inode->i_num_streams; i++) { + struct blob_descriptor *blob; + + blob = stream_blob(&inode->i_streams[i], + wim->blob_table); + if (blob) + blob->refcnt += inode->i_nlink; + } + } + + select_wim_image(wim, wim->hdr.image_count); + ctx->metadata_resource = NULL; return 0; +err_undo_export: + xml_delete_image(wim->xml_info, wim->hdr.image_count); + wim->image_metadata[image - 1] = wim->image_metadata[wim->hdr.image_count - 1]; + wim->current_image = image; err_undo_append: wim->hdr.image_count--; -err_free_new_blob: - free_blob_descriptor(new_blob); -err_put_replace_imd: - put_image_metadata(replace_imd, NULL); +err_put_imd: + imd->metadata_blob = NULL; + put_image_metadata(imd); err: return ret; } @@ -1081,12 +1131,8 @@ commit_image(struct wimfs_context *ctx, int unmount_flags, mqd_t mq) int ret = renew_current_image(ctx); if (ret) return ret; - } else { - release_extra_refcnts(ctx); } - INIT_LIST_HEAD(&ctx->orig_blob_list); delete_empty_blobs(ctx); - xml_update_image_info(ctx->wim, ctx->wim->current_image); write_flags = 0; @@ -1174,8 +1220,66 @@ out: return ret; } +static void * +wimfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + /* + * Cache positive name lookups indefinitely, since names can only be + * added, removed, or modified through the mounted filesystem itself. + */ + cfg->entry_timeout = 1000000000; + + /* + * Cache negative name lookups indefinitely, since names can only be + * added, removed, or modified through the mounted filesystem itself. + */ + cfg->negative_timeout = 1000000000; + + /* + * Don't cache file/directory attributes. This is needed as a + * workaround for the fact that when caching attributes, the high level + * interface to libfuse considers a file which has several hard-linked + * names as several different files. (Otherwise, we could cache our + * file/directory attributes indefinitely, since they can only be + * changed through the mounted filesystem itself.) + */ + cfg->attr_timeout = 0; + + /* + * If an open file is unlinked, unlink it for real rather than renaming + * it to a hidden file. Our code supports this; an unlinked inode is + * retained until all its file descriptors have been closed. + */ + cfg->hard_remove = 1; + + /* + * Make FUSE use the inode numbers we provide. We want this, because we + * have inodes and will number them ourselves. + */ + cfg->use_ino = 1; + + /* + * Cache the contents of files. This will speed up repeated access to + * files on a mounted WIM image, since they won't need to be + * decompressed repeatedly. This option is valid because data in the + * WIM image should never be changed externally. (Although, if someone + * really wanted to they could modify the WIM file or mess with the + * staging directory; but then they're asking for trouble.) + */ + cfg->kernel_cache = 1; + + /* + * We keep track of file descriptor structures (struct wimfs_fd), so + * there is no need to have the file path provided on operations such as + * read(). + */ + cfg->nullpath_ok = 1; + + return wimfs_get_context(); +} + static int -wimfs_chmod(const char *path, mode_t mask) +wimfs_chmod(const char *path, mode_t mask, struct fuse_file_info *fi) { const struct wimfs_context *ctx = wimfs_get_context(); struct wim_inode *inode; @@ -1184,10 +1288,13 @@ wimfs_chmod(const char *path, mode_t mask) if (!(ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA)) return -EOPNOTSUPP; - inode = wim_pathname_to_inode(ctx->wim, path); - if (!inode) - return -errno; - + if (fi) { + inode = WIMFS_FD(fi)->f_inode; + } else { + inode = wim_pathname_to_inode(ctx->wim, path); + if (!inode) + return -errno; + } unix_data.uid = ctx->owner_uid; unix_data.gid = ctx->owner_gid; unix_data.mode = mask; @@ -1200,7 +1307,7 @@ wimfs_chmod(const char *path, mode_t mask) } static int -wimfs_chown(const char *path, uid_t uid, gid_t gid) +wimfs_chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi) { const struct wimfs_context *ctx = wimfs_get_context(); struct wim_inode *inode; @@ -1210,9 +1317,13 @@ wimfs_chown(const char *path, uid_t uid, gid_t gid) if (!(ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA)) return -EOPNOTSUPP; - inode = wim_pathname_to_inode(ctx->wim, path); - if (!inode) - return -errno; + if (fi) { + inode = WIMFS_FD(fi)->f_inode; + } else { + inode = wim_pathname_to_inode(ctx->wim, path); + if (!inode) + return -errno; + } which = 0; @@ -1238,38 +1349,32 @@ wimfs_chown(const char *path, uid_t uid, gid_t gid) } static int -wimfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) -{ - struct wimfs_fd *fd = WIMFS_FD(fi); - return inode_to_stbuf(fd->f_inode, fd->f_blob, stbuf); -} - -static int -wimfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) -{ - struct wimfs_fd *fd = WIMFS_FD(fi); - if (ftruncate(fd->f_staging_fd.fd, size)) - return -errno; - touch_inode(fd->f_inode); - fd->f_blob->size = size; - return 0; -} - -static int -wimfs_getattr(const char *path, struct stat *stbuf) +wimfs_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { const struct wimfs_context *ctx = wimfs_get_context(); - struct wim_dentry *dentry; - struct wim_inode_stream *strm; + const struct wim_inode *inode; + const struct blob_descriptor *blob; int ret; - ret = wim_pathname_to_stream(ctx, path, LOOKUP_FLAG_DIRECTORY_OK, - &dentry, &strm); - if (ret) - return ret; + if (fi) { + const struct wimfs_fd *fd = WIMFS_FD(fi); - return inode_to_stbuf(dentry->d_inode, - stream_blob_resolved(strm), stbuf); + inode = fd->f_inode; + blob = fd->f_blob; + } else { + struct wim_dentry *dentry; + struct wim_inode_stream *strm; + + ret = wim_pathname_to_stream(ctx, path, + LOOKUP_FLAG_DIRECTORY_OK, + &dentry, &strm); + if (ret) + return ret; + inode = dentry->d_inode; + blob = stream_blob_resolved(strm); + } + + return inode_to_stbuf(inode, blob, stbuf); } static int @@ -1288,9 +1393,9 @@ wimfs_getxattr(const char *path, const char *name, char *value, size_t size) { const struct wimfs_context *ctx = wimfs_get_context(); - struct wim_inode *inode; - struct wim_inode_stream *strm; - struct blob_descriptor *blob; + const struct wim_inode *inode; + const struct wim_inode_stream *strm; + const struct blob_descriptor *blob; if (!strncmp(name, "wimfs.", 6)) { /* Handle some magical extended attributes. These really should @@ -1363,7 +1468,7 @@ wimfs_getxattr(const char *path, const char *name, char *value, if (size < blob->size) return -ERANGE; - if (read_full_blob_into_buf(blob, value)) + if (read_blob_into_buf(blob, value)) return errno ? -errno : -EIO; } return blob->size; @@ -1412,7 +1517,6 @@ wimfs_listxattr(const char *path, char *list, size_t size) const struct wimfs_context *ctx = wimfs_get_context(); const struct wim_inode *inode; char *p = list; - char *end = list + size; int total_size = 0; if (!(ctx->mount_flags & WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_XATTR)) @@ -1448,7 +1552,7 @@ wimfs_listxattr(const char *path, char *list, size_t size) total_size += stream_name_mbs_nbytes + 6; if (size) { - if (end - p < stream_name_mbs_nbytes + 6) { + if (list + size - p < stream_name_mbs_nbytes + 6) { FREE(stream_name_mbs); return -ERANGE; } @@ -1468,8 +1572,7 @@ wimfs_mkdir(const char *path, mode_t mode) int ret; /* Note: according to fuse.h, mode may not include S_IFDIR */ - ret = create_file(fuse_get_context(), path, mode | S_IFDIR, 0, - FILE_ATTRIBUTE_DIRECTORY, &dentry); + ret = create_file(fuse_get_context(), path, mode | S_IFDIR, 0, &dentry); if (ret) return ret; touch_parent(dentry); @@ -1531,11 +1634,7 @@ wimfs_mknod(const char *path, mode_t mode, dev_t rdev) !(wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA)) return -EPERM; - /* Note: we still use FILE_ATTRIBUTE_NORMAL for device nodes, - * named pipes, and sockets. The real mode is in the UNIX - * metadata. */ - ret = create_file(fuse_ctx, path, mode, rdev, - FILE_ATTRIBUTE_NORMAL, &dentry); + ret = create_file(fuse_ctx, path, mode, rdev, &dentry); if (ret) return ret; touch_parent(dentry); @@ -1573,7 +1672,7 @@ wimfs_open(const char *path, struct fuse_file_info *fi) (!blob || blob->blob_location != BLOB_IN_STAGING_FILE)) { ret = extract_blob_to_staging_dir(inode, strm, - blob ? blob->size : 0, + blob_size(blob), ctx); if (ret) return ret; @@ -1588,12 +1687,17 @@ wimfs_open(const char *path, struct fuse_file_info *fi) int raw_fd; raw_fd = openat(blob->staging_dir_fd, blob->staging_file_name, - (fi->flags & O_ACCMODE) | O_NOFOLLOW); + (fi->flags & (O_ACCMODE | O_TRUNC)) | + O_NOFOLLOW); if (raw_fd < 0) { close_wimfs_fd(fd); return -errno; } filedes_init(&fd->f_staging_fd, raw_fd); + if (fi->flags & O_TRUNC) { + blob->size = 0; + file_contents_changed(inode); + } } fi->fh = (uintptr_t)fd; return 0; @@ -1613,7 +1717,7 @@ wimfs_opendir(const char *path, struct fuse_file_info *fi) return -errno; if (!inode_is_directory(inode)) return -ENOTDIR; - strm = inode_get_unnamed_stream(inode, STREAM_TYPE_DATA); + strm = inode_get_unnamed_data_stream(inode); if (!strm) return -ENOTDIR; ret = alloc_wimfs_fd(inode, strm, &fd); @@ -1646,13 +1750,13 @@ wimfs_read(const char *path, char *buf, size_t size, switch (blob->blob_location) { case BLOB_IN_WIM: - if (read_partial_wim_blob_into_buf(blob, size, offset, buf)) + if (read_partial_wim_blob_into_buf(blob, offset, size, buf)) ret = errno ? -errno : -EIO; else ret = size; break; case BLOB_IN_STAGING_FILE: - ret = raw_pread(&fd->f_staging_fd, buf, size, offset); + ret = pread(fd->f_staging_fd.fd, buf, size, offset); if (ret < 0) ret = -errno; break; @@ -1669,7 +1773,8 @@ wimfs_read(const char *path, char *buf, size_t size, static int wimfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, - off_t offset, struct fuse_file_info *fi) + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { struct wimfs_fd *fd = WIMFS_FD(fi); const struct wim_inode *inode; @@ -1678,26 +1783,23 @@ wimfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, inode = fd->f_inode; - ret = filler(buf, ".", NULL, 0); + ret = filler(buf, ".", NULL, 0, 0); if (ret) return ret; - ret = filler(buf, "..", NULL, 0); + ret = filler(buf, "..", NULL, 0, 0); if (ret) return ret; for_inode_child(child, inode) { - char *file_name_mbs; - size_t file_name_mbs_nbytes; + char *name; + size_t name_nbytes; - ret = utf16le_to_tstr(child->file_name, - child->file_name_nbytes, - &file_name_mbs, - &file_name_mbs_nbytes); - if (ret) + if (utf16le_to_tstr(child->d_name, child->d_name_nbytes, + &name, &name_nbytes)) return -errno; - ret = filler(buf, file_name_mbs, NULL, 0); - FREE(file_name_mbs); + ret = filler(buf, name, NULL, 0, 0); + FREE(name); if (ret) return ret; } @@ -1705,27 +1807,24 @@ wimfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, } static int -wimfs_readlink(const char *path, char *buf, size_t buf_len) +wimfs_readlink(const char *path, char *buf, size_t bufsize) { - WIMStruct *wim = wimfs_get_WIMStruct(); + struct wimfs_context *ctx = wimfs_get_context(); const struct wim_inode *inode; - ssize_t ret; + int ret; - inode = wim_pathname_to_inode(wim, path); + inode = wim_pathname_to_inode(ctx->wim, path); if (!inode) return -errno; - if (!inode_is_symlink(inode)) - return -EINVAL; - if (buf_len == 0) + if (bufsize <= 0) return -EINVAL; - ret = wim_inode_readlink(inode, buf, buf_len - 1, NULL); - if (ret >= 0) { - buf[ret] = '\0'; - ret = 0; - } else if (ret == -ENAMETOOLONG) { - buf[buf_len - 1] = '\0'; - } - return ret; + ret = wim_inode_readlink(inode, buf, bufsize - 1, NULL, + ctx->mountpoint_abspath, + ctx->mountpoint_abspath_nchars); + if (ret < 0) + return ret; + buf[ret] = '\0'; + return 0; } /* We use this for both release() and releasedir(), since in both cases we @@ -1768,10 +1867,13 @@ wimfs_removexattr(const char *path, const char *name) } static int -wimfs_rename(const char *from, const char *to) +wimfs_rename(const char *from, const char *to, unsigned int flags) { + if (flags & RENAME_EXCHANGE) + return -EINVAL; return rename_wim_path(wimfs_get_WIMStruct(), from, to, - WIMLIB_CASE_SENSITIVE, NULL); + WIMLIB_CASE_SENSITIVE, + (flags & RENAME_NOREPLACE), NULL); } static int @@ -1884,11 +1986,9 @@ wimfs_symlink(const char *to, const char *from) struct wim_dentry *dentry; int ret; - ret = create_file(fuse_ctx, from, S_IFLNK | 0777, 0, - FILE_ATTRIBUTE_REPARSE_POINT, &dentry); + ret = create_file(fuse_ctx, from, S_IFLNK | 0777, 0, &dentry); if (ret) return ret; - dentry->d_inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK; ret = wim_inode_set_symlink(dentry->d_inode, to, wimfs_ctx->wim->blob_table); if (ret) { @@ -1904,14 +2004,32 @@ wimfs_symlink(const char *to, const char *from) } static int -wimfs_truncate(const char *path, off_t size) +do_truncate(int staging_fd, off_t size, + struct wim_inode *inode, struct blob_descriptor *blob) +{ + if (ftruncate(staging_fd, size)) + return -errno; + file_contents_changed(inode); + blob->size = size; + return 0; +} + +static int +wimfs_truncate(const char *path, off_t size, struct fuse_file_info *fi) { const struct wimfs_context *ctx = wimfs_get_context(); struct wim_dentry *dentry; struct wim_inode_stream *strm; struct blob_descriptor *blob; int ret; - int fd; + int staging_fd; + + if (fi) { + struct wimfs_fd *fd = WIMFS_FD(fi); + + return do_truncate(fd->f_staging_fd.fd, size, fd->f_inode, + fd->f_blob); + } ret = wim_pathname_to_stream(ctx, path, 0, &dentry, &strm); if (ret) @@ -1928,16 +2046,14 @@ wimfs_truncate(const char *path, off_t size) } /* Truncate the staging file. */ - fd = openat(blob->staging_dir_fd, blob->staging_file_name, - O_WRONLY | O_NOFOLLOW); - if (fd < 0) - return -errno; - ret = ftruncate(fd, size); - if (close(fd) || ret) + staging_fd = openat(blob->staging_dir_fd, blob->staging_file_name, + O_WRONLY | O_NOFOLLOW); + if (staging_fd < 0) return -errno; - blob->size = size; - touch_inode(dentry->d_inode); - return 0; + ret = do_truncate(staging_fd, size, dentry->d_inode, blob); + if (close(staging_fd) && !ret) + ret = -errno; + return ret; } static int @@ -1962,21 +2078,24 @@ wimfs_unlink(const char *path) return 0; } -#ifdef HAVE_UTIMENSAT /* * Change the timestamp on a file dentry. * * Note that alternate data streams do not have their own timestamps. */ static int -wimfs_utimens(const char *path, const struct timespec tv[2]) +wimfs_utimens(const char *path, const struct timespec tv[2], + struct fuse_file_info *fi) { - WIMStruct *wim = wimfs_get_WIMStruct(); struct wim_inode *inode; - inode = wim_pathname_to_inode(wim, path); - if (!inode) - return -errno; + if (fi) { + inode = WIMFS_FD(fi)->f_inode; + } else { + inode = wim_pathname_to_inode(wimfs_get_WIMStruct(), path); + if (!inode) + return -errno; + } if (tv[0].tv_nsec != UTIME_OMIT) { if (tv[0].tv_nsec == UTIME_NOW) @@ -1992,22 +2111,6 @@ wimfs_utimens(const char *path, const struct timespec tv[2]) } return 0; } -#else /* HAVE_UTIMENSAT */ -static int -wimfs_utime(const char *path, struct utimbuf *times) -{ - WIMStruct *wim = wimfs_get_WIMStruct(); - struct wim_inode *inode; - - inode = wim_pathname_to_inode(wim, path); - if (!inode) - return -errno; - - inode->i_last_access_time = time_t_to_wim_timestamp(times->actime); - inode->i_last_write_time = time_t_to_wim_timestamp(times->modtime); - return 0; -} -#endif /* !HAVE_UTIMENSAT */ static int wimfs_write(const char *path, const char *buf, size_t size, @@ -2016,22 +2119,21 @@ wimfs_write(const char *path, const char *buf, size_t size, struct wimfs_fd *fd = WIMFS_FD(fi); ssize_t ret; - ret = raw_pwrite(&fd->f_staging_fd, buf, size, offset); + ret = pwrite(fd->f_staging_fd.fd, buf, size, offset); if (ret < 0) return -errno; if (offset + size > fd->f_blob->size) fd->f_blob->size = offset + size; - touch_inode(fd->f_inode); + file_contents_changed(fd->f_inode); return ret; } -static struct fuse_operations wimfs_operations = { +static const struct fuse_operations wimfs_operations = { + .init = wimfs_init, .chmod = wimfs_chmod, .chown = wimfs_chown, - .fgetattr = wimfs_fgetattr, - .ftruncate = wimfs_ftruncate, .getattr = wimfs_getattr, .getxattr = wimfs_getxattr, .link = wimfs_link, @@ -2052,23 +2154,9 @@ static struct fuse_operations wimfs_operations = { .symlink = wimfs_symlink, .truncate = wimfs_truncate, .unlink = wimfs_unlink, -#ifdef HAVE_UTIMENSAT .utimens = wimfs_utimens, -#else - .utime = wimfs_utime, -#endif .write = wimfs_write, - /* We keep track of file descriptor structures (struct wimfs_fd), so - * there is no need to have the file path provided on operations such as - * read(). */ -#if FUSE_MAJOR_VERSION > 2 || (FUSE_MAJOR_VERSION == 2 && FUSE_MINOR_VERSION >= 8) - .flag_nullpath_ok = 1, -#endif -#if FUSE_MAJOR_VERSION > 2 || (FUSE_MAJOR_VERSION == 2 && FUSE_MINOR_VERSION >= 9) - .flag_nopath = 1, - .flag_utime_omit_ok = 1, -#endif }; /* API function documented in wimlib.h */ @@ -2111,20 +2199,26 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, /* Get the metadata for the image to mount. */ imd = wim_get_current_image_metadata(wim); - if (imd->modified) { - /* To avoid complicating things, we don't support mounting - * images to which in-memory modifications have already been - * made. */ + /* To avoid complicating things, we don't support mounting images to + * which in-memory modifications have already been made. */ + if (is_image_dirty(imd)) { ERROR("Cannot mount a modified WIM image!"); return WIMLIB_ERR_INVALID_PARAM; } if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { + if (imd->refcnt > 1) + return WIMLIB_ERR_IMAGE_HAS_MULTIPLE_REFERENCES; ret = lock_wim_for_append(wim); if (ret) return ret; } + if (wim_has_solid_resources(wim)) { + WARNING("Mounting a WIM file containing solid-compressed data; " + "file access may be slow."); + } + /* If the user did not specify an interface for accessing named * data streams, use the default (extended attributes). */ if (!(mount_flags & (WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_NONE | @@ -2138,55 +2232,31 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, ctx.mount_flags = mount_flags; if (mount_flags & WIMLIB_MOUNT_FLAG_STREAM_INTERFACE_WINDOWS) ctx.default_lookup_flags = LOOKUP_FLAG_ADS_OK; - /* For read-write mount, create the staging directory. */ + + /* For read-write mounts, create the staging directory, save a reference + * to the image's metadata resource, and mark the image dirty. */ if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { ret = make_staging_dir(&ctx, staging_dir); if (ret) - goto out_unlock; + goto out; + ret = WIMLIB_ERR_NOMEM; + ctx.metadata_resource = clone_blob_descriptor( + imd->metadata_blob); + if (!ctx.metadata_resource) + goto out; + mark_image_dirty(imd); } ctx.owner_uid = getuid(); ctx.owner_gid = getgid(); - /* Add each blob referenced by files in the image to a list and - * preemptively double the number of references to each. This is done - * to allow implementing the WIMLIB_UNMOUNT_FLAG_NEW_IMAGE semantics. - */ - INIT_LIST_HEAD(&ctx.orig_blob_list); - if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { - unsigned i; - struct wim_inode *inode; - struct blob_descriptor *blob; - - image_for_each_inode(inode, imd) { - for (i = 0; i < inode->i_num_streams; i++) { - blob = stream_blob(&inode->i_streams[i], - wim->blob_table); - if (blob) - blob->out_refcnt = 0; - } - } - - image_for_each_inode(inode, imd) { - for (i = 0; i < inode->i_num_streams; i++) { - blob = stream_blob(&inode->i_streams[i], - wim->blob_table); - if (blob) { - if (blob->out_refcnt == 0) - list_add(&blob->orig_blob_list, - &ctx.orig_blob_list); - blob->out_refcnt += inode->i_nlink; - blob->refcnt += inode->i_nlink; - } - } - } - } - - /* Assign new inode numbers. */ - reassign_inode_numbers(&ctx); + /* Number the inodes in the mounted image sequentially and initialize + * the file descriptor arrays */ + prepare_inodes(&ctx); - /* If a read-write mount, mark the image as modified. */ - if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) - imd->modified = 1; + /* Save the absolute path to the mountpoint directory. */ + ctx.mountpoint_abspath = realpath(dir, NULL); + if (ctx.mountpoint_abspath) + ctx.mountpoint_abspath_nchars = strlen(ctx.mountpoint_abspath); /* Build the FUSE command line. */ @@ -2204,61 +2274,15 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, /* * Build the FUSE mount options: * - * use_ino - * FUSE will use the inode numbers we provide. We want this, - * because we have inodes and will number them ourselves. - * * subtype=wimfs * Name for our filesystem (main type is "fuse"). * - * hard_remove - * If an open file is unlinked, unlink it for real rather than - * renaming it to a hidden file. Our code supports this; an - * unlinked inode is retained until all its file descriptors have - * been closed. - * * default_permissions * FUSE will perform permission checking. Useful when * WIMLIB_MOUNT_FLAG_UNIX_DATA is provided and the WIM image * contains the UNIX permissions for each file. - * - * kernel_cache - * Cache the contents of files. This will speed up repeated access - * to files on a mounted WIM image, since they won't need to be - * decompressed repeatedly. This option is valid because data in - * the WIM image should never be changed externally. (Although, if - * someone really wanted to they could modify the WIM file or mess - * with the staging directory; but then they're asking for - * trouble.) - * - * entry_timeout=1000000000 - * Cache positive name lookups indefinitely, since names can only - * be added, removed, or modified through the mounted filesystem - * itself. - * - * negative_timeout=1000000000 - * Cache negative name lookups indefinitely, since names can only - * be added, removed, or modified through the mounted filesystem - * itself. - * - * attr_timeout=0 - * Don't cache file/directory attributes. This is needed as a - * workaround for the fact that when caching attributes, the high - * level interface to libfuse considers a file which has several - * hard-linked names as several different files. (Otherwise, we - * could cache our file/directory attributes indefinitely, since - * they can only be changed through the mounted filesystem itself.) */ - char optstring[256] = - "use_ino" - ",subtype=wimfs" - ",hard_remove" - ",default_permissions" - ",kernel_cache" - ",entry_timeout=1000000000" - ",negative_timeout=1000000000" - ",attr_timeout=0" - ; + char optstring[128] = "subtype=wimfs,default_permissions"; fuse_argv[fuse_argc++] = "-o"; fuse_argv[fuse_argc++] = optstring; if (!(mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)) @@ -2273,10 +2297,11 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, /* Cleanup and return. */ if (ret) ret = WIMLIB_ERR_FUSE; - release_extra_refcnts(&ctx); - if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) +out: + FREE(ctx.mountpoint_abspath); + free_blob_descriptor(ctx.metadata_resource); + if (ctx.staging_dir_name) delete_staging_dir(&ctx); -out_unlock: unlock_wim_for_append(wim); return ret; } @@ -2313,7 +2338,7 @@ generate_message_queue_name(char name[WIMFS_MQUEUE_NAME_LEN + 1]) { name[0] = '/'; memcpy(name + 1, "wimfs-", 6); - randomize_char_array_with_alnum(name + 7, WIMFS_MQUEUE_NAME_LEN - 7); + get_random_alnum_chars(name + 7, WIMFS_MQUEUE_NAME_LEN - 7); name[WIMFS_MQUEUE_NAME_LEN] = '\0'; } @@ -2396,9 +2421,9 @@ do_unmount_commit(const char *dir, int unmount_flags, wimlib_progress_func_t progfunc, void *progctx) { struct wimfs_unmount_info unmount_info; - mqd_t mq; + mqd_t mq = (mqd_t)-1; struct commit_progress_thread_args args; - pthread_t commit_progress_tid; + struct thread commit_progress_tid; int ret; memset(&unmount_info, 0, sizeof(unmount_info)); @@ -2417,11 +2442,8 @@ do_unmount_commit(const char *dir, int unmount_flags, args.mq = mq; args.progfunc = progfunc; args.progctx = progctx; - ret = pthread_create(&commit_progress_tid, NULL, - commit_progress_thread_proc, &args); - if (ret) { - errno = ret; - ERROR_WITH_ERRNO("Can't create thread"); + if (!thread_create(&commit_progress_tid, + commit_progress_thread_proc, &args)) { ret = WIMLIB_ERR_NOMEM; goto out_delete_mq; } @@ -2435,7 +2457,7 @@ do_unmount_commit(const char *dir, int unmount_flags, /* Terminate the progress thread. */ char empty[1]; mq_send(mq, empty, 0, 1); - pthread_join(commit_progress_tid, NULL); + thread_join(&commit_progress_tid); } out_delete_mq: if (progfunc) { @@ -2495,7 +2517,7 @@ wimlib_unmount_image_with_progress(const char *dir, int unmount_flags, int mount_flags; int ret; - ret = wimlib_global_init(WIMLIB_INIT_FLAG_ASSUME_UTF8); + ret = wimlib_global_init(0); if (ret) return ret; @@ -2526,7 +2548,7 @@ wimlib_unmount_image_with_progress(const char *dir, int unmount_flags, static int mount_unsupported_error(void) { -#if defined(__WIN32__) +#ifdef _WIN32 ERROR("Sorry-- Mounting WIM images is not supported on Windows!"); #else ERROR("wimlib was compiled with --without-fuse, which disables support "