From 3d60e7641b52ac98d789ab091c6ada143dba5f69 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 11 Nov 2004 14:44:04 +0000 Subject: [PATCH] fix --- ChangeLog | 8 +++ kernel/dev.c | 151 ++++++++++++++++++------------------------------ kernel/fuse_i.h | 8 ++- kernel/inode.c | 79 ++++++++++++++++++++++--- 4 files changed, 144 insertions(+), 102 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8134ae7..5a785cb 100644 --- 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 * Separate configure for the kernel directory diff --git a/kernel/dev.c b/kernel/dev.c index 71d306a..0c601d2 100644 --- a/kernel/dev.c +++ b/kernel/dev.c @@ -12,13 +12,21 @@ #include #include -/* 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, }; diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h index 3c18975..40dedb2 100644 --- a/kernel/fuse_i.h +++ b/kernel/fuse_i.h @@ -49,6 +49,10 @@ #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)) diff --git a/kernel/inode.c b/kernel/inode.c index 6ad42ee..591a4df 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -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; -- 2.30.2