[PATCH] FUSE - mount options
authorMiklos Szeredi <miklos@szeredi.hu>
Fri, 9 Sep 2005 20:10:31 +0000 (13:10 -0700)
committerLinus Torvalds <torvalds@g5.osdl.org>
Fri, 9 Sep 2005 21:03:45 +0000 (14:03 -0700)
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 <miklos@szeredi.hu>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
fs/fuse/dev.c
fs/fuse/dir.c
fs/fuse/file.c
fs/fuse/fuse_i.h
fs/fuse/inode.c

index e8f3170..ca6fc0e 100644 (file)
@@ -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);
index 8adc1ee..0950455 100644 (file)
@@ -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;
index de8c9c7..96ea302 100644 (file)
@@ -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);
index b4aa8f7..c8e6c87 100644 (file)
 /** 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
@@ -372,6 +402,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
  */
 int fuse_do_getattr(struct inode *inode);
index f229d69..458c62c 100644 (file)
@@ -15,7 +15,6 @@
 #include <linux/seq_file.h>
 #include <linux/init.h>
 #include <linux/module.h>
-#include <linux/moduleparam.h>
 #include <linux/parser.h>
 #include <linux/statfs.h>
 
@@ -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;
 }