From: Miklos Szeredi Date: Tue, 6 Dec 2005 17:59:55 +0000 (+0000) Subject: fix X-Git-Tag: fuse_2_5_0_pre1~3 X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=044da2e9e04e48ff1c275b9a674725cb5fd2bd1d;p=qemu-gpiodev%2Flibfuse.git fix --- diff --git a/ChangeLog b/ChangeLog index cc49014..637ca23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2005-12-06 Miklos Szeredi + + * Use bigger request buffer size. write() did not work on archs + with > 4k page size, Bug report by Mark Haney + + * ABI version 7.5: + + * Extend INIT reply with data size limits + 2005-12-02 Miklos Szeredi * Fix memory leak in fuse_read_cmd()/fuse_process_cmd(). Bug diff --git a/kernel/dev.c b/kernel/dev.c index d5fb2b6..4f1e8c0 100644 --- a/kernel/dev.c +++ b/kernel/dev.c @@ -188,6 +188,37 @@ void fuse_release_background(struct fuse_req *req) spin_unlock(&fuse_lock); } +static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req) +{ + int i; + struct fuse_init_out *arg = &req->misc.init_out; + + if (arg->major != FUSE_KERNEL_VERSION) + fc->conn_error = 1; + else { + fc->minor = arg->minor; + if (fc->minor >= 5) { + fc->name_max = arg->name_max; + fc->symlink_max = arg->symlink_max; + fc->xattr_size_max = arg->xattr_size_max; + fc->max_write = arg->max_write; + } else { + /* Old fix values */ + fc->name_max = 1024; + fc->symlink_max = 4096; + fc->xattr_size_max = 4096; + fc->max_write = 4096; + } + } + + /* After INIT reply is received other requests can go + out. So do (FUSE_MAX_OUTSTANDING - 1) number of + up()s on outstanding_sem. The last up() is done in + fuse_putback_request() */ + for (i = 1; i < FUSE_MAX_OUTSTANDING; i++) + up(&fc->outstanding_sem); +} + /* * This function is called when a request is finished. Either a reply * has arrived or it was interrupted (and not yet sent) or some error @@ -212,21 +243,9 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req) up_read(&fc->sbput_sem); } wake_up(&req->waitq); - if (req->in.h.opcode == FUSE_INIT) { - int i; - - if (req->misc.init_in_out.major != FUSE_KERNEL_VERSION) - fc->conn_error = 1; - - fc->minor = req->misc.init_in_out.minor; - - /* After INIT reply is received other requests can go - out. So do (FUSE_MAX_OUTSTANDING - 1) number of - up()s on outstanding_sem. The last up() is done in - fuse_putback_request() */ - for (i = 1; i < FUSE_MAX_OUTSTANDING; i++) - up(&fc->outstanding_sem); - } else if (req->in.h.opcode == FUSE_RELEASE && req->inode == NULL) { + if (req->in.h.opcode == FUSE_INIT) + process_init_reply(fc, req); + else if (req->in.h.opcode == FUSE_RELEASE && req->inode == NULL) { /* Special case for failed iget in CREATE */ u64 nodeid = req->in.h.nodeid; __fuse_get_request(req); @@ -399,7 +418,7 @@ void fuse_send_init(struct fuse_conn *fc) /* This is called from fuse_read_super() so there's guaranteed to be a request available */ struct fuse_req *req = do_get_request(fc); - struct fuse_init_in_out *arg = &req->misc.init_in_out; + struct fuse_init_in *arg = &req->misc.init_in; arg->major = FUSE_KERNEL_VERSION; arg->minor = FUSE_KERNEL_MINOR_VERSION; req->in.h.opcode = FUSE_INIT; @@ -407,8 +426,12 @@ void fuse_send_init(struct fuse_conn *fc) req->in.args[0].size = sizeof(*arg); req->in.args[0].value = arg; req->out.numargs = 1; - req->out.args[0].size = sizeof(*arg); - req->out.args[0].value = arg; + /* Variable length arguement used for backward compatibility + with interface version < 7.5. Rest of init_out is zeroed + by do_get_request(), so a short reply is not a problem */ + req->out.argvar = 1; + req->out.args[0].size = sizeof(struct fuse_init_out); + req->out.args[0].value = &req->misc.init_out; request_send_background(fc, req); } diff --git a/kernel/dir.c b/kernel/dir.c index 991edcf..3258d42 100644 --- a/kernel/dir.c +++ b/kernel/dir.c @@ -202,7 +202,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, struct dentry *newent; #endif - if (entry->d_name.len > FUSE_NAME_MAX) + if (entry->d_name.len > fc->name_max) return ERR_PTR(-ENAMETOOLONG); req = fuse_get_request(fc); @@ -274,7 +274,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode, goto out; err = -ENAMETOOLONG; - if (entry->d_name.len > FUSE_NAME_MAX) + if (entry->d_name.len > fc->name_max) goto out; err = -EINTR; @@ -455,7 +455,7 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry, unsigned len = strlen(link) + 1; struct fuse_req *req; - if (len > FUSE_SYMLINK_MAX) + if (len > fc->symlink_max) return -ENAMETOOLONG; req = fuse_get_request(fc); @@ -561,7 +561,8 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, fuse_invalidate_attr(newdir); /* newent will end up negative */ - fuse_invalidate_entry_cache(newent); + if (newent->d_inode) + fuse_invalidate_entry_cache(newent); } else if (err == -EINTR) { /* If request was interrupted, DEITY only knows if the rename actually took place. If the invalidation @@ -798,11 +799,13 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) static int parse_dirfile(char *buf, size_t nbytes, struct file *file, void *dstbuf, filldir_t filldir) { + struct fuse_conn *fc = get_fuse_conn(file->f_dentry->d_inode); + while (nbytes >= FUSE_NAME_OFFSET) { struct fuse_dirent *dirent = (struct fuse_dirent *) buf; size_t reclen = FUSE_DIRENT_SIZE(dirent); int over; - if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX) + if (!dirent->namelen || dirent->namelen > fc->name_max) return -EIO; if (reclen > nbytes) break; @@ -1125,7 +1128,7 @@ static int fuse_setxattr(struct dentry *entry, const char *name, struct fuse_setxattr_in inarg; int err; - if (size > FUSE_XATTR_SIZE_MAX) + if (size > fc->xattr_size_max) return -E2BIG; if (fc->no_setxattr) diff --git a/kernel/file.c b/kernel/file.c index a4cc04d..2365d2e 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -516,7 +516,12 @@ static int fuse_commit_write(struct file *file, struct page *page, struct inode *inode = page->mapping->host; struct fuse_conn *fc = get_fuse_conn(inode); loff_t pos = page_offset(page) + offset; - struct fuse_req *req = fuse_get_request(fc); + struct fuse_req *req; + + if (count > fc->max_write) + return -EIO; + + req = fuse_get_request(fc); if (!req) return -EINTR; diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h index 9ae1eec..7f26b65 100644 --- a/kernel/fuse_i.h +++ b/kernel/fuse_i.h @@ -236,7 +236,8 @@ struct fuse_req { union { struct fuse_forget_in forget_in; struct fuse_release_in release_in; - struct fuse_init_in_out init_in_out; + struct fuse_init_in init_in; + struct fuse_init_out init_out; } misc; /** page vector */ @@ -284,6 +285,15 @@ struct fuse_conn { /** Maximum write size */ unsigned max_write; + /** Maximum path segment length */ + unsigned name_max; + + /** Maximum symbolic link size */ + unsigned symlink_max; + + /** Maximum size of xattr data */ + unsigned xattr_size_max; + /** Readers of the connection are waiting on this */ wait_queue_head_t waitq; diff --git a/kernel/fuse_kernel.h b/kernel/fuse_kernel.h index e43153e..26fff13 100644 --- a/kernel/fuse_kernel.h +++ b/kernel/fuse_kernel.h @@ -49,7 +49,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 4 +#define FUSE_KERNEL_MINOR_VERSION 5 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -143,13 +143,6 @@ enum fuse_opcode { FUSE_CREATE = 35 }; -/* Conservative buffer size for the client */ -#define FUSE_MAX_IN 8192 - -#define FUSE_NAME_MAX 1024 -#define FUSE_SYMLINK_MAX 4096 -#define FUSE_XATTR_SIZE_MAX 4096 - struct fuse_entry_out { __u64 nodeid; /* Inode ID */ __u64 generation; /* Inode generation: nodeid:gen must @@ -283,9 +276,18 @@ struct fuse_access_in { __u32 padding; }; -struct fuse_init_in_out { +struct fuse_init_in { + __u32 major; + __u32 minor; +}; + +struct fuse_init_out { __u32 major; __u32 minor; + __u32 name_max; + __u32 symlink_max; + __u32 xattr_size_max; + __u32 max_write; }; struct fuse_in_header { diff --git a/kernel/inode.c b/kernel/inode.c index 1d56566..8c7c7d6 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -647,7 +647,6 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) if (fc->max_read / PAGE_CACHE_SIZE < fc->bdi.ra_pages) fc->bdi.ra_pages = fc->max_read / PAGE_CACHE_SIZE; #endif - fc->max_write = FUSE_MAX_IN / 2; err = -ENOMEM; root = get_root_inode(sb, d.rootmode); diff --git a/lib/fuse_kern_chan.c b/lib/fuse_kern_chan.c index d2999c5..fbe3943 100644 --- a/lib/fuse_kern_chan.c +++ b/lib/fuse_kern_chan.c @@ -64,6 +64,8 @@ static void fuse_kern_chan_destroy(struct fuse_chan *ch) close(fuse_chan_fd(ch)); } +#define MIN_BUFSIZE 0x21000 + struct fuse_chan *fuse_kern_chan_new(int fd) { struct fuse_chan_ops op = { @@ -71,5 +73,7 @@ struct fuse_chan *fuse_kern_chan_new(int fd) .send = fuse_kern_chan_send, .destroy = fuse_kern_chan_destroy, }; - return fuse_chan_new(&op, fd, FUSE_MAX_IN, NULL); + size_t bufsize = getpagesize() + 0x1000; + bufsize = bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : bufsize; + return fuse_chan_new(&op, fd, bufsize, NULL); } diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c index 37f0922..3566c60 100644 --- a/lib/fuse_loop_mt.c +++ b/lib/fuse_loop_mt.c @@ -155,7 +155,7 @@ int fuse_session_loop_mt(struct fuse_session *se) memset(w, 0, sizeof(struct fuse_worker)); w->se = se; w->prevch = fuse_session_next_chan(se, NULL); - w->ch = fuse_chan_new(&cop, -1, 0, w); + w->ch = fuse_chan_new(&cop, -1, fuse_chan_bufsize(w->prevch), w); if (w->ch == NULL) { free(w); return -1; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 3cc49f0..0787368 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -19,6 +19,17 @@ #define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) +/* PATH_MAX is 4k on Linux, but I don't dare to define it to PATH_MAX, + because it may be much larger on other systems */ +#define MIN_SYMLINK 0x1000 + +/* Generous 4k overhead for headers, includes room for xattr name + (XATTR_NAME_MAX = 255) */ +#define HEADER_OVERHEAD 0x1000 + +/* 8k, the same as the old FUSE_MAX_IN constant */ +#define MIN_BUFFER_SIZE (MIN_SYMLINK + HEADER_OVERHEAD) + struct fuse_ll { unsigned int debug : 1; unsigned int allow_root : 1; @@ -683,10 +694,11 @@ static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, char *name) fuse_reply_err(req, ENOSYS); } -static void do_init(fuse_req_t req, struct fuse_init_in_out *arg) +static void do_init(fuse_req_t req, struct fuse_init_in *arg) { - struct fuse_init_in_out outarg; + struct fuse_init_out outarg; struct fuse_ll *f = req->f; + size_t bufsize = fuse_chan_bufsize(req->ch); if (f->debug) { printf("INIT: %u.%u\n", arg->major, arg->minor); @@ -699,16 +711,37 @@ static void do_init(fuse_req_t req, struct fuse_init_in_out *arg) f->major = FUSE_KERNEL_VERSION; f->minor = arg->minor; + if (bufsize < MIN_BUFFER_SIZE) { + fprintf(stderr, "fuse: warning: buffer size too small: %i\n", bufsize); + bufsize = MIN_BUFFER_SIZE; + } + + bufsize -= HEADER_OVERHEAD; + memset(&outarg, 0, sizeof(outarg)); outarg.major = f->major; outarg.minor = FUSE_KERNEL_MINOR_VERSION; + /* The calculated limits may be oversized, but because of the + limits in VFS names and symlinks are never larger than PATH_MAX - 1 + and xattr values never larger than XATTR_SIZE_MAX */ + + /* Max two names per request */ + outarg.symlink_max = outarg.name_max = bufsize / 2; + /* But if buffer is small, give more room to link name */ + if (outarg.symlink_max < MIN_SYMLINK) { + outarg.symlink_max = MIN_SYMLINK; + /* Borrow from header overhead for the SYMLINK operation */ + outarg.name_max = HEADER_OVERHEAD / 4; + } + outarg.xattr_size_max = outarg.max_write = bufsize; + if (f->debug) { printf(" INIT: %u.%u\n", outarg.major, outarg.minor); fflush(stdout); } - send_reply_ok(req, &outarg, sizeof(outarg)); + send_reply_ok(req, &outarg, arg->minor < 5 ? 8 : sizeof(outarg)); } void *fuse_req_userdata(fuse_req_t req) @@ -759,7 +792,7 @@ static void fuse_ll_process(void *data, const char *buf, size_t len, fuse_reply_err(req, EACCES); } else switch (in->opcode) { case FUSE_INIT: - do_init(req, (struct fuse_init_in_out *) inarg); + do_init(req, (struct fuse_init_in *) inarg); break; case FUSE_LOOKUP: