public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 3/13] Filesystem in Userspace
@ 2004-11-20 23:09 Miklos Szeredi
  2004-11-20 23:37 ` Diego Calleja
  0 siblings, 1 reply; 5+ messages in thread
From: Miklos Szeredi @ 2004-11-20 23:09 UTC (permalink / raw)
  To: akpm, torvalds; +Cc: linux-fsdevel, linux-kernel

This patch adds FUSE core. 

This contains the following files:

 o dev.c
    - fuse device operations (read, write, release, poll)
    - adds /sys/fs/fuse/version
    - registers misc device

 o dir.c
    - directory operations (only lookup this patch)

 o inode.c
    - superblock operations (alloc_inode, destroy_inode, read_inode,
      clear_inode, put_super, show_options)
    - registers FUSE filesystem

 o util.c
    - module initialization and cleanup

 o fuse_i.h
    - private header file

Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
--- linux-2.6.10-rc2/fs/fuse/dev.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/dev.c	2004-11-20 22:56:25.000000000 +0100
@@ -0,0 +1,538 @@
+/*
+    FUSE: Filesystem in Userspace
+    Copyright (C) 2001-2004  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/kobject.h>
+#include <linux/miscdevice.h>
+#include <linux/file.h>
+
+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 = 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 = kmem_cache_alloc(fuse_req_cachep, SLAB_KERNEL);
+	if (req) {
+		memset(req, 0, sizeof(*req));
+		INIT_LIST_HEAD(&req->list);
+		init_waitqueue_head(&req->waitq);
+	}
+	return req;
+}
+
+void fuse_request_free(struct fuse_req *req)
+{
+	kmem_cache_free(fuse_req_cachep, req);
+}
+
+/* Called with fuse_lock held.  Releases, and then reaquires it. */
+static void request_wait_answer(struct fuse_req *req)
+{
+	spin_unlock(&fuse_lock);
+	wait_event(req->waitq, req->finished);
+	spin_lock(&fuse_lock);
+}
+
+static int get_unique(struct fuse_conn *fc)
+{
+	fc->reqctr++;
+	if (fc->reqctr == 0)
+		fc->reqctr = 1;
+	return fc->reqctr;
+}
+
+void fuse_reset_request(struct fuse_req *req)
+{
+	int preallocated = req->preallocated;
+	
+	memset(req, 0, sizeof(*req));
+	INIT_LIST_HEAD(&req->list);
+	init_waitqueue_head(&req->waitq);
+	req->preallocated = preallocated;
+}
+
+static struct fuse_req *do_get_request(struct fuse_conn *fc)
+{
+	struct fuse_req *req;
+
+	spin_lock(&fuse_lock);
+	BUG_ON(list_empty(&fc->unused_list));
+	req = list_entry(fc->unused_list.next, struct fuse_req, list);
+	list_del_init(&req->list);
+	spin_unlock(&fuse_lock);
+	fuse_reset_request(req);
+	req->in.h.uid = current->fsuid;
+	req->in.h.gid = current->fsgid;
+	req->in.h.pid = current->pid;
+	return req;
+}
+
+struct fuse_req *fuse_get_request(struct fuse_conn *fc)
+{
+	if (down_interruptible(&fc->unused_sem))
+		return NULL;
+	return  do_get_request(fc);
+}
+
+struct fuse_req *fuse_get_request_nonblock(struct fuse_conn *fc)
+{
+	if (down_trylock(&fc->unused_sem))
+		return NULL;
+	return  do_get_request(fc);
+}
+
+void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
+{
+	if (!req->preallocated)
+		fuse_request_free(req);
+	else {
+		spin_lock(&fuse_lock);
+		list_add(&req->list, &fc->unused_list);
+		spin_unlock(&fuse_lock);
+		up(&fc->unused_sem);
+	}
+}
+
+/* Must be called with fuse_lock held, and unlocks it */
+static void request_end(struct fuse_conn *fc, struct fuse_req *req)
+{
+	wake_up(&req->waitq);
+	spin_unlock(&fuse_lock);
+}
+
+void request_send(struct fuse_conn *fc, struct fuse_req *req)
+{
+	req->isreply = 1;
+		
+	spin_lock(&fuse_lock);
+	req->out.h.error = -ENOTCONN;
+	if (fc->file) {
+		req->in.h.unique = get_unique(fc);		
+		list_add_tail(&req->list, &fc->pending);
+		wake_up(&fc->waitq);
+		request_wait_answer(req);
+		list_del(&req->list);
+	}
+	spin_unlock(&fuse_lock);
+}
+
+void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
+{
+	req->isreply = 0;
+
+	spin_lock(&fuse_lock);
+	if (fc->file) {
+		list_add_tail(&req->list, &fc->pending);
+		wake_up(&fc->waitq);
+		spin_unlock(&fuse_lock);
+	} else {
+		spin_unlock(&fuse_lock);
+		fuse_put_request(fc, req);
+	}
+}
+
+static void request_wait(struct fuse_conn *fc)
+{
+	DECLARE_WAITQUEUE(wait, current);
+
+	add_wait_queue_exclusive(&fc->waitq, &wait);
+	while (fc->sb && list_empty(&fc->pending)) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (signal_pending(current))
+			break;
+
+		spin_unlock(&fuse_lock);
+		schedule();
+		spin_lock(&fuse_lock);
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&fc->waitq, &wait);
+}
+
+static inline int copy_in_one(const void *src, size_t srclen,
+			      char __user **dstp, size_t *dstlenp)
+{
+	if (*dstlenp < srclen) {
+		printk("fuse_dev_read: buffer too small\n");
+		return -EINVAL;
+	}
+			
+	if (srclen && copy_to_user(*dstp, src, srclen))
+		return -EFAULT;
+
+	*dstp += srclen;
+	*dstlenp -= srclen;
+
+	return 0;
+}
+
+static inline int copy_in_args(struct fuse_in *in, char __user *buf,
+			       size_t nbytes)
+{
+	int err;
+	int i;
+	size_t orignbytes = nbytes;
+		
+	err = copy_in_one(&in->h, sizeof(in->h), &buf, &nbytes);
+	if (err)
+		return err;
+
+	for (i = 0; i < in->numargs; i++) {
+		struct fuse_in_arg *arg = &in->args[i];
+		err = copy_in_one(arg->value, arg->size, &buf, &nbytes);
+		if (err)
+			return err;
+	}
+
+	return orignbytes - nbytes;
+}
+
+static ssize_t fuse_dev_read(struct file *file, char __user *buf,
+			     size_t nbytes, loff_t *off)
+{
+	ssize_t ret;
+	struct fuse_conn *fc;
+	struct fuse_req *req = NULL;
+
+	spin_lock(&fuse_lock);
+	fc = file->private_data;
+	if (!fc) {
+		spin_unlock(&fuse_lock);
+		return -EPERM;
+	}
+	request_wait(fc);
+	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);
+	}
+	spin_unlock(&fuse_lock);
+	if (!fc)
+		return -ENODEV;
+	if (req == NULL)
+		return -EINTR;
+
+	ret = copy_in_args(&req->in, buf, nbytes);
+	spin_lock(&fuse_lock);
+	if (req->isreply) {
+		if (ret < 0) {
+			req->out.h.error = -EPROTO;
+			req->finished = 1;
+		} else
+			list_add_tail(&req->list, &fc->processing);
+		if (ret < 0)
+			/* Unlocks fuse_lock: */
+			request_end(fc, req);
+		else
+			spin_unlock(&fuse_lock);
+	} else {
+		spin_unlock(&fuse_lock);
+		fuse_put_request(fc, req);
+	}
+	return ret;
+}
+
+static struct fuse_req *request_find(struct fuse_conn *fc, unsigned int unique)
+{
+	struct list_head *entry;
+	struct fuse_req *req = NULL;
+
+	list_for_each(entry, &fc->processing) {
+		struct fuse_req *tmp;
+		tmp = list_entry(entry, struct fuse_req, list);
+		if (tmp->in.h.unique == unique) {
+			req = tmp;
+			break;
+		}
+	}
+
+	return req;
+}
+
+static inline int copy_out_one(struct fuse_out_arg *arg,
+			       const char __user **srcp,
+			       size_t *srclenp, int allowvar)
+{
+	size_t dstlen = arg->size;
+	if (*srclenp < dstlen) {
+		if (!allowvar) {
+			printk("fuse_dev_write: write is short\n");
+			return -EINVAL;
+		}
+		dstlen = *srclenp;
+	}
+
+	if (dstlen && copy_from_user(arg->value, *srcp, dstlen))
+		return -EFAULT;
+
+	*srcp += dstlen;
+	*srclenp -= dstlen;
+	arg->size = dstlen;
+
+	return 0;
+}
+
+static inline int copy_out_args(struct fuse_req *req, const char __user *buf,
+				size_t nbytes)
+{
+	struct fuse_out *out = &req->out;
+	int err;
+	int i;
+
+	buf += sizeof(struct fuse_out_header);
+	nbytes -= sizeof(struct fuse_out_header);
+		
+	if (!out->h.error) {
+		for (i = 0; i < out->numargs; i++) {
+			struct fuse_out_arg *arg = &out->args[i];
+			int allowvar;
+			
+			if (out->argvar && i == out->numargs - 1)
+				allowvar = 1;
+			else
+				allowvar = 0;
+			
+			err = copy_out_one(arg, &buf, &nbytes, allowvar);
+			if (err)
+				return err;
+		}
+	}
+
+	if (nbytes != 0) {
+		printk("fuse_dev_write: write is long\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static inline int copy_out_header(struct fuse_out_header *oh,
+				  const char __user *buf, size_t nbytes)
+{
+	if (nbytes < sizeof(struct fuse_out_header)) {
+		printk("fuse_dev_write: write is short\n");
+		return -EINVAL;
+	}
+	
+	if (copy_from_user(oh, buf, sizeof(struct fuse_out_header)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static ssize_t fuse_dev_write(struct file *file, const char __user *buf,
+			      size_t nbytes, loff_t *off)
+{
+	int err;
+	struct fuse_conn *fc = fuse_get_conn(file);
+	struct fuse_req *req;
+	struct fuse_out_header oh;
+	
+	if (!fc)
+		return -ENODEV;
+
+	err = copy_out_header(&oh, buf, nbytes);
+	if (err)
+		return err;
+
+	if (!oh.unique)	{
+		err = -EINVAL;
+		goto out;
+	}     
+
+        if (oh.error <= -1000 || oh.error > 0) {
+                printk("fuse_dev_write: bad error value\n");
+                return -EINVAL;
+        }
+
+	spin_lock(&fuse_lock);
+	req = request_find(fc, oh.unique);
+	if (req != NULL)
+		list_del_init(&req->list);
+	spin_unlock(&fuse_lock);
+	if (!req)
+		return -ENOENT;
+
+	req->out.h = oh;
+	err = copy_out_args(req, buf, nbytes);
+
+	spin_lock(&fuse_lock);
+	if (err)
+		req->out.h.error = -EPROTO;
+
+	req->finished = 1;
+	/* Unlocks fuse_lock: */
+	request_end(fc, req);
+
+  out:
+	if (!err)
+		return nbytes;
+	else
+		return err;
+}
+
+static unsigned int fuse_dev_poll(struct file *file, poll_table *wait)
+{
+	struct fuse_conn *fc = fuse_get_conn(file);
+	unsigned int mask = POLLOUT | POLLWRNORM;
+
+	if (!fc)
+		return -ENODEV;
+
+	poll_wait(file, &fc->waitq, wait);
+
+	spin_lock(&fuse_lock);
+	if (!list_empty(&fc->pending))
+                mask |= POLLIN | POLLRDNORM;
+	spin_unlock(&fuse_lock);
+
+	return mask;
+}
+
+static void end_requests(struct fuse_conn *fc, struct list_head *head)
+{
+	while (!list_empty(head)) {
+		struct fuse_req *req;
+		req = list_entry(head->next, struct fuse_req, list);
+		list_del_init(&req->list);
+		if (req->isreply) {
+			req->out.h.error = -ECONNABORTED;
+			req->finished = 1;
+			/* Unlocks fuse_lock: */
+			request_end(fc, req);
+			spin_lock(&fuse_lock);
+		} else {
+			spin_unlock(&fuse_lock);
+			fuse_put_request(fc, req);
+			spin_lock(&fuse_lock);
+		}
+	}
+}
+
+static int fuse_dev_release(struct inode *inode, struct file *file)
+{
+	struct fuse_conn *fc;
+
+	spin_lock(&fuse_lock);
+	fc = 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;
+}
+
+struct file_operations fuse_dev_operations = {
+	.owner		= THIS_MODULE,
+	.read		= fuse_dev_read,
+	.write		= fuse_dev_write,
+	.poll		= fuse_dev_poll,
+	.release	= fuse_dev_release,
+};
+
+#define FUSE_MINOR MISC_DYNAMIC_MINOR
+
+static decl_subsys(fuse, NULL, NULL);
+
+static ssize_t version_show(struct subsystem *subsys, char *buf)
+{
+	return sprintf(buf, "%i.%i\n", FUSE_KERNEL_VERSION,
+		       FUSE_KERNEL_MINOR_VERSION);
+}
+static struct subsys_attribute fuse_attr_version = __ATTR_RO(version);
+
+static struct miscdevice fuse_miscdevice = {
+	.minor = FUSE_MINOR,
+	.name  = "fuse",
+	.fops = &fuse_dev_operations,
+};
+
+static int __init fuse_sysfs_init(void)
+{
+	int err;
+	err = fs_subsys_register(&fuse_subsys);
+	if (err)
+		return err;
+	err = subsys_create_file(&fuse_subsys, &fuse_attr_version);
+	if (err) {
+		subsystem_unregister(&fuse_subsys);
+		return err;
+	}
+	return 0;
+}
+
+static void fuse_sysfs_clean(void)
+{
+	subsys_remove_file(&fuse_subsys, &fuse_attr_version);
+	subsystem_unregister(&fuse_subsys);
+}
+
+static int __init fuse_device_init(void)
+{
+	int err = fuse_sysfs_init();
+	if (err)
+		return err;
+
+	err = misc_register(&fuse_miscdevice);
+	if (err) {
+		fuse_sysfs_clean();
+		return err;
+	}
+	return 0;
+}
+
+static void fuse_device_clean(void)
+{
+	misc_deregister(&fuse_miscdevice);
+	fuse_sysfs_clean();
+}
+
+int __init fuse_dev_init(void)
+{
+	int err;
+	err = fuse_device_init();
+	if (err)
+		goto out;
+
+	err = -ENOMEM;
+	fuse_req_cachep = kmem_cache_create("fuser_request",
+					    sizeof(struct fuse_req),
+					    0, 0, NULL, NULL);
+	if (!fuse_req_cachep)
+		goto out_device_clean;
+	
+	return 0;
+	
+ out_device_clean:
+	fuse_device_clean();
+ out:
+	return err;
+}
+
+void fuse_dev_cleanup(void)
+{
+	fuse_device_clean();
+	kmem_cache_destroy(fuse_req_cachep);
+}
--- linux-2.6.10-rc2/fs/fuse/dir.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/dir.c	2004-11-20 22:56:25.000000000 +0100
@@ -0,0 +1,166 @@
+/*
+    FUSE: Filesystem in Userspace
+    Copyright (C) 2001-2004  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/pagemap.h>
+#include <linux/file.h>
+
+static struct inode_operations fuse_dir_inode_operations;
+
+static void change_attributes(struct inode *inode, struct fuse_attr *attr)
+{
+	if (S_ISREG(inode->i_mode) && i_size_read(inode) != attr->size) {
+		invalidate_inode_pages(inode->i_mapping);
+	}
+
+	inode->i_ino     = attr->ino;
+	inode->i_mode    = (inode->i_mode & S_IFMT) + (attr->mode & 07777);
+	inode->i_nlink   = attr->nlink;
+	inode->i_uid     = attr->uid;
+	inode->i_gid     = attr->gid;
+	i_size_write(inode, attr->size);
+	inode->i_blksize = PAGE_CACHE_SIZE;
+	inode->i_blocks  = attr->blocks;
+	inode->i_atime.tv_sec   = attr->atime;
+	inode->i_atime.tv_nsec  = attr->atimensec;
+	inode->i_mtime.tv_sec   = attr->mtime;
+	inode->i_mtime.tv_nsec  = attr->mtimensec;
+	inode->i_ctime.tv_sec   = attr->ctime;
+	inode->i_ctime.tv_nsec  = attr->ctimensec;
+}
+
+static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
+{
+	inode->i_mode = attr->mode & S_IFMT;
+	i_size_write(inode, attr->size);
+	if (S_ISDIR(inode->i_mode))
+		inode->i_op = &fuse_dir_inode_operations;
+}
+
+static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
+{
+	unsigned long nodeid = *(unsigned long *) _nodeidp;
+	struct fuse_inode *fi = INO_FI(inode);
+	if (fi->nodeid == nodeid)
+		return 1;
+	else
+		return 0;
+}
+
+static int fuse_inode_set(struct inode *inode, void *_nodeidp)
+{
+	unsigned long nodeid = *(unsigned long *) _nodeidp;
+	struct fuse_inode *fi = INO_FI(inode);
+	fi->nodeid = nodeid;
+	return 0;
+}
+
+struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid,
+			int generation, struct fuse_attr *attr, int version)
+{
+	struct inode *inode;
+
+	inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
+	if (!inode)
+		return NULL;
+
+	if ((inode->i_state & I_NEW)) {
+		inode->i_generation = generation;
+		fuse_init_inode(inode, attr);
+		unlock_new_inode(inode);
+	} else if (inode->i_generation != generation)
+		printk("fuse_iget: bad generation for node %lu\n", nodeid);
+
+	change_attributes(inode, attr);
+	inode->i_version = version;
+	return inode;
+}
+
+static int fuse_send_lookup(struct fuse_conn *fc, struct fuse_req *req,
+			    struct inode *dir, struct dentry *entry, 
+			    struct fuse_entry_out *outarg, int *version)
+{
+	struct fuse_inode *fi = INO_FI(dir);
+	req->in.h.opcode = FUSE_LOOKUP;
+	req->in.h.nodeid = fi->nodeid;
+	req->in.numargs = 1;
+	req->in.args[0].size = entry->d_name.len + 1;
+	req->in.args[0].value = entry->d_name.name;
+	req->out.numargs = 1;
+	req->out.args[0].size = sizeof(struct fuse_entry_out);
+	req->out.args[0].value = outarg;
+	request_send(fc, req);
+	*version = req->out.h.unique;
+	return req->out.h.error;
+}
+
+static inline unsigned long time_to_jiffies(unsigned long sec,
+					    unsigned long nsec)
+{
+	/* prevent wrapping of jiffies */
+	if (sec + 1 >= LONG_MAX / HZ)
+		return 0;
+	
+	return jiffies + sec * HZ + nsec / (1000000000 / HZ);
+}
+
+static int fuse_lookup_iget(struct inode *dir, struct dentry *entry,
+			    struct inode **inodep)
+{
+	struct fuse_conn *fc = INO_FC(dir);
+	int err;
+	struct fuse_entry_out outarg;
+	int version;
+	struct inode *inode = NULL;
+	struct fuse_req *req;
+
+	if (entry->d_name.len > FUSE_NAME_MAX)
+		return -ENAMETOOLONG;
+	req = fuse_get_request(fc);
+	if (!req)
+		return -ERESTARTSYS;
+
+	err = fuse_send_lookup(fc, req, dir, entry, &outarg, &version);
+	if (!err) {
+		inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
+				  &outarg.attr, version);
+		if (!inode) {
+			fuse_send_forget(fc, req, outarg.nodeid, version);
+			return -ENOMEM;
+		}
+	} 
+	fuse_put_request(fc, req);
+	if (err && err != -ENOENT)
+		return err;
+
+	if (inode) {
+		struct fuse_inode *fi = INO_FI(inode);
+		entry->d_time =	time_to_jiffies(outarg.entry_valid,
+						outarg.entry_valid_nsec);
+		fi->i_time = time_to_jiffies(outarg.attr_valid,
+					     outarg.attr_valid_nsec);
+	}
+
+	*inodep = inode;
+	return 0;
+}
+
+static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
+				   struct nameidata *nd)
+{
+	struct inode *inode;
+	int err = fuse_lookup_iget(dir, entry, &inode);
+	if (err)
+		return ERR_PTR(err);
+	return d_splice_alias(inode, entry);
+}
+
+static struct inode_operations fuse_dir_inode_operations = {
+	.lookup		= fuse_lookup,
+};
--- linux-2.6.10-rc2/fs/fuse/fuse_i.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/fuse_i.h	2004-11-20 22:56:25.000000000 +0100
@@ -0,0 +1,216 @@
+/*
+    FUSE: Filesystem in Userspace
+    Copyright (C) 2001-2004  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+*/
+
+
+#include <linux/fuse.h>
+#include <linux/fs.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <asm/semaphore.h>
+
+/* If more requests are outstanding, then the operation will block */
+#define FUSE_MAX_OUTSTANDING 10
+
+/** FUSE specific inode data */
+struct fuse_inode {
+	unsigned long nodeid;
+	struct fuse_req *forget_req;
+	unsigned long i_time;
+};
+
+/** One input argument of a request */
+struct fuse_in_arg {
+	unsigned int size;
+	const void *value;
+};
+
+/** The request input */
+struct fuse_in {
+	struct fuse_in_header h;
+	unsigned int numargs;
+	struct fuse_in_arg args[3];
+};
+
+/** One output argument of a request */
+struct fuse_out_arg {
+	unsigned int size;
+	void *value;
+};
+
+/** The request output */
+struct fuse_out {
+	struct fuse_out_header h;
+	unsigned int argvar;
+	unsigned int numargs;
+	struct fuse_out_arg args[3];
+};
+
+struct fuse_req;
+struct fuse_conn;
+
+/**
+ * A request to the client
+ */
+struct fuse_req {
+	/** The request list */
+	struct list_head list;
+
+	/** True if the request has reply */
+	unsigned int isreply:1;
+
+	/* The request is preallocated */
+	unsigned int preallocated:1;
+
+	/* The request is finished */
+	unsigned int finished;
+
+	/** The request input */
+	struct fuse_in in;
+
+	/** The request output */
+	struct fuse_out out;
+
+	/** Used to wake up the task waiting for completion of request*/
+	wait_queue_head_t waitq;
+
+	/** Data for asynchronous requests */
+	union {
+		struct fuse_forget_in forget_in;
+	} misc;
+};
+
+/**
+ * A Fuse connection.
+ *
+ * This structure is created, when the client device is opened, and is
+ * destroyed, when the client device is closed _and_ the filesystem is
+ * unmounted.
+ */
+struct fuse_conn {
+	/** The superblock of the mounted filesystem */
+	struct super_block *sb;
+	
+	/** The opened client device */
+	struct file *file;
+
+	/** The user id for this mount */
+	uid_t uid;
+
+	/** Readers of the connection are waiting on this */
+	wait_queue_head_t waitq;
+
+	/** The list of pending requests */
+	struct list_head pending;
+
+	/** The list of requests being processed */
+	struct list_head processing;
+
+	/** Controls the maximum number of outstanding requests */
+	struct semaphore unused_sem;
+
+	/** The list of unused requests */
+	struct list_head unused_list;
+	
+	/** The next unique request id */
+	int reqctr;
+};
+
+#define SB_FC(sb) ((sb)->s_fs_info)
+#define INO_FC(inode) SB_FC((inode)->i_sb)
+#define INO_FI(i) ((struct fuse_inode *) (((struct inode *)(i))+1))
+
+/** Device operations */
+extern struct file_operations fuse_dev_operations;
+
+/**
+ * This is the single global spinlock which protects FUSE's structures
+ *
+ * The following data is protected by this lock:
+ * 
+ *  - the private_data field of the device file
+ *  - the s_fs_info field of the super block
+ *  - unused_list, pending, processing lists in fuse_conn
+ *  - the unique request ID counter reqctr in fuse_conn
+ *  - the sb (super_block) field in fuse_conn
+ *  - the file (device file) field in fuse_conn
+ */
+extern spinlock_t fuse_lock;
+
+/**
+ * Get a filled in inode
+ */
+struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid,
+			int generation, struct fuse_attr *attr, int version);
+
+/**
+ * Send FORGET command
+ */
+void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,
+		      unsigned long nodeid, int version);
+
+/**
+ * Check if the connection can be released, and if yes, then free the
+ * connection structure
+ */
+void fuse_release_conn(struct fuse_conn *fc);
+
+/**
+ * Initialize the client device
+ */
+int fuse_dev_init(void);
+
+/**
+ * Cleanup the client device
+ */
+void fuse_dev_cleanup(void);
+
+/**
+ * Initialize the fuse filesystem 
+ */
+int fuse_fs_init(void);
+
+/**
+ * Cleanup the fuse filesystem
+ */
+void fuse_fs_cleanup(void);
+
+/** 
+ * Allocate a request
+ */
+struct fuse_req *fuse_request_alloc(void);
+
+/**
+ * Free a request
+ */
+void fuse_request_free(struct fuse_req *req);
+
+/**
+ * Reinitialize a request, the preallocated flag is left unmodified
+ */
+void fuse_reset_request(struct fuse_req *req);
+
+/**
+ * Reserve a preallocated request
+ */
+struct fuse_req *fuse_get_request(struct fuse_conn *fc);
+
+/**
+ * Free a request
+ */
+void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req);
+
+/**
+ * Send a request
+ */
+void request_send(struct fuse_conn *fc, struct fuse_req *req);
+
+/**
+ * Send a request for which a reply is not expected
+ */
+void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req);
--- linux-2.6.10-rc2/fs/fuse/inode.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/inode.c	2004-11-20 22:56:25.000000000 +0100
@@ -0,0 +1,361 @@
+/*
+    FUSE: Filesystem in Userspace
+    Copyright (C) 2001-2004  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/file.h>
+#include <linux/mount.h>
+#include <linux/seq_file.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/parser.h>
+
+static kmem_cache_t *fuse_inode_cachep;
+
+#define FUSE_SUPER_MAGIC 0x65735546
+
+struct fuse_mount_data {
+	int fd;
+	unsigned int rootmode;
+	unsigned int uid;
+};
+
+static struct inode *fuse_alloc_inode(struct super_block *sb)
+{
+	struct inode *inode;
+	struct fuse_inode *fi;
+
+	inode = kmem_cache_alloc(fuse_inode_cachep, SLAB_KERNEL);
+	if (!inode)
+		return NULL;
+
+
+	fi = INO_FI(inode);
+	memset(fi, 0, sizeof(*fi));
+	fi->forget_req = fuse_request_alloc();
+	if (!fi->forget_req) {
+		kmem_cache_free(fuse_inode_cachep, inode);
+		return NULL;
+	}
+	return inode;
+}
+
+static void fuse_destroy_inode(struct inode *inode)
+{
+	struct fuse_inode *fi = INO_FI(inode);
+	if (fi->forget_req)
+		fuse_request_free(fi->forget_req);
+	kmem_cache_free(fuse_inode_cachep, inode);
+}
+
+static void fuse_read_inode(struct inode *inode)
+{
+	/* No op */
+}
+
+void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req,
+		      unsigned long nodeid, int version)
+{
+	struct fuse_forget_in *inarg = &req->misc.forget_in;
+	inarg->version = version;
+	req->in.h.opcode = FUSE_FORGET;
+	req->in.h.nodeid = nodeid;
+	req->in.numargs = 1;
+	req->in.args[0].size = sizeof(struct fuse_forget_in);
+	req->in.args[0].value = inarg;
+	request_send_noreply(fc, req);
+}
+
+static void fuse_clear_inode(struct inode *inode)
+{
+	struct fuse_conn *fc = INO_FC(inode);
+	
+	if (fc) {
+		struct fuse_inode *fi = INO_FI(inode);
+		fuse_send_forget(fc, fi->forget_req, fi->nodeid, inode->i_version);
+		fi->forget_req = NULL;
+	}
+}
+
+static void fuse_put_super(struct super_block *sb)
+{
+	struct fuse_conn *fc = SB_FC(sb);
+
+	spin_lock(&fuse_lock);
+	fc->sb = NULL;
+	fc->uid = 0;
+	/* Flush all readers on this fs */
+	wake_up_all(&fc->waitq);
+	fuse_release_conn(fc);
+	SB_FC(sb) = NULL;
+	spin_unlock(&fuse_lock);
+}
+
+enum {
+	OPT_FD,
+	OPT_ROOTMODE,
+	OPT_UID,
+	OPT_ERR 
+};
+
+static match_table_t tokens = {
+	{OPT_FD,			"fd=%u"},
+	{OPT_ROOTMODE,			"rootmode=%o"},
+	{OPT_UID,			"uid=%u"},
+	{OPT_ERR,			NULL}
+};
+
+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;
+
+	while ((p = strsep(&opt, ",")) != NULL) {
+		int token;
+		int value;
+		substring_t args[MAX_OPT_ARGS];
+		if (!*p)
+			continue;
+		
+		token = match_token(p, tokens, args);
+		switch (token) {
+		case OPT_FD:
+			if (match_int(&args[0], &value))
+				return 0;
+			d->fd = value;
+			break;
+
+		case OPT_ROOTMODE:
+			if (match_octal(&args[0], &value))
+				return 0;
+			d->rootmode = value;
+			break;
+			
+		case OPT_UID:
+			if (match_int(&args[0], &value))
+				return 0;
+			d->uid = value;
+			break;
+			
+		default:
+			return 0;
+		}
+	}
+	if (d->fd == -1)
+		return 0;
+
+	return 1;
+}
+
+static int fuse_show_options(struct seq_file *m, struct vfsmount *mnt)
+{
+	struct fuse_conn *fc = SB_FC(mnt->mnt_sb);
+
+	seq_printf(m, ",uid=%u", fc->uid);
+	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->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);
+		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;
+
+	if (file->f_op != &fuse_dev_operations) {
+		printk("FUSE: bad communication file descriptor\n");
+		return NULL;
+	}
+	fc = new_conn();
+	if (fc == NULL) {
+		printk("FUSE: failed to allocate connection data\n");
+		return NULL;
+	}
+	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;
+}
+
+static struct inode *get_root_inode(struct super_block *sb, unsigned int mode)
+{
+	struct fuse_attr attr;
+	memset(&attr, 0, sizeof(attr));
+
+	attr.mode = mode;
+	attr.ino = FUSE_ROOT_ID;
+	return fuse_iget(sb, 1, 0, &attr, 0);
+}
+
+static struct super_operations fuse_super_operations = {
+	.alloc_inode    = fuse_alloc_inode,
+	.destroy_inode  = fuse_destroy_inode,
+	.read_inode	= fuse_read_inode,
+	.clear_inode	= fuse_clear_inode,
+	.put_super	= fuse_put_super,
+	.show_options	= fuse_show_options,
+};
+
+static int fuse_read_super(struct super_block *sb, void *data, int silent)
+{	
+	struct fuse_conn *fc;
+	struct inode *root;
+	struct fuse_mount_data d;
+	struct file *file;
+
+	if (!parse_fuse_opt((char *) data, &d))
+		return -EINVAL;
+
+	sb->s_blocksize = PAGE_CACHE_SIZE;
+	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+	sb->s_magic = FUSE_SUPER_MAGIC;
+	sb->s_op = &fuse_super_operations;
+	sb->s_maxbytes = MAX_LFS_FILESIZE;
+
+	file = fget(d.fd);
+	if (!file)
+		return -EINVAL;
+
+	fc = get_conn(file, sb);
+	fput(file);
+	if (fc == NULL)
+		return -EINVAL;
+
+	fc->uid = d.uid;
+	
+	SB_FC(sb) = fc;
+
+	root = get_root_inode(sb, d.rootmode);
+	if (root == NULL) {
+		printk("fuse_read_super: failed to get root inode\n");
+		goto err;
+	}
+
+	sb->s_root = d_alloc_root(root);
+	if (!sb->s_root) {
+		iput(root);
+		goto err;
+	}
+
+	return 0;
+
+ err:
+	spin_lock(&fuse_lock);
+	fc->sb = NULL;
+	fuse_release_conn(fc);
+	spin_unlock(&fuse_lock);
+	SB_FC(sb) = NULL;
+	return -EINVAL;
+}
+
+static struct super_block *fuse_get_sb(struct file_system_type *fs_type,
+				       int flags, const char *dev_name,
+				       void *raw_data)
+{
+	return get_sb_nodev(fs_type, flags, raw_data, fuse_read_super);
+}
+
+static struct file_system_type fuse_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "fuse",
+	.get_sb		= fuse_get_sb,
+	.kill_sb	= kill_anon_super,
+};
+
+static void fuse_inode_init_once(void * foo, kmem_cache_t * cachep,
+				 unsigned long flags)
+{
+	struct inode * inode = foo;
+
+	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+	    SLAB_CTOR_CONSTRUCTOR)
+		inode_init_once(inode);
+}
+
+int fuse_fs_init(void)
+{
+	int err;
+
+	err = register_filesystem(&fuse_fs_type);
+	if (err)
+		printk("fuse: failed to register filesystem\n");
+	else {
+		fuse_inode_cachep = kmem_cache_create("fuse_inode",
+						      sizeof(struct inode) + sizeof(struct fuse_inode) ,
+						      0, SLAB_HWCACHE_ALIGN,
+						      fuse_inode_init_once, NULL);
+		if (!fuse_inode_cachep) {
+			unregister_filesystem(&fuse_fs_type);
+			err = -ENOMEM;
+		}
+	}
+
+	return err;
+}
+
+void fuse_fs_cleanup(void)
+{
+	unregister_filesystem(&fuse_fs_type);
+	kmem_cache_destroy(fuse_inode_cachep);
+}
--- linux-2.6.10-rc2/fs/fuse/util.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/util.c	2004-11-20 22:41:18.000000000 +0100
@@ -0,0 +1,53 @@
+/*
+    FUSE: Filesystem in Userspace
+    Copyright (C) 2001-2004  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/init.h>
+#include <linux/module.h>
+
+MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
+MODULE_DESCRIPTION("Filesystem in Userspace");
+MODULE_LICENSE("GPL");
+
+spinlock_t fuse_lock;
+
+int __init fuse_init(void)
+{
+	int res;
+
+	printk("fuse init (API version %i.%i)\n",
+	       FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
+	spin_lock_init(&fuse_lock);
+	res = fuse_fs_init();
+	if (res)
+		goto err;
+	
+	res = fuse_dev_init();
+	if (res)
+		goto err_fs_cleanup;
+	
+	return 0;
+
+  err_fs_cleanup:
+	fuse_fs_cleanup();
+  err:
+	return res;
+}
+
+void __exit fuse_exit(void)
+{
+	printk(KERN_DEBUG "fuse exit\n");
+	
+	fuse_fs_cleanup();
+	fuse_dev_cleanup();
+}
+
+module_init(fuse_init);
+module_exit(fuse_exit);
--- linux-2.6.10-rc2/fs/fuse/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/fs/fuse/Makefile	2004-11-20 22:56:24.000000000 +0100
@@ -0,0 +1,7 @@
+#
+# Makefile for the linux fuse routines.
+#
+
+obj-$(CONFIG_FUSE) += fuse.o
+
+fuse-objs := dev.o dir.o inode.o util.o
--- linux-2.6.10-rc2/include/linux/fuse.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.10-rc2-fuse/include/linux/fuse.h	2004-11-20 22:56:25.000000000 +0100
@@ -0,0 +1,103 @@
+/*
+    FUSE: Filesystem in Userspace
+    Copyright (C) 2001-2004  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+*/
+
+/* This file defines the kernel interface of FUSE */
+
+/** Version number of this interface */
+#define FUSE_KERNEL_VERSION 4
+
+/** Minor version number of this interface */
+#define FUSE_KERNEL_MINOR_VERSION 2
+
+/** The node ID of the root inode */
+#define FUSE_ROOT_ID 1
+
+/** Opening this will yield a new control file */
+#define FUSE_DEV "/dev/fuse"
+
+/** The file containing the version in the form MAJOR.MINOR */
+#define FUSE_VERSION_FILE "/sys/fs/fuse/version"
+
+struct fuse_attr {
+	unsigned long       ino;
+	unsigned int        mode;
+	unsigned int        nlink;
+	unsigned int        uid;
+	unsigned int        gid;
+	unsigned int        rdev;
+	unsigned long long  size;
+	unsigned long       blocks;
+	unsigned long       atime;
+	unsigned long       atimensec;
+	unsigned long       mtime;
+	unsigned long       mtimensec;
+	unsigned long       ctime;
+	unsigned long       ctimensec;
+};
+
+enum fuse_opcode {
+	FUSE_LOOKUP	   = 1,
+	FUSE_FORGET	   = 2,  /* no reply */
+	/* FUSE_GETATTR	   = 3, */
+	/* FUSE_SETATTR	   = 4, */
+	/* FUSE_READLINK   = 5, */
+	/* FUSE_SYMLINK	   = 6, */
+	/* FUSE_GETDIR	   = 7, */
+	/* FUSE_MKNOD	   = 8, */
+	/* FUSE_MKDIR	   = 9, */
+	/* FUSE_UNLINK	   = 10, */
+	/* FUSE_RMDIR	   = 11, */
+	/* FUSE_RENAME	   = 12, */
+	/* FUSE_LINK	   = 13, */
+	/* FUSE_OPEN	   = 14, */
+	/* FUSE_READ	   = 15, */
+	/* FUSE_WRITE	   = 16, */
+	/* FUSE_STATFS	   = 17, */
+	/* FUSE_RELEASE       = 18, */
+	/* FUSE_INVALIDATE    = 19, */
+	/* FUSE_FSYNC         = 20, */
+	/* FUSE_SETXATTR      = 21, */
+	/* FUSE_GETXATTR      = 22, */
+	/* FUSE_LISTXATTR     = 23, */
+	/* FUSE_REMOVEXATTR   = 24, */
+	/* FUSE_FLUSH         = 25, */
+};
+
+/* Conservative buffer size for the client */
+#define FUSE_MAX_IN 8192
+
+#define FUSE_NAME_MAX 1024
+
+struct fuse_entry_out {
+	unsigned long nodeid;      /* Inode ID */
+	unsigned long generation;  /* Inode generation: nodeid:gen must
+                                      be unique for the fs's lifetime */
+	unsigned long entry_valid; /* Cache timeout for the name */
+	unsigned long entry_valid_nsec;
+	unsigned long attr_valid;  /* Cache timeout for the attributes */
+	unsigned long attr_valid_nsec;
+	struct fuse_attr attr;
+};
+
+struct fuse_forget_in {
+	int version;
+};
+
+struct fuse_in_header {
+	int unique;
+	enum fuse_opcode opcode;
+	unsigned long nodeid;
+	unsigned int uid;
+	unsigned int gid;
+	unsigned int pid;
+};
+
+struct fuse_out_header {
+	int unique;
+	int error;
+};

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2004-11-21  7:16 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-11-20 23:09 [PATCH 3/13] Filesystem in Userspace Miklos Szeredi
2004-11-20 23:37 ` Diego Calleja
2004-11-21  0:34   ` Miklos Szeredi
2004-11-21  2:14     ` Arnd Bergmann
2004-11-21  7:15       ` Miklos Szeredi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox