performance improvements
diff --git a/BUGS b/BUGS
new file mode 100644
index 0000000..df5f428
--- /dev/null
+++ b/BUGS
@@ -0,0 +1,4 @@
+- It is allowed to mount a directory on a non-directory.
+
+- When a non-directory is mounted the root inode is not filled in, only at
+ the first getattr
diff --git a/example/.cvsignore b/example/.cvsignore
index bcfd8b2..94a32e3 100644
--- a/example/.cvsignore
+++ b/example/.cvsignore
@@ -2,3 +2,4 @@
Makefile
.deps
fusexmp
+null
diff --git a/example/Makefile.am b/example/Makefile.am
index 71748f5..19a3c32 100644
--- a/example/Makefile.am
+++ b/example/Makefile.am
@@ -1,7 +1,8 @@
## Process this file with automake to produce Makefile.in
-noinst_PROGRAMS = fusexmp
+noinst_PROGRAMS = fusexmp null
fusexmp_SOURCES = fusexmp.c
+null_SOURCES = null.c
-fusexmp_LDADD = ../lib/libfuse.a -lpthread
+LDADD = ../lib/libfuse.a -lpthread
diff --git a/example/null.c b/example/null.c
new file mode 100644
index 0000000..379ba7a
--- /dev/null
+++ b/example/null.c
@@ -0,0 +1,178 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001 Miklos Szeredi (mszeredi@inf.bme.hu)
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+
+#define UNUSED __attribute__((unused))
+
+static char *unmount_cmd;
+
+static int null_getattr(const char *path, struct stat *stbuf)
+{
+ if(strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ stbuf->st_mode = S_IFREG | 0644;
+ stbuf->st_nlink = 1;
+ stbuf->st_uid = getuid();
+ stbuf->st_gid = getgid();
+ stbuf->st_size = (1 << 30); /* 1G */
+ stbuf->st_blocks = 0;
+ stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
+
+ return 0;
+}
+
+static int null_truncate(const char *path, off_t UNUSED(size))
+{
+ if(strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int null_open(const char *path, int UNUSED(flags))
+{
+ if(strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int null_read(const char *path, char *UNUSED(buf), size_t size,
+ off_t UNUSED(offset))
+{
+ if(strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ return size;
+}
+
+static int null_write(const char *path, const char *UNUSED(buf), size_t size,
+ off_t UNUSED(offset))
+{
+ if(strcmp(path, "/") != 0)
+ return -ENOENT;
+
+ return size;
+}
+
+
+static struct fuse_operations null_oper = {
+ getattr: null_getattr,
+ readlink: NULL,
+ getdir: NULL,
+ mknod: NULL,
+ mkdir: NULL,
+ symlink: NULL,
+ unlink: NULL,
+ rmdir: NULL,
+ rename: NULL,
+ link: NULL,
+ chmod: NULL,
+ chown: NULL,
+ truncate: null_truncate,
+ utime: NULL,
+ open: null_open,
+ read: null_read,
+ write: null_write,
+};
+
+
+static void exit_handler()
+{
+ close(0);
+ system(unmount_cmd);
+ exit(0);
+}
+
+static void set_signal_handlers()
+{
+ struct sigaction sa;
+
+ sa.sa_handler = exit_handler;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+
+ if (sigaction(SIGHUP, &sa, NULL) == -1 ||
+ sigaction(SIGINT, &sa, NULL) == -1 ||
+ sigaction(SIGTERM, &sa, NULL) == -1) {
+
+ perror("Cannot set exit signal handlers");
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+
+ if(sigaction(SIGPIPE, &sa, NULL) == -1) {
+ perror("Cannot set ignored signals");
+ exit(1);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int argctr;
+ int flags;
+ int multithreaded;
+ struct fuse *fuse;
+
+ if(argc < 2) {
+ fprintf(stderr,
+ "usage: %s unmount_cmd [options] \n"
+ "Options:\n"
+ " -d enable debug output\n"
+ " -s disable multithreaded operation\n",
+ argv[0]);
+ exit(1);
+ }
+
+ argctr = 1;
+ unmount_cmd = argv[argctr++];
+
+ set_signal_handlers();
+
+ flags = 0;
+ multithreaded = 1;
+ for(; argctr < argc && argv[argctr][0] == '-'; argctr ++) {
+ switch(argv[argctr][1]) {
+ case 'd':
+ flags |= FUSE_DEBUG;
+ break;
+
+ case 's':
+ multithreaded = 0;
+ break;
+
+ default:
+ fprintf(stderr, "invalid option: %s\n", argv[argctr]);
+ exit(1);
+ }
+ }
+ if(argctr != argc) {
+ fprintf(stderr, "missing or surplus argument\n");
+ exit(1);
+ }
+
+ fuse = fuse_new(0, flags);
+ fuse_set_operations(fuse, &null_oper);
+
+ if(multithreaded)
+ fuse_loop_mt(fuse);
+ else
+ fuse_loop(fuse);
+
+ return 0;
+}
diff --git a/include/linux/fuse.h b/include/linux/fuse.h
index 0cd9e5c..fd5ef9c 100644
--- a/include/linux/fuse.h
+++ b/include/linux/fuse.h
@@ -56,22 +56,22 @@
#define FATTR_UTIME (1 << 4)
enum fuse_opcode {
- FUSE_LOOKUP = 1,
- FUSE_FORGET,
- FUSE_GETATTR,
- FUSE_SETATTR,
- FUSE_READLINK,
- FUSE_SYMLINK,
- FUSE_GETDIR,
- FUSE_MKNOD,
- FUSE_MKDIR,
- FUSE_UNLINK,
- FUSE_RMDIR,
- FUSE_RENAME,
- FUSE_LINK,
- FUSE_OPEN,
- FUSE_READ,
- FUSE_WRITE,
+ FUSE_LOOKUP = 1,
+ FUSE_FORGET = 2,
+ 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,
};
/* Conservative buffer size for the client */
@@ -98,7 +98,7 @@
struct fuse_mknod_in {
unsigned short mode;
unsigned short rdev;
- char name[1];
+ char name[0];
};
struct fuse_mknod_out {
@@ -108,17 +108,17 @@
struct fuse_mkdir_in {
unsigned short mode;
- char name[1];
+ char name[0];
};
struct fuse_rename_in {
unsigned long newdir;
- char names[1];
+ char names[0];
};
struct fuse_link_in {
unsigned long newdir;
- char name[1];
+ char name[0];
};
struct fuse_setattr_in {
@@ -142,7 +142,7 @@
struct fuse_write_in {
unsigned long long offset;
unsigned int size;
- char buf[1];
+ char buf[0];
};
struct fuse_in_header {
diff --git a/kernel/dev.c b/kernel/dev.c
index d4206e1..4395736 100644
--- a/kernel/dev.c
+++ b/kernel/dev.c
@@ -12,13 +12,39 @@
#include <linux/proc_fs.h>
#include <linux/file.h>
-#define IHSIZE sizeof(struct fuse_in_header)
-#define OHSIZE sizeof(struct fuse_out_header)
+/* If more requests are outstanding, then the operation will block */
+#define MAX_OUTSTANDING 10
static struct proc_dir_entry *proc_fs_fuse;
struct proc_dir_entry *proc_fuse_dev;
+static kmem_cache_t *fuse_req_cachep;
-static int interrupt_error(enum fuse_opcode opcode)
+static struct fuse_req *request_new(void)
+{
+ struct fuse_req *req;
+
+ req = (struct fuse_req *) kmem_cache_alloc(fuse_req_cachep, SLAB_NOFS);
+ if(req) {
+ INIT_LIST_HEAD(&req->list);
+ req->issync = 0;
+ req->locked = 0;
+ req->interrupted = 0;
+ req->sent = 0;
+ req->finished = 0;
+ req->in = NULL;
+ req->out = NULL;
+ init_waitqueue_head(&req->waitq);
+ }
+
+ return req;
+}
+
+static void request_free(struct fuse_req *req)
+{
+ kmem_cache_free(fuse_req_cachep, req);
+}
+
+static int request_restartable(enum fuse_opcode opcode)
{
switch(opcode) {
case FUSE_LOOKUP:
@@ -28,222 +54,214 @@
case FUSE_OPEN:
case FUSE_READ:
case FUSE_WRITE:
- return -ERESTARTSYS;
+ return 1;
default:
- /* Operations which modify the filesystem cannot be safely
- restarted, because it is uncertain whether the
- operation has completed or not... */
- return -EINTR;
+ return 0;
}
}
-static int request_wait_answer(struct fuse_req *req)
+/* Called with fuse_lock held. Releases, and then reaquires it. */
+static void request_wait_answer(struct fuse_req *req)
{
- int ret = 0;
- DECLARE_WAITQUEUE(wait, current);
+ int intr;
+
+ spin_unlock(&fuse_lock);
+ intr = wait_event_interruptible(req->waitq, req->finished);
+ spin_lock(&fuse_lock);
+ if(!intr)
+ return;
- add_wait_queue(&req->waitq, &wait);
- while(!list_empty(&req->list)) {
- set_current_state(TASK_INTERRUPTIBLE);
- if(signal_pending(current)) {
- ret = interrupt_error(req->opcode);
- break;
- }
+ /* Request interrupted... Wait for it to be unlocked */
+ if(req->locked) {
+ req->interrupted = 1;
spin_unlock(&fuse_lock);
- schedule();
+ wait_event(req->waitq, !req->locked);
spin_lock(&fuse_lock);
}
- set_current_state(TASK_RUNNING);
- remove_wait_queue(&req->waitq, &wait);
-
- return ret;
-}
-
-static int request_check(struct fuse_req *req, struct fuse_out *outp)
-{
- struct fuse_out_header *oh;
- unsigned int size;
-
- if(!req->out)
- return -ECONNABORTED;
-
- oh = (struct fuse_out_header *) req->out;
- size = req->outsize - OHSIZE;
- if (oh->error <= -512 || oh->error > 0) {
- printk("fuse: bad error value: %i\n", oh->error);
- return -EPROTO;
- }
-
- if(size > outp->argsize ||
- (oh->error == 0 && !outp->argvar && size != outp->argsize) ||
- (oh->error != 0 && size != 0)) {
- printk("fuse: invalid argument length: %i (%i)\n", size,
- req->opcode);
- return -EPROTO;
- }
-
- memcpy(&outp->h, oh, OHSIZE);
- outp->argsize = size;
- if(size)
- memcpy(outp->arg, req->out + OHSIZE, size);
-
- return oh->error;
-}
-
-static void request_free(struct fuse_req *req)
-{
- kfree(req->in);
- kfree(req->out);
- kfree(req);
-}
-
-
-static struct fuse_req *request_new(struct fuse_conn *fc, struct fuse_in *inp,
- struct fuse_out *outp)
-{
- struct fuse_req *req;
-
- req = kmalloc(sizeof(*req), GFP_NOFS);
- if(!req)
- return NULL;
-
- if(outp)
- req->outsize = OHSIZE + outp->argsize;
+ /* Operations which modify the filesystem cannot safely be
+ restarted, because it is uncertain whether the operation has
+ completed or not... */
+ if(req->sent && !request_restartable(req->in->h.opcode))
+ req->out->h.error = -EINTR;
else
- req->outsize = 0;
- req->out = NULL;
-
- req->insize = IHSIZE + inp->argsize;
- req->in = kmalloc(req->insize, GFP_NOFS);
- if(!req->in) {
- request_free(req);
- return NULL;
- }
- memcpy(req->in, &inp->h, IHSIZE);
- if(inp->argsize)
- memcpy(req->in + IHSIZE, inp->arg, inp->argsize);
-
- req->opcode = inp->h.opcode;
- init_waitqueue_head(&req->waitq);
-
- return req;
+ req->out->h.error = -ERESTARTSYS;
}
-/* If 'outp' is NULL then the request this is asynchronous */
-void request_send(struct fuse_conn *fc, struct fuse_in *inp,
- struct fuse_out *outp)
+static int get_unique(struct fuse_conn *fc)
{
- int ret;
- struct fuse_in_header *ih;
+ do fc->reqctr++;
+ while(!fc->reqctr);
+ return fc->reqctr;
+}
+
+void request_send(struct fuse_conn *fc, struct fuse_in *in,
+ struct fuse_out *out)
+{
struct fuse_req *req;
- ret = -ENOMEM;
- req = request_new(fc, inp, outp);
+ out->h.error = -ERESTARTSYS;
+ if(down_interruptible(&fc->outstanding))
+ return;
+
+ out->h.error = -ENOMEM;
+ req = request_new();
if(!req)
- goto out;
+ return;
+
+ req->in = in;
+ req->out = out;
+ req->issync = 1;
spin_lock(&fuse_lock);
- ret = -ENOTCONN;
- if(!fc->file)
- goto out_unlock_free;
-
- ih = (struct fuse_in_header *) req->in;
- if(outp) {
- do fc->reqctr++;
- while(!fc->reqctr);
- ih->unique = req->unique = fc->reqctr;
+ out->h.error = -ENOTCONN;
+ if(fc->file) {
+ 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);
}
- else
- ih->unique = req->unique = 0;
+ spin_unlock(&fuse_lock);
+ request_free(req);
+
+ up(&fc->outstanding);
+}
+
+
+static inline void destroy_request(struct fuse_conn *fc, struct fuse_req *req)
+{
+ if(req) {
+ int i;
+
+ for(i = 0; i < req->in->numargs; i++)
+ kfree(req->in->args[i].value);
+ kfree(req->in);
+ request_free(req);
+ }
+}
+
+/* This one is currently only used for sending the FORGET request, which is
+ a kernel initiated request. So the outstanding semaphore is not used. */
+int request_send_noreply(struct fuse_conn *fc, struct fuse_in *in)
+{
+ struct fuse_req *req;
+
+ req = request_new();
+ if(!req)
+ return -ENOMEM;
+
+ req->in = in;
+ req->issync = 0;
+
+ spin_lock(&fuse_lock);
+ if(!fc->file) {
+ spin_unlock(&fuse_lock);
+ request_free(req);
+ return -ENOTCONN;
+ }
list_add_tail(&req->list, &fc->pending);
wake_up(&fc->waitq);
-
- /* Async reqests are freed in fuse_dev_read() */
- if(!outp)
- goto out_unlock;
-
- ret = request_wait_answer(req);
- list_del(&req->list);
- if(!ret)
- ret = request_check(req, outp);
-
- out_unlock_free:
- request_free(req);
- out_unlock:
spin_unlock(&fuse_lock);
- out:
- if(outp)
- outp->h.error = ret;
+ return 0;
}
-static int request_wait(struct fuse_conn *fc)
+static void request_wait(struct fuse_conn *fc)
{
- int ret = 0;
DECLARE_WAITQUEUE(wait, current);
-
+
add_wait_queue_exclusive(&fc->waitq, &wait);
while(list_empty(&fc->pending)) {
set_current_state(TASK_INTERRUPTIBLE);
- if(signal_pending(current)) {
- ret = -ERESTARTSYS;
+ 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);
-
- return ret;
}
+static inline int copy_in_one(const void *src, size_t srclen, char **dstp,
+ size_t *dstlenp)
+{
+ if(*dstlenp < srclen) {
+ printk("fuse_dev_read: buffer too small\n");
+ return -EIO;
+ }
+
+ if(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 *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 *buf, size_t nbytes,
loff_t *off)
{
- int ret;
+ ssize_t ret;
struct fuse_conn *fc = DEV_FC(file);
- struct fuse_req *req;
- char *tmpbuf;
- unsigned int size;
+ struct fuse_req *req = NULL;
if(fc->sb == NULL)
return -EPERM;
-
+
spin_lock(&fuse_lock);
- ret = request_wait(fc);
- if(ret)
- goto err;
-
- req = list_entry(fc->pending.next, struct fuse_req, list);
- size = req->insize;
- if(nbytes < size) {
- printk("fuse_dev_read: buffer too small\n");
- ret = -EIO;
- goto err;
+ request_wait(fc);
+ if(!list_empty(&fc->pending)) {
+ req = list_entry(fc->pending.next, struct fuse_req, list);
+ list_del_init(&req->list);
+ req->locked = 1;
}
- tmpbuf = req->in;
- req->in = NULL;
-
- list_del(&req->list);
- if(req->outsize)
- list_add_tail(&req->list, &fc->processing);
- else
- request_free(req);
spin_unlock(&fuse_lock);
+ if(req == NULL)
+ return -ERESTARTSYS;
- if(copy_to_user(buf, tmpbuf, size))
- return -EFAULT;
-
- kfree(tmpbuf);
- return size;
-
- err:
+ ret = copy_in_args(req->in, buf, nbytes);
+ spin_lock(&fuse_lock);
+ if(req->issync || ret < 0) {
+ if(ret < 0)
+ list_add_tail(&req->list, &fc->pending);
+ else {
+ list_add_tail(&req->list, &fc->processing);
+ req->sent = 1;
+ }
+ req->locked = 0;
+ if(req->interrupted)
+ wake_up(&req->waitq);
+
+ req = NULL;
+ }
spin_unlock(&fuse_lock);
+ destroy_request(fc, req);
+
return ret;
}
@@ -255,7 +273,7 @@
list_for_each(entry, &fc->processing) {
struct fuse_req *tmp;
tmp = list_entry(entry, struct fuse_req, list);
- if(tmp->unique == unique) {
+ if(tmp->in->h.unique == unique) {
req = tmp;
break;
}
@@ -264,58 +282,135 @@
return req;
}
+static void process_getdir(struct fuse_req *req)
+{
+ struct fuse_getdir_out *arg;
+ arg = (struct fuse_getdir_out *) req->out->args[0].value;
+ arg->file = fget(arg->fd);
+}
+
+static inline int copy_out_one(struct fuse_out_arg *arg, const char **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 -EIO;
+ }
+ dstlen = *srclenp;
+ }
+
+ if(dstlen) {
+ if(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_out *out, const char *buf,
+ size_t nbytes)
+{
+ 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 -EIO;
+ }
+
+ return 0;
+}
+
+static inline int copy_out_header(struct fuse_out_header *oh, const char *buf,
+ size_t nbytes)
+{
+ if(nbytes < sizeof(struct fuse_out_header)) {
+ printk("fuse_dev_write: write is short\n");
+ return -EIO;
+ }
+
+ if(copy_from_user(oh, buf, sizeof(struct fuse_out_header)))
+ return -EFAULT;
+
+ if (oh->error <= -512 || oh->error > 0) {
+ printk("fuse_dev_write: bad error value\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
static ssize_t fuse_dev_write(struct file *file, const char *buf,
size_t nbytes, loff_t *off)
{
- ssize_t ret;
+ int err;
struct fuse_conn *fc = DEV_FC(file);
struct fuse_req *req;
- char *tmpbuf;
- struct fuse_out_header *oh;
+ struct fuse_out_header oh;
if(!fc->sb)
return -EPERM;
- ret = -EIO;
- if(nbytes < OHSIZE || nbytes > OHSIZE + PAGE_SIZE) {
- printk("fuse_dev_write: write is short or long\n");
- goto out;
- }
-
- ret = -ENOMEM;
- tmpbuf = kmalloc(nbytes, GFP_NOFS);
- if(!tmpbuf)
- goto out;
-
- ret = -EFAULT;
- if(copy_from_user(tmpbuf, buf, nbytes))
- goto out_free;
+ err = copy_out_header(&oh, buf, nbytes);
+ if(err)
+ return err;
spin_lock(&fuse_lock);
- oh = (struct fuse_out_header *) tmpbuf;
- req = request_find(fc, oh->unique);
- if(req == NULL) {
- ret = -ENOENT;
- goto out_free_unlock;
+ req = request_find(fc, oh.unique);
+ if(req != NULL) {
+ list_del_init(&req->list);
+ req->locked = 1;
}
- list_del_init(&req->list);
- if(req->opcode == FUSE_GETDIR) {
- /* fget() needs to be done in this context */
- struct fuse_getdir_out *arg;
- arg = (struct fuse_getdir_out *) (tmpbuf + OHSIZE);
- arg->file = fget(arg->fd);
- }
- req->out = tmpbuf;
- req->outsize = nbytes;
- tmpbuf = NULL;
- ret = nbytes;
- wake_up(&req->waitq);
- out_free_unlock:
spin_unlock(&fuse_lock);
- out_free:
- kfree(tmpbuf);
- out:
- return ret;
+ if(!req)
+ return -ENOENT;
+
+ req->out->h = oh;
+ err = copy_out_args(req->out, buf, nbytes);
+
+ spin_lock(&fuse_lock);
+ if(err)
+ list_add_tail(&fc->processing, &req->list);
+ else {
+ /* fget() needs to be done in this context */
+ if(req->in->h.opcode == FUSE_GETDIR && !oh.error)
+ process_getdir(req);
+ req->finished = 1;
+ }
+ req->locked = 0;
+ if(!err || req->interrupted)
+ wake_up(&req->waitq);
+ spin_unlock(&fuse_lock);
+
+ if(!err)
+ return nbytes;
+ else
+ return err;
}
@@ -350,6 +445,7 @@
init_waitqueue_head(&fc->waitq);
INIT_LIST_HEAD(&fc->pending);
INIT_LIST_HEAD(&fc->processing);
+ sema_init(&fc->outstanding, MAX_OUTSTANDING);
fc->reqctr = 1;
}
return fc;
@@ -369,16 +465,18 @@
return 0;
}
-static void end_requests(struct list_head *head)
+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->outsize)
+ if(req->issync) {
+ req->out->h.error = -ECONNABORTED;
wake_up(&req->waitq);
+ }
else
- request_free(req);
+ destroy_request(fc, req);
}
}
@@ -388,8 +486,8 @@
spin_lock(&fuse_lock);
fc->file = NULL;
- end_requests(&fc->pending);
- end_requests(&fc->processing);
+ end_requests(fc, &fc->pending);
+ end_requests(fc, &fc->processing);
fuse_release_conn(fc);
spin_unlock(&fuse_lock);
return 0;
@@ -411,6 +509,12 @@
proc_fs_fuse = NULL;
proc_fuse_dev = NULL;
+ fuse_req_cachep = kmem_cache_create("fuser_request",
+ sizeof(struct fuse_req),
+ 0, 0, NULL, NULL);
+ if(!fuse_req_cachep)
+ return -ENOMEM;
+
ret = -EIO;
proc_fs_fuse = proc_mkdir("fuse", proc_root_fs);
if(!proc_fs_fuse) {
@@ -440,6 +544,8 @@
remove_proc_entry("dev", proc_fs_fuse);
remove_proc_entry("fuse", proc_root_fs);
}
+
+ kmem_cache_destroy(fuse_req_cachep);
}
/*
diff --git a/kernel/dir.c b/kernel/dir.c
index 537b71c..a3f1485 100644
--- a/kernel/dir.c
+++ b/kernel/dir.c
@@ -79,29 +79,52 @@
return inode;
}
+/* If the inode belongs to an existing directory, then it cannot be
+ assigned to new dentry */
+static int inode_ok(struct inode *inode)
+{
+ struct dentry *alias;
+ if(S_ISDIR(inode->i_mode) && (alias = d_find_alias(inode)) != NULL) {
+ dput(alias);
+ printk("fuse: cannot assign an existing directory\n");
+ return 0;
+
+ }
+ return 1;
+}
+
static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry)
{
int ret;
struct fuse_conn *fc = INO_FC(dir);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_lookup_out arg;
+ struct fuse_lookup_out outarg;
struct inode *inode;
in.h.opcode = FUSE_LOOKUP;
in.h.ino = dir->i_ino;
- in.argsize = entry->d_name.len + 1;
- in.arg = entry->d_name.name;
- out.argsize = sizeof(arg);
- out.arg = &arg;
+ in.numargs = 1;
+ in.args[0].size = entry->d_name.len + 1;
+ in.args[0].value = entry->d_name.name;
+ out.numargs = 1;
+ out.args[0].size = sizeof(outarg);
+ out.args[0].value = &outarg;
request_send(fc, &in, &out);
inode = NULL;
if(!out.h.error) {
ret = -ENOMEM;
- inode = fuse_iget(dir->i_sb, arg.ino, &arg.attr, out.h.unique);
+ inode = fuse_iget(dir->i_sb, outarg.ino, &outarg.attr,
+ out.h.unique);
if(!inode)
goto err;
+
+ ret = -EPROTO;
+ if(!inode_ok(inode)) {
+ iput(inode);
+ goto err;
+ }
}
else if(out.h.error != -ENOENT) {
ret = out.h.error;
@@ -126,28 +149,25 @@
struct fuse_conn *fc = INO_FC(dir);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_mknod_in *inarg;
- unsigned int insize;
+ struct fuse_mknod_in inarg;
struct fuse_mknod_out outarg;
struct inode *inode;
-
- insize = offsetof(struct fuse_mknod_in, name) + entry->d_name.len + 1;
- inarg = kmalloc(insize, GFP_KERNEL);
- if(!inarg)
- return -ENOMEM;
-
- inarg->mode = mode;
- inarg->rdev = rdev;
- strcpy(inarg->name, entry->d_name.name);
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.mode = mode;
+ inarg.rdev = rdev;
in.h.opcode = FUSE_MKNOD;
in.h.ino = dir->i_ino;
- in.argsize = insize;
- in.arg = inarg;
- out.argsize = sizeof(outarg);
- out.arg = &outarg;
+ in.numargs = 2;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
+ in.args[1].size = entry->d_name.len + 1;
+ in.args[1].value = entry->d_name.name;
+ out.numargs = 1;
+ out.args[0].size = sizeof(outarg);
+ out.args[0].value = &outarg;
request_send(fc, &in, &out);
- kfree(inarg);
if(out.h.error)
return out.h.error;
@@ -156,6 +176,18 @@
if(!inode)
return -ENOMEM;
+ /* Don't allow userspace to do really stupid things... */
+ if((inode->i_mode ^ mode) & S_IFMT) {
+ iput(inode);
+ printk("fuse_mknod: inode has wrong type\n");
+ return -EPROTO;
+ }
+
+ if(!inode_ok(inode)) {
+ iput(inode);
+ return -EPROTO;
+ }
+
d_instantiate(entry, inode);
return 0;
}
@@ -171,23 +203,19 @@
struct fuse_conn *fc = INO_FC(dir);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_mkdir_in *inarg;
- unsigned int insize;
-
- insize = offsetof(struct fuse_mkdir_in, name) + entry->d_name.len + 1;
- inarg = kmalloc(insize, GFP_KERNEL);
- if(!inarg)
- return -ENOMEM;
-
- inarg->mode = mode;
- strcpy(inarg->name, entry->d_name.name);
+ struct fuse_mkdir_in inarg;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.mode = mode;
in.h.opcode = FUSE_MKDIR;
in.h.ino = dir->i_ino;
- in.argsize = insize;
- in.arg = inarg;
+ in.numargs = 2;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
+ in.args[1].size = entry->d_name.len + 1;
+ in.args[1].value = entry->d_name.name;
request_send(fc, &in, &out);
- kfree(inarg);
return out.h.error;
}
@@ -198,24 +226,16 @@
struct fuse_conn *fc = INO_FC(dir);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- char *inarg;
- unsigned int insize;
-
- insize = entry->d_name.len + 1 + strlen(link) + 1;
- inarg = kmalloc(insize, GFP_KERNEL);
- if(!inarg)
- return -ENOMEM;
-
- strcpy(inarg, entry->d_name.name);
- strcpy(inarg + entry->d_name.len + 1, link);
in.h.opcode = FUSE_SYMLINK;
in.h.ino = dir->i_ino;
- in.argsize = insize;
- in.arg = inarg;
+ in.numargs = 2;
+ in.args[0].size = entry->d_name.len + 1;
+ in.args[0].value = entry->d_name.name;
+ in.args[1].size = strlen(link) + 1;
+ in.args[1].value = link;
request_send(fc, &in, &out);
- kfree(inarg);
-
+
return out.h.error;
}
@@ -228,9 +248,11 @@
in.h.opcode = op;
in.h.ino = dir->i_ino;
- in.argsize = entry->d_name.len + 1;
- in.arg = entry->d_name.name;
+ in.numargs = 1;
+ in.args[0].size = entry->d_name.len + 1;
+ in.args[0].value = entry->d_name.name;
request_send(fc, &in, &out);
+
return out.h.error;
}
@@ -250,27 +272,21 @@
struct fuse_conn *fc = INO_FC(olddir);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_rename_in *inarg;
- unsigned int oldnamsize = oldent->d_name.len + 1;
- unsigned int newnamsize = newent->d_name.len + 1;
- unsigned int insize;
+ struct fuse_rename_in inarg;
- insize = offsetof(struct fuse_rename_in, names) + oldnamsize +
- newnamsize;
- inarg = kmalloc(insize, GFP_KERNEL);
- if(!inarg)
- return -ENOMEM;
-
- inarg->newdir = newdir->i_ino;
- strcpy(inarg->names, oldent->d_name.name);
- strcpy(inarg->names + oldnamsize, newent->d_name.name);
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.newdir = newdir->i_ino;
in.h.opcode = FUSE_RENAME;
in.h.ino = olddir->i_ino;
- in.argsize = insize;
- in.arg = inarg;
+ in.numargs = 3;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
+ in.args[1].size = oldent->d_name.len + 1;
+ in.args[1].value = oldent->d_name.name;
+ in.args[2].size = newent->d_name.len + 1;
+ in.args[2].value = newent->d_name.name;
request_send(fc, &in, &out);
- kfree(inarg);
return out.h.error;
}
@@ -282,23 +298,19 @@
struct fuse_conn *fc = INO_FC(inode);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_link_in *inarg;
- unsigned int insize;
+ struct fuse_link_in inarg;
- insize = offsetof(struct fuse_link_in, name) + newent->d_name.len + 1;
- inarg = kmalloc(insize, GFP_KERNEL);
- if(!inarg)
- return -ENOMEM;
-
- inarg->newdir = newdir->i_ino;
- strcpy(inarg->name, newent->d_name.name);
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.newdir = newdir->i_ino;
in.h.opcode = FUSE_LINK;
in.h.ino = inode->i_ino;
- in.argsize = insize;
- in.arg = inarg;
+ in.numargs = 2;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
+ in.args[1].size = newent->d_name.len + 1;
+ in.args[1].value = newent->d_name.name;
request_send(fc, &in, &out);
- kfree(inarg);
return out.h.error;
}
@@ -328,8 +340,9 @@
in.h.opcode = FUSE_GETATTR;
in.h.ino = inode->i_ino;
- out.argsize = sizeof(arg);
- out.arg = &arg;
+ out.numargs = 1;
+ out.args[0].size = sizeof(arg);
+ out.args[0].value = &arg;
request_send(fc, &in, &out);
if(!out.h.error)
@@ -400,16 +413,17 @@
in.h.opcode = FUSE_READLINK;
in.h.ino = inode->i_ino;
- out.arg = link;
- out.argsize = PAGE_SIZE - 1;
out.argvar = 1;
+ out.numargs = 1;
+ out.args[0].size = PAGE_SIZE - 1;
+ out.args[0].value = link;
request_send(fc, &in, &out);
if(out.h.error) {
free_page((unsigned long) link);
return ERR_PTR(out.h.error);
}
- link[out.argsize] = '\0';
+ link[out.args[0].size] = '\0';
return link;
}
@@ -453,8 +467,9 @@
in.h.opcode = FUSE_GETDIR;
in.h.ino = inode->i_ino;
- out.argsize = sizeof(outarg);
- out.arg = &outarg;
+ out.numargs = 1;
+ out.args[0].size = sizeof(outarg);
+ out.args[0].value = &outarg;
request_send(fc, &in, &out);
if(!out.h.error) {
struct file *cfile = outarg.file;
@@ -521,14 +536,17 @@
struct fuse_setattr_in inarg;
struct fuse_setattr_out outarg;
+ memset(&inarg, 0, sizeof(inarg));
inarg.valid = iattr_to_fattr(attr, &inarg.attr);
in.h.opcode = FUSE_SETATTR;
in.h.ino = inode->i_ino;
- in.argsize = sizeof(inarg);
- in.arg = &inarg;
- out.argsize = sizeof(outarg);
- out.arg = &outarg;
+ in.numargs = 1;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
+ out.numargs = 1;
+ out.args[0].size = sizeof(outarg);
+ out.args[0].value = &outarg;
request_send(fc, &in, &out);
if(!out.h.error) {
diff --git a/kernel/file.c b/kernel/file.c
index a67bdcd..b7cb2f4 100644
--- a/kernel/file.c
+++ b/kernel/file.c
@@ -10,19 +10,21 @@
#include <linux/pagemap.h>
#include <linux/slab.h>
-
static int fuse_open(struct inode *inode, struct file *file)
{
struct fuse_conn *fc = INO_FC(inode);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_open_in arg;
+ struct fuse_open_in inarg;
- arg.flags = file->f_flags & ~O_EXCL;
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.flags = file->f_flags & ~O_EXCL;
+
in.h.opcode = FUSE_OPEN;
in.h.ino = inode->i_ino;
- in.argsize = sizeof(arg);
- in.arg = &arg;
+ in.numargs = 1;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
request_send(fc, &in, &out);
if(!out.h.error)
invalidate_inode_pages(inode);
@@ -37,27 +39,30 @@
struct fuse_conn *fc = INO_FC(inode);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_read_in arg;
+ struct fuse_read_in inarg;
char *buffer;
buffer = kmap(page);
-
- arg.offset = page->index << PAGE_CACHE_SHIFT;
- arg.size = PAGE_CACHE_SIZE;
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.offset = page->index << PAGE_CACHE_SHIFT;
+ inarg.size = PAGE_CACHE_SIZE;
in.h.opcode = FUSE_READ;
in.h.ino = inode->i_ino;
- in.argsize = sizeof(arg);
- in.arg = &arg;
- out.argsize = PAGE_CACHE_SIZE;
+ in.numargs = 1;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
out.argvar = 1;
- out.arg = buffer;
+ out.numargs = 1;
+ out.args[0].size = PAGE_CACHE_SIZE;
+ out.args[0].value = buffer;
request_send(fc, &in, &out);
if(!out.h.error) {
- if(out.argsize < PAGE_CACHE_SIZE)
- memset(buffer + out.argsize, 0,
- PAGE_CACHE_SIZE - out.argsize);
+ size_t outsize = out.args[0].size;
+ if(outsize < PAGE_CACHE_SIZE)
+ memset(buffer + outsize, 0, PAGE_CACHE_SIZE - outsize);
SetPageUptodate(page);
}
@@ -73,27 +78,25 @@
struct fuse_conn *fc = INO_FC(inode);
struct fuse_in in = FUSE_IN_INIT;
struct fuse_out out = FUSE_OUT_INIT;
- struct fuse_write_in *arg;
- size_t argsize;
+ struct fuse_write_in inarg;
char *buffer;
- argsize = offsetof(struct fuse_write_in, buf) + count;
- arg = kmalloc(argsize, GFP_KERNEL);
- if(!arg)
- return -ENOMEM;
-
- arg->offset = (page->index << PAGE_CACHE_SHIFT) + offset;
- arg->size = count;
buffer = kmap(page);
- memcpy(arg->buf, buffer + offset, count);
- kunmap(page);
+
+ memset(&inarg, 0, sizeof(inarg));
+ inarg.offset = (page->index << PAGE_CACHE_SHIFT) + offset;
+ inarg.size = count;
in.h.opcode = FUSE_WRITE;
in.h.ino = inode->i_ino;
- in.argsize = argsize;
- in.arg = arg;
+ in.numargs = 2;
+ in.args[0].size = sizeof(inarg);
+ in.args[0].value = &inarg;
+ in.args[1].size = count;
+ in.args[1].value = buffer + offset;
request_send(fc, &in, &out);
- kfree(arg);
+
+ kunmap(page);
return out.h.error;
}
diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h
index e8dde37..27d8eb3 100644
--- a/kernel/fuse_i.h
+++ b/kernel/fuse_i.h
@@ -14,14 +14,12 @@
#include <linux/list.h>
#include <linux/spinlock.h>
-#define MAX_CLEARED 256
-
/**
* 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
- * umounted.
+ * unmounted.
*/
struct fuse_conn {
/** The superblock of the mounted filesystem */
@@ -36,7 +34,7 @@
/** The fuse mount flags for this mount */
unsigned int flags;
- /** The client wait queue */
+ /** Readers of the connection are waiting on this */
wait_queue_head_t waitq;
/** The list of pending requests */
@@ -45,10 +43,43 @@
/** The list of requests being processed */
struct list_head processing;
- /** The request id */
+ /** Controls the maximum number of outstanding requests */
+ struct semaphore outstanding;
+
+ /** The next unique request id */
int reqctr;
};
+/** 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];
+};
+
+#define FUSE_IN_INIT { {0, 0, 0}, 0}
+#define FUSE_OUT_INIT { {0, 0}, 0, 0}
+
/**
* A request to the client
*/
@@ -56,25 +87,28 @@
/** The request list */
struct list_head list;
- /** The request ID */
- int unique;
+ /** True if the request is synchronous */
+ unsigned int issync:1;
- /** The opcode */
- enum fuse_opcode opcode;
-
- /** The request input size */
- unsigned int insize;
+ /** The request is locked */
+ unsigned int locked:1;
+
+ /** The request has been interrupted while it was locked */
+ unsigned int interrupted:1;
+
+ /* The request has been sent to the client */
+ unsigned int sent:1;
+
+ /* The request is finished */
+ unsigned int finished:1;
/** The request input */
- char *in;
-
- /** The maximum request output size */
- unsigned int outsize;
+ struct fuse_in *in;
/** The request output */
- char *out;
+ struct fuse_out *out;
- /** The request wait queue */
+ /** Used to wake up the task waiting for completion of request*/
wait_queue_head_t waitq;
};
@@ -82,22 +116,6 @@
#define INO_FC(inode) ((struct fuse_conn *) (inode)->i_sb->u.generic_sbp)
#define DEV_FC(file) ((struct fuse_conn *) (file)->private_data)
-struct fuse_in {
- struct fuse_in_header h;
- unsigned int argsize;
- const void *arg;
-};
-
-struct fuse_out {
- struct fuse_out_header h;
- unsigned int argsize;
- unsigned int argvar;
- void *arg;
-};
-
-#define FUSE_IN_INIT { {0, 0, 0}, 0, 0 }
-#define FUSE_OUT_INIT { {0, 0}, 0, 0, 0 }
-
/**
* The proc entry for the client device ("/proc/fs/fuse/dev")
@@ -155,6 +173,11 @@
void request_send(struct fuse_conn *fc, struct fuse_in *in,
struct fuse_out *out);
+/**
+ * Send a request for which a reply is not expected
+ */
+int request_send_noreply(struct fuse_conn *fc, struct fuse_in *in);
+
/*
* Local Variables:
* indent-tabs-mode: t
diff --git a/kernel/inode.c b/kernel/inode.c
index 36819b7..df402ba 100644
--- a/kernel/inode.c
+++ b/kernel/inode.c
@@ -23,17 +23,35 @@
static void fuse_clear_inode(struct inode *inode)
{
struct fuse_conn *fc = INO_FC(inode);
- struct fuse_in in = FUSE_IN_INIT;
- struct fuse_forget_in arg;
+ struct fuse_in *in = NULL;
+ struct fuse_forget_in *inarg = NULL;
- arg.version = inode->i_version;
+ if(fc == NULL)
+ return;
+
+ in = kmalloc(sizeof(struct fuse_in), GFP_NOFS);
+ if(!in)
+ return;
+
+ inarg = kmalloc(sizeof(struct fuse_forget_in), GFP_NOFS);
+ if(!inarg)
+ goto out_free;
- in.h.opcode = FUSE_FORGET;
- in.h.ino = inode->i_ino;
- in.argsize = sizeof(arg);
- in.arg = &arg;
-
- request_send(fc, &in, NULL);
+ memset(inarg, 0, sizeof(struct fuse_forget_in));
+ inarg->version = inode->i_version;
+
+ in->h.opcode = FUSE_FORGET;
+ in->h.ino = inode->i_ino;
+ in->numargs = 1;
+ in->args[0].size = sizeof(struct fuse_forget_in);
+ in->args[0].value = inarg;
+
+ if(!request_send_noreply(fc, in))
+ return;
+
+ out_free:
+ kfree(inarg);
+ kfree(in);
}
static void fuse_put_super(struct super_block *sb)
@@ -45,6 +63,7 @@
fc->uid = 0;
fc->flags = 0;
fuse_release_conn(fc);
+ sb->u.generic_sbp = NULL;
spin_unlock(&fuse_lock);
}
diff --git a/lib/fuse.c b/lib/fuse.c
index 8ea13ad..2ed5169 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -165,20 +165,6 @@
return get_ino(node);
}
-static fino_t find_node_dir(struct fuse *f, fino_t parent, char *name)
-{
- struct node *node;
-
- pthread_mutex_lock(&f->lock);
- node = lookup_node(f, parent, name);
- pthread_mutex_unlock(&f->lock);
-
- if(node != NULL)
- return get_ino(node);
- else
- return (fino_t) -1;
-}
-
static char *add_name(char *buf, char *s, const char *name)
{
size_t len = strlen(name);
@@ -310,7 +296,7 @@
size_t reclen;
size_t res;
- dirent.ino = find_node_dir(dh->fuse, dh->dir, name);
+ dirent.ino = (unsigned long) -1;
dirent.namelen = strlen(name);
strncpy(dirent.name, name, sizeof(dirent.name));
dirent.type = type;
@@ -323,10 +309,28 @@
return 0;
}
+static void send_reply_raw(struct fuse *f, char *outbuf, size_t outsize)
+{
+ int res;
+
+ if((f->flags & FUSE_DEBUG)) {
+ struct fuse_out_header *out = (struct fuse_out_header *) outbuf;
+ printf(" unique: %i, error: %i (%s), outsize: %i\n", out->unique,
+ out->error, strerror(-out->error), outsize);
+ fflush(stdout);
+ }
+
+ res = write(f->fd, outbuf, outsize);
+ if(res == -1) {
+ /* ENOENT means the operation was interrupted */
+ if(errno != ENOENT)
+ perror("writing fuse device");
+ }
+}
+
static void send_reply(struct fuse *f, struct fuse_in_header *in, int error,
void *arg, size_t argsize)
{
- int res;
char *outbuf;
size_t outsize;
struct fuse_out_header *out;
@@ -347,18 +351,7 @@
if(argsize != 0)
memcpy(outbuf + sizeof(struct fuse_out_header), arg, argsize);
- if((f->flags & FUSE_DEBUG)) {
- printf(" unique: %i, error: %i (%s), outsize: %i\n", out->unique,
- out->error, strerror(-out->error), outsize);
- fflush(stdout);
- }
-
- res = write(f->fd, outbuf, outsize);
- if(res == -1) {
- /* ENOENT means the operation was interrupted */
- if(errno != ENOENT)
- perror("writing fuse device");
- }
+ send_reply_raw(f, outbuf, outsize);
free(outbuf);
}
@@ -388,6 +381,10 @@
static void do_forget(struct fuse *f, struct fuse_in_header *in,
struct fuse_forget_in *arg)
{
+ if(f->flags & FUSE_DEBUG) {
+ printf("FORGET %li/%i\n", in->ino, arg->version);
+ fflush(stdout);
+ }
destroy_node(f, in->ino, arg->version);
}
@@ -697,8 +694,11 @@
{
int res;
char *path;
- char *buf = (char *) malloc(arg->size);
+ char *outbuf = (char *) malloc(sizeof(struct fuse_out_header) + arg->size);
+ struct fuse_out_header *out = (struct fuse_out_header *) outbuf;
+ char *buf = outbuf + sizeof(struct fuse_out_header);
size_t size;
+ size_t outsize;
res = -ENOENT;
path = get_path(f, in->ino);
@@ -714,9 +714,12 @@
size = res;
res = 0;
}
-
- send_reply(f, in, res, buf, size);
- free(buf);
+ out->unique = in->unique;
+ out->error = res;
+ outsize = sizeof(struct fuse_out_header) + size;
+
+ send_reply_raw(f, outbuf, outsize);
+ free(outbuf);
}
static void do_write(struct fuse *f, struct fuse_in_header *in,
@@ -747,6 +750,12 @@
send_reply(f, in, res, NULL, 0);
}
+static void free_cmd(struct fuse_cmd *cmd)
+{
+ free(cmd->buf);
+ free(cmd);
+}
+
void __fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd)
{
struct fuse_in_header *in = (struct fuse_in_header *) cmd->buf;
@@ -766,10 +775,6 @@
do_lookup(f, in, (char *) inarg);
break;
- case FUSE_FORGET:
- do_forget(f, in, (struct fuse_forget_in *) inarg);
- break;
-
case FUSE_GETATTR:
do_getattr(f, in);
break;
@@ -826,37 +831,45 @@
default:
fprintf(stderr, "Operation %i not implemented\n", in->opcode);
- /* No need to send reply to async requests */
- if(in->unique != 0)
- send_reply(f, in, -ENOSYS, NULL, 0);
+ send_reply(f, in, -ENOSYS, NULL, 0);
}
-
- free(cmd->buf);
- free(cmd);
+
+ free_cmd(cmd);
}
struct fuse_cmd *__fuse_read_cmd(struct fuse *f)
{
ssize_t res;
- char inbuf[FUSE_MAX_IN];
struct fuse_cmd *cmd;
-
- res = read(f->fd, inbuf, sizeof(inbuf));
- if(res == -1) {
- perror("reading fuse device");
- /* BAD... This will happen again */
- return NULL;
- }
- if((size_t) res < sizeof(struct fuse_in_header)) {
- fprintf(stderr, "short read on fuse device\n");
- /* Cannot happen */
- return NULL;
- }
+ struct fuse_in_header *in;
- cmd = (struct fuse_cmd *) malloc(sizeof(*cmd));
- cmd->buflen = res;
- cmd->buf = (char *) malloc(cmd->buflen);
- memcpy(cmd->buf, inbuf, cmd->buflen);
+ cmd = (struct fuse_cmd *) malloc(sizeof(struct fuse_cmd));
+ cmd->buf = (char *) malloc(FUSE_MAX_IN);
+
+ do {
+ res = read(f->fd, cmd->buf, FUSE_MAX_IN);
+ if(res == -1) {
+ perror("reading fuse device");
+ /* BAD... This will happen again */
+ free_cmd(cmd);
+ return NULL;
+ }
+ if((size_t) res < sizeof(struct fuse_in_header)) {
+ fprintf(stderr, "short read on fuse device\n");
+ /* Cannot happen */
+ free_cmd(cmd);
+ return NULL;
+ }
+ cmd->buflen = res;
+
+ /* FORGET is special: it can be done without calling filesystem
+ methods. */
+ in = (struct fuse_in_header *) cmd->buf;
+ if(in->opcode == FUSE_FORGET) {
+ void *inarg = cmd->buf + sizeof(struct fuse_in_header);
+ do_forget(f, in, (struct fuse_forget_in *) inarg);
+ }
+ } while(in->opcode == FUSE_FORGET);
return cmd;
}
diff --git a/lib/fuse_mt.c b/lib/fuse_mt.c
index 19cc33c..6120554 100644
--- a/lib/fuse_mt.c
+++ b/lib/fuse_mt.c
@@ -13,23 +13,64 @@
#include <string.h>
#include <pthread.h>
#include <signal.h>
+#include <errno.h>
+#include <sys/time.h>
-struct fuse_thr_data {
+#define FUSE_WORKER_IDLE 10
+
+static pthread_mutex_t fuse_mt_lock = PTHREAD_MUTEX_INITIALIZER;
+
+
+struct fuse_worker {
+ struct fuse_worker *next;
+ struct fuse_worker *prev;
struct fuse *f;
void *data;
fuse_processor_t proc;
struct fuse_cmd *cmd;
+ int avail;
+ pthread_cond_t start;
};
static void *do_work(void *data)
{
- struct fuse_thr_data *d = (struct fuse_thr_data *) data;
- d->proc(d->f, d->cmd, d->data);
- free(d);
+ struct fuse_worker *w = (struct fuse_worker *) data;
+ int ret;
+
+ do {
+ struct timeval now;
+ struct timespec timeout;
+
+ w->proc(w->f, w->cmd, w->data);
+
+ pthread_mutex_lock(&fuse_mt_lock);
+ w->avail = 1;
+ w->cmd = NULL;
+ gettimeofday(&now, NULL);
+ timeout.tv_sec = now.tv_sec + FUSE_WORKER_IDLE;
+ timeout.tv_nsec = now.tv_usec * 1000;
+
+ ret = 0;
+ while(w->cmd == NULL && ret != ETIMEDOUT)
+ ret = pthread_cond_timedwait(&w->start, &fuse_mt_lock, &timeout);
+
+ if(ret == ETIMEDOUT) {
+ struct fuse_worker *next = w->next;
+ struct fuse_worker *prev = w->prev;
+ prev->next = next;
+ next->prev = prev;
+ pthread_cond_destroy(&w->start);
+ free(w);
+ }
+ w->avail = 0;
+ pthread_mutex_unlock(&fuse_mt_lock);
+
+ } while(ret != ETIMEDOUT);
+
return NULL;
}
-static void start_thread(struct fuse_thr_data *d)
+static void start_thread(struct fuse_worker *w)
{
pthread_t thrid;
sigset_t oldset;
@@ -39,7 +80,7 @@
/* Disallow signal reception in worker threads */
sigfillset(&newset);
pthread_sigmask(SIG_SETMASK, &newset, &oldset);
- res = pthread_create(&thrid, NULL, do_work, d);
+ res = pthread_create(&thrid, NULL, do_work, w);
pthread_sigmask(SIG_SETMASK, &oldset, NULL);
if(res != 0) {
fprintf(stderr, "Error creating thread: %s\n", strerror(res));
@@ -50,19 +91,47 @@
void __fuse_loop_mt(struct fuse *f, fuse_processor_t proc, void *data)
{
+ struct fuse_worker *head;
+
+ head = malloc(sizeof(struct fuse_worker));
+ head->next = head;
+ head->prev = head;
+
while(1) {
- struct fuse_thr_data *d;
+ struct fuse_worker *w;
struct fuse_cmd *cmd = __fuse_read_cmd(f);
if(cmd == NULL)
exit(1);
- d = malloc(sizeof(struct fuse_thr_data));
- d->proc = proc;
- d->f = f;
- d->cmd = cmd;
- d->data = data;
-
- start_thread(d);
+ pthread_mutex_lock(&fuse_mt_lock);
+ for(w = head->next; w != head; w = w->next)
+ if(w->avail)
+ break;
+
+ if(w != head) {
+ pthread_cond_signal(&w->start);
+ w->cmd = cmd;
+ w = NULL;
+ }
+ else {
+ struct fuse_worker *prev = head->prev;
+ struct fuse_worker *next = head;
+ w = malloc(sizeof(struct fuse_worker));
+ w->prev = prev;
+ w->next = next;
+ next->prev = w;
+ prev->next = w;
+ w->f = f;
+ w->data = data;
+ w->proc = proc;
+ w->cmd = cmd;
+ w->avail = 0;
+ pthread_cond_init(&w->start, NULL);
+ }
+ pthread_mutex_unlock(&fuse_mt_lock);
+
+ if(w != NULL)
+ start_thread(w);
}
}