From 43696434f8fd5cd1107658f329b6922ea947ed1f Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sun, 18 Nov 2001 19:15:05 +0000 Subject: [PATCH] performance improvements --- BUGS | 4 + example/.cvsignore | 1 + example/Makefile.am | 5 +- example/null.c | 178 ++++++++++++++++ include/linux/fuse.h | 42 ++-- kernel/dev.c | 494 ++++++++++++++++++++++++++----------------- kernel/dir.c | 198 +++++++++-------- kernel/file.c | 63 +++--- kernel/fuse_i.h | 91 +++++--- kernel/inode.c | 37 +++- lib/fuse.c | 131 ++++++------ lib/fuse_mt.c | 97 +++++++-- 12 files changed, 888 insertions(+), 453 deletions(-) create mode 100644 BUGS create mode 100644 example/null.c 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.in 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 +#include +#include +#include +#include +#include +#include +#include + +#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 @@ struct fuse_attr { #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_getdir_out { 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_mknod_out { 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_read_in { 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 #include -#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 @@ static int interrupt_error(enum fuse_opcode opcode) 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); + + /* 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->out->h.error = -ERESTARTSYS; +} - return ret; +static int get_unique(struct fuse_conn *fc) +{ + do fc->reqctr++; + while(!fc->reqctr); + return fc->reqctr; } -static int request_check(struct fuse_req *req, struct fuse_out *outp) +void request_send(struct fuse_conn *fc, struct fuse_in *in, + struct fuse_out *out) { - struct fuse_out_header *oh; - unsigned int size; + struct fuse_req *req; - if(!req->out) - return -ECONNABORTED; + out->h.error = -ERESTARTSYS; + if(down_interruptible(&fc->outstanding)) + return; - 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; - } + out->h.error = -ENOMEM; + req = request_new(); + if(!req) + return; + + req->in = in; + req->out = out; + req->issync = 1; - 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; + spin_lock(&fuse_lock); + 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); } - - memcpy(&outp->h, oh, OHSIZE); - outp->argsize = size; - if(size) - memcpy(outp->arg, req->out + OHSIZE, size); - - return oh->error; -} + spin_unlock(&fuse_lock); + request_free(req); -static void request_free(struct fuse_req *req) -{ - kfree(req->in); - kfree(req->out); - kfree(req); + up(&fc->outstanding); } -static struct fuse_req *request_new(struct fuse_conn *fc, struct fuse_in *inp, - struct fuse_out *outp) +static inline void destroy_request(struct fuse_conn *fc, struct fuse_req *req) { - struct fuse_req *req; - - req = kmalloc(sizeof(*req), GFP_NOFS); - if(!req) - return NULL; + if(req) { + int i; - if(outp) - req->outsize = OHSIZE + outp->argsize; - else - req->outsize = 0; - req->out = NULL; - - req->insize = IHSIZE + inp->argsize; - req->in = kmalloc(req->insize, GFP_NOFS); - if(!req->in) { + for(i = 0; i < req->in->numargs; i++) + kfree(req->in->args[i].value); + kfree(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; } -/* 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) +/* 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) { - int ret; - struct fuse_in_header *ih; struct fuse_req *req; - ret = -ENOMEM; - req = request_new(fc, inp, outp); + req = request_new(); if(!req) - goto out; + return -ENOMEM; + + req->in = in; + req->issync = 0; 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; + if(!fc->file) { + spin_unlock(&fuse_lock); + request_free(req); + return -ENOTCONN; } - else - ih->unique = req->unique = 0; 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 @@ static struct fuse_req *request_find(struct fuse_conn *fc, unsigned int unique) 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 @@ static struct fuse_req *request_find(struct fuse_conn *fc, unsigned int unique) 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) { + spin_unlock(&fuse_lock); + 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 */ - 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: + 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); - out_free: - kfree(tmpbuf); - out: - return ret; + + if(!err) + return nbytes; + else + return err; } @@ -350,6 +445,7 @@ static struct fuse_conn *new_conn(void) 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 @@ static int fuse_dev_open(struct inode *inode, struct file *file) 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 @@ static int fuse_dev_release(struct inode *inode, struct file *file) 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 @@ int fuse_dev_init() 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 @@ void fuse_dev_cleanup() 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 @@ struct inode *fuse_iget(struct super_block *sb, ino_t ino, 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 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode, 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 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode, 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 @@ static int fuse_mkdir(struct inode *dir, struct dentry *entry, int mode) 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 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry, 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 @@ static int fuse_remove(struct inode *dir, struct dentry *entry, 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 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, 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 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, 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; - - insize = offsetof(struct fuse_link_in, name) + newent->d_name.len + 1; - inarg = kmalloc(insize, GFP_KERNEL); - if(!inarg) - return -ENOMEM; + struct fuse_link_in inarg; - 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 @@ static int fuse_revalidate(struct dentry *entry) 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 @@ static char *read_link(struct dentry *dentry) 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 @@ static int fuse_dir_open(struct inode *inode, struct file *file) 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 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) 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 #include - 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; + + memset(&inarg, 0, sizeof(inarg)); + inarg.flags = file->f_flags & ~O_EXCL; - arg.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 @@ static int fuse_readpage(struct file *file, struct page *page) 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 @@ static int write_buffer(struct inode *inode, struct page *page, 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 #include -#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 @@ struct fuse_conn { /** 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 @@ struct fuse_conn { /** 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 @@ struct fuse_req { /** 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 @@ struct fuse_req { #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 fuse_fs_cleanup(void); 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_read_inode(struct inode *inode) 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; - - in.h.opcode = FUSE_FORGET; - in.h.ino = inode->i_ino; - in.argsize = sizeof(arg); - in.arg = &arg; + 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; - 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 @@ static void fuse_put_super(struct super_block *sb) 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 @@ static fino_t find_node(struct fuse *f, fino_t parent, char *name, 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 @@ static int fill_dir(struct fuse_dirhandle *dh, char *name, int type) 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 @@ static int fill_dir(struct fuse_dirhandle *dh, char *name, int type) 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 @@ static void send_reply(struct fuse *f, struct fuse_in_header *in, int error, 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_lookup(struct fuse *f, struct fuse_in_header *in, char *name) 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 @@ static void do_read(struct fuse *f, struct fuse_in_header *in, { 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 @@ static void do_read(struct fuse *f, struct fuse_in_header *in, 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 @@ static void do_write(struct fuse *f, struct fuse_in_header *in, 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 @@ void __fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd) 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 @@ void __fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd) 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 #include #include +#include +#include -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 @@ static void start_thread(struct fuse_thr_data *d) /* 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 @@ static void start_thread(struct fuse_thr_data *d) 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); } } -- 2.30.2