From 7c35cf9df254b3d315a0036f8102afa72f0a382d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 14 Jan 2004 16:56:49 +0000 Subject: [PATCH] 2.6 fixes --- ChangeLog | 8 +++ configure.in | 2 +- kernel/dev.c | 114 ++++++++++++++++++++++++++++----------- kernel/dir.c | 10 +++- kernel/file.c | 138 +++++++++++++++++++++++++++++++++++++++--------- kernel/fuse_i.h | 17 ++++++ kernel/inode.c | 14 ++--- 7 files changed, 234 insertions(+), 69 deletions(-) diff --git a/ChangeLog b/ChangeLog index b065dbf..2c43391 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2004-01-14 Miklos Szeredi + + * Support non-blocking writepage on 2.6. This makes FUSE behave + much more nicely in low-memory situations + + * Fix 32-bit dev handling in getattr and mknod for 2.6 kernels. + (Note: the mknod method does not yet use 32bit device number) + 2004-01-13 Miklos Szeredi * Code cleanups diff --git a/configure.in b/configure.in index 359dc68..be93170 100644 --- a/configure.in +++ b/configure.in @@ -1,5 +1,5 @@ AC_INIT(lib/fuse.c) -AM_INIT_AUTOMAKE(fuse, 1.1-pre1) +AM_INIT_AUTOMAKE(fuse, 1.1-pre2) AM_CONFIG_HEADER(include/config.h) AC_PROG_CC diff --git a/kernel/dev.c b/kernel/dev.c index 4f45658..d3c56a6 100644 --- a/kernel/dev.c +++ b/kernel/dev.c @@ -96,6 +96,22 @@ static int get_unique(struct fuse_conn *fc) return fc->reqctr; } +/* Must be called with fuse_lock held, and unlocks it */ +static void request_end(struct fuse_conn *fc, struct fuse_req *req) +{ + fuse_reqend_t endfunc = req->end; + + if(!endfunc) { + wake_up(&req->waitq); + spin_unlock(&fuse_lock); + } else { + spin_unlock(&fuse_lock); + endfunc(fc, req->in, req->out, req->data); + request_free(req); + up(&fc->outstanding); + } +} + void request_send(struct fuse_conn *fc, struct fuse_in *in, struct fuse_out *out) { @@ -107,24 +123,24 @@ void request_send(struct fuse_conn *fc, struct fuse_in *in, out->h.error = -ENOMEM; req = request_new(); - if(!req) - return; - - req->in = in; - req->out = out; - req->issync = 1; - - 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); + if(req) { + req->in = in; + req->out = out; + req->issync = 1; + req->end = NULL; + + 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); + } + spin_unlock(&fuse_lock); + request_free(req); } - spin_unlock(&fuse_lock); - request_free(req); up(&fc->outstanding); } @@ -133,10 +149,6 @@ void request_send(struct fuse_conn *fc, struct fuse_in *in, static inline void destroy_request(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); } @@ -169,6 +181,42 @@ int request_send_noreply(struct fuse_conn *fc, struct fuse_in *in) return 0; } +int request_send_nonblock(struct fuse_conn *fc, struct fuse_in *in, + struct fuse_out *out, fuse_reqend_t end, void *data) +{ + int err; + struct fuse_req *req; + + BUG_ON(!end); + + if(down_trylock(&fc->outstanding)) + return -EWOULDBLOCK; + + err = -ENOMEM; + req = request_new(); + if(req) { + req->in = in; + req->out = out; + req->issync = 1; + req->end = end; + req->data = data; + + spin_lock(&fuse_lock); + err = -ENOTCONN; + if(fc->file) { + in->h.unique = get_unique(fc); + list_add_tail(&req->list, &fc->pending); + wake_up(&fc->waitq); + spin_unlock(&fuse_lock); + return 0; + } + spin_unlock(&fuse_lock); + request_free(req); + } + up(&fc->outstanding); + return err; +} + static void request_wait(struct fuse_conn *fc) { DECLARE_WAITQUEUE(wait, current); @@ -257,13 +305,14 @@ static ssize_t fuse_dev_read(struct file *file, char *buf, size_t nbytes, } req->locked = 0; if(ret < 0 || req->interrupted) - wake_up(&req->waitq); - - req = NULL; + /* Unlocks fuse_lock: */ + request_end(fc, req); + else + spin_unlock(&fuse_lock); + } else { + spin_unlock(&fuse_lock); + destroy_request(req); } - spin_unlock(&fuse_lock); - destroy_request(req); - return ret; } @@ -452,8 +501,8 @@ static ssize_t fuse_dev_write(struct file *file, const char *buf, } req->finished = 1; req->locked = 0; - wake_up(&req->waitq); - spin_unlock(&fuse_lock); + /* Unlocks fuse_lock: */ + request_end(fc, req); out: if(!err) @@ -523,9 +572,10 @@ static void end_requests(struct fuse_conn *fc, struct list_head *head) if(req->issync) { req->out->h.error = -ECONNABORTED; req->finished = 1; - wake_up(&req->waitq); - } - else + /* Unlocks fuse_lock: */ + request_end(fc, req); + spin_lock(&fuse_lock); + } else destroy_request(req); } } diff --git a/kernel/dir.c b/kernel/dir.c index 7da5bc2..00b1e98 100644 --- a/kernel/dir.c +++ b/kernel/dir.c @@ -23,6 +23,11 @@ static struct dentry_operations fuse_dentry_opertations; /* FIXME: This should be user configurable */ #define FUSE_REVALIDATE_TIME (1 * HZ) +#ifndef KERNEL_2_6 +#define new_decode_dev(x) (x) +#define new_encode_dev(x) (x) +#endif + static void change_attributes(struct inode *inode, struct fuse_attr *attr) { if(S_ISREG(inode->i_mode) && inode->i_size != attr->size) { @@ -71,7 +76,8 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) } else { inode->i_op = &fuse_file_inode_operations; - init_special_inode(inode, inode->i_mode, attr->rdev); + init_special_inode(inode, inode->i_mode, + new_decode_dev(attr->rdev)); } inode->u.generic_ip = inode; } @@ -176,7 +182,7 @@ static int _fuse_mknod(struct inode *dir, struct dentry *entry, int mode, memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; - inarg.rdev = rdev; + inarg.rdev = new_encode_dev(rdev); in.h.opcode = FUSE_MKNOD; in.h.ino = dir->i_ino; diff --git a/kernel/file.c b/kernel/file.c index e8fe696..0752943 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -11,6 +11,7 @@ #include #ifdef KERNEL_2_6 #include +#include #endif #ifndef KERNEL_2_6 @@ -57,17 +58,13 @@ static int fuse_release(struct inode *inode, struct file *file) struct fuse_conn *fc = INO_FC(inode); struct fuse_in *in = NULL; struct fuse_open_in *inarg = NULL; + unsigned int s = sizeof(struct fuse_in) + sizeof(struct fuse_open_in); - in = kmalloc(sizeof(struct fuse_in), GFP_NOFS); + in = kmalloc(s, GFP_NOFS); if(!in) return -ENOMEM; - memset(in, 0, sizeof(struct fuse_in)); - - inarg = kmalloc(sizeof(struct fuse_open_in), GFP_NOFS); - if(!inarg) - goto out_free; - memset(inarg, 0, sizeof(struct fuse_open_in)); - + memset(in, 0, s); + inarg = (struct fuse_open_in *) (in + 1); inarg->flags = file->f_flags & ~O_EXCL; in->h.opcode = FUSE_RELEASE; @@ -78,8 +75,6 @@ static int fuse_release(struct inode *inode, struct file *file) if(!request_send_noreply(fc, in)) return 0; - out_free: - kfree(inarg); kfree(in); return 0; } @@ -102,6 +97,10 @@ static int fuse_fsync(struct file *file, struct dentry *de, int datasync) in.args[0].value = &inarg; request_send(fc, &in, &out); return out.h.error; + + /* FIXME: need to ensure, that all write requests issued + before this request are completed. Should userspace take + care of this? */ } static int fuse_readpage(struct file *file, struct page *page) @@ -302,39 +301,130 @@ static int write_buffer(struct inode *inode, struct page *page, in.args[1].size = count; in.args[1].value = buffer + offset; request_send(fc, &in, &out); - kunmap(page); + if(out.h.error) + SetPageError(page); return out.h.error; } -#ifdef KERNEL_2_6 -static int fuse_writepage(struct page *page, struct writeback_control *wbc) -#else -static int fuse_writepage(struct page *page) -#endif +static int get_write_count(struct inode *inode, struct page *page) { - struct inode *inode = page->mapping->host; - unsigned count; unsigned long end_index; - int err; + int count; end_index = inode->i_size >> PAGE_CACHE_SHIFT; if(page->index < end_index) count = PAGE_CACHE_SIZE; else { count = inode->i_size & (PAGE_CACHE_SIZE - 1); - err = -EIO; if(page->index > end_index || count == 0) - goto out; + return 0; + } + return count; +} +#ifdef KERNEL_2_6 + +static void write_buffer_end(struct fuse_conn *fc, struct fuse_in *in, + struct fuse_out *out, void *_page) +{ + struct page *page = (struct page *) _page; + + lock_page(page); + if(out->h.error) { + SetPageError(page); + if(out->h.error == -ENOSPC) + set_bit(AS_ENOSPC, &page->mapping->flags); + else + set_bit(AS_EIO, &page->mapping->flags); } - err = write_buffer(inode, page, 0, count); - out: + end_page_writeback(page); + kunmap(page); unlock_page(page); - return 0; + kfree(in); } +static int write_buffer_nonblock(struct inode *inode, struct page *page, + unsigned offset, size_t count) +{ + int err; + struct fuse_conn *fc = INO_FC(inode); + struct fuse_in *in = NULL; + struct fuse_out *out = NULL; + struct fuse_write_in *inarg = NULL; + char *buffer; + unsigned int s = sizeof(struct fuse_in) + sizeof(struct fuse_out) + + sizeof(struct fuse_write_in); + + in = kmalloc(s, GFP_NOFS); + if(!in) + return -ENOMEM; + memset(in, 0, s); + out = (struct fuse_out *)(in + 1); + inarg = (struct fuse_write_in *)(out + 1); + + buffer = kmap(page); + + inarg->offset = ((unsigned long long) page->index << PAGE_CACHE_SHIFT) + offset; + inarg->size = count; + + in->h.opcode = FUSE_WRITE; + in->h.ino = inode->i_ino; + in->numargs = 2; + in->args[0].size = sizeof(struct fuse_write_in); + in->args[0].value = inarg; + in->args[1].size = count; + in->args[1].value = buffer + offset; + err = request_send_nonblock(fc, in, out, write_buffer_end, page); + if(err) { + if(err != -EWOULDBLOCK) + SetPageError(page); + kunmap(page); + kfree(in); + } + return err; +} + +static int fuse_writepage(struct page *page, struct writeback_control *wbc) +{ + int err; + struct inode *inode = page->mapping->host; + unsigned count = get_write_count(inode, page); + + err = -EINVAL; + if(count) { + /* FIXME: check sync_mode, and wait for previous writes (or + signal userspace to do this) */ + if(wbc->nonblocking) { + err = write_buffer_nonblock(inode, page, 0, count); + if(!err) + SetPageWriteback(page); + else if(err == -EWOULDBLOCK) { + __set_page_dirty_nobuffers(page); + err = 0; + } + } else + err = write_buffer(inode, page, 0, count); + } + + unlock_page(page); + return err; +} +#else +static int fuse_writepage(struct page *page) +{ + int err; + struct inode *inode = page->mapping->host; + int count = get_write_count(inode, page); + err = -EINVAL; + if(count) + err = write_buffer(inode, page, 0, count); + + unlock_page(page); + return err; +} +#endif static int fuse_prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to) diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h index a667176..f2e55d4 100644 --- a/kernel/fuse_i.h +++ b/kernel/fuse_i.h @@ -103,6 +103,10 @@ struct fuse_out { #define FUSE_IN_INIT { {0, 0, 0, current->fsuid, current->fsgid}, 0} #define FUSE_OUT_INIT { {0, 0}, 0, 0} +struct fuse_req; +typedef void (*fuse_reqend_t)(struct fuse_conn *, struct fuse_in *, + struct fuse_out *, void *data); + /** * A request to the client */ @@ -133,6 +137,12 @@ struct fuse_req { /** Used to wake up the task waiting for completion of request*/ wait_queue_head_t waitq; + + /** Request completion callback */ + fuse_reqend_t end; + + /** User data */ + void *data; }; #ifdef KERNEL_2_6 @@ -205,6 +215,13 @@ void request_send(struct fuse_conn *fc, struct fuse_in *in, */ int request_send_noreply(struct fuse_conn *fc, struct fuse_in *in); + +/** + * Send a synchronous request without blocking + */ +int request_send_nonblock(struct fuse_conn *fc, struct fuse_in *in, + struct fuse_out *out, fuse_reqend_t end, void *data); + /** * Get the attributes of a file */ diff --git a/kernel/inode.c b/kernel/inode.c index 81a6fa8..a868edb 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -33,20 +33,16 @@ static void fuse_clear_inode(struct inode *inode) struct fuse_conn *fc = INO_FC(inode); struct fuse_in *in = NULL; struct fuse_forget_in *inarg = NULL; + unsigned int s = sizeof(struct fuse_in) + sizeof(struct fuse_forget_in); if(fc == NULL) return; - in = kmalloc(sizeof(struct fuse_in), GFP_NOFS); + in = kmalloc(s, GFP_NOFS); if(!in) return; - memset(in, 0, sizeof(struct fuse_in)); - - inarg = kmalloc(sizeof(struct fuse_forget_in), GFP_NOFS); - if(!inarg) - goto out_free; - - memset(inarg, 0, sizeof(struct fuse_forget_in)); + memset(in, 0, s); + inarg = (struct fuse_forget_in *) (in + 1); inarg->version = inode->i_version; in->h.opcode = FUSE_FORGET; @@ -58,8 +54,6 @@ static void fuse_clear_inode(struct inode *inode) if(!request_send_noreply(fc, in)) return; - out_free: - kfree(inarg); kfree(in); } -- 2.30.2