fix
authorMiklos Szeredi <miklos@szeredi.hu>
Thu, 11 Nov 2004 14:44:04 +0000 (14:44 +0000)
committerMiklos Szeredi <miklos@szeredi.hu>
Thu, 11 Nov 2004 14:44:04 +0000 (14:44 +0000)
ChangeLog
kernel/dev.c
kernel/fuse_i.h
kernel/inode.c

index 8134ae7d5f999a202144fda3680c8936f50201f2..5a785cb746932e1df059d936a3c73d16c436fbc1 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,14 @@
        * Check kernel interface version in fusermount to prevent
        strangeness in case of mismatch.
 
+       * No need to allocate fuse_conn until actual mount happens
+
+       * Fix potential race between umount and fuse_invalidate
+
+       * Check superblock of proc file in addition to inode number
+
+       * Fix reace between request_send_noreply() and fuse_dev_release()
+
 2004-11-10  Miklos Szeredi <miklos@szeredi.hu>
 
        * Separate configure for the kernel directory
index 71d306a7283be8d0de9658a73693d9e134b089f5..0c601d281506fe19394131c77d0c3ef79cdc3f14 100644 (file)
 #include <linux/proc_fs.h>
 #include <linux/file.h>
 
-/* If more requests are outstanding, then the operation will block */
-#define MAX_OUTSTANDING 10
-
 static struct proc_dir_entry *proc_fs_fuse;
 struct proc_dir_entry *proc_fuse_dev;
 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 = (struct fuse_conn *) file->private_data;
+       if (fc && !fc->sb)
+               fc = NULL;
+       spin_unlock(&fuse_lock);
+       return fc;
+}
+
 struct fuse_req *fuse_request_alloc(void)
 {
        struct fuse_req *req;
@@ -211,13 +219,15 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
 {
        req->issync = 0;
 
+       spin_lock(&fuse_lock);
        if (fc->file) {
-               spin_lock(&fuse_lock);
                list_add_tail(&req->list, &fc->pending);
                wake_up(&fc->waitq);
                spin_unlock(&fuse_lock);
-       } else
+       } else {
+               spin_unlock(&fuse_lock);
                fuse_put_request(fc, req);
+       }
 }
 
 void request_send_nonblock(struct fuse_conn *fc, struct fuse_req *req, 
@@ -244,7 +254,7 @@ static void request_wait(struct fuse_conn *fc)
        DECLARE_WAITQUEUE(wait, current);
 
        add_wait_queue_exclusive(&fc->waitq, &wait);
-       while (fc->sb != NULL && list_empty(&fc->pending)) {
+       while (fc->sb && list_empty(&fc->pending)) {
                set_current_state(TASK_INTERRUPTIBLE);
                if (signal_pending(current))
                        break;
@@ -298,18 +308,25 @@ static ssize_t fuse_dev_read(struct file *file, char *buf, size_t nbytes,
                             loff_t *off)
 {
        ssize_t ret;
-       struct fuse_conn *fc = DEV_FC(file);
+       struct fuse_conn *fc;
        struct fuse_req *req = NULL;
 
        spin_lock(&fuse_lock);
+       fc = (struct fuse_conn *) file->private_data;
+       if (!fc) {
+               spin_unlock(&fuse_lock);
+               return -EPERM;
+       }
        request_wait(fc);
-       if (fc->sb != NULL && !list_empty(&fc->pending)) {
+       if (!fc->sb)
+               fc = NULL;
+       else if (!list_empty(&fc->pending)) {
                req = list_entry(fc->pending.next, struct fuse_req, list);
                list_del_init(&req->list);
                req->locked = 1;
        }
        spin_unlock(&fuse_lock);
-       if (fc->sb == NULL)
+       if (!fc)
                return -ENODEV;
        if (req == NULL)
                return -EINTR;
@@ -437,18 +454,26 @@ static inline int copy_out_header(struct fuse_out_header *oh, const char *buf,
 
 static int fuse_invalidate(struct fuse_conn *fc, struct fuse_user_header *uh)
 {
-       struct inode *inode = fuse_ilookup(fc, uh->ino, uh->nodeid);
-       if (!inode)
-               return -ENOENT;
-       fuse_sync_inode(inode);
+       int err;
+       down(&fc->sb_sem);
+       err = -ENODEV;
+       if (fc->sb) {
+               struct inode *inode = fuse_ilookup(fc, uh->ino, uh->nodeid);
+               err = -ENOENT;
+               if (inode) {
+                       fuse_sync_inode(inode);
 #ifdef KERNEL_2_6
-       invalidate_inode_pages(inode->i_mapping);
+                       invalidate_inode_pages(inode->i_mapping);
 #else
-       invalidate_inode_pages(inode);
+                       invalidate_inode_pages(inode);
 #endif
+                       iput(inode);
+                       err = 0;
+               }
+       }
+       up(&fc->sb_sem);
 
-       iput(inode);
-       return 0;
+       return err;
 }
 
 static int fuse_user_request(struct fuse_conn *fc, const char *buf,
@@ -481,12 +506,12 @@ static ssize_t fuse_dev_write(struct file *file, const char *buf,
                              size_t nbytes, loff_t *off)
 {
        int err;
-       struct fuse_conn *fc = DEV_FC(file);
+       struct fuse_conn *fc = fuse_get_conn(file);
        struct fuse_req *req;
        struct fuse_out_header oh;
-
-       if (!fc->sb)
-               return -EPERM;
+       
+       if (!fc)
+               return -ENODEV;
 
        err = copy_out_header(&oh, buf, nbytes);
        if (err)
@@ -538,11 +563,11 @@ static ssize_t fuse_dev_write(struct file *file, const char *buf,
 
 static unsigned int fuse_dev_poll(struct file *file, poll_table *wait)
 {
-       struct fuse_conn *fc = DEV_FC(file);
+       struct fuse_conn *fc = fuse_get_conn(file);
        unsigned int mask = POLLOUT | POLLWRNORM;
 
-       if (!fc->sb)
-               return -EPERM;
+       if (!fc)
+               return -ENODEV;
 
        poll_wait(file, &fc->waitq, wait);
 
@@ -554,70 +579,6 @@ static unsigned int fuse_dev_poll(struct file *file, poll_table *wait)
        return mask;
 }
 
-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 == NULL && fc->file == NULL) {
-               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->flags = 0;
-               fc->uid = 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->unused_sem, MAX_OUTSTANDING);
-               for (i = 0; i < MAX_OUTSTANDING; i++) {
-                       struct fuse_req *req = fuse_request_alloc();
-                       if (!req) {
-                               free_conn(fc);
-                               return NULL;
-                       }
-                       req->preallocated = 1;
-                       list_add(&req->list, &fc->unused_list);
-               }
-               fc->reqctr = 1;
-       }
-       return fc;
-}
-
-static int fuse_dev_open(struct inode *inode, struct file *file)
-{
-       struct fuse_conn *fc;
-
-       fc = new_conn();
-       if (!fc)
-               return -ENOMEM;
-
-       fc->file = file;
-       file->private_data = fc;
-
-       return 0;
-}
-
 static void end_requests(struct fuse_conn *fc, struct list_head *head)
 {
        while (!list_empty(head)) {
@@ -640,13 +601,16 @@ static void end_requests(struct fuse_conn *fc, struct list_head *head)
 
 static int fuse_dev_release(struct inode *inode, struct file *file)
 {
-       struct fuse_conn *fc = DEV_FC(file);
+       struct fuse_conn *fc;
 
        spin_lock(&fuse_lock);
-       fc->file = NULL;
-       end_requests(fc, &fc->pending);
-       end_requests(fc, &fc->processing);
-       fuse_release_conn(fc);
+       fc = (struct fuse_conn *) 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;
 }
@@ -656,7 +620,6 @@ static struct file_operations fuse_dev_operations = {
        .read           = fuse_dev_read,
        .write          = fuse_dev_write,
        .poll           = fuse_dev_poll,
-       .open           = fuse_dev_open,
        .release        = fuse_dev_release,
 };
 
index 3c1897587942e5088f55678961040cbe5e1dd2c2..40dedb2ae6b55f52462ae91c3c3287301c0250b8 100644 (file)
 
 #define FUSE_MAX_PAGES_PER_REQ 32
 
+/* 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 */
@@ -225,6 +229,9 @@ struct fuse_conn {
        /** Controls the maximum number of outstanding requests */
        struct semaphore unused_sem;
 
+       /** Semaphore protecting the super block from going away */
+       struct semaphore sb_sem;
+
        /** The list of unused requests */
        struct list_head unused_list;
        
@@ -261,7 +268,6 @@ struct fuse_getdir_out_i {
 #define SB_FC(sb) ((sb)->u.generic_sbp)
 #endif
 #define INO_FC(inode) SB_FC((inode)->i_sb)
-#define DEV_FC(file) ((file)->private_data)
 #define INO_FI(i) ((struct fuse_inode *) (((struct inode *)(i))+1))
 
 
index 6ad42eed7c2ca5dc0c3033199652a13c9c5e8688..591a4df6593c0aa035d6db6c42ce05097211d89f 100644 (file)
@@ -126,8 +126,10 @@ static void fuse_put_super(struct super_block *sb)
 {
        struct fuse_conn *fc = SB_FC(sb);
 
+       down(&fc->sb_sem);
        spin_lock(&fuse_lock);
        fc->sb = NULL;
+       up(&fc->sb_sem);
        fc->uid = 0;
        fc->flags = 0;
        /* Flush all readers on this fs */
@@ -308,22 +310,85 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt)
        return 0;
 }
 
+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->flags = 0;
+               fc->uid = 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->unused_sem, FUSE_MAX_OUTSTANDING);
+               sema_init(&fc->sb_sem, 1);
+               for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) {
+                       struct fuse_req *req = fuse_request_alloc();
+                       if (!req) {
+                               free_conn(fc);
+                               return NULL;
+                       }
+                       req->preallocated = 1;
+                       list_add(&req->list, &fc->unused_list);
+               }
+               fc->reqctr = 1;
+       }
+       return fc;
+}
+
 static struct fuse_conn *get_conn(struct file *file, struct super_block *sb)
 {
        struct fuse_conn *fc;
        struct inode *ino;
 
        ino = file->f_dentry->d_inode;
-       if (!ino || !proc_fuse_dev || proc_fuse_dev->low_ino != ino->i_ino) {
+       if (!ino || !proc_fuse_dev || 
+           strcmp(ino->i_sb->s_type->name, "proc") != 0 ||
+           proc_fuse_dev->low_ino != ino->i_ino) {
                printk("FUSE: bad communication file descriptor\n");
                return NULL;
        }
-       fc = file->private_data;
-       if (fc->sb != NULL) {
-               printk("fuse_read_super: connection already mounted\n");
+       fc = new_conn();
+       if (fc == NULL) {
+               printk("FUSE: failed to allocate connection data\n");
                return NULL;
        }
-       fc->sb = sb;
+       spin_lock(&fuse_lock);
+       if (file->private_data) {
+               printk("fuse_read_super: connection already mounted\n");
+               free_conn(fc);
+               fc = NULL;
+       } else {
+               file->private_data = fc;
+               fc->sb = sb;
+               fc->file = file;
+       }
+       spin_unlock(&fuse_lock);
        return fc;
 }
 
@@ -407,9 +472,7 @@ static int fuse_read_super(struct super_block *sb, void *data, int silent)
        if (!file)
                return -EINVAL;
 
-       spin_lock(&fuse_lock);
        fc = get_conn(file, sb);
-       spin_unlock(&fuse_lock);
        fput(file);
        if (fc == NULL)
                return -EINVAL;
@@ -438,8 +501,10 @@ static int fuse_read_super(struct super_block *sb, void *data, int silent)
        return 0;
 
  err:
+       down(&fc->sb_sem);
        spin_lock(&fuse_lock);
        fc->sb = NULL;
+       up(&fc->sb_sem);
        fuse_release_conn(fc);
        spin_unlock(&fuse_lock);
        SB_FC(sb) = NULL;