summaryrefslogtreecommitdiff
path: root/fs/fuse/inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/fuse/inode.c')
-rw-r--r--fs/fuse/inode.c282
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();
}