[PATCH] fuse: simplify locking

This is in preparation for removing the global spinlock in favor of a
per-mount one.

The only critical part is the interaction between fuse_dev_release() and
fuse_fill_super(): fuse_dev_release() must see the assignment to
file->private_data, otherwise it will leak the reference to fuse_conn.

This is ensured by the fput() operation, which will synchronize the assignment
with other CPU's that may do a final fput() soon after this.

Also redundant locking is removed from fuse_fill_super(), where exclusion is
already ensured by the BKL held for this function by the VFS.

Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
Miklos Szeredi 2006-04-10 22:54:55 -07:00 коммит произвёл Linus Torvalds
Родитель e5ac1d1e70
Коммит 0720b31597
2 изменённых файлов: 33 добавлений и 62 удалений

Просмотреть файл

@ -23,13 +23,11 @@ static kmem_cache_t *fuse_req_cachep;
static struct fuse_conn *fuse_get_conn(struct file *file) static struct fuse_conn *fuse_get_conn(struct file *file)
{ {
struct fuse_conn *fc; /*
spin_lock(&fuse_lock); * Lockless access is OK, because file->private data is set
fc = file->private_data; * once during mount and is valid until the file is released.
if (fc && !fc->connected) */
fc = NULL; return file->private_data;
spin_unlock(&fuse_lock);
return fc;
} }
static void fuse_request_init(struct fuse_req *req) static void fuse_request_init(struct fuse_req *req)
@ -607,19 +605,16 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
unsigned long nr_segs, loff_t *off) unsigned long nr_segs, loff_t *off)
{ {
int err; int err;
struct fuse_conn *fc;
struct fuse_req *req; struct fuse_req *req;
struct fuse_in *in; struct fuse_in *in;
struct fuse_copy_state cs; struct fuse_copy_state cs;
unsigned reqsize; unsigned reqsize;
struct fuse_conn *fc = fuse_get_conn(file);
if (!fc)
return -EPERM;
restart: restart:
spin_lock(&fuse_lock); spin_lock(&fuse_lock);
fc = file->private_data;
err = -EPERM;
if (!fc)
goto err_unlock;
err = -EAGAIN; err = -EAGAIN;
if ((file->f_flags & O_NONBLOCK) && fc->connected && if ((file->f_flags & O_NONBLOCK) && fc->connected &&
list_empty(&fc->pending)) list_empty(&fc->pending))
@ -915,17 +910,13 @@ void fuse_abort_conn(struct fuse_conn *fc)
static int fuse_dev_release(struct inode *inode, struct file *file) static int fuse_dev_release(struct inode *inode, struct file *file)
{ {
struct fuse_conn *fc; struct fuse_conn *fc = fuse_get_conn(file);
spin_lock(&fuse_lock);
fc = file->private_data;
if (fc) { if (fc) {
spin_lock(&fuse_lock);
fc->connected = 0; fc->connected = 0;
end_requests(fc, &fc->pending); end_requests(fc, &fc->pending);
end_requests(fc, &fc->processing); end_requests(fc, &fc->processing);
} spin_unlock(&fuse_lock);
spin_unlock(&fuse_lock);
if (fc) {
fasync_helper(-1, file, 0, &fc->fasync); fasync_helper(-1, file, 0, &fc->fasync);
kobject_put(&fc->kobj); kobject_put(&fc->kobj);
} }

Просмотреть файл

@ -414,37 +414,6 @@ static struct fuse_conn *new_conn(void)
return fc; return fc;
} }
static struct fuse_conn *get_conn(struct file *file, struct super_block *sb)
{
struct fuse_conn *fc;
int err;
err = -EINVAL;
if (file->f_op != &fuse_dev_operations)
goto out_err;
err = -ENOMEM;
fc = new_conn();
if (!fc)
goto out_err;
spin_lock(&fuse_lock);
err = -EINVAL;
if (file->private_data)
goto out_unlock;
kobject_get(&fc->kobj);
file->private_data = fc;
spin_unlock(&fuse_lock);
return fc;
out_unlock:
spin_unlock(&fuse_lock);
kobject_put(&fc->kobj);
out_err:
return ERR_PTR(err);
}
static struct inode *get_root_inode(struct super_block *sb, unsigned mode) static struct inode *get_root_inode(struct super_block *sb, unsigned mode)
{ {
struct fuse_attr attr; struct fuse_attr attr;
@ -526,12 +495,9 @@ static void fuse_send_init(struct fuse_conn *fc)
static unsigned long long conn_id(void) static unsigned long long conn_id(void)
{ {
/* BKL is held for ->get_sb() */
static unsigned long long ctr = 1; static unsigned long long ctr = 1;
unsigned long long val; return ctr++;
spin_lock(&fuse_lock);
val = ctr++;
spin_unlock(&fuse_lock);
return val;
} }
static int fuse_fill_super(struct super_block *sb, void *data, int silent) static int fuse_fill_super(struct super_block *sb, void *data, int silent)
@ -556,10 +522,17 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
if (!file) if (!file)
return -EINVAL; return -EINVAL;
fc = get_conn(file, sb); if (file->f_op != &fuse_dev_operations)
fput(file); return -EINVAL;
if (IS_ERR(fc))
return PTR_ERR(fc); /* Setting file->private_data can't race with other mount()
instances, since BKL is held for ->get_sb() */
if (file->private_data)
return -EINVAL;
fc = new_conn();
if (!fc)
return -ENOMEM;
fc->flags = d.flags; fc->flags = d.flags;
fc->user_id = d.user_id; fc->user_id = d.user_id;
@ -589,10 +562,16 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
goto err_put_root; goto err_put_root;
sb->s_root = root_dentry; sb->s_root = root_dentry;
spin_lock(&fuse_lock);
fc->mounted = 1; fc->mounted = 1;
fc->connected = 1; fc->connected = 1;
spin_unlock(&fuse_lock); kobject_get(&fc->kobj);
file->private_data = fc;
/*
* atomic_dec_and_test() in fput() provides the necessary
* memory barrier for file->private_data to be visible on all
* CPUs after this
*/
fput(file);
fuse_send_init(fc); fuse_send_init(fc);
@ -601,6 +580,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
err_put_root: err_put_root:
dput(root_dentry); dput(root_dentry);
err: err:
fput(file);
kobject_put(&fc->kobj); kobject_put(&fc->kobj);
return err; return err;
} }