diff options
Diffstat (limited to 'fs/fuse/inode.c')
-rw-r--r-- | fs/fuse/inode.c | 282 |
1 files changed, 235 insertions, 47 deletions
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index e69a546844d0..c755a0440a66 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -24,6 +24,13 @@ MODULE_LICENSE("GPL"); spinlock_t fuse_lock; static kmem_cache_t *fuse_inode_cachep; +static struct subsystem connections_subsys; + +struct fuse_conn_attr { + struct attribute attr; + ssize_t (*show)(struct fuse_conn *, char *); + ssize_t (*store)(struct fuse_conn *, const char *, size_t); +}; #define FUSE_SUPER_MAGIC 0x65735546 @@ -135,12 +142,8 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) 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); - fuse_init_file_inode(inode); - } + } else + BUG(); } static int fuse_inode_eq(struct inode *inode, void *_nodeidp) @@ -193,6 +196,11 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, return inode; } +static void fuse_umount_begin(struct super_block *sb) +{ + fuse_abort_conn(get_fuse_conn_super(sb)); +} + static void fuse_put_super(struct super_block *sb) { struct fuse_conn *fc = get_fuse_conn_super(sb); @@ -204,20 +212,20 @@ 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; + fc->connected = 0; + spin_unlock(&fuse_lock); + up_write(&fc->sbput_sem); /* Flush all readers on this fs */ wake_up_all(&fc->waitq); - up_write(&fc->sbput_sem); - fuse_release_conn(fc); - spin_unlock(&fuse_lock); + kobject_del(&fc->kobj); + kobject_put(&fc->kobj); } 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_frsize = attr->frsize; stbuf->f_blocks = attr->blocks; stbuf->f_bfree = attr->bfree; stbuf->f_bavail = attr->bavail; @@ -238,10 +246,12 @@ static int fuse_statfs(struct super_block *sb, struct kstatfs *buf) if (!req) return -EINTR; + memset(&outarg, 0, sizeof(outarg)); 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].size = + fc->minor < 4 ? FUSE_COMPAT_STATFS_SIZE : sizeof(outarg); req->out.args[0].value = &outarg; request_send(fc, req); err = req->out.h.error; @@ -357,8 +367,10 @@ static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt) return 0; } -static void free_conn(struct fuse_conn *fc) +static void fuse_conn_release(struct kobject *kobj) { + struct fuse_conn *fc = get_fuse_conn_kobj(kobj); + while (!list_empty(&fc->unused_list)) { struct fuse_req *req; req = list_entry(fc->unused_list.next, struct fuse_req, list); @@ -368,33 +380,28 @@ static void free_conn(struct fuse_conn *fc) kfree(fc); } -/* Must be called with the fuse lock held */ -void fuse_release_conn(struct fuse_conn *fc) -{ - fc->count--; - if (!fc->count) - free_conn(fc); -} - static struct fuse_conn *new_conn(void) { struct fuse_conn *fc; - fc = kmalloc(sizeof(*fc), GFP_KERNEL); - if (fc != NULL) { + fc = kzalloc(sizeof(*fc), GFP_KERNEL); + if (fc) { int i; - memset(fc, 0, sizeof(*fc)); init_waitqueue_head(&fc->waitq); INIT_LIST_HEAD(&fc->pending); INIT_LIST_HEAD(&fc->processing); + INIT_LIST_HEAD(&fc->io); INIT_LIST_HEAD(&fc->unused_list); INIT_LIST_HEAD(&fc->background); - sema_init(&fc->outstanding_sem, 0); + sema_init(&fc->outstanding_sem, 1); /* One for INIT */ init_rwsem(&fc->sbput_sem); + kobj_set_kset_s(fc, connections_subsys); + kobject_init(&fc->kobj); + atomic_set(&fc->num_waiting, 0); for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) { struct fuse_req *req = fuse_request_alloc(); if (!req) { - free_conn(fc); + kobject_put(&fc->kobj); return NULL; } list_add(&req->list, &fc->unused_list); @@ -409,25 +416,32 @@ static struct fuse_conn *new_conn(void) 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) - return ERR_PTR(-EINVAL); + goto out_err; + + err = -ENOMEM; fc = new_conn(); - if (fc == NULL) - return ERR_PTR(-ENOMEM); + if (!fc) + goto out_err; + spin_lock(&fuse_lock); - if (file->private_data) { - free_conn(fc); - fc = ERR_PTR(-EINVAL); - } else { - file->private_data = fc; - *get_fuse_conn_super_p(sb) = fc; - fc->mounted = 1; - fc->connected = 1; - fc->count = 2; - } + 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) @@ -446,16 +460,74 @@ static struct super_operations fuse_super_operations = { .read_inode = fuse_read_inode, .clear_inode = fuse_clear_inode, .put_super = fuse_put_super, + .umount_begin = fuse_umount_begin, .statfs = fuse_statfs, .show_options = fuse_show_options, }; +static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req) +{ + int i; + struct fuse_init_out *arg = &req->misc.init_out; + + if (req->out.h.error || arg->major != FUSE_KERNEL_VERSION) + fc->conn_error = 1; + else { + fc->minor = arg->minor; + fc->max_write = arg->minor < 5 ? 4096 : arg->max_write; + } + + /* 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); + + fuse_put_request(fc, req); +} + +static void fuse_send_init(struct fuse_conn *fc) +{ + /* This is called from fuse_read_super() so there's guaranteed + to be exactly one request available */ + struct fuse_req *req = fuse_get_request(fc); + struct fuse_init_in *arg = &req->misc.init_in; + + 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; + /* Variable length arguement used for backward compatibility + with interface version < 7.5. Rest of init_out is zeroed + by do_get_request(), so a short reply is not a problem */ + req->out.argvar = 1; + req->out.args[0].size = sizeof(struct fuse_init_out); + req->out.args[0].value = &req->misc.init_out; + req->end = process_init_reply; + request_send_background(fc, req); +} + +static unsigned long long conn_id(void) +{ + static unsigned long long ctr = 1; + unsigned long long val; + 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) { struct fuse_conn *fc; struct inode *root; struct fuse_mount_data d; struct file *file; + struct dentry *root_dentry; int err; if (!parse_fuse_opt((char *) data, &d)) @@ -482,25 +554,43 @@ 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; + + /* Used by get_root_inode() */ + sb->s_fs_info = fc; err = -ENOMEM; root = get_root_inode(sb, d.rootmode); - if (root == NULL) + if (!root) goto err; - sb->s_root = d_alloc_root(root); - if (!sb->s_root) { + root_dentry = d_alloc_root(root); + if (!root_dentry) { iput(root); goto err; } + + err = kobject_set_name(&fc->kobj, "%llu", conn_id()); + if (err) + goto err_put_root; + + err = kobject_add(&fc->kobj); + if (err) + goto err_put_root; + + sb->s_root = root_dentry; + spin_lock(&fuse_lock); + fc->mounted = 1; + fc->connected = 1; + spin_unlock(&fuse_lock); + fuse_send_init(fc); + return 0; + err_put_root: + dput(root_dentry); err: - spin_lock(&fuse_lock); - fuse_release_conn(fc); - spin_unlock(&fuse_lock); + kobject_put(&fc->kobj); return err; } @@ -518,6 +608,69 @@ static struct file_system_type fuse_fs_type = { .kill_sb = kill_anon_super, }; +static ssize_t fuse_conn_waiting_show(struct fuse_conn *fc, char *page) +{ + return sprintf(page, "%i\n", atomic_read(&fc->num_waiting)); +} + +static ssize_t fuse_conn_abort_store(struct fuse_conn *fc, const char *page, + size_t count) +{ + fuse_abort_conn(fc); + return count; +} + +static struct fuse_conn_attr fuse_conn_waiting = + __ATTR(waiting, 0400, fuse_conn_waiting_show, NULL); +static struct fuse_conn_attr fuse_conn_abort = + __ATTR(abort, 0600, NULL, fuse_conn_abort_store); + +static struct attribute *fuse_conn_attrs[] = { + &fuse_conn_waiting.attr, + &fuse_conn_abort.attr, + NULL, +}; + +static ssize_t fuse_conn_attr_show(struct kobject *kobj, + struct attribute *attr, + char *page) +{ + struct fuse_conn_attr *fca = + container_of(attr, struct fuse_conn_attr, attr); + + if (fca->show) + return fca->show(get_fuse_conn_kobj(kobj), page); + else + return -EACCES; +} + +static ssize_t fuse_conn_attr_store(struct kobject *kobj, + struct attribute *attr, + const char *page, size_t count) +{ + struct fuse_conn_attr *fca = + container_of(attr, struct fuse_conn_attr, attr); + + if (fca->store) + return fca->store(get_fuse_conn_kobj(kobj), page, count); + else + return -EACCES; +} + +static struct sysfs_ops fuse_conn_sysfs_ops = { + .show = &fuse_conn_attr_show, + .store = &fuse_conn_attr_store, +}; + +static struct kobj_type ktype_fuse_conn = { + .release = fuse_conn_release, + .sysfs_ops = &fuse_conn_sysfs_ops, + .default_attrs = fuse_conn_attrs, +}; + +static decl_subsys(fuse, NULL, NULL); +static decl_subsys(connections, &ktype_fuse_conn, NULL); + static void fuse_inode_init_once(void *foo, kmem_cache_t *cachep, unsigned long flags) { @@ -555,6 +708,34 @@ static void fuse_fs_cleanup(void) kmem_cache_destroy(fuse_inode_cachep); } +static int fuse_sysfs_init(void) +{ + int err; + + kset_set_kset_s(&fuse_subsys, fs_subsys); + err = subsystem_register(&fuse_subsys); + if (err) + goto out_err; + + kset_set_kset_s(&connections_subsys, fuse_subsys); + err = subsystem_register(&connections_subsys); + if (err) + goto out_fuse_unregister; + + return 0; + + out_fuse_unregister: + subsystem_unregister(&fuse_subsys); + out_err: + return err; +} + +static void fuse_sysfs_cleanup(void) +{ + subsystem_unregister(&connections_subsys); + subsystem_unregister(&fuse_subsys); +} + static int __init fuse_init(void) { int res; @@ -571,8 +752,14 @@ static int __init fuse_init(void) if (res) goto err_fs_cleanup; + res = fuse_sysfs_init(); + if (res) + goto err_dev_cleanup; + return 0; + err_dev_cleanup: + fuse_dev_cleanup(); err_fs_cleanup: fuse_fs_cleanup(); err: @@ -583,6 +770,7 @@ static void __exit fuse_exit(void) { printk(KERN_DEBUG "fuse exit\n"); + fuse_sysfs_cleanup(); fuse_fs_cleanup(); fuse_dev_cleanup(); } |