From d8a5ba45457e4a22aa39c939121efd7bb6c76672 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:26 -0700 Subject: [PATCH] FUSE - core This patch adds FUSE core. This contains the following files: o inode.c - superblock operations (alloc_inode, destroy_inode, read_inode, clear_inode, put_super, show_options) - registers FUSE filesystem o fuse_i.h - private header file Requirements ============ The most important difference between orinary filesystems and FUSE is the fact, that the filesystem data/metadata is provided by a userspace process run with the privileges of the mount "owner" instead of the kernel, or some remote entity usually running with elevated privileges. The security implication of this is that a non-privileged user must not be able to use this capability to compromise the system. Obvious requirements arising from this are: - mount owner should not be able to get elevated privileges with the help of the mounted filesystem - mount owner should not be able to induce undesired behavior in other users' or the super user's processes - mount owner should not get illegitimate access to information from other users' and the super user's processes These are currently ensured with the following constraints: 1) mount is only allowed to directory or file which the mount owner can modify without limitation (write access + no sticky bit for directories) 2) nosuid,nodev mount options are forced 3) any process running with fsuid different from the owner is denied all access to the filesystem 1) and 2) are ensured by the "fusermount" mount utility which is a setuid root application doing the actual mount operation. 3) is ensured by a check in the permission() method in kernel I started thinking about doing 3) in a different way because Christoph H. made a big deal out of it, saying that FUSE is unacceptable into mainline in this form. The suggested use of private namespaces would be OK, but in their current form have many limitations that make their use impractical (as discussed in this thread). Suggested improvements that would address these limitations: - implement shared subtrees - allow a process to join an existing namespace (make namespaces first-class objects) - implement the namespace creation/joining in a PAM module With all that in place the check of owner against current->fsuid may be removed from the FUSE kernel module, without compromising the security requirements. Suid programs still interesting questions, since they get access even to the private namespace causing some information leak (exact order/timing of filesystem operations performed), giving some ptrace-like capabilities to unprivileged users. BTW this problem is not strictly limited to the namespace approach, since suid programs setting fsuid and accessing users' files will succeed with the current approach too. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/inode.c | 428 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 fs/fuse/inode.c (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c new file mode 100644 index 00000000000..ea6339c2b6a --- /dev/null +++ b/fs/fuse/inode.c @@ -0,0 +1,428 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2005 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include "fuse_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Miklos Szeredi "); +MODULE_DESCRIPTION("Filesystem in Userspace"); +MODULE_LICENSE("GPL"); + +spinlock_t fuse_lock; +static kmem_cache_t *fuse_inode_cachep; +static int mount_count; + +static int mount_max = 1000; +module_param(mount_max, int, 0644); +MODULE_PARM_DESC(mount_max, "Maximum number of FUSE mounts allowed, if -1 then unlimited (default: 1000)"); + +#define FUSE_SUPER_MAGIC 0x65735546 + +struct fuse_mount_data { + int fd; + unsigned rootmode; + unsigned user_id; +}; + +static struct inode *fuse_alloc_inode(struct super_block *sb) +{ + struct inode *inode; + struct fuse_inode *fi; + + inode = kmem_cache_alloc(fuse_inode_cachep, SLAB_KERNEL); + if (!inode) + return NULL; + + fi = get_fuse_inode(inode); + fi->i_time = jiffies - 1; + fi->nodeid = 0; + + return inode; +} + +static void fuse_destroy_inode(struct inode *inode) +{ + kmem_cache_free(fuse_inode_cachep, inode); +} + +static void fuse_read_inode(struct inode *inode) +{ + /* No op */ +} + +static void fuse_clear_inode(struct inode *inode) +{ +} + +void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) +{ + if (S_ISREG(inode->i_mode) && i_size_read(inode) != attr->size) + invalidate_inode_pages(inode->i_mapping); + + inode->i_ino = attr->ino; + inode->i_mode = (inode->i_mode & S_IFMT) + (attr->mode & 07777); + inode->i_nlink = attr->nlink; + inode->i_uid = attr->uid; + inode->i_gid = attr->gid; + i_size_write(inode, attr->size); + inode->i_blksize = PAGE_CACHE_SIZE; + inode->i_blocks = attr->blocks; + inode->i_atime.tv_sec = attr->atime; + inode->i_atime.tv_nsec = attr->atimensec; + inode->i_mtime.tv_sec = attr->mtime; + inode->i_mtime.tv_nsec = attr->mtimensec; + inode->i_ctime.tv_sec = attr->ctime; + inode->i_ctime.tv_nsec = attr->ctimensec; +} + +static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) +{ + inode->i_mode = attr->mode & S_IFMT; + i_size_write(inode, attr->size); +} + +static int fuse_inode_eq(struct inode *inode, void *_nodeidp) +{ + unsigned long nodeid = *(unsigned long *) _nodeidp; + if (get_node_id(inode) == nodeid) + return 1; + else + return 0; +} + +static int fuse_inode_set(struct inode *inode, void *_nodeidp) +{ + unsigned long nodeid = *(unsigned long *) _nodeidp; + get_fuse_inode(inode)->nodeid = nodeid; + return 0; +} + +struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, + int generation, struct fuse_attr *attr, int version) +{ + struct inode *inode; + struct fuse_conn *fc = get_fuse_conn_super(sb); + int retried = 0; + + retry: + inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid); + if (!inode) + return NULL; + + if ((inode->i_state & I_NEW)) { + inode->i_generation = generation; + inode->i_data.backing_dev_info = &fc->bdi; + fuse_init_inode(inode, attr); + unlock_new_inode(inode); + } else if ((inode->i_mode ^ attr->mode) & S_IFMT) { + BUG_ON(retried); + /* Inode has changed type, any I/O on the old should fail */ + make_bad_inode(inode); + iput(inode); + retried = 1; + goto retry; + } + + fuse_change_attributes(inode, attr); + inode->i_version = version; + return inode; +} + +static void fuse_put_super(struct super_block *sb) +{ + struct fuse_conn *fc = get_fuse_conn_super(sb); + + spin_lock(&fuse_lock); + mount_count --; + fc->sb = NULL; + fc->user_id = 0; + fuse_release_conn(fc); + *get_fuse_conn_super_p(sb) = NULL; + spin_unlock(&fuse_lock); +} + +enum { + OPT_FD, + OPT_ROOTMODE, + OPT_USER_ID, + OPT_DEFAULT_PERMISSIONS, + OPT_ALLOW_OTHER, + OPT_ALLOW_ROOT, + OPT_KERNEL_CACHE, + OPT_ERR +}; + +static match_table_t tokens = { + {OPT_FD, "fd=%u"}, + {OPT_ROOTMODE, "rootmode=%o"}, + {OPT_USER_ID, "user_id=%u"}, + {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, + {OPT_ALLOW_OTHER, "allow_other"}, + {OPT_ALLOW_ROOT, "allow_root"}, + {OPT_KERNEL_CACHE, "kernel_cache"}, + {OPT_ERR, NULL} +}; + +static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) +{ + char *p; + memset(d, 0, sizeof(struct fuse_mount_data)); + d->fd = -1; + + while ((p = strsep(&opt, ",")) != NULL) { + int token; + int value; + substring_t args[MAX_OPT_ARGS]; + if (!*p) + continue; + + token = match_token(p, tokens, args); + switch (token) { + case OPT_FD: + if (match_int(&args[0], &value)) + return 0; + d->fd = value; + break; + + case OPT_ROOTMODE: + if (match_octal(&args[0], &value)) + return 0; + d->rootmode = value; + break; + + case OPT_USER_ID: + if (match_int(&args[0], &value)) + return 0; + d->user_id = value; + break; + + default: + return 0; + } + } + if (d->fd == -1) + return 0; + + return 1; +} + +static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) +{ + struct fuse_conn *fc = get_fuse_conn_super(mnt->mnt_sb); + + seq_printf(m, ",user_id=%u", fc->user_id); + return 0; +} + +void fuse_release_conn(struct fuse_conn *fc) +{ + kfree(fc); +} + +static struct fuse_conn *new_conn(void) +{ + struct fuse_conn *fc; + + fc = kmalloc(sizeof(*fc), GFP_KERNEL); + if (fc != NULL) { + memset(fc, 0, sizeof(*fc)); + fc->sb = NULL; + fc->user_id = 0; + fc->bdi.ra_pages = (VM_MAX_READAHEAD * 1024) / PAGE_CACHE_SIZE; + fc->bdi.unplug_io_fn = default_unplug_io_fn; + } + return fc; +} + +static struct fuse_conn *get_conn(struct file *file, struct super_block *sb) +{ + struct fuse_conn *fc; + + fc = new_conn(); + if (fc == NULL) + return NULL; + spin_lock(&fuse_lock); + fc->sb = sb; + spin_unlock(&fuse_lock); + return fc; +} + +static struct inode *get_root_inode(struct super_block *sb, unsigned mode) +{ + struct fuse_attr attr; + memset(&attr, 0, sizeof(attr)); + + attr.mode = mode; + attr.ino = FUSE_ROOT_ID; + return fuse_iget(sb, 1, 0, &attr, 0); +} + +static struct super_operations fuse_super_operations = { + .alloc_inode = fuse_alloc_inode, + .destroy_inode = fuse_destroy_inode, + .read_inode = fuse_read_inode, + .clear_inode = fuse_clear_inode, + .put_super = fuse_put_super, + .show_options = fuse_show_options, +}; + +static int inc_mount_count(void) +{ + int success = 0; + spin_lock(&fuse_lock); + mount_count ++; + if (mount_max == -1 || mount_count <= mount_max) + success = 1; + spin_unlock(&fuse_lock); + return success; +} + +static int fuse_fill_super(struct super_block *sb, void *data, int silent) +{ + struct fuse_conn *fc; + struct inode *root; + struct fuse_mount_data d; + struct file *file; + int err; + + if (!parse_fuse_opt((char *) data, &d)) + return -EINVAL; + + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = FUSE_SUPER_MAGIC; + sb->s_op = &fuse_super_operations; + sb->s_maxbytes = MAX_LFS_FILESIZE; + + file = fget(d.fd); + if (!file) + return -EINVAL; + + fc = get_conn(file, sb); + fput(file); + if (fc == NULL) + return -EINVAL; + + fc->user_id = d.user_id; + + *get_fuse_conn_super_p(sb) = fc; + + err = -ENFILE; + if (!inc_mount_count() && current->uid != 0) + goto err; + + err = -ENOMEM; + root = get_root_inode(sb, d.rootmode); + if (root == NULL) + goto err; + + sb->s_root = d_alloc_root(root); + if (!sb->s_root) { + iput(root); + goto err; + } + return 0; + + err: + spin_lock(&fuse_lock); + mount_count --; + fc->sb = NULL; + fuse_release_conn(fc); + spin_unlock(&fuse_lock); + *get_fuse_conn_super_p(sb) = NULL; + return err; +} + +static struct super_block *fuse_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *raw_data) +{ + return get_sb_nodev(fs_type, flags, raw_data, fuse_fill_super); +} + +static struct file_system_type fuse_fs_type = { + .owner = THIS_MODULE, + .name = "fuse", + .get_sb = fuse_get_sb, + .kill_sb = kill_anon_super, +}; + +static void fuse_inode_init_once(void *foo, kmem_cache_t *cachep, + unsigned long flags) +{ + struct inode * inode = foo; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) + inode_init_once(inode); +} + +static int __init fuse_fs_init(void) +{ + int err; + + err = register_filesystem(&fuse_fs_type); + if (err) + printk("fuse: failed to register filesystem\n"); + else { + fuse_inode_cachep = kmem_cache_create("fuse_inode", + sizeof(struct fuse_inode), + 0, SLAB_HWCACHE_ALIGN, + fuse_inode_init_once, NULL); + if (!fuse_inode_cachep) { + unregister_filesystem(&fuse_fs_type); + err = -ENOMEM; + } + } + + return err; +} + +static void fuse_fs_cleanup(void) +{ + unregister_filesystem(&fuse_fs_type); + kmem_cache_destroy(fuse_inode_cachep); +} + +static int __init fuse_init(void) +{ + int res; + + printk("fuse init (API version %i.%i)\n", + FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION); + + spin_lock_init(&fuse_lock); + res = fuse_fs_init(); + if (res) + goto err; + + return 0; + + err: + return res; +} + +static void __exit fuse_exit(void) +{ + printk(KERN_DEBUG "fuse exit\n"); + + fuse_fs_cleanup(); +} + +module_init(fuse_init); +module_exit(fuse_exit); -- cgit v1.2.3-70-g09d2 From 334f485df85ac7736ebe14940bf0a059c5f26d7d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:27 -0700 Subject: [PATCH] FUSE - device functions This adds the FUSE device handling functions. This contains the following files: o dev.c - fuse device operations (read, write, release, poll) - registers misc device - support for sending requests to userspace Signed-off-by: Miklos Szeredi Signed-off-by: Adrian Bunk Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/filesystems/fuse.txt | 341 ++++++++++++++ fs/fuse/Makefile | 2 +- fs/fuse/dev.c | 884 +++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 223 ++++++++++ fs/fuse/inode.c | 58 ++- include/linux/fuse.h | 36 +- 6 files changed, 1537 insertions(+), 7 deletions(-) create mode 100644 Documentation/filesystems/fuse.txt create mode 100644 fs/fuse/dev.c (limited to 'fs/fuse/inode.c') diff --git a/Documentation/filesystems/fuse.txt b/Documentation/filesystems/fuse.txt new file mode 100644 index 00000000000..83f96cf5696 --- /dev/null +++ b/Documentation/filesystems/fuse.txt @@ -0,0 +1,341 @@ +Definitions +~~~~~~~~~~~ + +Userspace filesystem: + + A filesystem in which data and metadata are provided by an ordinary + userspace process. The filesystem can be accessed normally through + the kernel interface. + +Filesystem daemon: + + The process(es) providing the data and metadata of the filesystem. + +Non-privileged mount (or user mount): + + A userspace filesystem mounted by a non-privileged (non-root) user. + The filesystem daemon is running with the privileges of the mounting + user. NOTE: this is not the same as mounts allowed with the "user" + option in /etc/fstab, which is not discussed here. + +Mount owner: + + The user who does the mounting. + +User: + + The user who is performing filesystem operations. + +What is FUSE? +~~~~~~~~~~~~~ + +FUSE is a userspace filesystem framework. It consists of a kernel +module (fuse.ko), a userspace library (libfuse.*) and a mount utility +(fusermount). + +One of the most important features of FUSE is allowing secure, +non-privileged mounts. This opens up new possibilities for the use of +filesystems. A good example is sshfs: a secure network filesystem +using the sftp protocol. + +The userspace library and utilities are available from the FUSE +homepage: + + http://fuse.sourceforge.net/ + +Mount options +~~~~~~~~~~~~~ + +'fd=N' + + The file descriptor to use for communication between the userspace + filesystem and the kernel. The file descriptor must have been + obtained by opening the FUSE device ('/dev/fuse'). + +'rootmode=M' + + The file mode of the filesystem's root in octal representation. + +'user_id=N' + + The numeric user id of the mount owner. + +'group_id=N' + + The numeric group id of the mount owner. + +'default_permissions' + + By default FUSE doesn't check file access permissions, the + filesystem is free to implement it's access policy or leave it to + the underlying file access mechanism (e.g. in case of network + filesystems). This option enables permission checking, restricting + access based on file mode. This is option is usually useful + together with the 'allow_other' mount option. + +'allow_other' + + This option overrides the security measure restricting file access + to the user mounting the filesystem. This option is by default only + allowed to root, but this restriction can be removed with a + (userspace) configuration option. + +'kernel_cache' + + This option disables flushing the cache of the file contents on + every open(). This should only be enabled on filesystems, where the + file data is never changed externally (not through the mounted FUSE + filesystem). Thus it is not suitable for network filesystems and + other "intermediate" filesystems. + + NOTE: if this option is not specified (and neither 'direct_io') data + is still cached after the open(), so a read() system call will not + always initiate a read operation. + +'direct_io' + + This option disables the use of page cache (file content cache) in + the kernel for this filesystem. This has several affects: + + - Each read() or write() system call will initiate one or more + read or write operations, data will not be cached in the + kernel. + + - The return value of the read() and write() system calls will + correspond to the return values of the read and write + operations. This is useful for example if the file size is not + known in advance (before reading it). + +'max_read=N' + + With this option the maximum size of read operations can be set. + The default is infinite. Note that the size of read requests is + limited anyway to 32 pages (which is 128kbyte on i386). + +How do non-privileged mounts work? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the mount() system call is a privileged operation, a helper +program (fusermount) is needed, which is installed setuid root. + +The implication of providing non-privileged mounts is that the mount +owner must not be able to use this capability to compromise the +system. Obvious requirements arising from this are: + + A) mount owner should not be able to get elevated privileges with the + help of the mounted filesystem + + B) mount owner should not get illegitimate access to information from + other users' and the super user's processes + + C) mount owner should not be able to induce undesired behavior in + other users' or the super user's processes + +How are requirements fulfilled? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A) The mount owner could gain elevated privileges by either: + + 1) creating a filesystem containing a device file, then opening + this device + + 2) creating a filesystem containing a suid or sgid application, + then executing this application + + The solution is not to allow opening device files and ignore + setuid and setgid bits when executing programs. To ensure this + fusermount always adds "nosuid" and "nodev" to the mount options + for non-privileged mounts. + + B) If another user is accessing files or directories in the + filesystem, the filesystem daemon serving requests can record the + exact sequence and timing of operations performed. This + information is otherwise inaccessible to the mount owner, so this + counts as an information leak. + + The solution to this problem will be presented in point 2) of C). + + C) There are several ways in which the mount owner can induce + undesired behavior in other users' processes, such as: + + 1) mounting a filesystem over a file or directory which the mount + owner could otherwise not be able to modify (or could only + make limited modifications). + + This is solved in fusermount, by checking the access + permissions on the mountpoint and only allowing the mount if + the mount owner can do unlimited modification (has write + access to the mountpoint, and mountpoint is not a "sticky" + directory) + + 2) Even if 1) is solved the mount owner can change the behavior + of other users' processes. + + i) It can slow down or indefinitely delay the execution of a + filesystem operation creating a DoS against the user or the + whole system. For example a suid application locking a + system file, and then accessing a file on the mount owner's + filesystem could be stopped, and thus causing the system + file to be locked forever. + + ii) It can present files or directories of unlimited length, or + directory structures of unlimited depth, possibly causing a + system process to eat up diskspace, memory or other + resources, again causing DoS. + + The solution to this as well as B) is not to allow processes + to access the filesystem, which could otherwise not be + monitored or manipulated by the mount owner. Since if the + mount owner can ptrace a process, it can do all of the above + without using a FUSE mount, the same criteria as used in + ptrace can be used to check if a process is allowed to access + the filesystem or not. + + Note that the ptrace check is not strictly necessary to + prevent B/2/i, it is enough to check if mount owner has enough + privilege to send signal to the process accessing the + filesystem, since SIGSTOP can be used to get a similar effect. + +I think these limitations are unacceptable? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a sysadmin trusts the users enough, or can ensure through other +measures, that system processes will never enter non-privileged +mounts, it can relax the last limitation with a "user_allow_other" +config option. If this config option is set, the mounting user can +add the "allow_other" mount option which disables the check for other +users' processes. + +Kernel - userspace interface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following diagram shows how a filesystem operation (in this +example unlink) is performed in FUSE. + +NOTE: everything in this description is greatly simplified + + | "rm /mnt/fuse/file" | FUSE filesystem daemon + | | + | | >sys_read() + | | >fuse_dev_read() + | | >request_wait() + | | [sleep on fc->waitq] + | | + | >sys_unlink() | + | >fuse_unlink() | + | [get request from | + | fc->unused_list] | + | >request_send() | + | [queue req on fc->pending] | + | [wake up fc->waitq] | [woken up] + | >request_wait_answer() | + | [sleep on req->waitq] | + | | pending] + | | [copy req to read buffer] + | | [add req to fc->processing] + | | sys_write() + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | [woken up] | [wake up req->waitq] + | | unused_list] | + | sys_unlink("/mnt/fuse/file") | + | [acquire inode semaphore | + | for "file"] | + | >fuse_unlink() | + | [sleep on req->waitq] | + | | sys_unlink("/mnt/fuse/file") + | | [acquire inode semaphore + | | for "file"] + | | *DEADLOCK* + +The solution for this is to allow requests to be interrupted while +they are in userspace: + + | [interrupted by signal] | + | fuse_unlink() + | | [queue req on fc->pending] + | | [wake up fc->waitq] + | | [sleep on req->waitq] + +If the filesystem daemon was single threaded, this will stop here, +since there's no other thread to dequeue and execute the request. +In this case the solution is to kill the FUSE daemon as well. If +there are multiple serving threads, you just have to kill them as +long as any remain. + +Moral: a filesystem which deadlocks, can soon find itself dead. + +Scenario 2 - Tricky deadlock +---------------------------- + +This one needs a carefully crafted filesystem. It's a variation on +the above, only the call back to the filesystem is not explicit, +but is caused by a pagefault. + + | Kamikaze filesystem thread 1 | Kamikaze filesystem thread 2 + | | + | [fd = open("/mnt/fuse/file")] | [request served normally] + | [mmap fd to 'addr'] | + | [close fd] | [FLUSH triggers 'magic' flag] + | [read a byte from addr] | + | >do_page_fault() | + | [find or create page] | + | [lock page] | + | >fuse_readpage() | + | [queue READ request] | + | [sleep on req->waitq] | + | | [read request to buffer] + | | [create reply header before addr] + | | >sys_write(addr - headerlength) + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | | >do_page_fault() + | | [find or create page] + | | [lock page] + | | * DEADLOCK * + +Solution is again to let the the request be interrupted (not +elaborated further). + +An additional problem is that while the write buffer is being +copied to the request, the request must not be interrupted. This +is because the destination address of the copy may not be valid +after the request is interrupted. + +This is solved with doing the copy atomically, and allowing +interruption while the page(s) belonging to the write buffer are +faulted with get_user_pages(). The 'req->locked' flag indicates +when the copy is taking place, and interruption is delayed until +this flag is unset. + diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 9c3e4cc7b1a..21021c35648 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_FUSE_FS) += fuse.o -fuse-objs := inode.o +fuse-objs := dev.o inode.o diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c new file mode 100644 index 00000000000..9aaf10a6588 --- /dev/null +++ b/fs/fuse/dev.c @@ -0,0 +1,884 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2005 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include "fuse_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_ALIAS_MISCDEV(FUSE_MINOR); + +static kmem_cache_t *fuse_req_cachep; + +static inline struct fuse_conn *fuse_get_conn(struct file *file) +{ + struct fuse_conn *fc; + spin_lock(&fuse_lock); + fc = file->private_data; + if (fc && !fc->sb) + fc = NULL; + spin_unlock(&fuse_lock); + return fc; +} + +static inline void fuse_request_init(struct fuse_req *req) +{ + memset(req, 0, sizeof(*req)); + INIT_LIST_HEAD(&req->list); + init_waitqueue_head(&req->waitq); + atomic_set(&req->count, 1); +} + +struct fuse_req *fuse_request_alloc(void) +{ + struct fuse_req *req = kmem_cache_alloc(fuse_req_cachep, SLAB_KERNEL); + if (req) + fuse_request_init(req); + return req; +} + +void fuse_request_free(struct fuse_req *req) +{ + kmem_cache_free(fuse_req_cachep, req); +} + +static inline void block_sigs(sigset_t *oldset) +{ + sigset_t mask; + + siginitsetinv(&mask, sigmask(SIGKILL)); + sigprocmask(SIG_BLOCK, &mask, oldset); +} + +static inline void restore_sigs(sigset_t *oldset) +{ + sigprocmask(SIG_SETMASK, oldset, NULL); +} + +void fuse_reset_request(struct fuse_req *req) +{ + int preallocated = req->preallocated; + BUG_ON(atomic_read(&req->count) != 1); + fuse_request_init(req); + req->preallocated = preallocated; +} + +static void __fuse_get_request(struct fuse_req *req) +{ + atomic_inc(&req->count); +} + +/* Must be called with > 1 refcount */ +static void __fuse_put_request(struct fuse_req *req) +{ + BUG_ON(atomic_read(&req->count) < 2); + atomic_dec(&req->count); +} + +static struct fuse_req *do_get_request(struct fuse_conn *fc) +{ + struct fuse_req *req; + + spin_lock(&fuse_lock); + BUG_ON(list_empty(&fc->unused_list)); + req = list_entry(fc->unused_list.next, struct fuse_req, list); + list_del_init(&req->list); + spin_unlock(&fuse_lock); + fuse_request_init(req); + req->preallocated = 1; + req->in.h.uid = current->fsuid; + req->in.h.gid = current->fsgid; + req->in.h.pid = current->pid; + return req; +} + +struct fuse_req *fuse_get_request(struct fuse_conn *fc) +{ + if (down_interruptible(&fc->outstanding_sem)) + return NULL; + return do_get_request(fc); +} + +/* + * Non-interruptible version of the above function is for operations + * which can't legally return -ERESTART{SYS,NOINTR}. This can still + * return NULL, but only in case the signal is SIGKILL. + */ +struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc) +{ + int intr; + sigset_t oldset; + + block_sigs(&oldset); + intr = down_interruptible(&fc->outstanding_sem); + restore_sigs(&oldset); + return intr ? NULL : do_get_request(fc); +} + +static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req) +{ + spin_lock(&fuse_lock); + if (req->preallocated) + list_add(&req->list, &fc->unused_list); + else + fuse_request_free(req); + + /* If we are in debt decrease that first */ + if (fc->outstanding_debt) + fc->outstanding_debt--; + else + up(&fc->outstanding_sem); + spin_unlock(&fuse_lock); +} + +void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) +{ + if (atomic_dec_and_test(&req->count)) + fuse_putback_request(fc, req); +} + +/* + * This function is called when a request is finished. Either a reply + * has arrived or it was interrupted (and not yet sent) or some error + * occured during communication with userspace, or the device file was + * closed. It decreases the referece count for the request. In case + * of a background request the referece to the stored objects are + * released. The requester thread is woken up (if still waiting), and + * finally the request is either freed or put on the unused_list + * + * Called with fuse_lock, unlocks it + */ +static void request_end(struct fuse_conn *fc, struct fuse_req *req) +{ + int putback; + req->finished = 1; + putback = atomic_dec_and_test(&req->count); + spin_unlock(&fuse_lock); + if (req->background) { + if (req->inode) + iput(req->inode); + if (req->inode2) + iput(req->inode2); + if (req->file) + fput(req->file); + } + wake_up(&req->waitq); + if (req->in.h.opcode == FUSE_INIT) { + int i; + + if (req->misc.init_in_out.major != FUSE_KERNEL_VERSION) + fc->conn_error = 1; + + /* After INIT reply is received other requests can go + out. So do (FUSE_MAX_OUTSTANDING - 1) number of + up()s on outstanding_sem. The last up() is done in + fuse_putback_request() */ + for (i = 1; i < FUSE_MAX_OUTSTANDING; i++) + up(&fc->outstanding_sem); + } + if (putback) + fuse_putback_request(fc, req); +} + +static void background_request(struct fuse_req *req) +{ + /* Need to get hold of the inode(s) and/or file used in the + request, so FORGET and RELEASE are not sent too early */ + req->background = 1; + if (req->inode) + req->inode = igrab(req->inode); + if (req->inode2) + req->inode2 = igrab(req->inode2); + if (req->file) + get_file(req->file); +} + +static int request_wait_answer_nonint(struct fuse_req *req) +{ + int err; + sigset_t oldset; + block_sigs(&oldset); + err = wait_event_interruptible(req->waitq, req->finished); + restore_sigs(&oldset); + return err; +} + +/* Called with fuse_lock held. Releases, and then reacquires it. */ +static void request_wait_answer(struct fuse_req *req, int interruptible) +{ + int intr; + + spin_unlock(&fuse_lock); + if (interruptible) + intr = wait_event_interruptible(req->waitq, req->finished); + else + intr = request_wait_answer_nonint(req); + spin_lock(&fuse_lock); + if (intr && interruptible && req->sent) { + /* If request is already in userspace, only allow KILL + signal to interrupt */ + spin_unlock(&fuse_lock); + intr = request_wait_answer_nonint(req); + spin_lock(&fuse_lock); + } + if (!intr) + return; + + if (!interruptible || req->sent) + req->out.h.error = -EINTR; + else + req->out.h.error = -ERESTARTNOINTR; + + req->interrupted = 1; + if (req->locked) { + /* This is uninterruptible sleep, because data is + being copied to/from the buffers of req. During + locked state, there mustn't be any filesystem + operation (e.g. page fault), since that could lead + to deadlock */ + spin_unlock(&fuse_lock); + wait_event(req->waitq, !req->locked); + spin_lock(&fuse_lock); + } + if (!req->sent && !list_empty(&req->list)) { + list_del(&req->list); + __fuse_put_request(req); + } else if (!req->finished && req->sent) + background_request(req); +} + +static unsigned len_args(unsigned numargs, struct fuse_arg *args) +{ + unsigned nbytes = 0; + unsigned i; + + for (i = 0; i < numargs; i++) + nbytes += args[i].size; + + return nbytes; +} + +static void queue_request(struct fuse_conn *fc, struct fuse_req *req) +{ + fc->reqctr++; + /* zero is special */ + if (fc->reqctr == 0) + fc->reqctr = 1; + req->in.h.unique = fc->reqctr; + req->in.h.len = sizeof(struct fuse_in_header) + + len_args(req->in.numargs, (struct fuse_arg *) req->in.args); + if (!req->preallocated) { + /* If request is not preallocated (either FORGET or + RELEASE), then still decrease outstanding_sem, so + user can't open infinite number of files while not + processing the RELEASE requests. However for + efficiency do it without blocking, so if down() + would block, just increase the debt instead */ + if (down_trylock(&fc->outstanding_sem)) + fc->outstanding_debt++; + } + list_add_tail(&req->list, &fc->pending); + wake_up(&fc->waitq); +} + +static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req, + int interruptible) +{ + req->isreply = 1; + spin_lock(&fuse_lock); + if (!fc->file) + req->out.h.error = -ENOTCONN; + else if (fc->conn_error) + req->out.h.error = -ECONNREFUSED; + else { + queue_request(fc, req); + /* acquire extra reference, since request is still needed + after request_end() */ + __fuse_get_request(req); + + request_wait_answer(req, interruptible); + } + spin_unlock(&fuse_lock); +} + +void request_send(struct fuse_conn *fc, struct fuse_req *req) +{ + request_send_wait(fc, req, 1); +} + +/* + * Non-interruptible version of the above function is for operations + * which can't legally return -ERESTART{SYS,NOINTR}. This can still + * be interrupted but only with SIGKILL. + */ +void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req) +{ + request_send_wait(fc, req, 0); +} + +static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req) +{ + spin_lock(&fuse_lock); + if (fc->file) { + queue_request(fc, req); + spin_unlock(&fuse_lock); + } else { + req->out.h.error = -ENOTCONN; + request_end(fc, req); + } +} + +void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req) +{ + req->isreply = 0; + request_send_nowait(fc, req); +} + +void request_send_background(struct fuse_conn *fc, struct fuse_req *req) +{ + req->isreply = 1; + background_request(req); + request_send_nowait(fc, req); +} + +void fuse_send_init(struct fuse_conn *fc) +{ + /* This is called from fuse_read_super() so there's guaranteed + to be a request available */ + struct fuse_req *req = do_get_request(fc); + struct fuse_init_in_out *arg = &req->misc.init_in_out; + arg->major = FUSE_KERNEL_VERSION; + arg->minor = FUSE_KERNEL_MINOR_VERSION; + req->in.h.opcode = FUSE_INIT; + req->in.numargs = 1; + req->in.args[0].size = sizeof(*arg); + req->in.args[0].value = arg; + req->out.numargs = 1; + req->out.args[0].size = sizeof(*arg); + req->out.args[0].value = arg; + request_send_background(fc, req); +} + +/* + * Lock the request. Up to the next unlock_request() there mustn't be + * anything that could cause a page-fault. If the request was already + * interrupted bail out. + */ +static inline int lock_request(struct fuse_req *req) +{ + int err = 0; + if (req) { + spin_lock(&fuse_lock); + if (req->interrupted) + err = -ENOENT; + else + req->locked = 1; + spin_unlock(&fuse_lock); + } + return err; +} + +/* + * Unlock request. If it was interrupted during being locked, the + * requester thread is currently waiting for it to be unlocked, so + * wake it up. + */ +static inline void unlock_request(struct fuse_req *req) +{ + if (req) { + spin_lock(&fuse_lock); + req->locked = 0; + if (req->interrupted) + wake_up(&req->waitq); + spin_unlock(&fuse_lock); + } +} + +struct fuse_copy_state { + int write; + struct fuse_req *req; + const struct iovec *iov; + unsigned long nr_segs; + unsigned long seglen; + unsigned long addr; + struct page *pg; + void *mapaddr; + void *buf; + unsigned len; +}; + +static void fuse_copy_init(struct fuse_copy_state *cs, int write, + struct fuse_req *req, const struct iovec *iov, + unsigned long nr_segs) +{ + memset(cs, 0, sizeof(*cs)); + cs->write = write; + cs->req = req; + cs->iov = iov; + cs->nr_segs = nr_segs; +} + +/* Unmap and put previous page of userspace buffer */ +static inline void fuse_copy_finish(struct fuse_copy_state *cs) +{ + if (cs->mapaddr) { + kunmap_atomic(cs->mapaddr, KM_USER0); + if (cs->write) { + flush_dcache_page(cs->pg); + set_page_dirty_lock(cs->pg); + } + put_page(cs->pg); + cs->mapaddr = NULL; + } +} + +/* + * Get another pagefull of userspace buffer, and map it to kernel + * address space, and lock request + */ +static int fuse_copy_fill(struct fuse_copy_state *cs) +{ + unsigned long offset; + int err; + + unlock_request(cs->req); + fuse_copy_finish(cs); + if (!cs->seglen) { + BUG_ON(!cs->nr_segs); + cs->seglen = cs->iov[0].iov_len; + cs->addr = (unsigned long) cs->iov[0].iov_base; + cs->iov ++; + cs->nr_segs --; + } + down_read(¤t->mm->mmap_sem); + err = get_user_pages(current, current->mm, cs->addr, 1, cs->write, 0, + &cs->pg, NULL); + up_read(¤t->mm->mmap_sem); + if (err < 0) + return err; + BUG_ON(err != 1); + offset = cs->addr % PAGE_SIZE; + cs->mapaddr = kmap_atomic(cs->pg, KM_USER0); + cs->buf = cs->mapaddr + offset; + cs->len = min(PAGE_SIZE - offset, cs->seglen); + cs->seglen -= cs->len; + cs->addr += cs->len; + + return lock_request(cs->req); +} + +/* Do as much copy to/from userspace buffer as we can */ +static inline int fuse_copy_do(struct fuse_copy_state *cs, void **val, + unsigned *size) +{ + unsigned ncpy = min(*size, cs->len); + if (val) { + if (cs->write) + memcpy(cs->buf, *val, ncpy); + else + memcpy(*val, cs->buf, ncpy); + *val += ncpy; + } + *size -= ncpy; + cs->len -= ncpy; + cs->buf += ncpy; + return ncpy; +} + +/* + * Copy a page in the request to/from the userspace buffer. Must be + * done atomically + */ +static inline int fuse_copy_page(struct fuse_copy_state *cs, struct page *page, + unsigned offset, unsigned count, int zeroing) +{ + if (page && zeroing && count < PAGE_SIZE) { + void *mapaddr = kmap_atomic(page, KM_USER1); + memset(mapaddr, 0, PAGE_SIZE); + kunmap_atomic(mapaddr, KM_USER1); + } + while (count) { + int err; + if (!cs->len && (err = fuse_copy_fill(cs))) + return err; + if (page) { + void *mapaddr = kmap_atomic(page, KM_USER1); + void *buf = mapaddr + offset; + offset += fuse_copy_do(cs, &buf, &count); + kunmap_atomic(mapaddr, KM_USER1); + } else + offset += fuse_copy_do(cs, NULL, &count); + } + if (page && !cs->write) + flush_dcache_page(page); + return 0; +} + +/* Copy pages in the request to/from userspace buffer */ +static int fuse_copy_pages(struct fuse_copy_state *cs, unsigned nbytes, + int zeroing) +{ + unsigned i; + struct fuse_req *req = cs->req; + unsigned offset = req->page_offset; + unsigned count = min(nbytes, (unsigned) PAGE_SIZE - offset); + + for (i = 0; i < req->num_pages && (nbytes || zeroing); i++) { + struct page *page = req->pages[i]; + int err = fuse_copy_page(cs, page, offset, count, zeroing); + if (err) + return err; + + nbytes -= count; + count = min(nbytes, (unsigned) PAGE_SIZE); + offset = 0; + } + return 0; +} + +/* Copy a single argument in the request to/from userspace buffer */ +static int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size) +{ + while (size) { + int err; + if (!cs->len && (err = fuse_copy_fill(cs))) + return err; + fuse_copy_do(cs, &val, &size); + } + return 0; +} + +/* Copy request arguments to/from userspace buffer */ +static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, + unsigned argpages, struct fuse_arg *args, + int zeroing) +{ + int err = 0; + unsigned i; + + for (i = 0; !err && i < numargs; i++) { + struct fuse_arg *arg = &args[i]; + if (i == numargs - 1 && argpages) + err = fuse_copy_pages(cs, arg->size, zeroing); + else + err = fuse_copy_one(cs, arg->value, arg->size); + } + return err; +} + +/* Wait until a request is available on the pending list */ +static void request_wait(struct fuse_conn *fc) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue_exclusive(&fc->waitq, &wait); + while (fc->sb && list_empty(&fc->pending)) { + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) + break; + + spin_unlock(&fuse_lock); + schedule(); + spin_lock(&fuse_lock); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&fc->waitq, &wait); +} + +/* + * Read a single request into the userspace filesystem's buffer. This + * function waits until a request is available, then removes it from + * the pending list and copies request data to userspace buffer. If + * no reply is needed (FORGET) or request has been interrupted or + * there was an error during the copying then it's finished by calling + * request_end(). Otherwise add it to the processing list, and set + * the 'sent' flag. + */ +static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov, + unsigned long nr_segs, loff_t *off) +{ + int err; + struct fuse_conn *fc; + struct fuse_req *req; + struct fuse_in *in; + struct fuse_copy_state cs; + unsigned reqsize; + + spin_lock(&fuse_lock); + fc = file->private_data; + err = -EPERM; + if (!fc) + goto err_unlock; + request_wait(fc); + err = -ENODEV; + if (!fc->sb) + goto err_unlock; + err = -ERESTARTSYS; + if (list_empty(&fc->pending)) + goto err_unlock; + + req = list_entry(fc->pending.next, struct fuse_req, list); + list_del_init(&req->list); + spin_unlock(&fuse_lock); + + in = &req->in; + reqsize = req->in.h.len; + fuse_copy_init(&cs, 1, req, iov, nr_segs); + err = -EINVAL; + if (iov_length(iov, nr_segs) >= reqsize) { + err = fuse_copy_one(&cs, &in->h, sizeof(in->h)); + if (!err) + err = fuse_copy_args(&cs, in->numargs, in->argpages, + (struct fuse_arg *) in->args, 0); + } + fuse_copy_finish(&cs); + + spin_lock(&fuse_lock); + req->locked = 0; + if (!err && req->interrupted) + err = -ENOENT; + if (err) { + if (!req->interrupted) + req->out.h.error = -EIO; + request_end(fc, req); + return err; + } + if (!req->isreply) + request_end(fc, req); + else { + req->sent = 1; + list_add_tail(&req->list, &fc->processing); + spin_unlock(&fuse_lock); + } + return reqsize; + + err_unlock: + spin_unlock(&fuse_lock); + return err; +} + +static ssize_t fuse_dev_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *off) +{ + struct iovec iov; + iov.iov_len = nbytes; + iov.iov_base = buf; + return fuse_dev_readv(file, &iov, 1, off); +} + +/* Look up request on processing list by unique ID */ +static struct fuse_req *request_find(struct fuse_conn *fc, u64 unique) +{ + struct list_head *entry; + + list_for_each(entry, &fc->processing) { + struct fuse_req *req; + req = list_entry(entry, struct fuse_req, list); + if (req->in.h.unique == unique) + return req; + } + return NULL; +} + +static int copy_out_args(struct fuse_copy_state *cs, struct fuse_out *out, + unsigned nbytes) +{ + unsigned reqsize = sizeof(struct fuse_out_header); + + if (out->h.error) + return nbytes != reqsize ? -EINVAL : 0; + + reqsize += len_args(out->numargs, out->args); + + if (reqsize < nbytes || (reqsize > nbytes && !out->argvar)) + return -EINVAL; + else if (reqsize > nbytes) { + struct fuse_arg *lastarg = &out->args[out->numargs-1]; + unsigned diffsize = reqsize - nbytes; + if (diffsize > lastarg->size) + return -EINVAL; + lastarg->size -= diffsize; + } + return fuse_copy_args(cs, out->numargs, out->argpages, out->args, + out->page_zeroing); +} + +/* + * Write a single reply to a request. First the header is copied from + * the write buffer. The request is then searched on the processing + * list by the unique ID found in the header. If found, then remove + * it from the list and copy the rest of the buffer to the request. + * The request is finished by calling request_end() + */ +static ssize_t fuse_dev_writev(struct file *file, const struct iovec *iov, + unsigned long nr_segs, loff_t *off) +{ + int err; + unsigned nbytes = iov_length(iov, nr_segs); + struct fuse_req *req; + struct fuse_out_header oh; + struct fuse_copy_state cs; + struct fuse_conn *fc = fuse_get_conn(file); + if (!fc) + return -ENODEV; + + fuse_copy_init(&cs, 0, NULL, iov, nr_segs); + if (nbytes < sizeof(struct fuse_out_header)) + return -EINVAL; + + err = fuse_copy_one(&cs, &oh, sizeof(oh)); + if (err) + goto err_finish; + err = -EINVAL; + if (!oh.unique || oh.error <= -1000 || oh.error > 0 || + oh.len != nbytes) + goto err_finish; + + spin_lock(&fuse_lock); + req = request_find(fc, oh.unique); + err = -EINVAL; + if (!req) + goto err_unlock; + + list_del_init(&req->list); + if (req->interrupted) { + request_end(fc, req); + fuse_copy_finish(&cs); + return -ENOENT; + } + req->out.h = oh; + req->locked = 1; + cs.req = req; + spin_unlock(&fuse_lock); + + err = copy_out_args(&cs, &req->out, nbytes); + fuse_copy_finish(&cs); + + spin_lock(&fuse_lock); + req->locked = 0; + if (!err) { + if (req->interrupted) + err = -ENOENT; + } else if (!req->interrupted) + req->out.h.error = -EIO; + request_end(fc, req); + + return err ? err : nbytes; + + err_unlock: + spin_unlock(&fuse_lock); + err_finish: + fuse_copy_finish(&cs); + return err; +} + +static ssize_t fuse_dev_write(struct file *file, const char __user *buf, + size_t nbytes, loff_t *off) +{ + struct iovec iov; + iov.iov_len = nbytes; + iov.iov_base = (char __user *) buf; + return fuse_dev_writev(file, &iov, 1, off); +} + +static unsigned fuse_dev_poll(struct file *file, poll_table *wait) +{ + struct fuse_conn *fc = fuse_get_conn(file); + unsigned mask = POLLOUT | POLLWRNORM; + + if (!fc) + return -ENODEV; + + poll_wait(file, &fc->waitq, wait); + + spin_lock(&fuse_lock); + if (!list_empty(&fc->pending)) + mask |= POLLIN | POLLRDNORM; + spin_unlock(&fuse_lock); + + return mask; +} + +/* Abort all requests on the given list (pending or processing) */ +static void end_requests(struct fuse_conn *fc, struct list_head *head) +{ + while (!list_empty(head)) { + struct fuse_req *req; + req = list_entry(head->next, struct fuse_req, list); + list_del_init(&req->list); + req->out.h.error = -ECONNABORTED; + request_end(fc, req); + spin_lock(&fuse_lock); + } +} + +static int fuse_dev_release(struct inode *inode, struct file *file) +{ + struct fuse_conn *fc; + + spin_lock(&fuse_lock); + fc = file->private_data; + if (fc) { + fc->file = NULL; + end_requests(fc, &fc->pending); + end_requests(fc, &fc->processing); + fuse_release_conn(fc); + } + spin_unlock(&fuse_lock); + return 0; +} + +struct file_operations fuse_dev_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = fuse_dev_read, + .readv = fuse_dev_readv, + .write = fuse_dev_write, + .writev = fuse_dev_writev, + .poll = fuse_dev_poll, + .release = fuse_dev_release, +}; + +static struct miscdevice fuse_miscdevice = { + .minor = FUSE_MINOR, + .name = "fuse", + .fops = &fuse_dev_operations, +}; + +int __init fuse_dev_init(void) +{ + int err = -ENOMEM; + fuse_req_cachep = kmem_cache_create("fuse_request", + sizeof(struct fuse_req), + 0, 0, NULL, NULL); + if (!fuse_req_cachep) + goto out; + + err = misc_register(&fuse_miscdevice); + if (err) + goto out_cache_clean; + + return 0; + + out_cache_clean: + kmem_cache_destroy(fuse_req_cachep); + out: + return err; +} + +void fuse_dev_cleanup(void) +{ + misc_deregister(&fuse_miscdevice); + kmem_cache_destroy(fuse_req_cachep); +} diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index eed6e89ce01..50ad6a0c39b 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -15,6 +15,12 @@ #include #include +/** Max number of pages that can be used in a single read request */ +#define FUSE_MAX_PAGES_PER_REQ 32 + +/** If more requests are outstanding, then the operation will block */ +#define FUSE_MAX_OUTSTANDING 10 + /** FUSE inode */ struct fuse_inode { /** Inode data */ @@ -28,6 +34,123 @@ struct fuse_inode { unsigned long i_time; }; +/** One input argument of a request */ +struct fuse_in_arg { + unsigned size; + const void *value; +}; + +/** The request input */ +struct fuse_in { + /** The request header */ + struct fuse_in_header h; + + /** True if the data for the last argument is in req->pages */ + unsigned argpages:1; + + /** Number of arguments */ + unsigned numargs; + + /** Array of arguments */ + struct fuse_in_arg args[3]; +}; + +/** One output argument of a request */ +struct fuse_arg { + unsigned size; + void *value; +}; + +/** The request output */ +struct fuse_out { + /** Header returned from userspace */ + struct fuse_out_header h; + + /** Last argument is variable length (can be shorter than + arg->size) */ + unsigned argvar:1; + + /** Last argument is a list of pages to copy data to */ + unsigned argpages:1; + + /** Zero partially or not copied pages */ + unsigned page_zeroing:1; + + /** Number or arguments */ + unsigned numargs; + + /** Array of arguments */ + struct fuse_arg args[3]; +}; + +struct fuse_req; +struct fuse_conn; + +/** + * A request to the client + */ +struct fuse_req { + /** This can be on either unused_list, pending or processing + lists in fuse_conn */ + struct list_head list; + + /** refcount */ + atomic_t count; + + /** True if the request has reply */ + unsigned isreply:1; + + /** The request is preallocated */ + unsigned preallocated:1; + + /** The request was interrupted */ + unsigned interrupted:1; + + /** Request is sent in the background */ + unsigned background:1; + + /** Data is being copied to/from the request */ + unsigned locked:1; + + /** Request has been sent to userspace */ + unsigned sent:1; + + /** The request is finished */ + unsigned finished:1; + + /** The request input */ + struct fuse_in in; + + /** The request output */ + struct fuse_out out; + + /** Used to wake up the task waiting for completion of request*/ + wait_queue_head_t waitq; + + /** Data for asynchronous requests */ + union { + struct fuse_init_in_out init_in_out; + } misc; + + /** page vector */ + struct page *pages[FUSE_MAX_PAGES_PER_REQ]; + + /** number of pages in vector */ + unsigned num_pages; + + /** offset of data on first page */ + unsigned page_offset; + + /** Inode used in the request */ + struct inode *inode; + + /** Second inode used in the request (or NULL) */ + struct inode *inode2; + + /** File used in the request (or NULL) */ + struct file *file; +}; + /** * A Fuse connection. * @@ -39,9 +162,37 @@ struct fuse_conn { /** The superblock of the mounted filesystem */ struct super_block *sb; + /** The opened client device */ + struct file *file; + /** The user id for this mount */ uid_t user_id; + /** Readers of the connection are waiting on this */ + wait_queue_head_t waitq; + + /** The list of pending requests */ + struct list_head pending; + + /** The list of requests being processed */ + struct list_head processing; + + /** Controls the maximum number of outstanding requests */ + struct semaphore outstanding_sem; + + /** This counts the number of outstanding requests if + outstanding_sem would go negative */ + unsigned outstanding_debt; + + /** The list of unused requests */ + struct list_head unused_list; + + /** The next unique request id */ + u64 reqctr; + + /** Connection failed (version mismatch) */ + unsigned conn_error : 1; + /** Backing dev info */ struct backing_dev_info bdi; }; @@ -71,13 +222,20 @@ static inline u64 get_node_id(struct inode *inode) return get_fuse_inode(inode)->nodeid; } +/** Device operations */ +extern struct file_operations fuse_dev_operations; + /** * This is the single global spinlock which protects FUSE's structures * * The following data is protected by this lock: * + * - the private_data field of the device file * - the s_fs_info field of the super block + * - unused_list, pending, processing lists in fuse_conn + * - the unique request ID counter reqctr in fuse_conn * - the sb (super_block) field in fuse_conn + * - the file (device file) field in fuse_conn */ extern spinlock_t fuse_lock; @@ -87,3 +245,68 @@ extern spinlock_t fuse_lock; */ void fuse_release_conn(struct fuse_conn *fc); +/** + * Initialize the client device + */ +int fuse_dev_init(void); + +/** + * Cleanup the client device + */ +void fuse_dev_cleanup(void); + +/** + * Allocate a request + */ +struct fuse_req *fuse_request_alloc(void); + +/** + * Free a request + */ +void fuse_request_free(struct fuse_req *req); + +/** + * Reinitialize a request, the preallocated flag is left unmodified + */ +void fuse_reset_request(struct fuse_req *req); + +/** + * Reserve a preallocated request + */ +struct fuse_req *fuse_get_request(struct fuse_conn *fc); + +/** + * Reserve a preallocated request, only interruptible by SIGKILL + */ +struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc); + +/** + * Decrement reference count of a request. If count goes to zero put + * on unused list (preallocated) or free reqest (not preallocated). + */ +void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req); + +/** + * Send a request (synchronous, interruptible) + */ +void request_send(struct fuse_conn *fc, struct fuse_req *req); + +/** + * Send a request (synchronous, non-interruptible except by SIGKILL) + */ +void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req); + +/** + * Send a request with no reply + */ +void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req); + +/** + * Send a request in the background + */ +void request_send_background(struct fuse_conn *fc, struct fuse_req *req); + +/** + * Send the INIT message + */ +void fuse_send_init(struct fuse_conn *fc); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index ea6339c2b6a..33fad334ba7 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -151,6 +151,8 @@ static void fuse_put_super(struct super_block *sb) mount_count --; fc->sb = NULL; fc->user_id = 0; + /* Flush all readers on this fs */ + wake_up_all(&fc->waitq); fuse_release_conn(fc); *get_fuse_conn_super_p(sb) = NULL; spin_unlock(&fuse_lock); @@ -229,22 +231,51 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) return 0; } -void fuse_release_conn(struct fuse_conn *fc) +static void free_conn(struct fuse_conn *fc) { + while (!list_empty(&fc->unused_list)) { + struct fuse_req *req; + req = list_entry(fc->unused_list.next, struct fuse_req, list); + list_del(&req->list); + fuse_request_free(req); + } kfree(fc); } +/* Must be called with the fuse lock held */ +void fuse_release_conn(struct fuse_conn *fc) +{ + if (!fc->sb && !fc->file) + free_conn(fc); +} + static struct fuse_conn *new_conn(void) { struct fuse_conn *fc; fc = kmalloc(sizeof(*fc), GFP_KERNEL); if (fc != NULL) { + int i; memset(fc, 0, sizeof(*fc)); fc->sb = NULL; + fc->file = NULL; fc->user_id = 0; + init_waitqueue_head(&fc->waitq); + INIT_LIST_HEAD(&fc->pending); + INIT_LIST_HEAD(&fc->processing); + INIT_LIST_HEAD(&fc->unused_list); + sema_init(&fc->outstanding_sem, 0); + for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) { + struct fuse_req *req = fuse_request_alloc(); + if (!req) { + free_conn(fc); + return NULL; + } + list_add(&req->list, &fc->unused_list); + } fc->bdi.ra_pages = (VM_MAX_READAHEAD * 1024) / PAGE_CACHE_SIZE; fc->bdi.unplug_io_fn = default_unplug_io_fn; + fc->reqctr = 0; } return fc; } @@ -253,11 +284,20 @@ static struct fuse_conn *get_conn(struct file *file, struct super_block *sb) { struct fuse_conn *fc; + if (file->f_op != &fuse_dev_operations) + return ERR_PTR(-EINVAL); fc = new_conn(); if (fc == NULL) - return NULL; + return ERR_PTR(-ENOMEM); spin_lock(&fuse_lock); - fc->sb = sb; + if (file->private_data) { + free_conn(fc); + fc = ERR_PTR(-EINVAL); + } else { + file->private_data = fc; + fc->sb = sb; + fc->file = file; + } spin_unlock(&fuse_lock); return fc; } @@ -315,8 +355,8 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) fc = get_conn(file, sb); fput(file); - if (fc == NULL) - return -EINVAL; + if (IS_ERR(fc)) + return PTR_ERR(fc); fc->user_id = d.user_id; @@ -336,6 +376,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) iput(root); goto err; } + fuse_send_init(fc); return 0; err: @@ -411,8 +452,14 @@ static int __init fuse_init(void) if (res) goto err; + res = fuse_dev_init(); + if (res) + goto err_fs_cleanup; + return 0; + err_fs_cleanup: + fuse_fs_cleanup(); err: return res; } @@ -422,6 +469,7 @@ static void __exit fuse_exit(void) printk(KERN_DEBUG "fuse exit\n"); fuse_fs_cleanup(); + fuse_dev_cleanup(); } module_init(fuse_init); diff --git a/include/linux/fuse.h b/include/linux/fuse.h index 2b1f4ae01e9..a1aebd7104c 100644 --- a/include/linux/fuse.h +++ b/include/linux/fuse.h @@ -11,7 +11,7 @@ #include /** Version number of this interface */ -#define FUSE_KERNEL_VERSION 5 +#define FUSE_KERNEL_VERSION 6 /** Minor version number of this interface */ #define FUSE_KERNEL_MINOR_VERSION 1 @@ -19,6 +19,12 @@ /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 +/** The major number of the fuse character device */ +#define FUSE_MAJOR 10 + +/** The minor number of the fuse character device */ +#define FUSE_MINOR 229 + struct fuse_attr { __u64 ino; __u64 size; @@ -36,3 +42,31 @@ struct fuse_attr { __u32 rdev; }; +enum fuse_opcode { + FUSE_INIT = 26 +}; + +/* Conservative buffer size for the client */ +#define FUSE_MAX_IN 8192 + +struct fuse_init_in_out { + __u32 major; + __u32 minor; +}; + +struct fuse_in_header { + __u32 len; + __u32 opcode; + __u64 unique; + __u64 nodeid; + __u32 uid; + __u32 gid; + __u32 pid; +}; + +struct fuse_out_header { + __u32 len; + __s32 error; + __u64 unique; +}; + -- cgit v1.2.3-70-g09d2 From e5e5558e923f35839108a12718494ecb73fb782f Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:28 -0700 Subject: [PATCH] FUSE - read-only operations This patch adds the read-only filesystem operations of FUSE. This contains the following files: o dir.c - directory, symlink and file-inode operations The following operations are added: o lookup o getattr o readlink o follow_link o directory open o readdir o directory release o permission o dentry revalidate o statfs Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/Makefile | 2 +- fs/fuse/dev.c | 9 ++ fs/fuse/dir.c | 413 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 51 +++++++ fs/fuse/inode.c | 81 ++++++++++ include/linux/fuse.h | 60 ++++++++ 6 files changed, 615 insertions(+), 1 deletion(-) create mode 100644 fs/fuse/dir.c (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 21021c35648..c34e268a0ed 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_FUSE_FS) += fuse.o -fuse-objs := dev.o inode.o +fuse-objs := dev.o dir.o inode.o diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 9aaf10a6588..e8f3170946f 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -691,6 +691,13 @@ static struct fuse_req *request_find(struct fuse_conn *fc, u64 unique) return NULL; } +/* fget() needs to be done in this context */ +static void process_getdir(struct fuse_req *req) +{ + struct fuse_getdir_out_i *arg = req->out.args[0].value; + arg->file = fget(arg->fd); +} + static int copy_out_args(struct fuse_copy_state *cs, struct fuse_out *out, unsigned nbytes) { @@ -770,6 +777,8 @@ static ssize_t fuse_dev_writev(struct file *file, const struct iovec *iov, if (!err) { if (req->interrupted) err = -ENOENT; + else if (req->in.h.opcode == FUSE_GETDIR && !oh.error) + process_getdir(req); } else if (!req->interrupted) req->out.h.error = -EIO; request_end(fc, req); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c new file mode 100644 index 00000000000..a89730e70c5 --- /dev/null +++ b/fs/fuse/dir.c @@ -0,0 +1,413 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2005 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include "fuse_i.h" + +#include +#include +#include +#include +#include + +static inline unsigned long time_to_jiffies(unsigned long sec, + unsigned long nsec) +{ + struct timespec ts = {sec, nsec}; + return jiffies + timespec_to_jiffies(&ts); +} + +static void fuse_lookup_init(struct fuse_req *req, struct inode *dir, + struct dentry *entry, + struct fuse_entry_out *outarg) +{ + req->in.h.opcode = FUSE_LOOKUP; + req->in.h.nodeid = get_node_id(dir); + req->inode = dir; + req->in.numargs = 1; + req->in.args[0].size = entry->d_name.len + 1; + req->in.args[0].value = entry->d_name.name; + req->out.numargs = 1; + req->out.args[0].size = sizeof(struct fuse_entry_out); + req->out.args[0].value = outarg; +} + +static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) +{ + if (!entry->d_inode || is_bad_inode(entry->d_inode)) + return 0; + else if (time_after(jiffies, entry->d_time)) { + int err; + int version; + struct fuse_entry_out outarg; + struct inode *inode = entry->d_inode; + struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req = fuse_get_request_nonint(fc); + if (!req) + return 0; + + fuse_lookup_init(req, entry->d_parent->d_inode, entry, &outarg); + request_send_nonint(fc, req); + version = req->out.h.unique; + err = req->out.h.error; + fuse_put_request(fc, req); + if (err || outarg.nodeid != get_node_id(inode) || + (outarg.attr.mode ^ inode->i_mode) & S_IFMT) + return 0; + + fuse_change_attributes(inode, &outarg.attr); + inode->i_version = version; + entry->d_time = time_to_jiffies(outarg.entry_valid, + outarg.entry_valid_nsec); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); + } + return 1; +} + +static struct dentry_operations fuse_dentry_operations = { + .d_revalidate = fuse_dentry_revalidate, +}; + +static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, + struct inode **inodep) +{ + int err; + int version; + struct fuse_entry_out outarg; + struct inode *inode = NULL; + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_req *req; + + if (entry->d_name.len > FUSE_NAME_MAX) + return -ENAMETOOLONG; + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + fuse_lookup_init(req, dir, entry, &outarg); + request_send(fc, req); + version = req->out.h.unique; + err = req->out.h.error; + if (!err) { + inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, + &outarg.attr, version); + if (!inode) { + fuse_send_forget(fc, req, outarg.nodeid, version); + return -ENOMEM; + } + } + fuse_put_request(fc, req); + if (err && err != -ENOENT) + return err; + + if (inode) { + struct fuse_inode *fi = get_fuse_inode(inode); + entry->d_time = time_to_jiffies(outarg.entry_valid, + outarg.entry_valid_nsec); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); + } + + entry->d_op = &fuse_dentry_operations; + *inodep = inode; + return 0; +} + +int fuse_do_getattr(struct inode *inode) +{ + int err; + struct fuse_attr_out arg; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + req->in.h.opcode = FUSE_GETATTR; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->out.numargs = 1; + req->out.args[0].size = sizeof(arg); + req->out.args[0].value = &arg; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) { + if ((inode->i_mode ^ arg.attr.mode) & S_IFMT) { + make_bad_inode(inode); + err = -EIO; + } else { + struct fuse_inode *fi = get_fuse_inode(inode); + fuse_change_attributes(inode, &arg.attr); + fi->i_time = time_to_jiffies(arg.attr_valid, + arg.attr_valid_nsec); + } + } + return err; +} + +static int fuse_revalidate(struct dentry *entry) +{ + struct inode *inode = entry->d_inode; + struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_conn *fc = get_fuse_conn(inode); + + if (get_node_id(inode) == FUSE_ROOT_ID) { + if (current->fsuid != fc->user_id) + return -EACCES; + } else if (time_before_eq(jiffies, fi->i_time)) + return 0; + + return fuse_do_getattr(inode); +} + +static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + + if (current->fsuid != fc->user_id) + return -EACCES; + else { + int mode = inode->i_mode; + if ((mask & MAY_WRITE) && IS_RDONLY(inode) && + (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) + return -EROFS; + if ((mask & MAY_EXEC) && !S_ISDIR(mode) && !(mode & S_IXUGO)) + return -EACCES; + return 0; + } +} + +static int parse_dirfile(char *buf, size_t nbytes, struct file *file, + void *dstbuf, filldir_t filldir) +{ + while (nbytes >= FUSE_NAME_OFFSET) { + struct fuse_dirent *dirent = (struct fuse_dirent *) buf; + size_t reclen = FUSE_DIRENT_SIZE(dirent); + int over; + if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX) + return -EIO; + if (reclen > nbytes) + break; + + over = filldir(dstbuf, dirent->name, dirent->namelen, + file->f_pos, dirent->ino, dirent->type); + if (over) + break; + + buf += reclen; + nbytes -= reclen; + file->f_pos = dirent->off; + } + + return 0; +} + +static int fuse_checkdir(struct file *cfile, struct file *file) +{ + struct inode *inode; + if (!cfile) + return -EIO; + inode = cfile->f_dentry->d_inode; + if (!S_ISREG(inode->i_mode)) { + fput(cfile); + return -EIO; + } + + file->private_data = cfile; + return 0; +} + +static int fuse_getdir(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req = fuse_get_request(fc); + struct fuse_getdir_out_i outarg; + int err; + + if (!req) + return -ERESTARTNOINTR; + + req->in.h.opcode = FUSE_GETDIR; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->out.numargs = 1; + req->out.args[0].size = sizeof(struct fuse_getdir_out); + req->out.args[0].value = &outarg; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) + err = fuse_checkdir(outarg.file, file); + return err; +} + +static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) +{ + struct file *cfile = file->private_data; + char *buf; + int ret; + + if (!cfile) { + ret = fuse_getdir(file); + if (ret) + return ret; + + cfile = file->private_data; + } + + buf = (char *) __get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = kernel_read(cfile, file->f_pos, buf, PAGE_SIZE); + if (ret > 0) + ret = parse_dirfile(buf, ret, file, dstbuf, filldir); + + free_page((unsigned long) buf); + return ret; +} + +static char *read_link(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req = fuse_get_request(fc); + char *link; + + if (!req) + return ERR_PTR(-ERESTARTNOINTR); + + link = (char *) __get_free_page(GFP_KERNEL); + if (!link) { + link = ERR_PTR(-ENOMEM); + goto out; + } + req->in.h.opcode = FUSE_READLINK; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->out.argvar = 1; + req->out.numargs = 1; + req->out.args[0].size = PAGE_SIZE - 1; + req->out.args[0].value = link; + request_send(fc, req); + if (req->out.h.error) { + free_page((unsigned long) link); + link = ERR_PTR(req->out.h.error); + } else + link[req->out.args[0].size] = '\0'; + out: + fuse_put_request(fc, req); + return link; +} + +static void free_link(char *link) +{ + if (!IS_ERR(link)) + free_page((unsigned long) link); +} + +static void *fuse_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + nd_set_link(nd, read_link(dentry)); + return NULL; +} + +static void fuse_put_link(struct dentry *dentry, struct nameidata *nd, void *c) +{ + free_link(nd_get_link(nd)); +} + +static int fuse_dir_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static int fuse_dir_release(struct inode *inode, struct file *file) +{ + struct file *cfile = file->private_data; + + if (cfile) + fput(cfile); + + return 0; +} + +static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry, + struct kstat *stat) +{ + struct inode *inode = entry->d_inode; + int err = fuse_revalidate(entry); + if (!err) + generic_fillattr(inode, stat); + + return err; +} + +static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, + struct nameidata *nd) +{ + struct inode *inode; + int err = fuse_lookup_iget(dir, entry, &inode); + if (err) + return ERR_PTR(err); + if (inode && S_ISDIR(inode->i_mode)) { + /* Don't allow creating an alias to a directory */ + struct dentry *alias = d_find_alias(inode); + if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) { + dput(alias); + iput(inode); + return ERR_PTR(-EIO); + } + } + return d_splice_alias(inode, entry); +} + +static struct inode_operations fuse_dir_inode_operations = { + .lookup = fuse_lookup, + .permission = fuse_permission, + .getattr = fuse_getattr, +}; + +static struct file_operations fuse_dir_operations = { + .read = generic_read_dir, + .readdir = fuse_readdir, + .open = fuse_dir_open, + .release = fuse_dir_release, +}; + +static struct inode_operations fuse_common_inode_operations = { + .permission = fuse_permission, + .getattr = fuse_getattr, +}; + +static struct inode_operations fuse_symlink_inode_operations = { + .follow_link = fuse_follow_link, + .put_link = fuse_put_link, + .readlink = generic_readlink, + .getattr = fuse_getattr, +}; + +void fuse_init_common(struct inode *inode) +{ + inode->i_op = &fuse_common_inode_operations; +} + +void fuse_init_dir(struct inode *inode) +{ + inode->i_op = &fuse_dir_inode_operations; + inode->i_fop = &fuse_dir_operations; +} + +void fuse_init_symlink(struct inode *inode) +{ + inode->i_op = &fuse_symlink_inode_operations; +} diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 50ad6a0c39b..8d91e1492f9 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -30,6 +30,9 @@ struct fuse_inode { * and kernel */ u64 nodeid; + /** The request used for sending the FORGET message */ + struct fuse_req *forget_req; + /** Time in jiffies until the file attributes are valid */ unsigned long i_time; }; @@ -129,6 +132,7 @@ struct fuse_req { /** Data for asynchronous requests */ union { + struct fuse_forget_in forget_in; struct fuse_init_in_out init_in_out; } misc; @@ -197,6 +201,11 @@ struct fuse_conn { struct backing_dev_info bdi; }; +struct fuse_getdir_out_i { + int fd; + void *file; /* Used by kernel only */ +}; + static inline struct fuse_conn **get_fuse_conn_super_p(struct super_block *sb) { return (struct fuse_conn **) &sb->s_fs_info; @@ -239,6 +248,38 @@ extern struct file_operations fuse_dev_operations; */ extern spinlock_t fuse_lock; +/** + * Get a filled in inode + */ +struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, + int generation, struct fuse_attr *attr, int version); + +/** + * Send FORGET command + */ +void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, + unsigned long nodeid, int version); + +/** + * Initialise inode operations on regular files and special files + */ +void fuse_init_common(struct inode *inode); + +/** + * Initialise inode and file operations on a directory + */ +void fuse_init_dir(struct inode *inode); + +/** + * Initialise inode operations on a symlink + */ +void fuse_init_symlink(struct inode *inode); + +/** + * Change attributes of an inode + */ +void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr); + /** * Check if the connection can be released, and if yes, then free the * connection structure @@ -306,6 +347,16 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req); */ void request_send_background(struct fuse_conn *fc, struct fuse_req *req); +/** + * Get the attributes of a file + */ +int fuse_do_getattr(struct inode *inode); + +/** + * Invalidate inode attributes + */ +void fuse_invalidate_attr(struct inode *inode); + /** * Send the INIT message */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 33fad334ba7..41498a1952a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -51,12 +51,20 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) fi = get_fuse_inode(inode); fi->i_time = jiffies - 1; fi->nodeid = 0; + fi->forget_req = fuse_request_alloc(); + if (!fi->forget_req) { + kmem_cache_free(fuse_inode_cachep, inode); + return NULL; + } return inode; } static void fuse_destroy_inode(struct inode *inode) { + struct fuse_inode *fi = get_fuse_inode(inode); + if (fi->forget_req) + fuse_request_free(fi->forget_req); kmem_cache_free(fuse_inode_cachep, inode); } @@ -65,8 +73,27 @@ static void fuse_read_inode(struct inode *inode) /* No op */ } +void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, + unsigned long nodeid, int version) +{ + struct fuse_forget_in *inarg = &req->misc.forget_in; + inarg->version = version; + req->in.h.opcode = FUSE_FORGET; + req->in.h.nodeid = nodeid; + req->in.numargs = 1; + req->in.args[0].size = sizeof(struct fuse_forget_in); + req->in.args[0].value = inarg; + request_send_noreply(fc, req); +} + static void fuse_clear_inode(struct inode *inode) { + struct fuse_conn *fc = get_fuse_conn(inode); + if (fc) { + struct fuse_inode *fi = get_fuse_inode(inode); + fuse_send_forget(fc, fi->forget_req, fi->nodeid, inode->i_version); + fi->forget_req = NULL; + } } void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr) @@ -94,6 +121,22 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) { inode->i_mode = attr->mode & S_IFMT; i_size_write(inode, attr->size); + if (S_ISREG(inode->i_mode)) { + fuse_init_common(inode); + } else if (S_ISDIR(inode->i_mode)) + fuse_init_dir(inode); + else if (S_ISLNK(inode->i_mode)) + fuse_init_symlink(inode); + else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || + S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { + fuse_init_common(inode); + init_special_inode(inode, inode->i_mode, + new_decode_dev(attr->rdev)); + } else { + /* Don't let user create weird files */ + inode->i_mode = S_IFREG; + fuse_init_common(inode); + } } static int fuse_inode_eq(struct inode *inode, void *_nodeidp) @@ -158,6 +201,43 @@ static void fuse_put_super(struct super_block *sb) spin_unlock(&fuse_lock); } +static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr) +{ + stbuf->f_type = FUSE_SUPER_MAGIC; + stbuf->f_bsize = attr->bsize; + stbuf->f_blocks = attr->blocks; + stbuf->f_bfree = attr->bfree; + stbuf->f_bavail = attr->bavail; + stbuf->f_files = attr->files; + stbuf->f_ffree = attr->ffree; + stbuf->f_namelen = attr->namelen; + /* fsid is left zero */ +} + +static int fuse_statfs(struct super_block *sb, struct kstatfs *buf) +{ + struct fuse_conn *fc = get_fuse_conn_super(sb); + struct fuse_req *req; + struct fuse_statfs_out outarg; + int err; + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTSYS; + + req->in.numargs = 0; + req->in.h.opcode = FUSE_STATFS; + req->out.numargs = 1; + req->out.args[0].size = sizeof(outarg); + req->out.args[0].value = &outarg; + request_send(fc, req); + err = req->out.h.error; + if (!err) + convert_fuse_statfs(buf, &outarg.st); + fuse_put_request(fc, req); + return err; +} + enum { OPT_FD, OPT_ROOTMODE, @@ -318,6 +398,7 @@ static struct super_operations fuse_super_operations = { .read_inode = fuse_read_inode, .clear_inode = fuse_clear_inode, .put_super = fuse_put_super, + .statfs = fuse_statfs, .show_options = fuse_show_options, }; diff --git a/include/linux/fuse.h b/include/linux/fuse.h index a1aebd7104c..21b9ba16f8d 100644 --- a/include/linux/fuse.h +++ b/include/linux/fuse.h @@ -42,13 +42,61 @@ struct fuse_attr { __u32 rdev; }; +struct fuse_kstatfs { + __u64 blocks; + __u64 bfree; + __u64 bavail; + __u64 files; + __u64 ffree; + __u32 bsize; + __u32 namelen; +}; + enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_READLINK = 5, + FUSE_GETDIR = 7, + FUSE_STATFS = 17, FUSE_INIT = 26 }; /* Conservative buffer size for the client */ #define FUSE_MAX_IN 8192 +#define FUSE_NAME_MAX 1024 + +struct fuse_entry_out { + __u64 nodeid; /* Inode ID */ + __u64 generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + __u64 entry_valid; /* Cache timeout for the name */ + __u64 attr_valid; /* Cache timeout for the attributes */ + __u32 entry_valid_nsec; + __u32 attr_valid_nsec; + struct fuse_attr attr; +}; + +struct fuse_forget_in { + __u64 version; +}; + +struct fuse_attr_out { + __u64 attr_valid; /* Cache timeout for the attributes */ + __u32 attr_valid_nsec; + __u32 dummy; + struct fuse_attr attr; +}; + +struct fuse_getdir_out { + __u32 fd; +}; + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + struct fuse_init_in_out { __u32 major; __u32 minor; @@ -70,3 +118,15 @@ struct fuse_out_header { __u64 unique; }; +struct fuse_dirent { + __u64 ino; + __u64 off; + __u32 namelen; + __u32 type; + char name[0]; +}; + +#define FUSE_NAME_OFFSET ((unsigned) ((struct fuse_dirent *) 0)->name) +#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(__u64) - 1) & ~(sizeof(__u64) - 1)) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) -- cgit v1.2.3-70-g09d2 From 9e6268db496a2592e89457537ea54a496feabb77 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:29 -0700 Subject: [PATCH] FUSE - read-write operations This patch adds the write filesystem operations of FUSE. The following operations are added: o setattr o symlink o mknod o mkdir o create o unlink o rmdir o rename o link Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/dir.c | 373 +++++++++++++++++++++++++++++++++++++++++++++++++-- fs/fuse/fuse_i.h | 7 +- fs/fuse/inode.c | 15 ++- include/linux/fuse.h | 43 +++++- 4 files changed, 419 insertions(+), 19 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index a89730e70c5..92c7188ccd1 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -42,7 +42,6 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) return 0; else if (time_after(jiffies, entry->d_time)) { int err; - int version; struct fuse_entry_out outarg; struct inode *inode = entry->d_inode; struct fuse_inode *fi = get_fuse_inode(inode); @@ -53,15 +52,19 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) fuse_lookup_init(req, entry->d_parent->d_inode, entry, &outarg); request_send_nonint(fc, req); - version = req->out.h.unique; err = req->out.h.error; + if (!err) { + if (outarg.nodeid != get_node_id(inode)) { + fuse_send_forget(fc, req, outarg.nodeid, 1); + return 0; + } + fi->nlookup ++; + } fuse_put_request(fc, req); - if (err || outarg.nodeid != get_node_id(inode) || - (outarg.attr.mode ^ inode->i_mode) & S_IFMT) + if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT) return 0; fuse_change_attributes(inode, &outarg.attr); - inode->i_version = version; entry->d_time = time_to_jiffies(outarg.entry_valid, outarg.entry_valid_nsec); fi->i_time = time_to_jiffies(outarg.attr_valid, @@ -78,7 +81,6 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, struct inode **inodep) { int err; - int version; struct fuse_entry_out outarg; struct inode *inode = NULL; struct fuse_conn *fc = get_fuse_conn(dir); @@ -93,13 +95,12 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, fuse_lookup_init(req, dir, entry, &outarg); request_send(fc, req); - version = req->out.h.unique; err = req->out.h.error; if (!err) { inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, - &outarg.attr, version); + &outarg.attr); if (!inode) { - fuse_send_forget(fc, req, outarg.nodeid, version); + fuse_send_forget(fc, req, outarg.nodeid, 1); return -ENOMEM; } } @@ -120,6 +121,264 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, return 0; } +void fuse_invalidate_attr(struct inode *inode) +{ + get_fuse_inode(inode)->i_time = jiffies - 1; +} + +static void fuse_invalidate_entry(struct dentry *entry) +{ + d_invalidate(entry); + entry->d_time = jiffies - 1; +} + +static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, + struct inode *dir, struct dentry *entry, + int mode) +{ + struct fuse_entry_out outarg; + struct inode *inode; + struct fuse_inode *fi; + int err; + + req->in.h.nodeid = get_node_id(dir); + req->inode = dir; + req->out.numargs = 1; + req->out.args[0].size = sizeof(outarg); + req->out.args[0].value = &outarg; + request_send(fc, req); + err = req->out.h.error; + if (err) { + fuse_put_request(fc, req); + return err; + } + inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, + &outarg.attr); + if (!inode) { + fuse_send_forget(fc, req, outarg.nodeid, 1); + return -ENOMEM; + } + fuse_put_request(fc, req); + + /* Don't allow userspace to do really stupid things... */ + if ((inode->i_mode ^ mode) & S_IFMT) { + iput(inode); + return -EIO; + } + + entry->d_time = time_to_jiffies(outarg.entry_valid, + outarg.entry_valid_nsec); + + fi = get_fuse_inode(inode); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); + + d_instantiate(entry, inode); + fuse_invalidate_attr(dir); + return 0; +} + +static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode, + dev_t rdev) +{ + struct fuse_mknod_in inarg; + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.mode = mode; + inarg.rdev = new_encode_dev(rdev); + req->in.h.opcode = FUSE_MKNOD; + req->in.numargs = 2; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + req->in.args[1].size = entry->d_name.len + 1; + req->in.args[1].value = entry->d_name.name; + return create_new_entry(fc, req, dir, entry, mode); +} + +static int fuse_create(struct inode *dir, struct dentry *entry, int mode, + struct nameidata *nd) +{ + return fuse_mknod(dir, entry, mode, 0); +} + +static int fuse_mkdir(struct inode *dir, struct dentry *entry, int mode) +{ + struct fuse_mkdir_in inarg; + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.mode = mode; + req->in.h.opcode = FUSE_MKDIR; + req->in.numargs = 2; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + req->in.args[1].size = entry->d_name.len + 1; + req->in.args[1].value = entry->d_name.name; + return create_new_entry(fc, req, dir, entry, S_IFDIR); +} + +static int fuse_symlink(struct inode *dir, struct dentry *entry, + const char *link) +{ + struct fuse_conn *fc = get_fuse_conn(dir); + unsigned len = strlen(link) + 1; + struct fuse_req *req; + + if (len > FUSE_SYMLINK_MAX) + return -ENAMETOOLONG; + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + req->in.h.opcode = FUSE_SYMLINK; + req->in.numargs = 2; + req->in.args[0].size = entry->d_name.len + 1; + req->in.args[0].value = entry->d_name.name; + req->in.args[1].size = len; + req->in.args[1].value = link; + return create_new_entry(fc, req, dir, entry, S_IFLNK); +} + +static int fuse_unlink(struct inode *dir, struct dentry *entry) +{ + int err; + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + req->in.h.opcode = FUSE_UNLINK; + req->in.h.nodeid = get_node_id(dir); + req->inode = dir; + req->in.numargs = 1; + req->in.args[0].size = entry->d_name.len + 1; + req->in.args[0].value = entry->d_name.name; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) { + struct inode *inode = entry->d_inode; + + /* Set nlink to zero so the inode can be cleared, if + the inode does have more links this will be + discovered at the next lookup/getattr */ + inode->i_nlink = 0; + fuse_invalidate_attr(inode); + fuse_invalidate_attr(dir); + } else if (err == -EINTR) + fuse_invalidate_entry(entry); + return err; +} + +static int fuse_rmdir(struct inode *dir, struct dentry *entry) +{ + int err; + struct fuse_conn *fc = get_fuse_conn(dir); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + req->in.h.opcode = FUSE_RMDIR; + req->in.h.nodeid = get_node_id(dir); + req->inode = dir; + req->in.numargs = 1; + req->in.args[0].size = entry->d_name.len + 1; + req->in.args[0].value = entry->d_name.name; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) { + entry->d_inode->i_nlink = 0; + fuse_invalidate_attr(dir); + } else if (err == -EINTR) + fuse_invalidate_entry(entry); + return err; +} + +static int fuse_rename(struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + int err; + struct fuse_rename_in inarg; + struct fuse_conn *fc = get_fuse_conn(olddir); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.newdir = get_node_id(newdir); + req->in.h.opcode = FUSE_RENAME; + req->in.h.nodeid = get_node_id(olddir); + req->inode = olddir; + req->inode2 = newdir; + req->in.numargs = 3; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + req->in.args[1].size = oldent->d_name.len + 1; + req->in.args[1].value = oldent->d_name.name; + req->in.args[2].size = newent->d_name.len + 1; + req->in.args[2].value = newent->d_name.name; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) { + fuse_invalidate_attr(olddir); + if (olddir != newdir) + fuse_invalidate_attr(newdir); + } else if (err == -EINTR) { + /* If request was interrupted, DEITY only knows if the + rename actually took place. If the invalidation + fails (e.g. some process has CWD under the renamed + directory), then there can be inconsistency between + the dcache and the real filesystem. Tough luck. */ + fuse_invalidate_entry(oldent); + if (newent->d_inode) + fuse_invalidate_entry(newent); + } + + return err; +} + +static int fuse_link(struct dentry *entry, struct inode *newdir, + struct dentry *newent) +{ + int err; + struct fuse_link_in inarg; + struct inode *inode = entry->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.oldnodeid = get_node_id(inode); + req->in.h.opcode = FUSE_LINK; + req->inode2 = inode; + req->in.numargs = 2; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + req->in.args[1].size = newent->d_name.len + 1; + req->in.args[1].value = newent->d_name.name; + err = create_new_entry(fc, req, newdir, newent, inode->i_mode); + /* Contrary to "normal" filesystems it can happen that link + makes two "logical" inodes point to the same "physical" + inode. We invalidate the attributes of the old one, so it + will reflect changes in the backing inode (link count, + etc.) + */ + if (!err || err == -EINTR) + fuse_invalidate_attr(inode); + return err; +} + int fuse_do_getattr(struct inode *inode) { int err; @@ -341,6 +600,91 @@ static int fuse_dir_release(struct inode *inode, struct file *file) return 0; } +static unsigned iattr_to_fattr(struct iattr *iattr, struct fuse_attr *fattr) +{ + unsigned ivalid = iattr->ia_valid; + unsigned fvalid = 0; + + memset(fattr, 0, sizeof(*fattr)); + + if (ivalid & ATTR_MODE) + fvalid |= FATTR_MODE, fattr->mode = iattr->ia_mode; + if (ivalid & ATTR_UID) + fvalid |= FATTR_UID, fattr->uid = iattr->ia_uid; + if (ivalid & ATTR_GID) + fvalid |= FATTR_GID, fattr->gid = iattr->ia_gid; + if (ivalid & ATTR_SIZE) + fvalid |= FATTR_SIZE, fattr->size = iattr->ia_size; + /* You can only _set_ these together (they may change by themselves) */ + if ((ivalid & (ATTR_ATIME | ATTR_MTIME)) == (ATTR_ATIME | ATTR_MTIME)) { + fvalid |= FATTR_ATIME | FATTR_MTIME; + fattr->atime = iattr->ia_atime.tv_sec; + fattr->mtime = iattr->ia_mtime.tv_sec; + } + + return fvalid; +} + +static int fuse_setattr(struct dentry *entry, struct iattr *attr) +{ + struct inode *inode = entry->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_req *req; + struct fuse_setattr_in inarg; + struct fuse_attr_out outarg; + int err; + int is_truncate = 0; + + if (attr->ia_valid & ATTR_SIZE) { + unsigned long limit; + is_truncate = 1; + limit = current->signal->rlim[RLIMIT_FSIZE].rlim_cur; + if (limit != RLIM_INFINITY && attr->ia_size > (loff_t) limit) { + send_sig(SIGXFSZ, current, 0); + return -EFBIG; + } + } + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTNOINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.valid = iattr_to_fattr(attr, &inarg.attr); + req->in.h.opcode = FUSE_SETATTR; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->in.numargs = 1; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + req->out.numargs = 1; + req->out.args[0].size = sizeof(outarg); + req->out.args[0].value = &outarg; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) { + if ((inode->i_mode ^ outarg.attr.mode) & S_IFMT) { + make_bad_inode(inode); + err = -EIO; + } else { + if (is_truncate) { + loff_t origsize = i_size_read(inode); + i_size_write(inode, outarg.attr.size); + if (origsize > outarg.attr.size) + vmtruncate(inode, outarg.attr.size); + } + fuse_change_attributes(inode, &outarg.attr); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); + } + } else if (err == -EINTR) + fuse_invalidate_attr(inode); + + return err; +} + static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry, struct kstat *stat) { @@ -373,6 +717,15 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, static struct inode_operations fuse_dir_inode_operations = { .lookup = fuse_lookup, + .mkdir = fuse_mkdir, + .symlink = fuse_symlink, + .unlink = fuse_unlink, + .rmdir = fuse_rmdir, + .rename = fuse_rename, + .link = fuse_link, + .setattr = fuse_setattr, + .create = fuse_create, + .mknod = fuse_mknod, .permission = fuse_permission, .getattr = fuse_getattr, }; @@ -385,11 +738,13 @@ static struct file_operations fuse_dir_operations = { }; static struct inode_operations fuse_common_inode_operations = { + .setattr = fuse_setattr, .permission = fuse_permission, .getattr = fuse_getattr, }; static struct inode_operations fuse_symlink_inode_operations = { + .setattr = fuse_setattr, .follow_link = fuse_follow_link, .put_link = fuse_put_link, .readlink = generic_readlink, diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 8d91e1492f9..87d25b8f2dc 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -30,6 +30,9 @@ struct fuse_inode { * and kernel */ u64 nodeid; + /** Number of lookups on this inode */ + u64 nlookup; + /** The request used for sending the FORGET message */ struct fuse_req *forget_req; @@ -252,13 +255,13 @@ extern spinlock_t fuse_lock; * Get a filled in inode */ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, - int generation, struct fuse_attr *attr, int version); + int generation, struct fuse_attr *attr); /** * Send FORGET command */ void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, - unsigned long nodeid, int version); + unsigned long nodeid, u64 nlookup); /** * Initialise inode operations on regular files and special files diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 41498a1952a..fa03f80806e 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -51,6 +51,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) fi = get_fuse_inode(inode); fi->i_time = jiffies - 1; fi->nodeid = 0; + fi->nlookup = 0; fi->forget_req = fuse_request_alloc(); if (!fi->forget_req) { kmem_cache_free(fuse_inode_cachep, inode); @@ -74,10 +75,10 @@ static void fuse_read_inode(struct inode *inode) } void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, - unsigned long nodeid, int version) + unsigned long nodeid, u64 nlookup) { struct fuse_forget_in *inarg = &req->misc.forget_in; - inarg->version = version; + inarg->nlookup = nlookup; req->in.h.opcode = FUSE_FORGET; req->in.h.nodeid = nodeid; req->in.numargs = 1; @@ -91,7 +92,7 @@ static void fuse_clear_inode(struct inode *inode) struct fuse_conn *fc = get_fuse_conn(inode); if (fc) { struct fuse_inode *fi = get_fuse_inode(inode); - fuse_send_forget(fc, fi->forget_req, fi->nodeid, inode->i_version); + fuse_send_forget(fc, fi->forget_req, fi->nodeid, fi->nlookup); fi->forget_req = NULL; } } @@ -156,9 +157,10 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp) } struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, - int generation, struct fuse_attr *attr, int version) + int generation, struct fuse_attr *attr) { struct inode *inode; + struct fuse_inode *fi; struct fuse_conn *fc = get_fuse_conn_super(sb); int retried = 0; @@ -181,8 +183,9 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, goto retry; } + fi = get_fuse_inode(inode); + fi->nlookup ++; fuse_change_attributes(inode, attr); - inode->i_version = version; return inode; } @@ -389,7 +392,7 @@ static struct inode *get_root_inode(struct super_block *sb, unsigned mode) attr.mode = mode; attr.ino = FUSE_ROOT_ID; - return fuse_iget(sb, 1, 0, &attr, 0); + return fuse_iget(sb, 1, 0, &attr); } static struct super_operations fuse_super_operations = { diff --git a/include/linux/fuse.h b/include/linux/fuse.h index 21b9ba16f8d..19d69a3e162 100644 --- a/include/linux/fuse.h +++ b/include/linux/fuse.h @@ -11,7 +11,7 @@ #include /** Version number of this interface */ -#define FUSE_KERNEL_VERSION 6 +#define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ #define FUSE_KERNEL_MINOR_VERSION 1 @@ -52,12 +52,28 @@ struct fuse_kstatfs { __u32 namelen; }; +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_CTIME (1 << 6) + enum fuse_opcode { FUSE_LOOKUP = 1, FUSE_FORGET = 2, /* no reply */ FUSE_GETATTR = 3, + FUSE_SETATTR = 4, FUSE_READLINK = 5, + FUSE_SYMLINK = 6, FUSE_GETDIR = 7, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, FUSE_STATFS = 17, FUSE_INIT = 26 }; @@ -66,6 +82,7 @@ enum fuse_opcode { #define FUSE_MAX_IN 8192 #define FUSE_NAME_MAX 1024 +#define FUSE_SYMLINK_MAX 4096 struct fuse_entry_out { __u64 nodeid; /* Inode ID */ @@ -79,7 +96,7 @@ struct fuse_entry_out { }; struct fuse_forget_in { - __u64 version; + __u64 nlookup; }; struct fuse_attr_out { @@ -93,6 +110,28 @@ struct fuse_getdir_out { __u32 fd; }; +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; +}; + +struct fuse_mkdir_in { + __u32 mode; +}; + +struct fuse_rename_in { + __u64 newdir; +}; + +struct fuse_link_in { + __u64 oldnodeid; +}; + +struct fuse_setattr_in { + __u32 valid; + struct fuse_attr attr; +}; + struct fuse_statfs_out { struct fuse_kstatfs st; }; -- cgit v1.2.3-70-g09d2 From b6aeadeda22a9aa322fdfcd3f4c69ccf0da5cbdd Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:30 -0700 Subject: [PATCH] FUSE - file operations This patch adds the file operations of FUSE. The following operations are added: o open o flush o release o fsync o readpage o commit_write Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/Makefile | 2 +- fs/fuse/dir.c | 1 + fs/fuse/file.c | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 21 ++++ fs/fuse/inode.c | 2 + include/linux/fuse.h | 47 +++++++ 6 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 fs/fuse/file.c (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index c34e268a0ed..c3e1f760cac 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_FUSE_FS) += fuse.o -fuse-objs := dev.o dir.o inode.o +fuse-objs := dev.o dir.o file.o inode.o diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 92c7188ccd1..8adc1eed164 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -731,6 +731,7 @@ static struct inode_operations fuse_dir_inode_operations = { }; static struct file_operations fuse_dir_operations = { + .llseek = generic_file_llseek, .read = generic_read_dir, .readdir = fuse_readdir, .open = fuse_dir_open, diff --git a/fs/fuse/file.c b/fs/fuse/file.c new file mode 100644 index 00000000000..de8c9c70246 --- /dev/null +++ b/fs/fuse/file.c @@ -0,0 +1,341 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2005 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +#include "fuse_i.h" + +#include +#include +#include + +static int fuse_open(struct inode *inode, struct file *file) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req; + struct fuse_open_in inarg; + struct fuse_open_out outarg; + struct fuse_file *ff; + int err; + /* Restarting the syscall is not allowed if O_CREAT and O_EXCL + are both set, because creation will fail on the restart */ + int excl = (file->f_flags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL); + + err = generic_file_open(inode, file); + if (err) + return err; + + /* If opening the root node, no lookup has been performed on + it, so the attributes must be refreshed */ + if (get_node_id(inode) == FUSE_ROOT_ID) { + int err = fuse_do_getattr(inode); + if (err) + return err; + } + + if (excl) + req = fuse_get_request_nonint(fc); + else + req = fuse_get_request(fc); + if (!req) + return excl ? -EINTR : -ERESTARTSYS; + + err = -ENOMEM; + ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL); + if (!ff) + goto out_put_request; + + ff->release_req = fuse_request_alloc(); + if (!ff->release_req) { + kfree(ff); + goto out_put_request; + } + + memset(&inarg, 0, sizeof(inarg)); + inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); + req->in.h.opcode = FUSE_OPEN; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->in.numargs = 1; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + req->out.numargs = 1; + req->out.args[0].size = sizeof(outarg); + req->out.args[0].value = &outarg; + if (excl) + request_send_nonint(fc, req); + else + request_send(fc, req); + err = req->out.h.error; + if (!err) + invalidate_inode_pages(inode->i_mapping); + if (err) { + fuse_request_free(ff->release_req); + kfree(ff); + } else { + ff->fh = outarg.fh; + file->private_data = ff; + } + + out_put_request: + fuse_put_request(fc, req); + return err; +} + +static int fuse_release(struct inode *inode, struct file *file) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; + struct fuse_req *req = ff->release_req; + struct fuse_release_in *inarg = &req->misc.release_in; + + inarg->fh = ff->fh; + inarg->flags = file->f_flags & ~O_EXCL; + req->in.h.opcode = FUSE_RELEASE; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->in.numargs = 1; + req->in.args[0].size = sizeof(struct fuse_release_in); + req->in.args[0].value = inarg; + request_send_background(fc, req); + kfree(ff); + + /* Return value is ignored by VFS */ + return 0; +} + +static int fuse_flush(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; + struct fuse_req *req; + struct fuse_flush_in inarg; + int err; + + if (fc->no_flush) + return 0; + + req = fuse_get_request_nonint(fc); + if (!req) + return -EINTR; + + memset(&inarg, 0, sizeof(inarg)); + inarg.fh = ff->fh; + req->in.h.opcode = FUSE_FLUSH; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->file = file; + req->in.numargs = 1; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + request_send_nonint(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (err == -ENOSYS) { + fc->no_flush = 1; + err = 0; + } + return err; +} + +static int fuse_fsync(struct file *file, struct dentry *de, int datasync) +{ + struct inode *inode = de->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; + struct fuse_req *req; + struct fuse_fsync_in inarg; + int err; + + if (fc->no_fsync) + return 0; + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTSYS; + + memset(&inarg, 0, sizeof(inarg)); + inarg.fh = ff->fh; + inarg.fsync_flags = datasync ? 1 : 0; + req->in.h.opcode = FUSE_FSYNC; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->file = file; + req->in.numargs = 1; + req->in.args[0].size = sizeof(inarg); + req->in.args[0].value = &inarg; + request_send(fc, req); + err = req->out.h.error; + fuse_put_request(fc, req); + if (err == -ENOSYS) { + fc->no_fsync = 1; + err = 0; + } + return err; +} + +static ssize_t fuse_send_read(struct fuse_req *req, struct file *file, + struct inode *inode, loff_t pos, size_t count) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; + struct fuse_read_in inarg; + + memset(&inarg, 0, sizeof(struct fuse_read_in)); + inarg.fh = ff->fh; + inarg.offset = pos; + inarg.size = count; + req->in.h.opcode = FUSE_READ; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->file = file; + req->in.numargs = 1; + req->in.args[0].size = sizeof(struct fuse_read_in); + req->in.args[0].value = &inarg; + req->out.argpages = 1; + req->out.argvar = 1; + req->out.numargs = 1; + req->out.args[0].size = count; + request_send_nonint(fc, req); + return req->out.args[0].size; +} + +static int fuse_readpage(struct file *file, struct page *page) +{ + struct inode *inode = page->mapping->host; + struct fuse_conn *fc = get_fuse_conn(inode); + loff_t pos = (loff_t) page->index << PAGE_CACHE_SHIFT; + struct fuse_req *req = fuse_get_request_nonint(fc); + int err = -EINTR; + if (!req) + goto out; + + req->out.page_zeroing = 1; + req->num_pages = 1; + req->pages[0] = page; + fuse_send_read(req, file, inode, pos, PAGE_CACHE_SIZE); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err) + SetPageUptodate(page); + out: + unlock_page(page); + return err; +} + +static ssize_t fuse_send_write(struct fuse_req *req, struct file *file, + struct inode *inode, loff_t pos, size_t count) +{ + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; + struct fuse_write_in inarg; + struct fuse_write_out outarg; + + memset(&inarg, 0, sizeof(struct fuse_write_in)); + inarg.fh = ff->fh; + inarg.offset = pos; + inarg.size = count; + req->in.h.opcode = FUSE_WRITE; + req->in.h.nodeid = get_node_id(inode); + req->inode = inode; + req->file = file; + req->in.argpages = 1; + req->in.numargs = 2; + req->in.args[0].size = sizeof(struct fuse_write_in); + req->in.args[0].value = &inarg; + req->in.args[1].size = count; + req->out.numargs = 1; + req->out.args[0].size = sizeof(struct fuse_write_out); + req->out.args[0].value = &outarg; + request_send_nonint(fc, req); + return outarg.size; +} + +static int fuse_prepare_write(struct file *file, struct page *page, + unsigned offset, unsigned to) +{ + /* No op */ + return 0; +} + +static int fuse_commit_write(struct file *file, struct page *page, + unsigned offset, unsigned to) +{ + int err; + ssize_t nres; + unsigned count = to - offset; + struct inode *inode = page->mapping->host; + struct fuse_conn *fc = get_fuse_conn(inode); + loff_t pos = ((loff_t) page->index << PAGE_CACHE_SHIFT) + offset; + struct fuse_req *req = fuse_get_request_nonint(fc); + if (!req) + return -EINTR; + + req->num_pages = 1; + req->pages[0] = page; + req->page_offset = offset; + nres = fuse_send_write(req, file, inode, pos, count); + err = req->out.h.error; + fuse_put_request(fc, req); + if (!err && nres != count) + err = -EIO; + if (!err) { + pos += count; + if (pos > i_size_read(inode)) + i_size_write(inode, pos); + + if (offset == 0 && to == PAGE_CACHE_SIZE) { + clear_page_dirty(page); + SetPageUptodate(page); + } + } else if (err == -EINTR || err == -EIO) + fuse_invalidate_attr(inode); + return err; +} + +static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + if ((vma->vm_flags & VM_SHARED)) { + if ((vma->vm_flags & VM_WRITE)) + return -ENODEV; + else + vma->vm_flags &= ~VM_MAYWRITE; + } + return generic_file_mmap(file, vma); +} + +static int fuse_set_page_dirty(struct page *page) +{ + printk("fuse_set_page_dirty: should not happen\n"); + dump_stack(); + return 0; +} + +static struct file_operations fuse_file_operations = { + .llseek = generic_file_llseek, + .read = generic_file_read, + .write = generic_file_write, + .mmap = fuse_file_mmap, + .open = fuse_open, + .flush = fuse_flush, + .release = fuse_release, + .fsync = fuse_fsync, + .sendfile = generic_file_sendfile, +}; + +static struct address_space_operations fuse_file_aops = { + .readpage = fuse_readpage, + .prepare_write = fuse_prepare_write, + .commit_write = fuse_commit_write, + .set_page_dirty = fuse_set_page_dirty, +}; + +void fuse_init_file_inode(struct inode *inode) +{ + inode->i_fop = &fuse_file_operations; + inode->i_data.a_ops = &fuse_file_aops; +} diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 87d25b8f2dc..b4aa8f7bc2c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -40,6 +40,15 @@ struct fuse_inode { unsigned long i_time; }; +/** FUSE specific file data */ +struct fuse_file { + /** Request reserved for flush and release */ + struct fuse_req *release_req; + + /** File handle used by userspace */ + u64 fh; +}; + /** One input argument of a request */ struct fuse_in_arg { unsigned size; @@ -136,6 +145,7 @@ struct fuse_req { /** Data for asynchronous requests */ union { struct fuse_forget_in forget_in; + struct fuse_release_in release_in; struct fuse_init_in_out init_in_out; } misc; @@ -200,6 +210,12 @@ struct fuse_conn { /** Connection failed (version mismatch) */ unsigned conn_error : 1; + /** Is fsync not implemented by fs? */ + unsigned no_fsync : 1; + + /** Is flush not implemented by fs? */ + unsigned no_flush : 1; + /** Backing dev info */ struct backing_dev_info bdi; }; @@ -263,6 +279,11 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, unsigned long nodeid, u64 nlookup); +/** + * Initialise file operations on a regular file + */ +void fuse_init_file_inode(struct inode *inode); + /** * Initialise inode operations on regular files and special files */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index fa03f80806e..f229d696264 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -124,6 +124,7 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) i_size_write(inode, attr->size); if (S_ISREG(inode->i_mode)) { fuse_init_common(inode); + fuse_init_file_inode(inode); } else if (S_ISDIR(inode->i_mode)) fuse_init_dir(inode); else if (S_ISLNK(inode->i_mode)) @@ -137,6 +138,7 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) /* Don't let user create weird files */ inode->i_mode = S_IFREG; fuse_init_common(inode); + fuse_init_file_inode(inode); } } diff --git a/include/linux/fuse.h b/include/linux/fuse.h index 19d69a3e162..61f34636ffb 100644 --- a/include/linux/fuse.h +++ b/include/linux/fuse.h @@ -74,7 +74,13 @@ enum fuse_opcode { FUSE_RMDIR = 11, FUSE_RENAME = 12, FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_FLUSH = 25, FUSE_INIT = 26 }; @@ -132,10 +138,51 @@ struct fuse_setattr_in { struct fuse_attr attr; }; +struct fuse_open_in { + __u32 flags; +}; + +struct fuse_open_out { + __u64 fh; + __u32 open_flags; +}; + +struct fuse_release_in { + __u64 fh; + __u32 flags; +}; + +struct fuse_flush_in { + __u64 fh; + __u32 flush_flags; +}; + +struct fuse_read_in { + __u64 fh; + __u64 offset; + __u32 size; +}; + +struct fuse_write_in { + __u64 fh; + __u64 offset; + __u32 size; + __u32 write_flags; +}; + +struct fuse_write_out { + __u32 size; +}; + struct fuse_statfs_out { struct fuse_kstatfs st; }; +struct fuse_fsync_in { + __u64 fh; + __u32 fsync_flags; +}; + struct fuse_init_in_out { __u32 major; __u32 minor; -- cgit v1.2.3-70-g09d2 From 1e9a4ed9396e9c31139721b639550ffb1df17065 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:31 -0700 Subject: [PATCH] FUSE - mount options This patch adds miscellaneous mount options to the FUSE filesystem. The following mount options are added: o default_permissions: check permissions with generic_permission() o allow_other: allow other users to access files o allow_root: allow root to access files o kernel_cache: don't invalidate page cache on open Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/dev.c | 78 ++++++++++++++++++++++++++++++++++++++++++-------------- fs/fuse/dir.c | 35 ++++++++++++++++++++++--- fs/fuse/file.c | 2 +- fs/fuse/fuse_i.h | 45 ++++++++++++++++++++++++++++---- fs/fuse/inode.c | 77 +++++++++++++++++++++++++++---------------------------- 5 files changed, 170 insertions(+), 67 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index e8f3170946f..ca6fc0e96d7 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -26,7 +26,7 @@ static inline struct fuse_conn *fuse_get_conn(struct file *file) struct fuse_conn *fc; spin_lock(&fuse_lock); fc = file->private_data; - if (fc && !fc->sb) + if (fc && !fc->mounted) fc = NULL; spin_unlock(&fuse_lock); return fc; @@ -148,6 +148,17 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) fuse_putback_request(fc, req); } +void fuse_release_background(struct fuse_req *req) +{ + iput(req->inode); + iput(req->inode2); + if (req->file) + fput(req->file); + spin_lock(&fuse_lock); + list_del(&req->bg_entry); + spin_unlock(&fuse_lock); +} + /* * This function is called when a request is finished. Either a reply * has arrived or it was interrupted (and not yet sent) or some error @@ -166,12 +177,10 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req) putback = atomic_dec_and_test(&req->count); spin_unlock(&fuse_lock); if (req->background) { - if (req->inode) - iput(req->inode); - if (req->inode2) - iput(req->inode2); - if (req->file) - fput(req->file); + down_read(&fc->sbput_sem); + if (fc->mounted) + fuse_release_background(req); + up_read(&fc->sbput_sem); } wake_up(&req->waitq); if (req->in.h.opcode == FUSE_INIT) { @@ -191,11 +200,39 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req) fuse_putback_request(fc, req); } -static void background_request(struct fuse_req *req) +/* + * Unfortunately request interruption not just solves the deadlock + * problem, it causes problems too. These stem from the fact, that an + * interrupted request is continued to be processed in userspace, + * while all the locks and object references (inode and file) held + * during the operation are released. + * + * To release the locks is exactly why there's a need to interrupt the + * request, so there's not a lot that can be done about this, except + * introduce additional locking in userspace. + * + * More important is to keep inode and file references until userspace + * has replied, otherwise FORGET and RELEASE could be sent while the + * inode/file is still used by the filesystem. + * + * For this reason the concept of "background" request is introduced. + * An interrupted request is backgrounded if it has been already sent + * to userspace. Backgrounding involves getting an extra reference to + * inode(s) or file used in the request, and adding the request to + * fc->background list. When a reply is received for a background + * request, the object references are released, and the request is + * removed from the list. If the filesystem is unmounted while there + * are still background requests, the list is walked and references + * are released as if a reply was received. + * + * There's one more use for a background request. The RELEASE message is + * always sent as background, since it doesn't return an error or + * data. + */ +static void background_request(struct fuse_conn *fc, struct fuse_req *req) { - /* Need to get hold of the inode(s) and/or file used in the - request, so FORGET and RELEASE are not sent too early */ req->background = 1; + list_add(&req->bg_entry, &fc->background); if (req->inode) req->inode = igrab(req->inode); if (req->inode2) @@ -215,7 +252,8 @@ static int request_wait_answer_nonint(struct fuse_req *req) } /* Called with fuse_lock held. Releases, and then reacquires it. */ -static void request_wait_answer(struct fuse_req *req, int interruptible) +static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req, + int interruptible) { int intr; @@ -255,7 +293,7 @@ static void request_wait_answer(struct fuse_req *req, int interruptible) list_del(&req->list); __fuse_put_request(req); } else if (!req->finished && req->sent) - background_request(req); + background_request(fc, req); } static unsigned len_args(unsigned numargs, struct fuse_arg *args) @@ -297,7 +335,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req, { req->isreply = 1; spin_lock(&fuse_lock); - if (!fc->file) + if (!fc->connected) req->out.h.error = -ENOTCONN; else if (fc->conn_error) req->out.h.error = -ECONNREFUSED; @@ -307,7 +345,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req, after request_end() */ __fuse_get_request(req); - request_wait_answer(req, interruptible); + request_wait_answer(fc, req, interruptible); } spin_unlock(&fuse_lock); } @@ -330,7 +368,7 @@ void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req) static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req) { spin_lock(&fuse_lock); - if (fc->file) { + if (fc->connected) { queue_request(fc, req); spin_unlock(&fuse_lock); } else { @@ -348,7 +386,9 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req) void request_send_background(struct fuse_conn *fc, struct fuse_req *req) { req->isreply = 1; - background_request(req); + spin_lock(&fuse_lock); + background_request(fc, req); + spin_unlock(&fuse_lock); request_send_nowait(fc, req); } @@ -583,7 +623,7 @@ static void request_wait(struct fuse_conn *fc) DECLARE_WAITQUEUE(wait, current); add_wait_queue_exclusive(&fc->waitq, &wait); - while (fc->sb && list_empty(&fc->pending)) { + while (fc->mounted && list_empty(&fc->pending)) { set_current_state(TASK_INTERRUPTIBLE); if (signal_pending(current)) break; @@ -622,7 +662,7 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov, goto err_unlock; request_wait(fc); err = -ENODEV; - if (!fc->sb) + if (!fc->mounted) goto err_unlock; err = -ERESTARTSYS; if (list_empty(&fc->pending)) @@ -839,7 +879,7 @@ static int fuse_dev_release(struct inode *inode, struct file *file) spin_lock(&fuse_lock); fc = file->private_data; if (fc) { - fc->file = NULL; + fc->connected = 0; end_requests(fc, &fc->pending); end_requests(fc, &fc->processing); fuse_release_conn(fc); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 8adc1eed164..0950455914d 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -418,7 +418,8 @@ static int fuse_revalidate(struct dentry *entry) struct fuse_conn *fc = get_fuse_conn(inode); if (get_node_id(inode) == FUSE_ROOT_ID) { - if (current->fsuid != fc->user_id) + if (!(fc->flags & FUSE_ALLOW_OTHER) && + current->fsuid != fc->user_id) return -EACCES; } else if (time_before_eq(jiffies, fi->i_time)) return 0; @@ -430,9 +431,31 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) { struct fuse_conn *fc = get_fuse_conn(inode); - if (current->fsuid != fc->user_id) + if (!(fc->flags & FUSE_ALLOW_OTHER) && current->fsuid != fc->user_id) return -EACCES; - else { + else if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { + int err = generic_permission(inode, mask, NULL); + + /* If permission is denied, try to refresh file + attributes. This is also needed, because the root + node will at first have no permissions */ + if (err == -EACCES) { + err = fuse_do_getattr(inode); + if (!err) + err = generic_permission(inode, mask, NULL); + } + + /* FIXME: Need some mechanism to revoke permissions: + currently if the filesystem suddenly changes the + file mode, we will not be informed about it, and + continue to allow access to the file/directory. + + This is actually not so grave, since the user can + simply keep access to the file/directory anyway by + keeping it open... */ + + return err; + } else { int mode = inode->i_mode; if ((mask & MAY_WRITE) && IS_RDONLY(inode) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) @@ -636,6 +659,12 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) int err; int is_truncate = 0; + if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { + err = inode_change_ok(inode, attr); + if (err) + return err; + } + if (attr->ia_valid & ATTR_SIZE) { unsigned long limit; is_truncate = 1; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index de8c9c70246..96ea302db18 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -70,7 +70,7 @@ static int fuse_open(struct inode *inode, struct file *file) else request_send(fc, req); err = req->out.h.error; - if (!err) + if (!err && !(fc->flags & FUSE_KERNEL_CACHE)) invalidate_inode_pages(inode->i_mapping); if (err) { fuse_request_free(ff->release_req); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index b4aa8f7bc2c..c8e6c87496e 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -21,6 +21,19 @@ /** If more requests are outstanding, then the operation will block */ #define FUSE_MAX_OUTSTANDING 10 +/** If the FUSE_DEFAULT_PERMISSIONS flag is given, the filesystem + module will check permissions based on the file mode. Otherwise no + permission checking is done in the kernel */ +#define FUSE_DEFAULT_PERMISSIONS (1 << 0) + +/** If the FUSE_ALLOW_OTHER flag is given, then not only the user + doing the mount will be allowed to access the filesystem */ +#define FUSE_ALLOW_OTHER (1 << 1) + +/** If the FUSE_KERNEL_CACHE flag is given, then cached data will not + be flushed on open */ +#define FUSE_KERNEL_CACHE (1 << 2) + /** FUSE inode */ struct fuse_inode { /** Inode data */ @@ -109,6 +122,9 @@ struct fuse_req { lists in fuse_conn */ struct list_head list; + /** Entry on the background list */ + struct list_head bg_entry; + /** refcount */ atomic_t count; @@ -176,15 +192,15 @@ struct fuse_req { * unmounted. */ struct fuse_conn { - /** The superblock of the mounted filesystem */ - struct super_block *sb; - - /** The opened client device */ - struct file *file; + /** Reference count */ + int count; /** The user id for this mount */ uid_t user_id; + /** The fuse mount flags for this mount */ + unsigned flags; + /** Readers of the connection are waiting on this */ wait_queue_head_t waitq; @@ -194,6 +210,10 @@ struct fuse_conn { /** The list of requests being processed */ struct list_head processing; + /** Requests put in the background (RELEASE or any other + interrupted request) */ + struct list_head background; + /** Controls the maximum number of outstanding requests */ struct semaphore outstanding_sem; @@ -201,12 +221,21 @@ struct fuse_conn { outstanding_sem would go negative */ unsigned outstanding_debt; + /** RW semaphore for exclusion with fuse_put_super() */ + struct rw_semaphore sbput_sem; + /** The list of unused requests */ struct list_head unused_list; /** The next unique request id */ u64 reqctr; + /** Mount is active */ + unsigned mounted : 1; + + /** Connection established */ + unsigned connected : 1; + /** Connection failed (version mismatch) */ unsigned conn_error : 1; @@ -261,6 +290,7 @@ extern struct file_operations fuse_dev_operations; * - the private_data field of the device file * - the s_fs_info field of the super block * - unused_list, pending, processing lists in fuse_conn + * - background list in fuse_conn * - the unique request ID counter reqctr in fuse_conn * - the sb (super_block) field in fuse_conn * - the file (device file) field in fuse_conn @@ -371,6 +401,11 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req); */ void request_send_background(struct fuse_conn *fc, struct fuse_req *req); +/** + * Release inodes and file assiciated with background request + */ +void fuse_release_background(struct fuse_req *req); + /** * Get the attributes of a file */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index f229d696264..458c62ca0fe 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -25,11 +24,6 @@ MODULE_LICENSE("GPL"); spinlock_t fuse_lock; static kmem_cache_t *fuse_inode_cachep; -static int mount_count; - -static int mount_max = 1000; -module_param(mount_max, int, 0644); -MODULE_PARM_DESC(mount_max, "Maximum number of FUSE mounts allowed, if -1 then unlimited (default: 1000)"); #define FUSE_SUPER_MAGIC 0x65735546 @@ -37,6 +31,7 @@ struct fuse_mount_data { int fd; unsigned rootmode; unsigned user_id; + unsigned flags; }; static struct inode *fuse_alloc_inode(struct super_block *sb) @@ -89,8 +84,8 @@ void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, static void fuse_clear_inode(struct inode *inode) { - struct fuse_conn *fc = get_fuse_conn(inode); - if (fc) { + if (inode->i_sb->s_flags & MS_ACTIVE) { + struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_inode *fi = get_fuse_inode(inode); fuse_send_forget(fc, fi->forget_req, fi->nodeid, fi->nlookup); fi->forget_req = NULL; @@ -195,14 +190,19 @@ static void fuse_put_super(struct super_block *sb) { struct fuse_conn *fc = get_fuse_conn_super(sb); + down_write(&fc->sbput_sem); + while (!list_empty(&fc->background)) + fuse_release_background(list_entry(fc->background.next, + struct fuse_req, bg_entry)); + spin_lock(&fuse_lock); - mount_count --; - fc->sb = NULL; + fc->mounted = 0; fc->user_id = 0; + fc->flags = 0; /* Flush all readers on this fs */ wake_up_all(&fc->waitq); + up_write(&fc->sbput_sem); fuse_release_conn(fc); - *get_fuse_conn_super_p(sb) = NULL; spin_unlock(&fuse_lock); } @@ -249,7 +249,6 @@ enum { OPT_USER_ID, OPT_DEFAULT_PERMISSIONS, OPT_ALLOW_OTHER, - OPT_ALLOW_ROOT, OPT_KERNEL_CACHE, OPT_ERR }; @@ -260,7 +259,6 @@ static match_table_t tokens = { {OPT_USER_ID, "user_id=%u"}, {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ALLOW_OTHER, "allow_other"}, - {OPT_ALLOW_ROOT, "allow_root"}, {OPT_KERNEL_CACHE, "kernel_cache"}, {OPT_ERR, NULL} }; @@ -298,6 +296,18 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->user_id = value; break; + case OPT_DEFAULT_PERMISSIONS: + d->flags |= FUSE_DEFAULT_PERMISSIONS; + break; + + case OPT_ALLOW_OTHER: + d->flags |= FUSE_ALLOW_OTHER; + break; + + case OPT_KERNEL_CACHE: + d->flags |= FUSE_KERNEL_CACHE; + break; + default: return 0; } @@ -313,6 +323,12 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) struct fuse_conn *fc = get_fuse_conn_super(mnt->mnt_sb); seq_printf(m, ",user_id=%u", fc->user_id); + if (fc->flags & FUSE_DEFAULT_PERMISSIONS) + seq_puts(m, ",default_permissions"); + if (fc->flags & FUSE_ALLOW_OTHER) + seq_puts(m, ",allow_other"); + if (fc->flags & FUSE_KERNEL_CACHE) + seq_puts(m, ",kernel_cache"); return 0; } @@ -330,7 +346,8 @@ static void free_conn(struct fuse_conn *fc) /* Must be called with the fuse lock held */ void fuse_release_conn(struct fuse_conn *fc) { - if (!fc->sb && !fc->file) + fc->count--; + if (!fc->count) free_conn(fc); } @@ -342,14 +359,13 @@ static struct fuse_conn *new_conn(void) if (fc != NULL) { int i; memset(fc, 0, sizeof(*fc)); - fc->sb = NULL; - fc->file = NULL; - fc->user_id = 0; init_waitqueue_head(&fc->waitq); INIT_LIST_HEAD(&fc->pending); INIT_LIST_HEAD(&fc->processing); INIT_LIST_HEAD(&fc->unused_list); + INIT_LIST_HEAD(&fc->background); sema_init(&fc->outstanding_sem, 0); + init_rwsem(&fc->sbput_sem); for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) { struct fuse_req *req = fuse_request_alloc(); if (!req) { @@ -380,8 +396,10 @@ static struct fuse_conn *get_conn(struct file *file, struct super_block *sb) fc = ERR_PTR(-EINVAL); } else { file->private_data = fc; - fc->sb = sb; - fc->file = file; + *get_fuse_conn_super_p(sb) = fc; + fc->mounted = 1; + fc->connected = 1; + fc->count = 2; } spin_unlock(&fuse_lock); return fc; @@ -407,17 +425,6 @@ static struct super_operations fuse_super_operations = { .show_options = fuse_show_options, }; -static int inc_mount_count(void) -{ - int success = 0; - spin_lock(&fuse_lock); - mount_count ++; - if (mount_max == -1 || mount_count <= mount_max) - success = 1; - spin_unlock(&fuse_lock); - return success; -} - static int fuse_fill_super(struct super_block *sb, void *data, int silent) { struct fuse_conn *fc; @@ -444,14 +451,9 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) if (IS_ERR(fc)) return PTR_ERR(fc); + fc->flags = d.flags; fc->user_id = d.user_id; - *get_fuse_conn_super_p(sb) = fc; - - err = -ENFILE; - if (!inc_mount_count() && current->uid != 0) - goto err; - err = -ENOMEM; root = get_root_inode(sb, d.rootmode); if (root == NULL) @@ -467,11 +469,8 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) err: spin_lock(&fuse_lock); - mount_count --; - fc->sb = NULL; fuse_release_conn(fc); spin_unlock(&fuse_lock); - *get_fuse_conn_super_p(sb) = NULL; return err; } -- cgit v1.2.3-70-g09d2 From db50b96c0f28a21c5a4a19ecaba12d0972aab06a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:33 -0700 Subject: [PATCH] FUSE - readpages operation This patch adds readpages support to FUSE. With the help of the readpages() operation multiple reads are bundled together and sent as a single request to userspace. This can improve reading performace. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/file.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 3 +++ fs/fuse/inode.c | 15 +++++++++++++ 3 files changed, 85 insertions(+) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 96ea302db18..86ffb6db5fe 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -227,6 +227,72 @@ static int fuse_readpage(struct file *file, struct page *page) return err; } +static int fuse_send_readpages(struct fuse_req *req, struct file *file, + struct inode *inode) +{ + loff_t pos = (loff_t) req->pages[0]->index << PAGE_CACHE_SHIFT; + size_t count = req->num_pages << PAGE_CACHE_SHIFT; + unsigned i; + req->out.page_zeroing = 1; + fuse_send_read(req, file, inode, pos, count); + for (i = 0; i < req->num_pages; i++) { + struct page *page = req->pages[i]; + if (!req->out.h.error) + SetPageUptodate(page); + unlock_page(page); + } + return req->out.h.error; +} + +struct fuse_readpages_data { + struct fuse_req *req; + struct file *file; + struct inode *inode; +}; + +static int fuse_readpages_fill(void *_data, struct page *page) +{ + struct fuse_readpages_data *data = _data; + struct fuse_req *req = data->req; + struct inode *inode = data->inode; + struct fuse_conn *fc = get_fuse_conn(inode); + + if (req->num_pages && + (req->num_pages == FUSE_MAX_PAGES_PER_REQ || + (req->num_pages + 1) * PAGE_CACHE_SIZE > fc->max_read || + req->pages[req->num_pages - 1]->index + 1 != page->index)) { + int err = fuse_send_readpages(req, data->file, inode); + if (err) { + unlock_page(page); + return err; + } + fuse_reset_request(req); + } + req->pages[req->num_pages] = page; + req->num_pages ++; + return 0; +} + +static int fuse_readpages(struct file *file, struct address_space *mapping, + struct list_head *pages, unsigned nr_pages) +{ + struct inode *inode = mapping->host; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_readpages_data data; + int err; + data.file = file; + data.inode = inode; + data.req = fuse_get_request_nonint(fc); + if (!data.req) + return -EINTR; + + err = read_cache_pages(mapping, pages, fuse_readpages_fill, &data); + if (!err && data.req->num_pages) + err = fuse_send_readpages(data.req, file, inode); + fuse_put_request(fc, data.req); + return err; +} + static ssize_t fuse_send_write(struct fuse_req *req, struct file *file, struct inode *inode, loff_t pos, size_t count) { @@ -331,6 +397,7 @@ static struct address_space_operations fuse_file_aops = { .readpage = fuse_readpage, .prepare_write = fuse_prepare_write, .commit_write = fuse_commit_write, + .readpages = fuse_readpages, .set_page_dirty = fuse_set_page_dirty, }; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 86183c56210..aff3a01ea02 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -201,6 +201,9 @@ struct fuse_conn { /** The fuse mount flags for this mount */ unsigned flags; + /** Maximum read size */ + unsigned max_read; + /** Readers of the connection are waiting on this */ wait_queue_head_t waitq; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 458c62ca0fe..0b75c73386e 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -32,6 +32,7 @@ struct fuse_mount_data { unsigned rootmode; unsigned user_id; unsigned flags; + unsigned max_read; }; static struct inode *fuse_alloc_inode(struct super_block *sb) @@ -250,6 +251,7 @@ enum { OPT_DEFAULT_PERMISSIONS, OPT_ALLOW_OTHER, OPT_KERNEL_CACHE, + OPT_MAX_READ, OPT_ERR }; @@ -260,6 +262,7 @@ static match_table_t tokens = { {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ALLOW_OTHER, "allow_other"}, {OPT_KERNEL_CACHE, "kernel_cache"}, + {OPT_MAX_READ, "max_read=%u"}, {OPT_ERR, NULL} }; @@ -268,6 +271,7 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) char *p; memset(d, 0, sizeof(struct fuse_mount_data)); d->fd = -1; + d->max_read = ~0; while ((p = strsep(&opt, ",")) != NULL) { int token; @@ -308,6 +312,12 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->flags |= FUSE_KERNEL_CACHE; break; + case OPT_MAX_READ: + if (match_int(&args[0], &value)) + return 0; + d->max_read = value; + break; + default: return 0; } @@ -329,6 +339,8 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) seq_puts(m, ",allow_other"); if (fc->flags & FUSE_KERNEL_CACHE) seq_puts(m, ",kernel_cache"); + if (fc->max_read != ~0) + seq_printf(m, ",max_read=%u", fc->max_read); return 0; } @@ -453,6 +465,9 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) fc->flags = d.flags; fc->user_id = d.user_id; + fc->max_read = d.max_read; + if (fc->max_read / PAGE_CACHE_SIZE < fc->bdi.ra_pages) + fc->bdi.ra_pages = fc->max_read / PAGE_CACHE_SIZE; err = -ENOMEM; root = get_root_inode(sb, d.rootmode); -- cgit v1.2.3-70-g09d2 From 87729a5514e855ce2c71e3e33833a106b8caf2ae Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:34 -0700 Subject: [PATCH] FUSE: tighten check for processes allowed access This patch tightens the check for allowing processes to access non-privileged mounts. The rational is that the filesystem implementation can control the behavior or get otherwise unavailable information of the filesystem user. If the filesystem user process has the same uid, gid, and is not suid or sgid application, then access is safe. Otherwise access is not allowed unless the "allow_other" mount option is given (for which policy is controlled by the userspace mount utility). Thanks to everyone linux-fsdevel, especially Martin Mares who helped uncover problems with the previous approach. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/dir.c | 40 ++++++++++++++++++++++++++++++++++------ fs/fuse/fuse_i.h | 3 +++ fs/fuse/inode.c | 12 ++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f127625543b..65da6e1b6de 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -411,17 +411,45 @@ int fuse_do_getattr(struct inode *inode) return err; } +/* + * Calling into a user-controlled filesystem gives the filesystem + * daemon ptrace-like capabilities over the requester process. This + * means, that the filesystem daemon is able to record the exact + * filesystem operations performed, and can also control the behavior + * of the requester process in otherwise impossible ways. For example + * it can delay the operation for arbitrary length of time allowing + * DoS against the requester. + * + * For this reason only those processes can call into the filesystem, + * for which the owner of the mount has ptrace privilege. This + * excludes processes started by other users, suid or sgid processes. + */ +static int fuse_allow_task(struct fuse_conn *fc, struct task_struct *task) +{ + if (fc->flags & FUSE_ALLOW_OTHER) + return 1; + + if (task->euid == fc->user_id && + task->suid == fc->user_id && + task->uid == fc->user_id && + task->egid == fc->group_id && + task->sgid == fc->group_id && + task->gid == fc->group_id) + return 1; + + return 0; +} + static int fuse_revalidate(struct dentry *entry) { struct inode *inode = entry->d_inode; struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_conn *fc = get_fuse_conn(inode); - if (get_node_id(inode) == FUSE_ROOT_ID) { - if (!(fc->flags & FUSE_ALLOW_OTHER) && - current->fsuid != fc->user_id) - return -EACCES; - } else if (time_before_eq(jiffies, fi->i_time)) + if (!fuse_allow_task(fc, current)) + return -EACCES; + if (get_node_id(inode) != FUSE_ROOT_ID && + time_before_eq(jiffies, fi->i_time)) return 0; return fuse_do_getattr(inode); @@ -431,7 +459,7 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) { struct fuse_conn *fc = get_fuse_conn(inode); - if (!(fc->flags & FUSE_ALLOW_OTHER) && current->fsuid != fc->user_id) + if (!fuse_allow_task(fc, current)) return -EACCES; else if (fc->flags & FUSE_DEFAULT_PERMISSIONS) { int err = generic_permission(inode, mask, NULL); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index aff3a01ea02..3ec2aff3fdb 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -198,6 +198,9 @@ struct fuse_conn { /** The user id for this mount */ uid_t user_id; + /** The group id for this mount */ + gid_t group_id; + /** The fuse mount flags for this mount */ unsigned flags; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 0b75c73386e..c8e54c0658f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -31,6 +31,7 @@ struct fuse_mount_data { int fd; unsigned rootmode; unsigned user_id; + unsigned group_id; unsigned flags; unsigned max_read; }; @@ -199,6 +200,7 @@ static void fuse_put_super(struct super_block *sb) spin_lock(&fuse_lock); fc->mounted = 0; fc->user_id = 0; + fc->group_id = 0; fc->flags = 0; /* Flush all readers on this fs */ wake_up_all(&fc->waitq); @@ -248,6 +250,7 @@ enum { OPT_FD, OPT_ROOTMODE, OPT_USER_ID, + OPT_GROUP_ID, OPT_DEFAULT_PERMISSIONS, OPT_ALLOW_OTHER, OPT_KERNEL_CACHE, @@ -259,6 +262,7 @@ static match_table_t tokens = { {OPT_FD, "fd=%u"}, {OPT_ROOTMODE, "rootmode=%o"}, {OPT_USER_ID, "user_id=%u"}, + {OPT_GROUP_ID, "group_id=%u"}, {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ALLOW_OTHER, "allow_other"}, {OPT_KERNEL_CACHE, "kernel_cache"}, @@ -300,6 +304,12 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->user_id = value; break; + case OPT_GROUP_ID: + if (match_int(&args[0], &value)) + return 0; + d->group_id = value; + break; + case OPT_DEFAULT_PERMISSIONS: d->flags |= FUSE_DEFAULT_PERMISSIONS; break; @@ -333,6 +343,7 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) struct fuse_conn *fc = get_fuse_conn_super(mnt->mnt_sb); seq_printf(m, ",user_id=%u", fc->user_id); + seq_printf(m, ",group_id=%u", fc->group_id); if (fc->flags & FUSE_DEFAULT_PERMISSIONS) seq_puts(m, ",default_permissions"); if (fc->flags & FUSE_ALLOW_OTHER) @@ -465,6 +476,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) fc->flags = d.flags; fc->user_id = d.user_id; + fc->group_id = d.group_id; fc->max_read = d.max_read; if (fc->max_read / PAGE_CACHE_SIZE < fc->bdi.ra_pages) fc->bdi.ra_pages = fc->max_read / PAGE_CACHE_SIZE; -- cgit v1.2.3-70-g09d2 From 5a53368277efa2d80dd2206dddc1f4b19ef0c32a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:34 -0700 Subject: [PATCH] fuse: stricter mount option checking Check for the presence of all mandatory mount options. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/inode.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index c8e54c0658f..298c1d4c153 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -32,6 +32,10 @@ struct fuse_mount_data { unsigned rootmode; unsigned user_id; unsigned group_id; + unsigned fd_present : 1; + unsigned rootmode_present : 1; + unsigned user_id_present : 1; + unsigned group_id_present : 1; unsigned flags; unsigned max_read; }; @@ -274,7 +278,6 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) { char *p; memset(d, 0, sizeof(struct fuse_mount_data)); - d->fd = -1; d->max_read = ~0; while ((p = strsep(&opt, ",")) != NULL) { @@ -290,24 +293,28 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) if (match_int(&args[0], &value)) return 0; d->fd = value; + d->fd_present = 1; break; case OPT_ROOTMODE: if (match_octal(&args[0], &value)) return 0; d->rootmode = value; + d->rootmode_present = 1; break; case OPT_USER_ID: if (match_int(&args[0], &value)) return 0; d->user_id = value; + d->user_id_present = 1; break; case OPT_GROUP_ID: if (match_int(&args[0], &value)) return 0; d->group_id = value; + d->group_id_present = 1; break; case OPT_DEFAULT_PERMISSIONS: @@ -332,7 +339,9 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) return 0; } } - if (d->fd == -1) + + if (!d->fd_present || !d->rootmode_present || + !d->user_id_present || !d->group_id_present) return 0; return 1; -- cgit v1.2.3-70-g09d2 From 413ef8cb302511d8e995e2b0e5517ee1a65b9c77 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:35 -0700 Subject: [PATCH] FUSE - direct I/O This patch adds support for the "direct_io" mount option of FUSE. When this mount option is specified, the page cache is bypassed for read and write operations. This is useful for example, if the filesystem doesn't know the size of files before reading them, or when any kind of caching is harmful. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/file.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- fs/fuse/fuse_i.h | 6 +++ fs/fuse/inode.c | 9 ++++ 3 files changed, 146 insertions(+), 2 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 86ffb6db5fe..6bc3fb26de3 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -363,6 +363,118 @@ static int fuse_commit_write(struct file *file, struct page *page, return err; } +static void fuse_release_user_pages(struct fuse_req *req, int write) +{ + unsigned i; + + for (i = 0; i < req->num_pages; i++) { + struct page *page = req->pages[i]; + if (write) + set_page_dirty_lock(page); + put_page(page); + } +} + +static int fuse_get_user_pages(struct fuse_req *req, const char __user *buf, + unsigned nbytes, int write) +{ + unsigned long user_addr = (unsigned long) buf; + unsigned offset = user_addr & ~PAGE_MASK; + int npages; + + /* This doesn't work with nfsd */ + if (!current->mm) + return -EPERM; + + nbytes = min(nbytes, (unsigned) FUSE_MAX_PAGES_PER_REQ << PAGE_SHIFT); + npages = (nbytes + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; + npages = min(npages, FUSE_MAX_PAGES_PER_REQ); + down_read(¤t->mm->mmap_sem); + npages = get_user_pages(current, current->mm, user_addr, npages, write, + 0, req->pages, NULL); + up_read(¤t->mm->mmap_sem); + if (npages < 0) + return npages; + + req->num_pages = npages; + req->page_offset = offset; + return 0; +} + +static ssize_t fuse_direct_io(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, int write) +{ + struct inode *inode = file->f_dentry->d_inode; + struct fuse_conn *fc = get_fuse_conn(inode); + size_t nmax = write ? fc->max_write : fc->max_read; + loff_t pos = *ppos; + ssize_t res = 0; + struct fuse_req *req = fuse_get_request(fc); + if (!req) + return -ERESTARTSYS; + + while (count) { + size_t tmp; + size_t nres; + size_t nbytes = min(count, nmax); + int err = fuse_get_user_pages(req, buf, nbytes, !write); + if (err) { + res = err; + break; + } + tmp = (req->num_pages << PAGE_SHIFT) - req->page_offset; + nbytes = min(nbytes, tmp); + if (write) + nres = fuse_send_write(req, file, inode, pos, nbytes); + else + nres = fuse_send_read(req, file, inode, pos, nbytes); + fuse_release_user_pages(req, !write); + if (req->out.h.error) { + if (!res) + res = req->out.h.error; + break; + } else if (nres > nbytes) { + res = -EIO; + break; + } + count -= nres; + res += nres; + pos += nres; + buf += nres; + if (nres != nbytes) + break; + if (count) + fuse_reset_request(req); + } + fuse_put_request(fc, req); + if (res > 0) { + if (write && pos > i_size_read(inode)) + i_size_write(inode, pos); + *ppos = pos; + } else if (write && (res == -EINTR || res == -EIO)) + fuse_invalidate_attr(inode); + + return res; +} + +static ssize_t fuse_direct_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return fuse_direct_io(file, buf, count, ppos, 0); +} + +static ssize_t fuse_direct_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode = file->f_dentry->d_inode; + ssize_t res; + /* Don't allow parallel writes to the same file */ + down(&inode->i_sem); + res = fuse_direct_io(file, buf, count, ppos, 1); + up(&inode->i_sem); + return res; +} + static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) { if ((vma->vm_flags & VM_SHARED)) { @@ -393,6 +505,17 @@ static struct file_operations fuse_file_operations = { .sendfile = generic_file_sendfile, }; +static struct file_operations fuse_direct_io_file_operations = { + .llseek = generic_file_llseek, + .read = fuse_direct_read, + .write = fuse_direct_write, + .open = fuse_open, + .flush = fuse_flush, + .release = fuse_release, + .fsync = fuse_fsync, + /* no mmap and sendfile */ +}; + static struct address_space_operations fuse_file_aops = { .readpage = fuse_readpage, .prepare_write = fuse_prepare_write, @@ -403,6 +526,12 @@ static struct address_space_operations fuse_file_aops = { void fuse_init_file_inode(struct inode *inode) { - inode->i_fop = &fuse_file_operations; - inode->i_data.a_ops = &fuse_file_aops; + struct fuse_conn *fc = get_fuse_conn(inode); + + if (fc->flags & FUSE_DIRECT_IO) + inode->i_fop = &fuse_direct_io_file_operations; + else { + inode->i_fop = &fuse_file_operations; + inode->i_data.a_ops = &fuse_file_aops; + } } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 3ec2aff3fdb..0af1ac64692 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -34,6 +34,9 @@ be flushed on open */ #define FUSE_KERNEL_CACHE (1 << 2) +/** Bypass the page cache for read and write operations */ +#define FUSE_DIRECT_IO (1 << 3) + /** FUSE inode */ struct fuse_inode { /** Inode data */ @@ -207,6 +210,9 @@ struct fuse_conn { /** Maximum read size */ unsigned max_read; + /** Maximum write size */ + unsigned max_write; + /** Readers of the connection are waiting on this */ wait_queue_head_t waitq; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 298c1d4c153..652c9d5df97 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -258,6 +258,7 @@ enum { OPT_DEFAULT_PERMISSIONS, OPT_ALLOW_OTHER, OPT_KERNEL_CACHE, + OPT_DIRECT_IO, OPT_MAX_READ, OPT_ERR }; @@ -270,6 +271,7 @@ static match_table_t tokens = { {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ALLOW_OTHER, "allow_other"}, {OPT_KERNEL_CACHE, "kernel_cache"}, + {OPT_DIRECT_IO, "direct_io"}, {OPT_MAX_READ, "max_read=%u"}, {OPT_ERR, NULL} }; @@ -329,6 +331,10 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->flags |= FUSE_KERNEL_CACHE; break; + case OPT_DIRECT_IO: + d->flags |= FUSE_DIRECT_IO; + break; + case OPT_MAX_READ: if (match_int(&args[0], &value)) return 0; @@ -359,6 +365,8 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) seq_puts(m, ",allow_other"); if (fc->flags & FUSE_KERNEL_CACHE) seq_puts(m, ",kernel_cache"); + if (fc->flags & FUSE_DIRECT_IO) + seq_puts(m, ",direct_io"); if (fc->max_read != ~0) seq_printf(m, ",max_read=%u", fc->max_read); return 0; @@ -489,6 +497,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) fc->max_read = d.max_read; if (fc->max_read / PAGE_CACHE_SIZE < fc->bdi.ra_pages) fc->bdi.ra_pages = fc->max_read / PAGE_CACHE_SIZE; + fc->max_write = FUSE_MAX_IN / 2; err = -ENOMEM; root = get_root_inode(sb, d.rootmode); -- cgit v1.2.3-70-g09d2 From 45323fb76465a9576220c7427dbac7b1e7ad3caf Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:37 -0700 Subject: [PATCH] fuse: more flexible caching Make data caching behavior selectable on a per-open basis instead of per-mount. Compatibility for the old mount options 'kernel_cache' and 'direct_io' is retained in the userspace library (version 2.4.0-pre1 or later). Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- Documentation/filesystems/fuse.txt | 26 -------------------------- fs/fuse/file.c | 18 ++++++++---------- fs/fuse/fuse_i.h | 6 ------ fs/fuse/inode.c | 16 ---------------- include/linux/fuse.h | 11 ++++++++++- 5 files changed, 18 insertions(+), 59 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/Documentation/filesystems/fuse.txt b/Documentation/filesystems/fuse.txt index 83f96cf5696..6b5741e651a 100644 --- a/Documentation/filesystems/fuse.txt +++ b/Documentation/filesystems/fuse.txt @@ -80,32 +80,6 @@ Mount options allowed to root, but this restriction can be removed with a (userspace) configuration option. -'kernel_cache' - - This option disables flushing the cache of the file contents on - every open(). This should only be enabled on filesystems, where the - file data is never changed externally (not through the mounted FUSE - filesystem). Thus it is not suitable for network filesystems and - other "intermediate" filesystems. - - NOTE: if this option is not specified (and neither 'direct_io') data - is still cached after the open(), so a read() system call will not - always initiate a read operation. - -'direct_io' - - This option disables the use of page cache (file content cache) in - the kernel for this filesystem. This has several affects: - - - Each read() or write() system call will initiate one or more - read or write operations, data will not be cached in the - kernel. - - - The return value of the read() and write() system calls will - correspond to the return values of the read and write - operations. This is useful for example if the file size is not - known in advance (before reading it). - 'max_read=N' With this option the maximum size of read operations can be set. diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 224453557cf..a8dc88527fb 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -12,6 +12,8 @@ #include #include +static struct file_operations fuse_direct_io_file_operations; + int fuse_open_common(struct inode *inode, struct file *file, int isdir) { struct fuse_conn *fc = get_fuse_conn(inode); @@ -70,12 +72,14 @@ int fuse_open_common(struct inode *inode, struct file *file, int isdir) else request_send(fc, req); err = req->out.h.error; - if (!err && !(fc->flags & FUSE_KERNEL_CACHE)) - invalidate_inode_pages(inode->i_mapping); if (err) { fuse_request_free(ff->release_req); kfree(ff); } else { + if (!isdir && (outarg.open_flags & FOPEN_DIRECT_IO)) + file->f_op = &fuse_direct_io_file_operations; + if (!(outarg.open_flags & FOPEN_KEEP_CACHE)) + invalidate_inode_pages(inode->i_mapping); ff->fh = outarg.fh; file->private_data = ff; } @@ -544,12 +548,6 @@ static struct address_space_operations fuse_file_aops = { void fuse_init_file_inode(struct inode *inode) { - struct fuse_conn *fc = get_fuse_conn(inode); - - if (fc->flags & FUSE_DIRECT_IO) - inode->i_fop = &fuse_direct_io_file_operations; - else { - inode->i_fop = &fuse_file_operations; - inode->i_data.a_ops = &fuse_file_aops; - } + inode->i_fop = &fuse_file_operations; + inode->i_data.a_ops = &fuse_file_aops; } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 8593d5bae7a..84849601363 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -30,12 +30,6 @@ doing the mount will be allowed to access the filesystem */ #define FUSE_ALLOW_OTHER (1 << 1) -/** If the FUSE_KERNEL_CACHE flag is given, then cached data will not - be flushed on open */ -#define FUSE_KERNEL_CACHE (1 << 2) - -/** Bypass the page cache for read and write operations */ -#define FUSE_DIRECT_IO (1 << 3) /** FUSE inode */ struct fuse_inode { diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 652c9d5df97..8dc66760b41 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -257,8 +257,6 @@ enum { OPT_GROUP_ID, OPT_DEFAULT_PERMISSIONS, OPT_ALLOW_OTHER, - OPT_KERNEL_CACHE, - OPT_DIRECT_IO, OPT_MAX_READ, OPT_ERR }; @@ -270,8 +268,6 @@ static match_table_t tokens = { {OPT_GROUP_ID, "group_id=%u"}, {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ALLOW_OTHER, "allow_other"}, - {OPT_KERNEL_CACHE, "kernel_cache"}, - {OPT_DIRECT_IO, "direct_io"}, {OPT_MAX_READ, "max_read=%u"}, {OPT_ERR, NULL} }; @@ -327,14 +323,6 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->flags |= FUSE_ALLOW_OTHER; break; - case OPT_KERNEL_CACHE: - d->flags |= FUSE_KERNEL_CACHE; - break; - - case OPT_DIRECT_IO: - d->flags |= FUSE_DIRECT_IO; - break; - case OPT_MAX_READ: if (match_int(&args[0], &value)) return 0; @@ -363,10 +351,6 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) seq_puts(m, ",default_permissions"); if (fc->flags & FUSE_ALLOW_OTHER) seq_puts(m, ",allow_other"); - if (fc->flags & FUSE_KERNEL_CACHE) - seq_puts(m, ",kernel_cache"); - if (fc->flags & FUSE_DIRECT_IO) - seq_puts(m, ",direct_io"); if (fc->max_read != ~0) seq_printf(m, ",max_read=%u", fc->max_read); return 0; diff --git a/include/linux/fuse.h b/include/linux/fuse.h index cdfaa51b901..c65124a213a 100644 --- a/include/linux/fuse.h +++ b/include/linux/fuse.h @@ -14,7 +14,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 1 +#define FUSE_KERNEL_MINOR_VERSION 2 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -63,6 +63,15 @@ struct fuse_kstatfs { #define FATTR_MTIME (1 << 5) #define FATTR_CTIME (1 << 6) +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) + enum fuse_opcode { FUSE_LOOKUP = 1, FUSE_FORGET = 2, /* no reply */ -- cgit v1.2.3-70-g09d2 From b36c31ba95f0fe0a03c727300d9c4c54438a5636 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:38 -0700 Subject: [PATCH] fuse: don't update file times Don't change mtime/ctime/atime to local time on read/write. Rather invalidate file attributes, so next stat() will force a GETATTR call. Bug reported by Ben Grimm. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/dir.c | 2 ++ fs/fuse/file.c | 10 ++++++---- fs/fuse/inode.c | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index cf5d1faed7a..9b43fd46aaa 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -552,6 +552,7 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) filldir); __free_page(page); + fuse_invalidate_attr(inode); /* atime changed */ return err; } @@ -585,6 +586,7 @@ static char *read_link(struct dentry *dentry) link[req->out.args[0].size] = '\0'; out: fuse_put_request(fc, req); + fuse_invalidate_attr(inode); /* atime changed */ return link; } diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a8dc88527fb..6dcae74ce7f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -244,6 +244,7 @@ static int fuse_readpage(struct file *file, struct page *page) fuse_put_request(fc, req); if (!err) SetPageUptodate(page); + fuse_invalidate_attr(inode); /* atime changed */ out: unlock_page(page); return err; @@ -312,6 +313,7 @@ static int fuse_readpages(struct file *file, struct address_space *mapping, if (!err && data.req->num_pages) err = fuse_send_readpages(data.req, file, inode); fuse_put_request(fc, data.req); + fuse_invalidate_attr(inode); /* atime changed */ return err; } @@ -380,8 +382,8 @@ static int fuse_commit_write(struct file *file, struct page *page, clear_page_dirty(page); SetPageUptodate(page); } - } else if (err == -EINTR || err == -EIO) - fuse_invalidate_attr(inode); + } + fuse_invalidate_attr(inode); return err; } @@ -473,8 +475,8 @@ static ssize_t fuse_direct_io(struct file *file, const char __user *buf, if (write && pos > i_size_read(inode)) i_size_write(inode, pos); *ppos = pos; - } else if (write && (res == -EINTR || res == -EIO)) - fuse_invalidate_attr(inode); + } + fuse_invalidate_attr(inode); return res; } diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8dc66760b41..52e954f4bb9 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -173,6 +173,7 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, return NULL; if ((inode->i_state & I_NEW)) { + inode->i_flags |= S_NOATIME|S_NOCMTIME; inode->i_generation = generation; inode->i_data.backing_dev_info = &fc->bdi; fuse_init_inode(inode, attr); -- cgit v1.2.3-70-g09d2 From 7c352bdf048811b8128019ffc1e886161e09c11c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Fri, 9 Sep 2005 13:10:39 -0700 Subject: [PATCH] FUSE: don't allow restarting of system calls This patch removes ability to interrupt and restart operations while there hasn't been any side-effect. The reason: applications. There are some apps it seems that generate signals at a fast rate. This means, that if the operation cannot make enough progress between two signals, it will be restarted for ever. This bug actually manifested itself with 'krusader' trying to open a file for writing under sshfs. Thanks to Eduard Czimbalmos for the report. The problem can be solved just by making open() uninterruptible, because in this case it was the truncate operation that slowed down the progress. But it's better to solve this by simply not allowing interrupts at all (except SIGKILL), because applications don't expect file operations to be interruptible anyway. As an added bonus the code is simplified somewhat. Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- fs/fuse/dev.c | 73 ++++++++++---------------------------------------------- fs/fuse/dir.c | 36 ++++++++++++++-------------- fs/fuse/file.c | 33 ++++++++++--------------- fs/fuse/fuse_i.h | 12 +--------- fs/fuse/inode.c | 2 +- 5 files changed, 45 insertions(+), 111 deletions(-) (limited to 'fs/fuse/inode.c') diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index e4ada021d08..d4c869c6d01 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -103,19 +103,8 @@ static struct fuse_req *do_get_request(struct fuse_conn *fc) return req; } +/* This can return NULL, but only in case it's interrupted by a SIGKILL */ struct fuse_req *fuse_get_request(struct fuse_conn *fc) -{ - if (down_interruptible(&fc->outstanding_sem)) - return NULL; - return do_get_request(fc); -} - -/* - * Non-interruptible version of the above function is for operations - * which can't legally return -ERESTART{SYS,NOINTR}. This can still - * return NULL, but only in case the signal is SIGKILL. - */ -struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc) { int intr; sigset_t oldset; @@ -241,43 +230,20 @@ static void background_request(struct fuse_conn *fc, struct fuse_req *req) get_file(req->file); } -static int request_wait_answer_nonint(struct fuse_req *req) -{ - int err; - sigset_t oldset; - block_sigs(&oldset); - err = wait_event_interruptible(req->waitq, req->finished); - restore_sigs(&oldset); - return err; -} - /* Called with fuse_lock held. Releases, and then reacquires it. */ -static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req, - int interruptible) +static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req) { - int intr; + sigset_t oldset; spin_unlock(&fuse_lock); - if (interruptible) - intr = wait_event_interruptible(req->waitq, req->finished); - else - intr = request_wait_answer_nonint(req); + block_sigs(&oldset); + wait_event_interruptible(req->waitq, req->finished); + restore_sigs(&oldset); spin_lock(&fuse_lock); - if (intr && interruptible && req->sent) { - /* If request is already in userspace, only allow KILL - signal to interrupt */ - spin_unlock(&fuse_lock); - intr = request_wait_answer_nonint(req); - spin_lock(&fuse_lock); - } - if (!intr) + if (req->finished) return; - if (!interruptible || req->sent) - req->out.h.error = -EINTR; - else - req->out.h.error = -ERESTARTNOINTR; - + req->out.h.error = -EINTR; req->interrupted = 1; if (req->locked) { /* This is uninterruptible sleep, because data is @@ -330,8 +296,10 @@ static void queue_request(struct fuse_conn *fc, struct fuse_req *req) wake_up(&fc->waitq); } -static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req, - int interruptible) +/* + * This can only be interrupted by a SIGKILL + */ +void request_send(struct fuse_conn *fc, struct fuse_req *req) { req->isreply = 1; spin_lock(&fuse_lock); @@ -345,26 +313,11 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req, after request_end() */ __fuse_get_request(req); - request_wait_answer(fc, req, interruptible); + request_wait_answer(fc, req); } spin_unlock(&fuse_lock); } -void request_send(struct fuse_conn *fc, struct fuse_req *req) -{ - request_send_wait(fc, req, 1); -} - -/* - * Non-interruptible version of the above function is for operations - * which can't legally return -ERESTART{SYS,NOINTR}. This can still - * be interrupted but only with SIGKILL. - */ -void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req) -{ - request_send_wait(fc, req, 0); -} - static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req) { spin_lock(&fuse_lock); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 73792d65b6c..e79e49b3eec 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -46,12 +46,12 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) struct inode *inode = entry->d_inode; struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_req *req = fuse_get_request_nonint(fc); + struct fuse_req *req = fuse_get_request(fc); if (!req) return 0; fuse_lookup_init(req, entry->d_parent->d_inode, entry, &outarg); - request_send_nonint(fc, req); + request_send(fc, req); err = req->out.h.error; if (!err) { if (outarg.nodeid != get_node_id(inode)) { @@ -91,7 +91,7 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; fuse_lookup_init(req, dir, entry, &outarg); request_send(fc, req); @@ -185,7 +185,7 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode, struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; @@ -211,7 +211,7 @@ static int fuse_mkdir(struct inode *dir, struct dentry *entry, int mode) struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; @@ -236,7 +236,7 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry, req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; req->in.h.opcode = FUSE_SYMLINK; req->in.numargs = 2; @@ -253,7 +253,7 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry) struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; req->in.h.opcode = FUSE_UNLINK; req->in.h.nodeid = get_node_id(dir); @@ -284,7 +284,7 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) struct fuse_conn *fc = get_fuse_conn(dir); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; req->in.h.opcode = FUSE_RMDIR; req->in.h.nodeid = get_node_id(dir); @@ -311,7 +311,7 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, struct fuse_conn *fc = get_fuse_conn(olddir); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.newdir = get_node_id(newdir); @@ -356,7 +356,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.oldnodeid = get_node_id(inode); @@ -386,7 +386,7 @@ int fuse_do_getattr(struct inode *inode) struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; req->in.h.opcode = FUSE_GETATTR; req->in.h.nodeid = get_node_id(inode); @@ -533,7 +533,7 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) struct page *page; struct inode *inode = file->f_dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_req *req = fuse_get_request_nonint(fc); + struct fuse_req *req = fuse_get_request(fc); if (!req) return -EINTR; @@ -564,7 +564,7 @@ static char *read_link(struct dentry *dentry) char *link; if (!req) - return ERR_PTR(-ERESTARTNOINTR); + return ERR_PTR(-EINTR); link = (char *) __get_free_page(GFP_KERNEL); if (!link) { @@ -677,7 +677,7 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.valid = iattr_to_fattr(attr, &inarg.attr); @@ -761,7 +761,7 @@ static int fuse_setxattr(struct dentry *entry, const char *name, req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; @@ -801,7 +801,7 @@ static ssize_t fuse_getxattr(struct dentry *entry, const char *name, req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; @@ -851,7 +851,7 @@ static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size) req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; @@ -897,7 +897,7 @@ static int fuse_removexattr(struct dentry *entry, const char *name) req = fuse_get_request(fc); if (!req) - return -ERESTARTNOINTR; + return -EINTR; req->in.h.opcode = FUSE_REMOVEXATTR; req->in.h.nodeid = get_node_id(inode); diff --git a/fs/fuse/file.c b/fs/fuse/file.c index e225f8c0b26..6454022b053 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -22,9 +22,6 @@ int fuse_open_common(struct inode *inode, struct file *file, int isdir) struct fuse_open_out outarg; struct fuse_file *ff; int err; - /* Restarting the syscall is not allowed if O_CREAT and O_EXCL - are both set, because creation will fail on the restart */ - int excl = (file->f_flags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL); err = generic_file_open(inode, file); if (err) @@ -38,12 +35,9 @@ int fuse_open_common(struct inode *inode, struct file *file, int isdir) return err; } - if (excl) - req = fuse_get_request_nonint(fc); - else - req = fuse_get_request(fc); + req = fuse_get_request(fc); if (!req) - return excl ? -EINTR : -ERESTARTSYS; + return -EINTR; err = -ENOMEM; ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL); @@ -67,10 +61,7 @@ int fuse_open_common(struct inode *inode, struct file *file, int isdir) req->out.numargs = 1; req->out.args[0].size = sizeof(outarg); req->out.args[0].value = &outarg; - if (excl) - request_send_nonint(fc, req); - else - request_send(fc, req); + request_send(fc, req); err = req->out.h.error; if (err) { fuse_request_free(ff->release_req); @@ -133,7 +124,7 @@ static int fuse_flush(struct file *file) if (fc->no_flush) return 0; - req = fuse_get_request_nonint(fc); + req = fuse_get_request(fc); if (!req) return -EINTR; @@ -146,7 +137,7 @@ static int fuse_flush(struct file *file) req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; - request_send_nonint(fc, req); + request_send(fc, req); err = req->out.h.error; fuse_put_request(fc, req); if (err == -ENOSYS) { @@ -171,7 +162,7 @@ int fuse_fsync_common(struct file *file, struct dentry *de, int datasync, req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -EINTR; memset(&inarg, 0, sizeof(inarg)); inarg.fh = ff->fh; @@ -224,7 +215,7 @@ size_t fuse_send_read_common(struct fuse_req *req, struct file *file, req->out.argvar = 1; req->out.numargs = 1; req->out.args[0].size = count; - request_send_nonint(fc, req); + request_send(fc, req); return req->out.args[0].size; } @@ -240,7 +231,7 @@ static int fuse_readpage(struct file *file, struct page *page) struct inode *inode = page->mapping->host; struct fuse_conn *fc = get_fuse_conn(inode); loff_t pos = (loff_t) page->index << PAGE_CACHE_SHIFT; - struct fuse_req *req = fuse_get_request_nonint(fc); + struct fuse_req *req = fuse_get_request(fc); int err = -EINTR; if (!req) goto out; @@ -314,7 +305,7 @@ static int fuse_readpages(struct file *file, struct address_space *mapping, int err; data.file = file; data.inode = inode; - data.req = fuse_get_request_nonint(fc); + data.req = fuse_get_request(fc); if (!data.req) return -EINTR; @@ -350,7 +341,7 @@ static size_t fuse_send_write(struct fuse_req *req, struct file *file, req->out.numargs = 1; req->out.args[0].size = sizeof(struct fuse_write_out); req->out.args[0].value = &outarg; - request_send_nonint(fc, req); + request_send(fc, req); return outarg.size; } @@ -370,7 +361,7 @@ static int fuse_commit_write(struct file *file, struct page *page, struct inode *inode = page->mapping->host; struct fuse_conn *fc = get_fuse_conn(inode); loff_t pos = ((loff_t) page->index << PAGE_CACHE_SHIFT) + offset; - struct fuse_req *req = fuse_get_request_nonint(fc); + struct fuse_req *req = fuse_get_request(fc); if (!req) return -EINTR; @@ -444,7 +435,7 @@ static ssize_t fuse_direct_io(struct file *file, const char __user *buf, ssize_t res = 0; struct fuse_req *req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -EINTR; while (count) { size_t tmp; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index d7647289d8a..24d761518d8 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -409,11 +409,6 @@ void fuse_reset_request(struct fuse_req *req); */ struct fuse_req *fuse_get_request(struct fuse_conn *fc); -/** - * Reserve a preallocated request, only interruptible by SIGKILL - */ -struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc); - /** * Decrement reference count of a request. If count goes to zero put * on unused list (preallocated) or free reqest (not preallocated). @@ -421,15 +416,10 @@ struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc); void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req); /** - * Send a request (synchronous, interruptible) + * Send a request (synchronous) */ void request_send(struct fuse_conn *fc, struct fuse_req *req); -/** - * Send a request (synchronous, non-interruptible except by SIGKILL) - */ -void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req); - /** * Send a request with no reply */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 52e954f4bb9..e69a546844d 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -236,7 +236,7 @@ static int fuse_statfs(struct super_block *sb, struct kstatfs *buf) req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -EINTR; req->in.numargs = 0; req->in.h.opcode = FUSE_STATFS; -- cgit v1.2.3-70-g09d2