X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=fs%2Ffuse%2Finode.c;h=c755a0440a6640848fff524c522e940da50b294a;hb=095da6cbb6a1c54c19b11190218eb0fbac666b6d;hp=458c62ca0fec1692a6e9917183716c2ebccb59ea;hpb=1e9a4ed9396e9c31139721b639550ffb1df17065;p=linux-2.6 diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 458c62ca0f..c755a0440a 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 @@ -31,7 +38,13 @@ struct fuse_mount_data { int fd; unsigned rootmode; unsigned user_id; + unsigned group_id; + unsigned fd_present : 1; + unsigned rootmode_present : 1; + unsigned user_id_present : 1; + unsigned group_id_present : 1; unsigned flags; + unsigned max_read; }; static struct inode *fuse_alloc_inode(struct super_block *sb) @@ -129,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) @@ -167,6 +176,7 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, return NULL; if ((inode->i_state & I_NEW)) { + inode->i_flags |= S_NOATIME|S_NOCMTIME; inode->i_generation = generation; inode->i_data.backing_dev_info = &fc->bdi; fuse_init_inode(inode, attr); @@ -186,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); @@ -197,19 +212,20 @@ static void fuse_put_super(struct super_block *sb) spin_lock(&fuse_lock); fc->mounted = 0; - fc->user_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; @@ -228,12 +244,14 @@ static int fuse_statfs(struct super_block *sb, struct kstatfs *buf) req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + 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; @@ -247,9 +265,10 @@ enum { OPT_FD, OPT_ROOTMODE, OPT_USER_ID, + OPT_GROUP_ID, OPT_DEFAULT_PERMISSIONS, OPT_ALLOW_OTHER, - OPT_KERNEL_CACHE, + OPT_MAX_READ, OPT_ERR }; @@ -257,9 +276,10 @@ static match_table_t tokens = { {OPT_FD, "fd=%u"}, {OPT_ROOTMODE, "rootmode=%o"}, {OPT_USER_ID, "user_id=%u"}, + {OPT_GROUP_ID, "group_id=%u"}, {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_ALLOW_OTHER, "allow_other"}, - {OPT_KERNEL_CACHE, "kernel_cache"}, + {OPT_MAX_READ, "max_read=%u"}, {OPT_ERR, NULL} }; @@ -267,7 +287,7 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) { char *p; memset(d, 0, sizeof(struct fuse_mount_data)); - d->fd = -1; + d->max_read = ~0; while ((p = strsep(&opt, ",")) != NULL) { int token; @@ -282,18 +302,28 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) if (match_int(&args[0], &value)) return 0; d->fd = value; + d->fd_present = 1; break; case OPT_ROOTMODE: if (match_octal(&args[0], &value)) return 0; d->rootmode = value; + d->rootmode_present = 1; break; case OPT_USER_ID: if (match_int(&args[0], &value)) return 0; d->user_id = value; + d->user_id_present = 1; + break; + + case OPT_GROUP_ID: + if (match_int(&args[0], &value)) + return 0; + d->group_id = value; + d->group_id_present = 1; break; case OPT_DEFAULT_PERMISSIONS: @@ -304,15 +334,19 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->flags |= FUSE_ALLOW_OTHER; break; - case OPT_KERNEL_CACHE: - d->flags |= FUSE_KERNEL_CACHE; + case OPT_MAX_READ: + if (match_int(&args[0], &value)) + return 0; + d->max_read = value; break; default: return 0; } } - if (d->fd == -1) + + if (!d->fd_present || !d->rootmode_present || + !d->user_id_present || !d->group_id_present) return 0; return 1; @@ -323,17 +357,20 @@ 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); + seq_printf(m, ",group_id=%u", fc->group_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"); + if (fc->max_read != ~0) + seq_printf(m, ",max_read=%u", fc->max_read); 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); @@ -343,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); @@ -384,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) @@ -421,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)) @@ -453,24 +550,47 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) fc->flags = d.flags; fc->user_id = d.user_id; + fc->group_id = d.group_id; + 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; + + /* 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; } @@ -488,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) { @@ -525,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; @@ -541,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: @@ -553,6 +770,7 @@ static void __exit fuse_exit(void) { printk(KERN_DEBUG "fuse exit\n"); + fuse_sysfs_cleanup(); fuse_fs_cleanup(); fuse_dev_cleanup(); }