X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fmount_image.c;h=3f1d272dceae0b5cce6478404c577b64018afbbe;hp=0e77169cea9b99abef19ab3dd9a36bd169ffac6d;hb=61e88c80f4b0262be1da706d2dbb05597b265cd1;hpb=85a067284a7e20133b0dd50b91f0d6bcc32c9a3e diff --git a/src/mount_image.c b/src/mount_image.c index 0e77169c..3f1d272d 100644 --- a/src/mount_image.c +++ b/src/mount_image.c @@ -1,29 +1,27 @@ /* * mount_image.c * - * This file implements mounting of WIM files using FUSE, which stands for - * Filesystem in Userspace. FUSE allows a filesystem to be implemented in a - * userspace process by implementing the filesystem primitives--- read(), - * write(), readdir(), etc. + * This file implements mounting of WIM images using FUSE + * (Filesystem in Userspace). See http://fuse.sourceforge.net/. + * + * Currently it is only expected to work on Linux. */ /* * Copyright (C) 2012, 2013, 2014 Eric Biggers * - * This file is part of wimlib, a library for working with WIM files. - * - * wimlib is free software; you can redistribute it and/or modify it under the - * terms of the GNU 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 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. * - * wimlib 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 General Public License for more + * 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 General Public License - * along with wimlib; if not, see http://www.gnu.org/licenses/. + * 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 @@ -31,6 +29,7 @@ #endif #include "wimlib.h" +#include "wimlib/error.h" #ifdef WITH_FUSE @@ -161,6 +160,10 @@ struct wimfs_context { /* Original list of single-instance streams in the mounted image, linked * by 'struct wim_lookup_table_entry'.orig_stream_list. */ struct list_head orig_stream_list; + + /* Parameters for unmounting the image (can be set via extended + * attribute "wimfs.unmount_info"). */ + struct wimfs_unmount_info unmount_info; }; #define WIMFS_CTX(fuse_ctx) ((struct wimfs_context*)(fuse_ctx)->private_data) @@ -374,31 +377,30 @@ wim_pathname_to_stream(const struct wimfs_context *ctx, const char *path, struct wim_dentry *dentry; struct wim_lookup_table_entry *lte; u16 stream_idx; - const tchar *stream_name = NULL; + const char *stream_name = NULL; struct wim_inode *inode; - tchar *p = NULL; + char *p = NULL; lookup_flags |= ctx->default_lookup_flags; if (lookup_flags & LOOKUP_FLAG_ADS_OK) { stream_name = path_stream_name(path); if (stream_name) { - p = (tchar*)stream_name - 1; - *p = T('\0'); + p = (char *)stream_name - 1; + *p = '\0'; } } dentry = get_dentry(wim, path, WIMLIB_CASE_SENSITIVE); if (p) - *p = T(':'); + *p = ':'; if (!dentry) return -errno; inode = dentry->d_inode; - if (!inode->i_resolved) - if (inode_resolve_streams(inode, wim->lookup_table, false)) - return -EIO; + if (inode_resolve_streams(inode, wim->lookup_table, false)) + return -EIO; if (!(lookup_flags & LOOKUP_FLAG_DIRECTORY_OK) && inode_is_directory(inode)) @@ -500,8 +502,7 @@ create_dentry(struct fuse_context *fuse_ctx, const char *path, dentry_add_child(parent, new_dentry); - if (dentry_ret) - *dentry_ret = new_dentry; + *dentry_ret = new_dentry; return 0; } @@ -939,7 +940,7 @@ out_restore_wim_filename: return ret; } -/* Deletes the staging directory, undoing the effects of a succesful call to +/* Deletes the staging directory, undoing the effects of a successful call to * make_staging_dir(). */ static void delete_staging_dir(struct wimfs_context *ctx) @@ -961,6 +962,7 @@ 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) { @@ -987,6 +989,8 @@ release_extra_refcnts(struct wimfs_context *ctx) } } +/* Delete the 'struct wim_lookup_table_entry' for any stream that was modified + * or created in the read-write mounted image and had a final size of 0. */ static void delete_empty_streams(struct wimfs_context *ctx) { @@ -1004,6 +1008,9 @@ delete_empty_streams(struct wimfs_context *ctx) } } +/* Close all file descriptors open to the specified inode. + * + * Note: closing the last file descriptor might free the inode. */ static void inode_close_fds(struct wim_inode *inode) { @@ -1016,6 +1023,7 @@ inode_close_fds(struct wim_inode *inode) } } +/* Close all file descriptors open to the mounted image. */ static void close_all_fds(struct wimfs_context *ctx) { @@ -1064,7 +1072,7 @@ renew_current_image(struct wimfs_context *ctx) if (ret) goto err_free_new_lte; - ret = xml_add_image(wim, T("")); + ret = xml_add_image(wim, ""); if (ret) goto err_undo_append; @@ -1123,60 +1131,89 @@ commit_image(struct wimfs_context *ctx, int unmount_flags, mqd_t mq) xml_update_image_info(ctx->wim, ctx->wim->current_image); write_flags = 0; + if (unmount_flags & WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY) write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY; + if (unmount_flags & WIMLIB_UNMOUNT_FLAG_REBUILD) write_flags |= WIMLIB_WRITE_FLAG_REBUILD; + if (unmount_flags & WIMLIB_UNMOUNT_FLAG_RECOMPRESS) write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS; + return wimlib_overwrite(ctx->wim, write_flags, 0); } +/* In the case of an allow_other mount, only the owner and root should be + * allowed to unmount the filesystem. */ +static bool +may_unmount_wimfs(void) +{ + const struct fuse_context *fuse_ctx = fuse_get_context(); + const struct wimfs_context *wimfs_ctx = WIMFS_CTX(fuse_ctx); + + return (fuse_ctx->uid == wimfs_ctx->owner_uid || + fuse_ctx->uid == 0); +} + +/* Unmount the mounted image, called from the daemon process. */ static int -unmount_wimfs(const struct wimfs_unmount_info *info) +unmount_wimfs(void) { struct fuse_context *fuse_ctx = fuse_get_context(); struct wimfs_context *wimfs_ctx = WIMFS_CTX(fuse_ctx); + const struct wimfs_unmount_info *info = &wimfs_ctx->unmount_info; int unmount_flags = info->unmount_flags; mqd_t mq = (mqd_t)-1; - int status; - - if (fuse_ctx->uid != wimfs_ctx->owner_uid && - fuse_ctx->uid != 0) - return -EPERM; - - if (info->mq_name[0]) { - mq = mq_open(info->mq_name, O_WRONLY | O_NONBLOCK); - if (mq == (mqd_t)-1) - return -errno; - } + int ret; /* Ignore COMMIT if the image is mounted read-only. */ if (!(wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)) unmount_flags &= ~WIMLIB_UNMOUNT_FLAG_COMMIT; + if (unmount_flags & WIMLIB_UNMOUNT_FLAG_SEND_PROGRESS) { + mq = mq_open(info->mq_name, O_WRONLY | O_NONBLOCK); + if (mq == (mqd_t)-1) { + ret = WIMLIB_ERR_MQUEUE; + goto out; + } + } + if (wimfs_ctx->num_open_fds) { + + /* There are still open file descriptors to the image. */ + + /* With COMMIT, refuse to unmount unless FORCE is also + * specified. */ if ((unmount_flags & (WIMLIB_UNMOUNT_FLAG_COMMIT | WIMLIB_UNMOUNT_FLAG_FORCE)) == WIMLIB_UNMOUNT_FLAG_COMMIT) { - status = WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY; - goto out_send_status; + ret = WIMLIB_ERR_MOUNTED_IMAGE_IS_BUSY; + goto out; } + + /* Force-close all file descriptors. */ close_all_fds(wimfs_ctx); } if (unmount_flags & WIMLIB_UNMOUNT_FLAG_COMMIT) - status = commit_image(wimfs_ctx, unmount_flags, mq); + ret = commit_image(wimfs_ctx, unmount_flags, mq); else - status = 0; - fuse_exit(fuse_ctx->fuse); -out_send_status: - if (mq != (mqd_t)-1) { - mq_send(mq, (const char *)&status, sizeof(int), 1); - mq_close(mq); + ret = 0; /* Read-only mount, or discarding changes to + a read-write mount */ + +out: + /* Leave the image mounted if commit failed, unless this is a + * forced unmount. The user can retry without COMMIT if they + * want. */ + if (!ret || (unmount_flags & WIMLIB_UNMOUNT_FLAG_FORCE)) { + unlock_wim_for_append(wimfs_ctx->wim); + fuse_exit(fuse_ctx->fuse); } - return 0; + if (mq != (mqd_t)-1) + mq_close(mq); + return ret; } static int @@ -1278,11 +1315,11 @@ wimfs_getattr(const char *path, struct stat *stbuf) static int copy_xattr(char *dest, size_t destsize, const void *src, size_t srcsize) { - if (!destsize) - return srcsize; - if (destsize < srcsize) - return -ERANGE; - memcpy(dest, src, srcsize); + if (destsize) { + if (destsize < srcsize) + return -ERANGE; + memcpy(dest, src, srcsize); + } return srcsize; } @@ -1290,8 +1327,7 @@ static int wimfs_getxattr(const char *path, const char *name, char *value, size_t size) { - struct fuse_context *fuse_ctx = fuse_get_context(); - const struct wimfs_context *ctx = WIMFS_CTX(fuse_ctx); + const struct wimfs_context *ctx = wimfs_get_context(); struct wim_inode *inode; struct wim_ads_entry *ads_entry; struct wim_lookup_table_entry *lte; @@ -1321,9 +1357,17 @@ wimfs_getxattr(const char *path, const char *name, char *value, &ctx->mount_flags, sizeof(int)); } if (!strcmp(name, "unmount")) { - struct wimfs_unmount_info info; - memset(&info, 0, sizeof(info)); - return unmount_wimfs(&info); + if (!may_unmount_wimfs()) + return -EPERM; + if (size) { + int status; + + if (size < sizeof(int)) + return -ERANGE; + status = unmount_wimfs(); + memcpy(value, &status, sizeof(int)); + } + return sizeof(int); } return -ENOATTR; } @@ -1353,11 +1397,11 @@ wimfs_getxattr(const char *path, const char *name, char *value, return -EFBIG; if (size) { - if (lte->size > size) + if (size < lte->size) return -ERANGE; if (read_full_stream_into_buf(lte, value)) - return -errno; + return errno ? -errno : -EIO; } return lte->size; } @@ -1632,7 +1676,7 @@ wimfs_read(const char *path, char *buf, size_t size, switch (lte->resource_location) { case RESOURCE_IN_WIM: if (read_partial_wim_stream_into_buf(lte, size, offset, buf)) - ret = -errno; + ret = errno ? -errno : -EIO; else ret = size; break; @@ -1788,12 +1832,17 @@ wimfs_setxattr(const char *path, const char *name, if (!strncmp(name, "wimfs.", 6)) { /* Handle some magical extended attributes. These really should * be ioctls, but directory ioctls aren't supported until - * libfuse 2.9, and even then they are broken. */ + * libfuse 2.9, and even then they are broken. [Fixed by + * libfuse commit e3b7d4c278a26520be63d99d6ea84b26906fe73d] */ name += 6; - if (!strcmp(name, "unmount")) { + if (!strcmp(name, "unmount_info")) { + if (!may_unmount_wimfs()) + return -EPERM; if (size < sizeof(struct wimfs_unmount_info)) return -EINVAL; - return unmount_wimfs((const void *)value); + memcpy(&ctx->unmount_info, value, + sizeof(struct wimfs_unmount_info)); + return 0; } return -ENOATTR; } @@ -2049,7 +2098,9 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, /* For read-write mount, check for write access to the WIM. */ if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { - ret = can_delete_from_wim(wim); + if (!wim->filename) + return WIMLIB_ERR_NO_FILENAME; + ret = can_modify_wim(wim); if (ret) return ret; } @@ -2070,9 +2121,11 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, return WIMLIB_ERR_INVALID_PARAM; } - ret = lock_wim_for_append(wim, wim->in_fd.fd); - if (ret) - return ret; + if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { + ret = lock_wim_for_append(wim); + if (ret) + return ret; + } /* If the user did not specify an interface for accessing named * data streams, use the default (extended attributes). */ @@ -2097,9 +2150,9 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, ctx.owner_gid = getgid(); /* Add each stream referenced by files in the image to a list and - * preemptively double the number of references to each. The latter is - * done to allow implementing the WIMLIB_UNMOUNT_FLAG_NEW_IMAGE - * semantics. */ + * 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_stream_list); if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) { unsigned i; @@ -2201,7 +2254,6 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, char optstring[256] = "use_ino" ",subtype=wimfs" - ",attr_timeout=0" ",hard_remove" ",default_permissions" ",kernel_cache" @@ -2227,7 +2279,7 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) delete_staging_dir(&ctx); out_unlock: - unlock_wim_for_append(wim, wim->in_fd.fd); + unlock_wim_for_append(wim); return ret; } @@ -2235,7 +2287,6 @@ struct commit_progress_thread_args { mqd_t mq; wimlib_progress_func_t progfunc; void *progctx; - int status; }; static void * @@ -2245,23 +2296,16 @@ commit_progress_thread_proc(void *_args) struct commit_progress_report report; ssize_t ret; - args->status = WIMLIB_ERR_NOT_A_MOUNTPOINT; for (;;) { ret = mq_receive(args->mq, (char *)&report, sizeof(report), NULL); - if (ret < 0) { - if (errno == EINTR) - continue; - break; - } - if (ret == sizeof(int)) { - args->status = *(int *)&report; - break; + if (ret == sizeof(report)) { + call_progress(args->progfunc, report.msg, + &report.info, args->progctx); + } else { + if (ret == 0 || (ret < 0 && errno != EINTR)) + break; } - if (ret < sizeof(report)) - continue; - call_progress(args->progfunc, report.msg, - &report.info, args->progctx); } return NULL; } @@ -2276,27 +2320,27 @@ generate_message_queue_name(char name[WIMFS_MQUEUE_NAME_LEN + 1]) } static mqd_t -create_message_queue(const char *name, bool have_progfunc) +create_message_queue(const char *name) { - bool am_root = (getuid() == 0); - mode_t umask_save = 0; - mode_t mode = 0600; + bool am_root; + mode_t umask_save; + mode_t mode; struct mq_attr attr; mqd_t mq; memset(&attr, 0, sizeof(attr)); attr.mq_maxmsg = 8; - if (have_progfunc) - attr.mq_msgsize = sizeof(struct commit_progress_report); - else - attr.mq_msgsize = sizeof(int); + attr.mq_msgsize = sizeof(struct commit_progress_report); + am_root = (geteuid() == 0); if (am_root) { /* Filesystem mounted as normal user with --allow-other should * be able to send messages to root user, if they're doing the * unmount. */ umask_save = umask(0); mode = 0666; + } else { + mode = 0600; } mq = mq_open(name, O_RDWR | O_CREAT | O_EXCL, mode, &attr); if (am_root) @@ -2304,6 +2348,50 @@ create_message_queue(const char *name, bool have_progfunc) return mq; } +/* Unmount a read-only or read-write mounted WIM image. */ +static int +do_unmount(const char *dir) +{ + int status; + ssize_t len; + + len = getxattr(dir, "wimfs.unmount", &status, sizeof(int)); + if (len == sizeof(int)) + return status; + else if (len < 0 && (errno == EACCES || errno == EPERM)) + return WIMLIB_ERR_NOT_PERMITTED_TO_UNMOUNT; + else + return WIMLIB_ERR_NOT_A_MOUNTPOINT; +} + +static int +set_unmount_info(const char *dir, const struct wimfs_unmount_info *unmount_info) +{ + if (!setxattr(dir, "wimfs.unmount_info", + unmount_info, sizeof(struct wimfs_unmount_info), 0)) + return 0; + else if (errno == EROFS) + return 0; + else if (errno == EACCES || errno == EPERM) + return WIMLIB_ERR_NOT_PERMITTED_TO_UNMOUNT; + else + return WIMLIB_ERR_NOT_A_MOUNTPOINT; +} + +static int +do_unmount_discard(const char *dir) +{ + int ret; + struct wimfs_unmount_info unmount_info; + + memset(&unmount_info, 0, sizeof(unmount_info)); + + ret = set_unmount_info(dir, &unmount_info); + if (ret) + return ret; + return do_unmount(dir); +} + /* Unmount a read-write mounted WIM image, committing the changes. */ static int do_unmount_commit(const char *dir, int unmount_flags, @@ -2317,17 +2405,17 @@ do_unmount_commit(const char *dir, int unmount_flags, memset(&unmount_info, 0, sizeof(unmount_info)); unmount_info.unmount_flags = unmount_flags; - generate_message_queue_name(unmount_info.mq_name); - mq = create_message_queue(unmount_info.mq_name, progfunc != NULL); - if (mq == (mqd_t)-1) { - ERROR_WITH_ERRNO("Can't create POSIX message queue"); - return WIMLIB_ERR_MQUEUE; - } - - /* The current thread will be stuck in setxattr() until the image is + /* The current thread will be stuck in getxattr() until the image is * committed. Create a thread to handle the progress messages. */ if (progfunc) { + generate_message_queue_name(unmount_info.mq_name); + + mq = create_message_queue(unmount_info.mq_name); + if (mq == (mqd_t)-1) { + ERROR_WITH_ERRNO("Can't create POSIX message queue"); + return WIMLIB_ERR_MQUEUE; + } args.mq = mq; args.progfunc = progfunc; args.progctx = progctx; @@ -2342,57 +2430,23 @@ do_unmount_commit(const char *dir, int unmount_flags, unmount_info.unmount_flags |= WIMLIB_UNMOUNT_FLAG_SEND_PROGRESS; } - if (!setxattr(dir, "wimfs.unmount", - (const char *)&unmount_info, sizeof(unmount_info), 0)) - ret = 0; - else if (errno == EACCES || errno == EPERM) - ret = WIMLIB_ERR_NOT_PERMITTED_TO_UNMOUNT; - else - ret = WIMLIB_ERR_NOT_A_MOUNTPOINT; - + ret = set_unmount_info(dir, &unmount_info); + if (!ret) + ret = do_unmount(dir); if (progfunc) { - /* Terminate the progress thread and retrieve final unmount - * status. */ - - int tmp = -1; - mq_send(mq, (const char *)&tmp, sizeof(int), 1); - + /* Terminate the progress thread. */ + char empty[1]; + mq_send(mq, empty, 0, 1); pthread_join(commit_progress_tid, NULL); - if (!ret && args.status != -1) - ret = args.status; - } else if (!ret) { - /* Retrieve the final unmount status. */ - - int tmp = -1; - int len; - - mq_send(mq, (const char *)&tmp, sizeof(int), 1); - len = mq_receive(mq, (char *)&tmp, sizeof(int), NULL); - - if (len == 4 && tmp != -1) - ret = tmp; - else - ret = WIMLIB_ERR_NOT_A_MOUNTPOINT; } out_delete_mq: - mq_close(mq); - mq_unlink(unmount_info.mq_name); + if (progfunc) { + mq_close(mq); + mq_unlink(unmount_info.mq_name); + } return ret; } -/* Unmount a read-only or read-write mounted WIM image, discarding any changes. - */ -static int -do_unmount_discard(const char *dir) -{ - if (!getxattr(dir, "wimfs.unmount", NULL, 0)) - return 0; - else if (errno == EACCES || errno == EPERM) - return WIMLIB_ERR_NOT_PERMITTED_TO_UNMOUNT; - else - return WIMLIB_ERR_NOT_A_MOUNTPOINT; -} - static int begin_unmount(const char *dir, int unmount_flags, int *mount_flags_ret, wimlib_progress_func_t progfunc, void *progctx) @@ -2443,7 +2497,9 @@ wimlib_unmount_image_with_progress(const char *dir, int unmount_flags, int mount_flags; int ret; - wimlib_global_init(WIMLIB_INIT_FLAG_ASSUME_UTF8); + ret = wimlib_global_init(WIMLIB_INIT_FLAG_ASSUME_UTF8); + if (ret) + return ret; if (unmount_flags & ~(WIMLIB_UNMOUNT_FLAG_CHECK_INTEGRITY | WIMLIB_UNMOUNT_FLAG_COMMIT |