diff options
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/dev.c | 494 | ||||
-rw-r--r-- | kernel/dir.c | 198 | ||||
-rw-r--r-- | kernel/file.c | 63 | ||||
-rw-r--r-- | kernel/fuse_i.h | 91 | ||||
-rw-r--r-- | kernel/inode.c | 37 |
5 files changed, 526 insertions, 357 deletions
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 @@ 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 <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; + + 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 <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 @@ 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); } |