From e56818b231a231a48e2616ec083999a14516a4ae Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sun, 12 Dec 2004 11:45:24 +0000 Subject: [PATCH] cleanup --- ChangeLog | 40 +++ Filesystems | 65 +++- include/Makefile.am | 8 +- include/fuse.h | 20 +- include/old/fuse.h | 9 + kernel/cleanup.sh | 7 +- kernel/dev.c | 778 ++++++++++++++++++++++++----------------- kernel/dir.c | 132 +++---- kernel/file.c | 392 ++++++--------------- kernel/fuse_i.h | 104 +++--- kernel/fuse_kernel.h | 14 +- kernel/inode.c | 46 +-- kernel/util.c | 8 +- lib/fuse.c | 99 ++---- lib/fuse_versionscript | 1 + lib/helper.c | 7 + util/fusermount.c | 35 +- 17 files changed, 840 insertions(+), 925 deletions(-) create mode 100644 include/old/fuse.h diff --git a/ChangeLog b/ChangeLog index 66e88c5..7029c0a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,43 @@ +2004-12-12 Miklos Szeredi + + * KERNEL: lots of cleanups related to avoiding possible deadlocks. + These will cause some regressions, but stability is considered + more important. If any of these features turns out to be + important, it can be readded with the deadlock problems addressed. + + * Make all requests interruptible (only with SIGKILL currently). + This can be used to break any deadlock produced by the userspace + filesystem accessing it's own exported files. The RELEASE request + is special, because if it's interrupted before sending it to + userspace it is still sent, but the reply is not awaited. + + * If request is interrupted before being sent to userspace, and if + it hasn't yet got any side effects, it is always restarted, + regardless of the SA_RESTART flag. This makes these interruptions + transparent to the process. + + * Remove shared-writable mmap support, which was prone to an + out-of-memory deadlock situation + + * Remove INVALIDATE userspace initiated request + + * Make readpages() synchronous. Asynchronous requests are + deadlock prone, since they cannot be interrupted. + + * Add readv/writev support to fuse device operations + + * Remove some printks, which userspace FS can use for a DoS + against syslog + + * Remove 'large_read' mount option from 2.6 in kernel, check it in + fusermount instead + + * LIB: improve compatibility with a fuse.h header installed in + ${prefix}/include which in turn includes the real header. + + * LIB: improve compatibility by defining fuse_main() (which is now + not used), so old configure scripts find it. + 2004-12-10 Miklos Szeredi * When mounting on a subdirectory of / don't duplicate slashes at diff --git a/Filesystems b/Filesystems index 720e3d0..553b714 100644 --- a/Filesystems +++ b/Filesystems @@ -1,6 +1,6 @@ Name: OW -Author: Paul H. Alfille +Author: Paul H. Alfille palfille at partners org Homepage: http://owfs.sourceforge.net @@ -12,8 +12,7 @@ Description: ============================================================================== Name: FunFS (status: alpha) -Author: Michael Grigoriev (Net Integration Technologies) +Author: Michael Grigoriev (Net Integration Technologies) mag at luminal org Homepage: http://www.luminal.org/wiki/index.php/FunFS/FunFS @@ -25,7 +24,7 @@ Description: ============================================================================== Name: EncFS -Author: Valient Gough +Author: Valient Gough vgough at pobox com Homepage: http://pobox.com/~vgough/encfs.html @@ -38,7 +37,7 @@ Description: ============================================================================== Name: FUSE-J -Author: Peter Levart +Author: Peter Levart peter.levart at select-tech si Download: http://www.select-tech.si/fuse/ @@ -50,7 +49,7 @@ Description: ============================================================================== Name: SMB for FUSE -Author: Vincent Wagelaar +Author: Vincent Wagelaar vincent at ricardis tudelft nl Homepage: http://hannibal.lr-s.tudelft.nl/~vincent/fusesmb/ @@ -62,7 +61,7 @@ Description: ============================================================================== Name: Run-Time-Access -Author: Bob Smith +Author: Bob Smith bsmith at linuxtoys org Homepage: http://www.runtimeaccess.com @@ -77,7 +76,7 @@ Description: ============================================================================== Name: PhoneBook -Author: David McNab +Author: David McNab david at rebirthing co nz Homepage: http://www.freenet.org.nz/phonebook @@ -92,7 +91,7 @@ Description: ============================================================================== Name: KIO Fuse Gateway -Author: Alexander Neundorf +Author: Alexander Neundorf neundorf at kde org Homepage: http://kde.ground.cz/tiki-index.php?page=KIO+Fuse+Gateway @@ -105,7 +104,7 @@ Description: ============================================================================== Name: C# bindings -Author: Valient Gough +Author: Valient Gough vgough at pobox com Homepage: http://pobox.com/~vgough/fuse-csharp.html @@ -118,7 +117,7 @@ Description: ============================================================================== Name: LUFS bridge (alpha) -Author: Miklos Szeredi +Author: Miklos Szeredi miklos at szeredi hu Homepage: http://sourceforge.net/project/showfiles.php?group_id=121684&package_id=132803 @@ -131,7 +130,7 @@ Description: ============================================================================== Name: btfs (Bluetooth FileSystemMapping) -Author: Collin R. Mulliner +Author: Collin R. Mulliner collin at betaversion net Homepage: http://www.mulliner.org/bluetooth/btfs.php @@ -145,7 +144,7 @@ Description: ============================================================================== Name: mcachefs -Author: Michael Still +Author: Michael Still mikal at stillhq com Homepage: http://lists.samba.org/archive/linux/2004-March/010211.html @@ -160,7 +159,7 @@ Description: ============================================================================== Name: Fusedav -Author: Lennart Poettering +Author: Lennart Poettering mzshfrqni at 0pointer de Homepage: http://0pointer.de/lennart/projects/fusedav/ @@ -173,7 +172,7 @@ Description: ============================================================================== Name: RelFS -Author: Vincenzo Ciancia +Author: Vincenzo Ciancia vincenzo_ml at yahoo it Homepage: http://relfs.sourceforge.net/ @@ -187,7 +186,7 @@ Description: ============================================================================== Name: GmailFS -Author: Richard Jones +Author: Richard Jones richard at jones name Homepage: http://richard.jones.name/google-hacks/gmail-filesystem/gmail-filesystem.html @@ -201,7 +200,7 @@ Description: ============================================================================== Name: DataDraw -Author: Bill Cox +Author: Bill Cox bill at viasic com Homepage: http://www.viasic.com/opensource/ @@ -213,7 +212,7 @@ Description: ============================================================================== Name: gphoto2-fuse-fs -Author: Christopher Lester +Author: Christopher Lester lester at hep phy cam ac uk Homepage: http://www.hep.phy.cam.ac.uk/~lester/gphoto2-fuse-fs/ @@ -225,3 +224,33 @@ Description: gphoto2" ============================================================================== +Name: cvsfs-fuse + +Author: Patrick Frank pfrank at gmx de + +Homepage: http://sourceforge.net/projects/cvsfs + +Description: + + This provides a package which presents the CVS contents as mountable + file system. It allows to view the versioned files as like they were + ordinary files on a disk. There is also a possibility to check + in/out some files for editing. + +============================================================================== +Name: Wayback (User-level Versioning File System for Linux) + +Author: Brian Cornell techie at northwestern edu + +Homepage: http://wayback.sourceforge.net/ + +Description: + + When you use a Wayback file system, old versions of files are never + lost. No matter how much you change a file or directory, everything + is always kept in a versioning file so that you never lose important + data. Wayback provides the ability to remount any already mounted + file system with versioning support under a different + directory. + +============================================================================== diff --git a/include/Makefile.am b/include/Makefile.am index 14ac9d9..269e0e1 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,11 +2,5 @@ fuseincludedir=$(includedir)/fuse fuseinclude_HEADERS = fuse.h fuse_compat.h +include_HEADERS = old/fuse.h noinst_HEADERS = fuse_kernel.h - -# remove fuse.h from old place to avoid collision with new one -install-data-local: - @if test -f $(includedir)/fuse.h; then \ - echo "Removing old FUSE header from $(includedir)/fuse.h"; \ - rm -f $(includedir)/fuse.h; \ - fi diff --git a/include/fuse.h b/include/fuse.h index 8d2c274..a0a6a70 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -83,6 +83,11 @@ struct fuse_file_info { * file system operations. A major exception is that instead of * returning an error in 'errno', the operation should return the * negated error value (-errno) directly. + * + * All methods are optional, but some are essential for a useful + * filesystem (e.g. getattr). Flush, release and fsync are special + * purpose methods, without which a full featured filesystem can still + * be implemented. */ struct fuse_operations { /** Get file attributes. @@ -187,12 +192,13 @@ struct fuse_operations { /** Possibly flush cached data * * BIG NOTE: This is not equivalent to fsync(). It's not a - * request to sync dirty data, and can safely be ignored. + * request to sync dirty data. * * Flush is called on each close() of a file descriptor. So if a * filesystem wants to return write errors in close() and the file * has cached dirty data, this is a good place to write back data - * and return any errors. + * and return any errors. Since many applications ignore close() + * errors this is not always useful. * * NOTE: The flush() method may be called more than once for each * open(). This happens if more than one file descriptor refers @@ -210,10 +216,10 @@ struct fuse_operations { * are unmapped. * * For every open() call there will be exactly one release() call - * with the same flags. It is possible to have a file opened more - * than once, in which case only the last release will mean, that - * no more reads/writes will happen on the file. The return value - * of release is ignored. Implementing this method is optional. + * with the same flags and file descriptor. It is possible to + * have a file opened more than once, in which case only the last + * release will mean, that no more reads/writes will happen on the + * file. The return value of release is ignored. */ int (*release) (const char *, struct fuse_file_info *); @@ -442,7 +448,7 @@ void fuse_set_getcontext_func(struct fuse_context *(*func)(void)); * ----------------------------------------------------------- */ #if FUSE_USE_VERSION == 21 || FUSE_USE_VERSION == 11 -# include +# include "fuse_compat.h" # define fuse_dirfil_t fuse_dirfil_t_compat # define __fuse_read_cmd fuse_read_cmd # define __fuse_process_cmd fuse_process_cmd diff --git a/include/old/fuse.h b/include/old/fuse.h new file mode 100644 index 0000000..3db0945 --- /dev/null +++ b/include/old/fuse.h @@ -0,0 +1,9 @@ +/* + This header is for compatibility with older software using FUSE. + + Please use 'pkg-config --cflags fuse' to set include path. The + correct usage is still '#include ', not '#include + '. +*/ + +#include "fuse/fuse.h" diff --git a/kernel/cleanup.sh b/kernel/cleanup.sh index fb0807e..9d7b92b 100755 --- a/kernel/cleanup.sh +++ b/kernel/cleanup.sh @@ -11,6 +11,11 @@ if test "$destdir" = "."; then exit 1 fi +mkdir -p $destdir/fs/fuse +mkdir -p $destdir/include/linux + + for f in dev.c dir.c file.c inode.c util.c fuse_i.h; do - unifdef -DKERNEL_2_6 -DKERNEL_2_6_6_PLUS -DKERNEL_2_6_10_PLUS -DHAVE_KERNEL_XATTR -DFS_SAFE -DMAX_LFS_FILESIZE -DFUSE_MAINLINE -DBUG_ON -DHAVE_FS_SUBSYS -D__user -DMODULE_LICENSE $f > $destdir/$f + unifdef -DKERNEL_2_6 -DKERNEL_2_6_6_PLUS -DKERNEL_2_6_10_PLUS -DHAVE_KERNEL_XATTR -DFS_SAFE -DMAX_LFS_FILESIZE -DFUSE_MAINLINE -DBUG_ON -DHAVE_FS_SUBSYS -D__user -DMODULE_LICENSE $f > $destdir/fs/fuse/$f done +cp fuse_kernel.h $destdir/include/linux/fuse.h diff --git a/kernel/dev.c b/kernel/dev.c index 220918a..b2a1b90 100644 --- a/kernel/dev.c +++ b/kernel/dev.c @@ -1,9 +1,9 @@ /* - FUSE: Filesystem in Userspace - Copyright (C) 2001-2004 Miklos Szeredi + FUSE: Filesystem in Userspace + Copyright (C) 2001-2004 Miklos Szeredi - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. */ #include "fuse_i.h" @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef KERNEL_2_6 #include #else @@ -38,6 +39,7 @@ static inline void fuse_request_init(struct fuse_req *req) memset(req, 0, sizeof(*req)); INIT_LIST_HEAD(&req->list); init_waitqueue_head(&req->waitq); + atomic_set(&req->count, 1); } struct fuse_req *fuse_request_alloc(void) @@ -53,29 +55,66 @@ void fuse_request_free(struct fuse_req *req) kmem_cache_free(fuse_req_cachep, req); } -/* Called with fuse_lock held. Releases, and then reaquires it. */ -static void request_wait_answer(struct fuse_req *req) -{ - spin_unlock(&fuse_lock); - wait_event(req->waitq, req->finished); - spin_lock(&fuse_lock); -} - static int get_unique(struct fuse_conn *fc) { fc->reqctr++; + /* zero is special */ if (fc->reqctr == 0) fc->reqctr = 1; return fc->reqctr; } +#ifdef KERNEL_2_6 +static inline void block_sigs(sigset_t *oldset) +{ + sigset_t sigmask; + + siginitsetinv(&sigmask, sigmask(SIGKILL)); + sigprocmask(SIG_BLOCK, &sigmask, oldset); +} + +static inline void restore_sigs(sigset_t *oldset) +{ + sigprocmask(SIG_SETMASK, oldset, NULL); +} +#else +static inline void block_sigs(sigset_t *oldset) +{ + spin_lock_irq(¤t->sigmask_lock); + *oldset = current->blocked; + siginitsetinv(¤t->blocked, sigmask(SIGKILL) & ~oldset->sig[0]); + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); +} + +static inline void restore_sigs(sigset_t *oldset) +{ + spin_lock_irq(¤t->sigmask_lock); + current->blocked = *oldset; + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); +} +#endif + void fuse_reset_request(struct fuse_req *req) { int preallocated = req->preallocated; + BUG_ON(atomic_read(&req->count) != 1); fuse_request_init(req); req->preallocated = preallocated; } +static void __fuse_get_request(struct fuse_req *req) +{ + atomic_inc(&req->count); +} + +/* Must be called with > 1 refcount */ +static void __fuse_put_request(struct fuse_req *req) +{ + atomic_dec(&req->count); +} + static struct fuse_req *do_get_request(struct fuse_conn *fc) { struct fuse_req *req; @@ -85,7 +124,8 @@ static struct fuse_req *do_get_request(struct fuse_conn *fc) req = list_entry(fc->unused_list.next, struct fuse_req, list); list_del_init(&req->list); spin_unlock(&fuse_lock); - fuse_reset_request(req); + fuse_request_init(req); + req->preallocated = 1; req->in.h.uid = current->fsuid; req->in.h.gid = current->fsgid; req->in.h.pid = current->pid; @@ -101,18 +141,16 @@ struct fuse_req *fuse_get_request(struct fuse_conn *fc) struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc) { - down(&fc->unused_sem); - return do_get_request(fc); -} + int intr; + sigset_t oldset; -struct fuse_req *fuse_get_request_nonblock(struct fuse_conn *fc) -{ - if (down_trylock(&fc->unused_sem)) - return NULL; - return do_get_request(fc); + block_sigs(&oldset); + intr = down_interruptible(&fc->unused_sem); + restore_sigs(&oldset); + return intr ? NULL : do_get_request(fc); } -void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) +void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req) { if (!req->preallocated) fuse_request_free(req); @@ -124,61 +162,114 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) } } -/* Must be called with fuse_lock held, and unlocks it */ +void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) +{ + if (atomic_dec_and_test(&req->count)) + fuse_putback_request(fc, req); +} + +/* Called with fuse_lock, unlocks it */ static void request_end(struct fuse_conn *fc, struct fuse_req *req) { - fuse_reqend_t endfunc = req->end; + int putback; + req->finished = 1; + putback = atomic_dec_and_test(&req->count); + spin_unlock(&fuse_lock); + wake_up(&req->waitq); + if (putback) + fuse_putback_request(fc, req); +} + +static int request_wait_answer_nonint(struct fuse_req *req) +{ + int err; + sigset_t oldset; + block_sigs(&oldset); + err = wait_event_interruptible(req->waitq, req->finished); + restore_sigs(&oldset); + return err; +} + +/* Called with fuse_lock held. Releases, and then reaquires it. */ +static void request_wait_answer(struct fuse_req *req, int interruptible, + int background) +{ + int intr; - if (!endfunc) { - wake_up(&req->waitq); + spin_unlock(&fuse_lock); + if (interruptible) + intr = wait_event_interruptible(req->waitq, req->finished); + else + intr = request_wait_answer_nonint(req); + spin_lock(&fuse_lock); + if (intr && interruptible && req->sent) { + /* If request is already in userspace, only allow KILL + signal to interrupt */ spin_unlock(&fuse_lock); - } else { + intr = request_wait_answer_nonint(req); + spin_lock(&fuse_lock); + } + if (!intr) + return; + + if (background && !req->sent) { + req->isreply = 0; + return; + } + req->out.h.error = -ERESTARTNOINTR; + req->interrupted = 1; + if (req->locked) { + /* This is uninterruptible sleep, because data is + being copied to/from the buffers of req. During + locked state, there musn't be any filesystem + operation (e.g. page fault), since that could lead + to deadlock */ spin_unlock(&fuse_lock); - endfunc(fc, req); + wait_event(req->waitq, !req->locked); + spin_lock(&fuse_lock); + } + if (!list_empty(&req->list)) { + /* request is still on one of the lists */ + list_del(&req->list); + __fuse_put_request(req); } } -void request_send(struct fuse_conn *fc, struct fuse_req *req) +static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req, + int interruptible, int background) { req->isreply = 1; - req->end = NULL; - spin_lock(&fuse_lock); req->out.h.error = -ENOTCONN; if (fc->file) { req->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); + /* acquire extra reference, since request is still needed + after request_end() */ + __fuse_get_request(req); + + request_wait_answer(req, interruptible, background); } spin_unlock(&fuse_lock); } -void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req) +void request_send(struct fuse_conn *fc, struct fuse_req *req) { - req->isreply = 0; - - spin_lock(&fuse_lock); - if (fc->file) { - list_add_tail(&req->list, &fc->pending); - wake_up(&fc->waitq); - spin_unlock(&fuse_lock); - } else { - spin_unlock(&fuse_lock); - fuse_put_request(fc, req); - } + request_send_wait(fc, req, 1, 0); } -void request_send_async(struct fuse_conn *fc, struct fuse_req *req, - fuse_reqend_t end) +void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req, + int background) { - req->end = end; - req->isreply = 1; + request_send_wait(fc, req, 0, background); +} +void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req) +{ + req->isreply = 0; spin_lock(&fuse_lock); if (fc->file) { - req->in.h.unique = get_unique(fc); list_add_tail(&req->list, &fc->pending); wake_up(&fc->waitq); spin_unlock(&fuse_lock); @@ -188,366 +279,410 @@ void request_send_async(struct fuse_conn *fc, struct fuse_req *req, } } -static void request_wait(struct fuse_conn *fc) +static inline int lock_request(struct fuse_req *req) { - DECLARE_WAITQUEUE(wait, current); - - add_wait_queue_exclusive(&fc->waitq, &wait); - while (fc->sb && list_empty(&fc->pending)) { - set_current_state(TASK_INTERRUPTIBLE); - if (signal_pending(current)) - break; - - spin_unlock(&fuse_lock); - schedule(); + int err = 0; + if (req) { spin_lock(&fuse_lock); + if (req->interrupted) + err = -ENOENT; + else + req->locked = 1; + spin_unlock(&fuse_lock); } - set_current_state(TASK_RUNNING); - remove_wait_queue(&fc->waitq, &wait); -} - -static inline int copy_in_page(char __user *buf, struct page *page, - unsigned offset, unsigned count) -{ - char *tmpbuf = kmap(page); - int err = copy_to_user(buf, tmpbuf + offset, count); - kunmap(page); return err; } -static int copy_in_pages(struct fuse_req *req, size_t nbytes, char __user *buf) +static inline void unlock_request(struct fuse_req *req) { - unsigned i; - unsigned offset = req->page_offset; - unsigned count = min(nbytes, (unsigned) PAGE_SIZE - offset); - for (i = 0; i < req->num_pages && nbytes; i++) { - struct page *page = req->pages[i]; - if (page && copy_in_page(buf, page, offset, count)) - return -EFAULT; - nbytes -= count; - buf += count; - count = min(nbytes, (unsigned) PAGE_SIZE); - offset = 0; + if (req) { + spin_lock(&fuse_lock); + req->locked = 0; + if (req->interrupted) + wake_up(&req->waitq); + spin_unlock(&fuse_lock); } - return 0; } -static inline int copy_in_one(struct fuse_req *req, size_t argsize, - const void *val, int islast, char __user *buf, - size_t nbytes) -{ - if (nbytes < argsize) { - printk("fuse_dev_read: buffer too small\n"); - return -EINVAL; - } - if (islast && req->in.argpages) - return copy_in_pages(req, argsize, buf); - else if (argsize && copy_to_user(buf, val, argsize)) - return -EFAULT; - else - return 0; -} +struct fuse_copy_state { + int write; + struct fuse_req *req; + const struct iovec *iov; + unsigned long nr_segs; + unsigned long seglen; + unsigned long addr; + struct page *pg; + void *mapaddr; + void *buf; + unsigned len; +}; -static int copy_in_args(struct fuse_req *req, char __user *buf, size_t nbytes) +static unsigned fuse_copy_init(struct fuse_copy_state *cs, int write, + struct fuse_req *req, const struct iovec *iov, + unsigned long nr_segs) { - int i; - int err; - struct fuse_in *in = &req->in; - size_t orignbytes = nbytes; - unsigned argsize; - - argsize = sizeof(in->h); - err = copy_in_one(req, argsize, &in->h, 0, buf, nbytes); - if (err) - return err; - - buf += argsize; - nbytes -= argsize; + unsigned i; + unsigned nbytes; - for (i = 0; i < in->numargs; i++) { - struct fuse_in_arg *arg = &in->args[i]; - int islast = (i == in->numargs - 1); - err = copy_in_one(req, arg->size, arg->value, islast, buf, - nbytes); - if (err) - return err; + memset(cs, 0, sizeof(*cs)); + cs->write = write; + cs->req = req; + cs->iov = iov; + cs->nr_segs = nr_segs; - buf += arg->size; - nbytes -= arg->size; - } + nbytes = 0; + for (i = 0; i < nr_segs; i++) + nbytes += iov[i].iov_len; - return orignbytes - nbytes; + return nbytes; } -static ssize_t fuse_dev_read(struct file *file, char __user *buf, - size_t nbytes, loff_t *off) +static inline void fuse_copy_finish(struct fuse_copy_state *cs) { - ssize_t ret; - struct fuse_conn *fc; - struct fuse_req *req = NULL; - - spin_lock(&fuse_lock); - fc = file->private_data; - if (!fc) { - spin_unlock(&fuse_lock); - return -EPERM; - } - request_wait(fc); - if (!fc->sb) - fc = NULL; - else if (!list_empty(&fc->pending)) { - req = list_entry(fc->pending.next, struct fuse_req, list); - list_del_init(&req->list); - } - spin_unlock(&fuse_lock); - if (!fc) - return -ENODEV; - if (req == NULL) - return -EINTR; - - ret = copy_in_args(req, buf, nbytes); - spin_lock(&fuse_lock); - if (req->isreply) { - if (ret < 0) { - req->out.h.error = -EPROTO; - req->finished = 1; - } else - list_add_tail(&req->list, &fc->processing); - if (ret < 0) - /* Unlocks fuse_lock: */ - request_end(fc, req); - else - spin_unlock(&fuse_lock); - } else { - spin_unlock(&fuse_lock); - fuse_put_request(fc, req); + if (cs->mapaddr) { + kunmap_atomic(cs->mapaddr, KM_USER0); + if (cs->write) { + flush_dcache_page(cs->pg); + set_page_dirty_lock(cs->pg); + } + put_page(cs->pg); + cs->mapaddr = NULL; } - return ret; } -static struct fuse_req *request_find(struct fuse_conn *fc, unsigned unique) +static int fuse_copy_fill(struct fuse_copy_state *cs) { - struct list_head *entry; - struct fuse_req *req = NULL; + unsigned long offset; + int err; - list_for_each(entry, &fc->processing) { - struct fuse_req *tmp; - tmp = list_entry(entry, struct fuse_req, list); - if (tmp->in.h.unique == unique) { - req = tmp; - break; - } + unlock_request(cs->req); + fuse_copy_finish(cs); + if (!cs->seglen) { + BUG_ON(!cs->nr_segs); + cs->seglen = cs->iov[0].iov_len; + cs->addr = (unsigned long) cs->iov[0].iov_base; + cs->iov ++; + cs->nr_segs --; } + down_read(¤t->mm->mmap_sem); + err = get_user_pages(current, current->mm, cs->addr, 1, cs->write, 0, + &cs->pg, NULL); + up_read(¤t->mm->mmap_sem); + if (err < 0) + return err; + BUG_ON(err != 1); + offset = cs->addr % PAGE_SIZE; + cs->mapaddr = kmap_atomic(cs->pg, KM_USER0); + cs->buf = cs->mapaddr + offset; + cs->len = min(PAGE_SIZE - offset, cs->seglen); + cs->seglen -= cs->len; + cs->addr += cs->len; - return req; + return lock_request(cs->req); } -static void process_getdir(struct fuse_req *req) +static inline int fuse_copy_do(struct fuse_copy_state *cs, void **val, + unsigned *size) { - struct fuse_getdir_out_i *arg = req->out.args[0].value; - arg->file = fget(arg->fd); + unsigned ncpy = min(*size, cs->len); + if (val) { + if (cs->write) + memcpy(cs->buf, *val, ncpy); + else + memcpy(*val, cs->buf, ncpy); + *val += ncpy; + } + *size -= ncpy; + cs->len -= ncpy; + cs->buf += ncpy; + return ncpy; } -static inline int copy_out_page(const char __user *buf, struct page *page, - unsigned offset, unsigned count, int zeroing) +static inline int fuse_copy_page(struct fuse_copy_state *cs, struct page *page, + unsigned offset, unsigned count, int zeroing) { - int err = 0; - char *tmpbuf = kmap(page); - if (count < PAGE_SIZE && zeroing) - memset(tmpbuf, 0, PAGE_SIZE); - if (count) - err = copy_from_user(tmpbuf + offset, buf, count); - flush_dcache_page(page); - kunmap(page); - return err; + if (page && zeroing && count < PAGE_SIZE) { + void *mapaddr = kmap_atomic(page, KM_USER1); + memset(mapaddr, 0, PAGE_SIZE); + kunmap_atomic(mapaddr, KM_USER1); + } + while (count) { + int err; + if (!cs->len && (err = fuse_copy_fill(cs))) + return err; + if (page) { + void *mapaddr = kmap_atomic(page, KM_USER1); + void *buf = mapaddr + offset; + offset += fuse_copy_do(cs, &buf, &count); + kunmap_atomic(mapaddr, KM_USER1); + } else + offset += fuse_copy_do(cs, NULL, &count); + } + if (page && !cs->write) + flush_dcache_page(page); + return 0; } -static int copy_out_pages(struct fuse_req *req, size_t nbytes, - const char __user *buf) +static int fuse_copy_pages(struct fuse_copy_state *cs, unsigned nbytes, + int zeroing) { unsigned i; + struct fuse_req *req = cs->req; unsigned offset = req->page_offset; - unsigned zeroing = req->out.page_zeroing; unsigned count = min(nbytes, (unsigned) PAGE_SIZE - offset); - for (i = 0; i < req->num_pages && (zeroing || nbytes); i++) { + for (i = 0; i < req->num_pages && nbytes; i++) { struct page *page = req->pages[i]; - if (page && copy_out_page(buf, page, offset, count, zeroing)) - return -EFAULT; + int err = fuse_copy_page(cs, page, offset, count, zeroing); + if (err) + return err; + nbytes -= count; - buf += count; count = min(nbytes, (unsigned) PAGE_SIZE); offset = 0; } return 0; } -static inline int copy_out_one(struct fuse_req *req, struct fuse_out_arg *arg, - int islast, const char __user *buf, size_t nbytes) +static int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size) { - if (nbytes < arg->size) { - if (!islast || !req->out.argvar) { - printk("fuse_dev_write: write is short\n"); - return -EINVAL; - } - arg->size = nbytes; + while (size) { + int err; + if (!cs->len && (err = fuse_copy_fill(cs))) + return err; + fuse_copy_do(cs, &val, &size); } - if (islast && req->out.argpages) - return copy_out_pages(req, arg->size, buf); - else if (arg->size && copy_from_user(arg->value, buf, arg->size)) - return -EFAULT; - else - return 0; + return 0; } -static int copy_out_args(struct fuse_req *req, const char __user *buf, - size_t nbytes) +static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, + unsigned argpages, struct fuse_arg *args, + int zeroing) { - struct fuse_out *out = &req->out; - int i; - - buf += sizeof(struct fuse_out_header); - nbytes -= sizeof(struct fuse_out_header); + int err = 0; + unsigned i; - if (!out->h.error) { - for (i = 0; i < out->numargs; i++) { - struct fuse_out_arg *arg = &out->args[i]; - int islast = (i == out->numargs - 1); - int err = copy_out_one(req, arg, islast, buf, nbytes); - if (err) - return err; - buf += arg->size; - nbytes -= arg->size; - } - } - if (nbytes != 0) { - printk("fuse_dev_write: write is long\n"); - return -EINVAL; + for (i = 0; !err && i < numargs; i++) { + struct fuse_arg *arg = &args[i]; + if (i == numargs - 1 && argpages) + err = fuse_copy_pages(cs, arg->size, zeroing); + else + err = fuse_copy_one(cs, arg->value, arg->size); } + return err; +} - return 0; +static unsigned len_args(unsigned numargs, struct fuse_arg *args) +{ + unsigned nbytes = 0; + unsigned i; + + for (i = 0; i < numargs; i++) + nbytes += args[i].size; + + return nbytes; } -static inline int copy_out_header(struct fuse_out_header *oh, - const char __user *buf, size_t nbytes) +static void request_wait(struct fuse_conn *fc) { - if (nbytes < sizeof(struct fuse_out_header)) { - printk("fuse_dev_write: write is short\n"); - return -EINVAL; - } - if (copy_from_user(oh, buf, sizeof(struct fuse_out_header))) - return -EFAULT; + DECLARE_WAITQUEUE(wait, current); - return 0; + add_wait_queue_exclusive(&fc->waitq, &wait); + while (fc->sb && list_empty(&fc->pending)) { + set_current_state(TASK_INTERRUPTIBLE); + 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); } -static int fuse_invalidate(struct fuse_conn *fc, struct fuse_user_header *uh) +static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov, + unsigned long nr_segs, loff_t *off) { int err; - down(&fc->sb_sem); + struct fuse_conn *fc; + struct fuse_req *req; + struct fuse_in *in; + struct fuse_copy_state cs; + unsigned nbytes; + unsigned reqsize; + + spin_lock(&fuse_lock); + fc = file->private_data; + err = -EPERM; + if (!fc) + goto err_unlock; + request_wait(fc); err = -ENODEV; - if (fc->sb) { - struct inode *inode; -#ifdef KERNEL_2_6 - inode = fuse_ilookup(fc->sb, uh->nodeid); -#else - inode = fuse_ilookup(fc->sb, uh->ino, uh->nodeid); -#endif + if (!fc->sb) + goto err_unlock; + err = -EINTR; + if (list_empty(&fc->pending)) + goto err_unlock; + + req = list_entry(fc->pending.next, struct fuse_req, list); + list_del_init(&req->list); + spin_unlock(&fuse_lock); + + in = &req->in; + reqsize = sizeof(struct fuse_in_header); + reqsize += len_args(in->numargs, (struct fuse_arg *) in->args); + nbytes = fuse_copy_init(&cs, 1, req, iov, nr_segs); + err = -EINVAL; + if (nbytes >= reqsize) { + err = fuse_copy_one(&cs, &in->h, sizeof(in->h)); + if (!err) + err = fuse_copy_args(&cs, in->numargs, in->argpages, + (struct fuse_arg *) in->args, 0); + } + fuse_copy_finish(&cs); + + spin_lock(&fuse_lock); + req->locked = 0; + if (!err && req->interrupted) err = -ENOENT; - if (inode) { - fuse_sync_inode(inode); -#ifdef KERNEL_2_6 - invalidate_inode_pages(inode->i_mapping); -#else - invalidate_inode_pages(inode); -#endif - iput(inode); - err = 0; - } + if (err) { + if (!req->interrupted) + req->out.h.error = -EIO; + request_end(fc, req); + return err; } - up(&fc->sb_sem); + if (!req->isreply) + request_end(fc, req); + else { + req->sent = 1; + list_add_tail(&req->list, &fc->processing); + spin_unlock(&fuse_lock); + } + return reqsize; + err_unlock: + spin_unlock(&fuse_lock); return err; } -static int fuse_user_request(struct fuse_conn *fc, const char __user *buf, - size_t nbytes) +static ssize_t fuse_dev_read(struct file *file, char __user *buf, + size_t nbytes, loff_t *off) { - struct fuse_user_header uh; - int err; + struct iovec iov; + iov.iov_len = nbytes; + iov.iov_base = buf; + return fuse_dev_readv(file, &iov, 1, off); +} - if (nbytes < sizeof(struct fuse_user_header)) { - printk("fuse_dev_write: write is short\n"); - return -EINVAL; +static struct fuse_req *request_find(struct fuse_conn *fc, unsigned unique) +{ + struct list_head *entry; + + list_for_each(entry, &fc->processing) { + struct fuse_req *req; + req = list_entry(entry, struct fuse_req, list); + if (req->in.h.unique == unique) + return req; } + return NULL; +} + +/* fget() needs to be done in this context */ +static void process_getdir(struct fuse_req *req) +{ + struct fuse_getdir_out_i *arg = req->out.args[0].value; + arg->file = fget(arg->fd); +} - if (copy_from_user(&uh, buf, sizeof(struct fuse_user_header))) - return -EFAULT; +static int copy_out_args(struct fuse_copy_state *cs, struct fuse_out *out, + unsigned nbytes) +{ + unsigned reqsize = sizeof(struct fuse_out_header); + + if (out->h.error) + return nbytes != reqsize ? -EINVAL : 0; - switch (uh.opcode) { - case FUSE_INVALIDATE: - err = fuse_invalidate(fc, &uh); - break; + reqsize += len_args(out->numargs, out->args); - default: - err = -ENOSYS; + if (reqsize < nbytes || (reqsize > nbytes && !out->argvar)) + return -EINVAL; + else if (reqsize > nbytes) { + struct fuse_arg *lastarg = &out->args[out->numargs-1]; + unsigned diffsize = reqsize - nbytes; + if (diffsize > lastarg->size) + return -EINVAL; + lastarg->size -= diffsize; } - return err; + return fuse_copy_args(cs, out->numargs, out->argpages, out->args, + out->page_zeroing); } -static ssize_t fuse_dev_write(struct file *file, const char __user *buf, - size_t nbytes, loff_t *off) +static ssize_t fuse_dev_writev(struct file *file, const struct iovec *iov, + unsigned long nr_segs, loff_t *off) { int err; - struct fuse_conn *fc = fuse_get_conn(file); + unsigned nbytes; struct fuse_req *req; struct fuse_out_header oh; - + struct fuse_copy_state cs; + struct fuse_conn *fc = fuse_get_conn(file); if (!fc) return -ENODEV; - err = copy_out_header(&oh, buf, nbytes); - if (err) - return err; - - if (!oh.unique) { - err = fuse_user_request(fc, buf, nbytes); - goto out; - } + nbytes = fuse_copy_init(&cs, 0, NULL, iov, nr_segs); + if (nbytes < sizeof(struct fuse_out_header)) + return -EINVAL; - if (oh.error <= -1000 || oh.error > 0) { - printk("fuse_dev_write: bad error value\n"); - return -EINVAL; - } + err = fuse_copy_one(&cs, &oh, sizeof(oh)); + if (err) + goto err_finish; + err = -EINVAL; + if (!oh.unique || oh.error <= -1000 || oh.error > 0) + goto err_finish; spin_lock(&fuse_lock); req = request_find(fc, oh.unique); - if (req != NULL) - list_del_init(&req->list); - spin_unlock(&fuse_lock); + err = -ENOENT; if (!req) - return -ENOENT; + goto err_unlock; + list_del_init(&req->list); req->out.h = oh; - err = copy_out_args(req, buf, nbytes); + req->locked = 1; + cs.req = req; + spin_unlock(&fuse_lock); + + err = copy_out_args(&cs, &req->out, nbytes); + fuse_copy_finish(&cs); spin_lock(&fuse_lock); - if (err) - req->out.h.error = -EPROTO; - else { - /* fget() needs to be done in this context */ - if (req->in.h.opcode == FUSE_GETDIR && !oh.error) + req->locked = 0; + if (!err) { + if (req->interrupted) + err = -ENOENT; + else if (req->in.h.opcode == FUSE_GETDIR && !oh.error) process_getdir(req); - } - req->finished = 1; - /* Unlocks fuse_lock: */ + } else if (!req->interrupted) + req->out.h.error = -EIO; request_end(fc, req); - out: - if (!err) - return nbytes; - else - return err; + return err ? err : nbytes; + + err_unlock: + spin_unlock(&fuse_lock); + err_finish: + fuse_copy_finish(&cs); + return err; +} + +static ssize_t fuse_dev_write(struct file *file, const char __user *buf, + size_t nbytes, loff_t *off) +{ + struct iovec iov; + iov.iov_len = nbytes; + iov.iov_base = (char __user *) buf; + return fuse_dev_writev(file, &iov, 1, off); } static unsigned fuse_dev_poll(struct file *file, poll_table *wait) @@ -574,17 +709,9 @@ static void end_requests(struct fuse_conn *fc, struct list_head *head) struct fuse_req *req; req = list_entry(head->next, struct fuse_req, list); list_del_init(&req->list); - if (req->isreply) { - req->out.h.error = -ECONNABORTED; - req->finished = 1; - /* Unlocks fuse_lock: */ - request_end(fc, req); - spin_lock(&fuse_lock); - } else { - spin_unlock(&fuse_lock); - fuse_put_request(fc, req); - spin_lock(&fuse_lock); - } + req->out.h.error = -ECONNABORTED; + request_end(fc, req); + spin_lock(&fuse_lock); } } @@ -607,7 +734,9 @@ static int fuse_dev_release(struct inode *inode, struct file *file) struct file_operations fuse_dev_operations = { .owner = THIS_MODULE, .read = fuse_dev_read, + .readv = fuse_dev_readv, .write = fuse_dev_write, + .writev = fuse_dev_writev, .poll = fuse_dev_poll, .release = fuse_dev_release, }; @@ -711,7 +840,6 @@ int __init fuse_dev_init(void) if (!fuse_req_cachep) goto out_version_clean; - err = misc_register(&fuse_miscdevice); if (err) goto out_cache_clean; diff --git a/kernel/dir.c b/kernel/dir.c index 1c76c53..992c87d 100644 --- a/kernel/dir.c +++ b/kernel/dir.c @@ -1,9 +1,9 @@ /* - FUSE: Filesystem in Userspace - Copyright (C) 2001-2004 Miklos Szeredi + FUSE: Filesystem in Userspace + Copyright (C) 2001-2004 Miklos Szeredi - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. */ #include "fuse_i.h" @@ -81,7 +81,6 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr) new_decode_dev(attr->rdev)); } else { /* Don't let user create weird files */ - printk("fuse_init_inode: bad file type: %o\n", inode->i_mode); inode->i_mode = S_IFREG; inode->i_op = &fuse_file_inode_operations; fuse_init_file_inode(inode); @@ -120,8 +119,7 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, inode->i_data.backing_dev_info = &fc->bdi; fuse_init_inode(inode, attr); unlock_new_inode(inode); - } else if (inode->i_generation != generation) - printk("fuse_iget: bad generation for node %lu\n", nodeid); + } change_attributes(inode, attr); inode->i_version = version; @@ -155,8 +153,7 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, inode->u.generic_ip = inode; inode->i_generation = generation; fuse_init_inode(inode, attr); - } else if (inode->i_generation != generation) - printk("fuse_iget: bad generation for node %lu\n", nodeid); + } change_attributes(inode, attr); inode->i_version = version; @@ -174,9 +171,9 @@ struct inode *fuse_ilookup(struct super_block *sb, ino_t ino, unsigned long node } #endif -static int fuse_send_lookup(struct fuse_conn *fc, struct fuse_req *req, - struct inode *dir, struct dentry *entry, - struct fuse_entry_out *outarg, int *version) +static void fuse_lookup_init(struct fuse_req *req, struct inode *dir, + struct dentry *entry, + struct fuse_entry_out *outarg) { req->in.h.opcode = FUSE_LOOKUP; req->in.h.nodeid = get_node_id(dir); @@ -186,27 +183,6 @@ static int fuse_send_lookup(struct fuse_conn *fc, struct fuse_req *req, req->out.numargs = 1; req->out.args[0].size = sizeof(struct fuse_entry_out); req->out.args[0].value = outarg; - request_send(fc, req); - *version = req->out.h.unique; - return req->out.h.error; -} - -static int fuse_do_lookup(struct inode *dir, struct dentry *entry, - struct fuse_entry_out *outarg, int *version) -{ - struct fuse_conn *fc = get_fuse_conn(dir); - struct fuse_req *req; - int err; - - if (entry->d_name.len > FUSE_NAME_MAX) - return -ENAMETOOLONG; - req = fuse_get_request(fc); - if (!req) - return -ERESTARTSYS; - - err = fuse_send_lookup(fc, req, dir, entry, outarg, version); - fuse_put_request(fc, req); - return err; } static inline unsigned long time_to_jiffies(unsigned long sec, @@ -233,9 +209,12 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, return -ENAMETOOLONG; req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; - err = fuse_send_lookup(fc, req, dir, entry, &outarg, &version); + fuse_lookup_init(req, dir, entry, &outarg); + request_send(fc, req); + version = req->out.h.unique; + err = req->out.h.error; if (!err) { inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, &outarg.attr, version); @@ -284,8 +263,7 @@ static int lookup_new_entry(struct fuse_conn *fc, struct fuse_req *req, /* 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; + return -EIO; } entry->d_time = time_to_jiffies(outarg->entry_valid, @@ -310,7 +288,7 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode, int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; @@ -350,7 +328,7 @@ static int fuse_mkdir(struct inode *dir, struct dentry *entry, int mode) int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.mode = mode; @@ -388,7 +366,7 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry, req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; req->in.h.opcode = FUSE_SYMLINK; req->in.h.nodeid = get_node_id(dir); @@ -417,7 +395,7 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry) int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; req->in.h.opcode = FUSE_UNLINK; req->in.h.nodeid = get_node_id(dir); @@ -447,7 +425,7 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; req->in.h.opcode = FUSE_RMDIR; req->in.h.nodeid = get_node_id(dir); @@ -473,7 +451,7 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.newdir = get_node_id(newdir); @@ -508,7 +486,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.newdir = get_node_id(newdir); @@ -542,7 +520,7 @@ int fuse_do_getattr(struct inode *inode) int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; req->in.h.opcode = FUSE_GETATTR; req->in.h.nodeid = get_node_id(inode); @@ -604,7 +582,7 @@ static int fuse_permission(struct inode *inode, int mask, struct nameidata *nd) #ifdef KERNEL_2_6_10_PLUS err = generic_permission(inode, mask, NULL); #else - err = vfs_permission(inode, mask); + err = vfs_permission(inode, mask); #endif } @@ -636,10 +614,8 @@ static int parse_dirfile(char *buf, size_t nbytes, struct file *file, struct fuse_dirent *dirent = (struct fuse_dirent *) buf; size_t reclen = FUSE_DIRENT_SIZE(dirent); int over; - if (dirent->namelen > NAME_MAX) { - printk("fuse_readdir: name too long\n"); - return -EPROTO; - } + if (dirent->namelen > NAME_MAX) + return -EIO; if (reclen > nbytes) break; @@ -659,15 +635,12 @@ static int parse_dirfile(char *buf, size_t nbytes, struct file *file, static int fuse_checkdir(struct file *cfile, struct file *file) { struct inode *inode; - if (!cfile) { - printk("fuse_getdir: invalid file\n"); - return -EPROTO; - } + if (!cfile) + return -EIO; inode = cfile->f_dentry->d_inode; if (!S_ISREG(inode->i_mode)) { - printk("fuse_getdir: not a regular file\n"); fput(cfile); - return -EPROTO; + return -EIO; } file->private_data = cfile; @@ -683,7 +656,7 @@ static int fuse_getdir(struct file *file) int err; if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; req->in.h.opcode = FUSE_GETDIR; req->in.h.nodeid = get_node_id(inode); @@ -717,9 +690,7 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) return -ENOMEM; ret = kernel_read(cfile, file->f_pos, buf, PAGE_SIZE); - if (ret < 0) - printk("fuse_readdir: failed to read container file\n"); - else + if (ret > 0) ret = parse_dirfile(buf, ret, file, dstbuf, filldir); free_page((unsigned long) buf); @@ -734,7 +705,7 @@ static char *read_link(struct dentry *dentry) char *link; if (!req) - return ERR_PTR(-ERESTARTSYS); + return ERR_PTR(-ERESTARTNOINTR); link = (char *) __get_free_page(GFP_KERNEL); if (!link) { @@ -866,10 +837,7 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; - - if (is_truncate) - down_write(&fi->write_sem); + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.valid = iattr_to_fattr(attr, &inarg.attr); @@ -889,15 +857,13 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) if (is_truncate) { loff_t origsize = i_size_read(inode); i_size_write(inode, outarg.attr.size); - up_write(&fi->write_sem); if (origsize > outarg.attr.size) vmtruncate(inode, outarg.attr.size); } change_attributes(inode, &outarg.attr); fi->i_time = time_to_jiffies(outarg.attr_valid, outarg.attr_valid_nsec); - } else if (is_truncate) - up_write(&fi->write_sem); + } return err; } @@ -907,15 +873,22 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) if (!entry->d_inode) return 0; else if (entry->d_time && time_after(jiffies, entry->d_time)) { + int err; + int version; + struct fuse_entry_out outarg; struct inode *inode = entry->d_inode; struct fuse_inode *fi = get_fuse_inode(inode); - struct fuse_entry_out outarg; - int version; - int ret; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_req *req = fuse_get_request_nonint(fc); + if (!req) + return 0; - ret = fuse_do_lookup(entry->d_parent->d_inode, entry, &outarg, - &version); - if (ret) + fuse_lookup_init(req, entry->d_parent->d_inode, entry, &outarg); + request_send_nonint(fc, req, 0); + version = req->out.h.unique; + err = req->out.h.error; + fuse_put_request(fc, req); + if (err) return 0; if (outarg.nodeid != get_node_id(inode)) @@ -966,8 +939,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry) (alias = d_find_alias(inode)) != NULL) { dput(alias); iput(inode); - printk("fuse: cannot assign an existing directory\n"); - return ERR_PTR(-EPROTO); + return ERR_PTR(-EIO); } d_add(entry, inode); @@ -1019,7 +991,7 @@ static int fuse_setxattr(struct dentry *entry, const char *name, req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; @@ -1058,7 +1030,7 @@ static ssize_t fuse_getxattr(struct dentry *entry, const char *name, req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; @@ -1107,7 +1079,7 @@ static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size) req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; memset(&inarg, 0, sizeof(inarg)); inarg.size = size; @@ -1152,7 +1124,7 @@ static int fuse_removexattr(struct dentry *entry, const char *name) req = fuse_get_request(fc); if (!req) - return -ERESTARTSYS; + return -ERESTARTNOINTR; req->in.h.opcode = FUSE_REMOVEXATTR; req->in.h.nodeid = get_node_id(inode); diff --git a/kernel/file.c b/kernel/file.c index 738f1f0..6758c2b 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -1,9 +1,9 @@ /* - FUSE: Filesystem in Userspace - Copyright (C) 2001-2004 Miklos Szeredi + FUSE: Filesystem in Userspace + Copyright (C) 2001-2004 Miklos Szeredi - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. */ #include "fuse_i.h" @@ -11,22 +11,11 @@ #include #include #include -#include -#ifdef KERNEL_2_6 -#include -#include -#endif -#include -static int user_mmap; -#ifdef KERNEL_2_6 -module_param(user_mmap, int, 0644); -#else -MODULE_PARM(user_mmap, "i"); +#ifndef KERNEL_2_6 #define PageUptodate(page) Page_Uptodate(page) +#define clear_page_dirty(page) ClearPageDirty(page) #endif -MODULE_PARM_DESC(user_mmap, "Allow non root user to create a shared writable mapping"); - static int fuse_open(struct inode *inode, struct file *file) { struct fuse_conn *fc = get_fuse_conn(inode); @@ -48,6 +37,7 @@ static int fuse_open(struct inode *inode, struct file *file) return err; } + /* Prevent concurrent unlink or rename */ down(&inode->i_sem); err = -ERESTARTSYS; req = fuse_get_request(fc); @@ -91,7 +81,6 @@ static int fuse_open(struct inode *inode, struct file *file) else { ff->fh = outarg.fh; file->private_data = ff; - INIT_LIST_HEAD(&ff->ff_list); } out_put_request: @@ -101,49 +90,23 @@ static int fuse_open(struct inode *inode, struct file *file) return err; } -void fuse_sync_inode(struct inode *inode) -{ -#ifdef KERNEL_2_6 - filemap_fdatawrite(inode->i_mapping); - filemap_fdatawait(inode->i_mapping); -#else -#ifndef NO_MM - filemap_fdatasync(inode->i_mapping); - filemap_fdatawait(inode->i_mapping); -#endif -#endif -} - static int fuse_release(struct inode *inode, struct file *file) { struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_file *ff = file->private_data; struct fuse_req *req = ff->release_req; - struct fuse_release_in inarg; - - down(&inode->i_sem); - if (file->f_mode & FMODE_WRITE) - fuse_sync_inode(inode); - - if (!list_empty(&ff->ff_list)) { - struct fuse_inode *fi = get_fuse_inode(inode); - down_write(&fi->write_sem); - list_del(&ff->ff_list); - up_write(&fi->write_sem); - } + struct fuse_release_in *inarg = &req->misc.release_in; - memset(&inarg, 0, sizeof(inarg)); - inarg.fh = ff->fh; - inarg.flags = file->f_flags & ~O_EXCL; + inarg->fh = ff->fh; + inarg->flags = file->f_flags & ~O_EXCL; req->in.h.opcode = FUSE_RELEASE; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 1; - req->in.args[0].size = sizeof(inarg); - req->in.args[0].value = &inarg; - request_send(fc, req); + req->in.args[0].size = sizeof(struct fuse_release_in); + req->in.args[0].value = inarg; + request_send_nonint(fc, req, 1); fuse_put_request(fc, req); kfree(ff); - up(&inode->i_sem); /* Return value is ignored by VFS */ return 0; @@ -154,14 +117,17 @@ static int fuse_flush(struct file *file) struct inode *inode = file->f_dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_file *ff = file->private_data; - struct fuse_req *req = ff->release_req; + struct fuse_req *req; struct fuse_flush_in inarg; int err; if (fc->no_flush) return 0; - down(&inode->i_sem); + req = fuse_get_request_nonint(fc); + if (!req) + return -EINTR; + memset(&inarg, 0, sizeof(inarg)); inarg.fh = ff->fh; req->in.h.opcode = FUSE_FLUSH; @@ -169,10 +135,9 @@ static int fuse_flush(struct file *file) req->in.numargs = 1; req->in.args[0].size = sizeof(inarg); req->in.args[0].value = &inarg; - request_send(fc, req); + request_send_nonint(fc, req, 0); err = req->out.h.error; - fuse_reset_request(req); - up(&inode->i_sem); + fuse_put_request(fc, req); if (err == -ENOSYS) { fc->no_flush = 1; err = 0; @@ -183,7 +148,6 @@ static int fuse_flush(struct file *file) static int fuse_fsync(struct file *file, struct dentry *de, int datasync) { struct inode *inode = de->d_inode; - struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_file *ff = file->private_data; struct fuse_req *req; @@ -197,11 +161,6 @@ static int fuse_fsync(struct file *file, struct dentry *de, int datasync) if (!req) return -ERESTARTSYS; - /* Make sure all writes to this inode are completed before - issuing the FSYNC request */ - down_write(&fi->write_sem); - up_write(&fi->write_sem); - memset(&inarg, 0, sizeof(inarg)); inarg.fh = ff->fh; inarg.datasync = datasync; @@ -212,47 +171,52 @@ static int fuse_fsync(struct file *file, struct dentry *de, int datasync) req->in.args[0].value = &inarg; request_send(fc, req); err = req->out.h.error; + fuse_put_request(fc, req); if (err == -ENOSYS) { fc->no_fsync = 1; err = 0; } - fuse_put_request(fc, req); return err; } -static void fuse_read_init(struct fuse_req *req, struct file *file, - struct inode *inode, loff_t pos, size_t count) +static ssize_t fuse_send_read(struct fuse_req *req, struct file *file, + struct inode *inode, loff_t pos, size_t count) { + struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_file *ff = file->private_data; - struct fuse_read_in *inarg = &req->misc.read_in; + struct fuse_read_in inarg; - inarg->fh = ff->fh; - inarg->offset = pos; - inarg->size = count; + memset(&inarg, 0, sizeof(struct fuse_read_in)); + inarg.fh = ff->fh; + inarg.offset = pos; + inarg.size = count; req->in.h.opcode = FUSE_READ; req->in.h.nodeid = get_node_id(inode); req->in.numargs = 1; req->in.args[0].size = sizeof(struct fuse_read_in); - req->in.args[0].value = inarg; + req->in.args[0].value = &inarg; req->out.argpages = 1; req->out.argvar = 1; req->out.numargs = 1; req->out.args[0].size = count; + request_send_nonint(fc, req, 0); + return req->out.args[0].size; } static int fuse_readpage(struct file *file, struct page *page) { + int err; struct inode *inode = page->mapping->host; struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_req *req = fuse_get_request_nonint(fc); loff_t pos = (loff_t) page->index << PAGE_CACHE_SHIFT; - int err; + struct fuse_req *req = fuse_get_request_nonint(fc); + if (!req) + return -EINTR; - fuse_read_init(req, file, inode, pos, PAGE_CACHE_SIZE); req->out.page_zeroing = 1; req->num_pages = 1; req->pages[0] = page; - request_send(fc, req); + fuse_send_read(req, file, inode, pos, PAGE_CACHE_SIZE); err = req->out.h.error; fuse_put_request(fc, req); if (!err) @@ -262,27 +226,21 @@ static int fuse_readpage(struct file *file, struct page *page) } #ifdef KERNEL_2_6 -static void read_pages_end(struct fuse_conn *fc, struct fuse_req *req) +static int fuse_send_readpages(struct fuse_req *req, struct file *file, + struct inode *inode) { + loff_t pos = (loff_t) req->pages[0]->index << PAGE_CACHE_SHIFT; + size_t count = req->num_pages << PAGE_CACHE_SHIFT; unsigned i; + req->out.page_zeroing = 1; + fuse_send_read(req, file, inode, pos, count); for (i = 0; i < req->num_pages; i++) { struct page *page = req->pages[i]; if (!req->out.h.error) SetPageUptodate(page); unlock_page(page); } - fuse_put_request(fc, req); -} - -static void fuse_send_readpages(struct fuse_req *req, struct file *file, - struct inode *inode) -{ - struct fuse_conn *fc = get_fuse_conn(inode); - loff_t pos = (loff_t) req->pages[0]->index << PAGE_CACHE_SHIFT; - size_t count = req->num_pages << PAGE_CACHE_SHIFT; - fuse_read_init(req, file, inode, pos, count); - req->out.page_zeroing = 1; - request_send_async(fc, req, read_pages_end); + return req->out.h.error; } struct fuse_readpages_data { @@ -302,9 +260,11 @@ static int fuse_readpages_fill(void *_data, struct page *page) (req->num_pages == FUSE_MAX_PAGES_PER_REQ || (req->num_pages + 1) * PAGE_CACHE_SIZE > fc->max_read || req->pages[req->num_pages - 1]->index + 1 != page->index)) { - struct fuse_conn *fc = get_fuse_conn(page->mapping->host); - fuse_send_readpages(req, data->file, inode); - data->req = req = fuse_get_request_nonint(fc); + int err = fuse_send_readpages(req, data->file, inode); + if (err) + return err; + + fuse_reset_request(req); } req->pages[req->num_pages] = page; req->num_pages ++; @@ -317,18 +277,19 @@ static int fuse_readpages(struct file *file, struct address_space *mapping, struct inode *inode = mapping->host; struct fuse_conn *fc = get_fuse_conn(inode); struct fuse_readpages_data data; + int err; - data.req = fuse_get_request_nonint(fc); data.file = file; data.inode = inode; + data.req = fuse_get_request_nonint(fc); + if (!data.req) + return -EINTR; - read_cache_pages(mapping, pages, fuse_readpages_fill, &data); - if (data.req->num_pages) + err = read_cache_pages(mapping, pages, fuse_readpages_fill, &data); + if (!err && data.req->num_pages) fuse_send_readpages(data.req, file, inode); - else - fuse_put_request(fc, data.req); - - return 0; + fuse_put_request(fc, data.req); + return err; } #else /* KERNEL_2_6 */ #define FUSE_BLOCK_SHIFT 16 @@ -356,15 +317,14 @@ static int fuse_is_block_uptodate(struct inode *inode, unsigned start, return 1; } -static void fuse_file_read_block(struct fuse_req *req, struct file *file, - struct inode *inode, unsigned start, - unsigned end) +static int fuse_file_read_block(struct fuse_req *req, struct file *file, + struct inode *inode, unsigned start, + unsigned end) { - struct fuse_conn *fc = get_fuse_conn(inode); loff_t pos; size_t count; int index; - int err = 1; + int err = -EBUSY; int i; for (index = start; index < end; index++) { @@ -380,8 +340,7 @@ static void fuse_file_read_block(struct fuse_req *req, struct file *file, } pos = (loff_t) start << PAGE_CACHE_SHIFT; count = req->num_pages << PAGE_CACHE_SHIFT; - fuse_read_init(req, file, inode, pos, count); - request_send(fc, req); + fuse_send_read(req, file, inode, pos, count); err = req->out.h.error; out: for (i = 0; i < req->num_pages; i++) { @@ -393,6 +352,7 @@ static void fuse_file_read_block(struct fuse_req *req, struct file *file, page_cache_release(page); } } + return err; } static int fuse_file_bigread(struct file *file, struct inode *inode, @@ -420,7 +380,9 @@ static int fuse_file_bigread(struct file *file, struct inode *inode, nexti = starti + (FUSE_BLOCK_SIZE >> PAGE_CACHE_SHIFT); nexti = min(nexti, endi); if (!fuse_is_block_uptodate(inode, starti, nexti)) { - fuse_file_read_block(req, file, inode, starti, nexti); + if (fuse_file_read_block(req, file, inode, starti, nexti)) + break; + fuse_reset_request(req); } } @@ -429,159 +391,32 @@ static int fuse_file_bigread(struct file *file, struct inode *inode, } #endif /* KERNEL_2_6 */ -static void fuse_write_init(struct fuse_req *req, struct fuse_file *ff, - struct inode *inode, loff_t pos, size_t count, - int iswritepage) +static ssize_t fuse_send_write(struct fuse_req *req, struct file *file, + struct inode *inode, loff_t pos, size_t count) { - struct fuse_write_in *inarg = &req->misc.write.in; + struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; + struct fuse_write_in inarg; + struct fuse_write_out outarg; - inarg->writepage = iswritepage; - inarg->fh = ff->fh; - inarg->offset = pos; - inarg->size = count; + memset(&inarg, 0, sizeof(struct fuse_write_in)); + inarg.writepage = 0; + inarg.fh = ff->fh; + inarg.offset = pos; + inarg.size = count; req->in.h.opcode = FUSE_WRITE; req->in.h.nodeid = get_node_id(inode); - if (iswritepage) { - req->in.h.uid = 0; - req->in.h.gid = 0; - req->in.h.pid = 0; - } req->in.argpages = 1; req->in.numargs = 2; req->in.args[0].size = sizeof(struct fuse_write_in); - req->in.args[0].value = inarg; + req->in.args[0].value = &inarg; req->in.args[1].size = count; req->out.numargs = 1; req->out.args[0].size = sizeof(struct fuse_write_out); - req->out.args[0].value = &req->misc.write.out; -} - -static int get_write_count(struct inode *inode, struct page *page) -{ - unsigned long end_index; - loff_t size = i_size_read(inode); - int count; - - end_index = size >> PAGE_CACHE_SHIFT; - if (page->index < end_index) - count = PAGE_CACHE_SIZE; - else { - count = size & (PAGE_CACHE_SIZE - 1); - if (page->index > end_index || count == 0) - return 0; - } - return count; -} - -static inline struct fuse_file *get_write_file(struct fuse_inode *fi) -{ - BUG_ON(list_empty(&fi->write_files)); - return list_entry(fi->write_files.next, struct fuse_file, ff_list); -} - -#ifdef KERNEL_2_6 -static void write_page_end(struct fuse_conn *fc, struct fuse_req *req) -{ - struct page *page = req->pages[0]; - struct inode *inode = page->mapping->host; - struct fuse_inode *fi = get_fuse_inode(inode); - if (!req->out.h.error && - req->misc.write.out.size != req->in.args[1].size) - req->out.h.error = -EPROTO; - - if (req->out.h.error) { - SetPageError(page); - if (req->out.h.error == -ENOSPC) - set_bit(AS_ENOSPC, &page->mapping->flags); - else - set_bit(AS_EIO, &page->mapping->flags); - } - up_read(&fi->write_sem); - end_page_writeback(page); - fuse_put_request(fc, req); -} - -static int fuse_writepage(struct page *page, struct writeback_control *wbc) -{ - struct inode *inode = page->mapping->host; - struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_inode *fi = get_fuse_inode(inode); - struct fuse_req *req; - int err; - - err = -EWOULDBLOCK; - if (wbc->nonblocking) - req = fuse_get_request_nonblock(fc); - else - req = fuse_get_request_nonint(fc); - if (req) { - int locked = 1; - if (wbc->nonblocking) - locked = down_read_trylock(&fi->write_sem); - else - down_read(&fi->write_sem); - if (locked) { - unsigned count = get_write_count(inode, page); - loff_t pos = (loff_t) page->index << PAGE_CACHE_SHIFT; - err = 0; - if (count) { - struct fuse_file *ff = get_write_file(fi); - SetPageWriteback(page); - fuse_write_init(req, ff, inode, pos, count, 1); - req->num_pages = 1; - req->pages[0] = page; - request_send_async(fc, req, write_page_end); - goto out; - } - up_read(&fi->write_sem); - } - fuse_put_request(fc, req); - } - if (err == -EWOULDBLOCK) { -#ifdef KERNEL_2_6_6_PLUS - redirty_page_for_writepage(wbc, page); -#else - __set_page_dirty_nobuffers(page); -#endif - err = 0; - } - out: - unlock_page(page); - return err; -} -#else -static int fuse_writepage(struct page *page) -{ - int err; - unsigned count; - struct inode *inode = page->mapping->host; - struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_inode *fi = get_fuse_inode(inode); - struct fuse_req *req = fuse_get_request_nonint(fc); - - down_read(&fi->write_sem); - count = get_write_count(inode, page); - err = 0; - if (count) { - struct fuse_file *ff = get_write_file(fi); - loff_t pos = ((loff_t) page->index << PAGE_CACHE_SHIFT); - - fuse_write_init(req, ff, inode, pos, count, 1); - req->num_pages = 1; - req->pages[0] = page; - request_send(fc, req); - err = req->out.h.error; - if (!err && req->misc.write.out.size != count) - err = -EPROTO; - } - up_read(&fi->write_sem); - fuse_put_request(fc, req); - if (err) - SetPageError(page); - unlock_page(page); - return err; + req->out.args[0].value = &outarg; + request_send_nonint(fc, req, 0); + return outarg.size; } -#endif static int fuse_prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to) @@ -594,34 +429,29 @@ static int fuse_commit_write(struct file *file, struct page *page, unsigned offset, unsigned to) { int err; + ssize_t nres; unsigned count = to - offset; struct inode *inode = page->mapping->host; - struct fuse_file *ff = file->private_data; struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_req *req = fuse_get_request(fc); loff_t pos = ((loff_t) page->index << PAGE_CACHE_SHIFT) + offset; + struct fuse_req *req = fuse_get_request_nonint(fc); if (!req) - return -ERESTARTSYS; + return -EINTR; - fuse_write_init(req, ff, inode, pos, count, 0); req->num_pages = 1; req->pages[0] = page; req->page_offset = offset; - request_send(fc, req); + nres = fuse_send_write(req, file, inode, pos, count); err = req->out.h.error; - if (!err && req->misc.write.out.size != count) - err = -EPROTO; + if (!err && nres != count) + err = -EIO; if (!err) { pos += count; if (pos > i_size_read(inode)) i_size_write(inode, pos); if (offset == 0 && to == PAGE_CACHE_SIZE) { -#ifdef KERNEL_2_6 clear_page_dirty(page); -#else - ClearPageDirty(page); -#endif SetPageUptodate(page); } } @@ -635,16 +465,9 @@ static void fuse_release_user_pages(struct fuse_req *req, int write) for (i = 0; i < req->num_pages; i++) { struct page *page = req->pages[i]; - if (write) { -#ifdef KERNEL_2_6 + if (write) set_page_dirty_lock(page); -#else - lock_page(page); - set_page_dirty(page); - unlock_page(page); -#endif - } - page_cache_release(page); + put_page(page); } } @@ -675,7 +498,6 @@ static ssize_t fuse_direct_io(struct file *file, const char __user *buf, { struct inode *inode = file->f_dentry->d_inode; struct fuse_conn *fc = get_fuse_conn(inode); - struct fuse_file *ff = file->private_data; unsigned nmax = write ? fc->max_write : fc->max_read; loff_t pos = *ppos; ssize_t res = 0; @@ -695,25 +517,18 @@ static ssize_t fuse_direct_io(struct file *file, const char __user *buf, tmp = (req->num_pages << PAGE_SHIFT) - req->page_offset; nbytes = min(nbytes, tmp); if (write) - fuse_write_init(req, ff, inode, pos, nbytes, 0); + nres = fuse_send_write(req, file, inode, pos, nbytes); else - fuse_read_init(req, file, inode, pos, nbytes); - request_send(fc, req); + nres = fuse_send_read(req, file, inode, pos, nbytes); fuse_release_user_pages(req, !write); if (req->out.h.error) { if (!res) res = req->out.h.error; break; + } else if (nres > nbytes) { + res = -EIO; + break; } - if (write) { - nres = req->misc.write.out.size; - if (nres > nbytes) { - res = -EPROTO; - break; - } - } - else - nres = req->out.args[0].size; count -= nres; res += nres; pos += nres; @@ -756,7 +571,6 @@ static ssize_t fuse_file_read(struct file *file, char __user *buf, else return generic_file_read(file, buf, count, ppos); #endif - } static ssize_t fuse_file_write(struct file *file, const char __user *buf, @@ -784,18 +598,11 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) if (fc->flags & FUSE_DIRECT_IO) return -ENODEV; else { - if ((vma->vm_flags & (VM_WRITE | VM_SHARED)) == - (VM_WRITE | VM_SHARED)) { - struct fuse_inode *fi = get_fuse_inode(inode); - struct fuse_file *ff = file->private_data; - - if (!user_mmap && current->uid != 0) - return -EPERM; - - down_write(&fi->write_sem); - if (list_empty(&ff->ff_list)) - list_add(&ff->ff_list, &fi->write_files); - up_write(&fi->write_sem); + if ((vma->vm_flags & VM_SHARED)) { + if ((vma->vm_flags & VM_WRITE)) + return -ENODEV; + else + vma->vm_flags &= ~VM_MAYWRITE; } return generic_file_mmap(file, vma); } @@ -816,7 +623,6 @@ static struct file_operations fuse_file_operations = { static struct address_space_operations fuse_file_aops = { .readpage = fuse_readpage, - .writepage = fuse_writepage, .prepare_write = fuse_prepare_write, .commit_write = fuse_commit_write, #ifdef KERNEL_2_6 diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h index d829f0d..b79d06b 100644 --- a/kernel/fuse_i.h +++ b/kernel/fuse_i.h @@ -1,9 +1,9 @@ /* - FUSE: Filesystem in Userspace - Copyright (C) 2001-2004 Miklos Szeredi + FUSE: Filesystem in Userspace + Copyright (C) 2001-2004 Miklos Szeredi - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. */ #ifdef FUSE_MAINLINE @@ -37,7 +37,7 @@ # define i_size_read(inode) ((inode)->i_size) # define i_size_write(inode, size) do { (inode)->i_size = size; } while(0) # endif -#endif +#endif /* KERNEL_2_6 */ #endif /* FUSE_MAINLINE */ #include #include @@ -55,13 +55,21 @@ #ifndef __user #define __user #endif +#ifndef KERNEL_2_6 +#include +static inline void set_page_dirty_lock(struct page *page) +{ + lock_page(page); + set_page_dirty(page); + unlock_page(page); +} +#endif /* Max number of pages that can be used in a single read request */ #define FUSE_MAX_PAGES_PER_REQ 32 /* If more requests are outstanding, then the operation will block */ #define FUSE_MAX_OUTSTANDING 10 - /** If the FUSE_DEFAULT_PERMISSIONS flag is given, the filesystem module will check permissions based on the file mode. Otherwise no permission checking is done in the kernel */ @@ -96,28 +104,17 @@ struct fuse_inode { /** The request used for sending the FORGET message */ struct fuse_req *forget_req; - /** Semaphore protects the 'write_files' list, and against - truncate racing with async writeouts */ - struct rw_semaphore write_sem; - /** Time in jiffies until the file attributes are valid */ unsigned long i_time; - - /* List of fuse_files which can provide file handles in - * writepage, protected by write_sem */ - struct list_head write_files; }; /** FUSE specific file data */ struct fuse_file { /** Request reserved for flush and release */ struct fuse_req *release_req; - + /** File handle used by userspace */ unsigned long fh; - - /** Element in fuse_inode->write_files */ - struct list_head ff_list; }; /** One input argument of a request */ @@ -142,7 +139,7 @@ struct fuse_in { }; /** One output argument of a request */ -struct fuse_out_arg { +struct fuse_arg { unsigned size; void *value; }; @@ -155,10 +152,10 @@ struct fuse_out { /** Last argument is variable length (can be shorter than arg->size) */ unsigned argvar:1; - + /** Last argument is a list of pages to copy data to */ unsigned argpages:1; - + /** Zero partially or not copied pages */ unsigned page_zeroing:1; @@ -166,30 +163,40 @@ struct fuse_out { unsigned numargs; /** Array of arguments */ - struct fuse_out_arg args[3]; + struct fuse_arg args[3]; }; struct fuse_req; struct fuse_conn; -/** Function called on finishing an async request */ -typedef void (*fuse_reqend_t)(struct fuse_conn *, struct fuse_req *); - /** * A request to the client */ struct fuse_req { - /** The request list */ + /** This can be on either unused_list, pending or processing + lists in fuse_conn */ struct list_head list; + /** refcount */ + atomic_t count; + /** True if the request has reply */ unsigned isreply:1; - /* The request is preallocated */ + /** The request is preallocated */ unsigned preallocated:1; - /* The request is finished */ - unsigned finished; + /** The request was interrupted */ + unsigned interrupted:1; + + /** Data is being copied to/from the request */ + unsigned locked:1; + + /** Request has been sent to userspace */ + unsigned sent:1; + + /** The request is finished */ + unsigned finished:1; /** The request input */ struct fuse_in in; @@ -200,17 +207,10 @@ 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; - /** Data for asynchronous requests */ union { - struct { - struct fuse_write_in in; - struct fuse_write_out out; - } write; - struct fuse_read_in read_in; struct fuse_forget_in forget_in; + struct fuse_release_in release_in; } misc; /** page vector */ @@ -261,9 +261,6 @@ struct fuse_conn { /** Controls the maximum number of outstanding requests */ struct semaphore unused_sem; - /** Semaphore protecting the super block from going away */ - struct semaphore sb_sem; - /** The list of unused requests */ struct list_head unused_list; @@ -418,42 +415,35 @@ void fuse_reset_request(struct fuse_req *req); struct fuse_req *fuse_get_request(struct fuse_conn *fc); /** - * Reserve a preallocated request, non-interruptible + * Reserve a preallocated request, only interruptible by SIGKILL */ struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc); -/** - * Reserve a preallocated request, non-blocking - */ -struct fuse_req *fuse_get_request_nonblock(struct fuse_conn *fc); - /** * Free a request */ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req); /** - * Send a request + * Send a request (synchronous, interruptible) */ void request_send(struct fuse_conn *fc, struct fuse_req *req); /** - * Send a request for which a reply is not expected + * Send a request (synchronous, non-interruptible except by SIGKILL) + * + * If background is non-zero and SIGKILL is received still send + * request asynchronously */ -void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req); +void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req, + int background); /** - * Send asynchronous request + * Send a request with no reply */ -void request_send_async(struct fuse_conn *fc, struct fuse_req *req, - fuse_reqend_t end); +void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req); /** * Get the attributes of a file */ int fuse_do_getattr(struct inode *inode); - -/** - * Write dirty pages - */ -void fuse_sync_inode(struct inode *inode); diff --git a/kernel/fuse_kernel.h b/kernel/fuse_kernel.h index 63973a7..a97c3e8 100644 --- a/kernel/fuse_kernel.h +++ b/kernel/fuse_kernel.h @@ -9,10 +9,10 @@ /* This file defines the kernel interface of FUSE */ /** Version number of this interface */ -#define FUSE_KERNEL_VERSION 4 +#define FUSE_KERNEL_VERSION 5 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 2 +#define FUSE_KERNEL_MINOR_VERSION 1 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -77,7 +77,7 @@ enum fuse_opcode { FUSE_WRITE = 16, FUSE_STATFS = 17, FUSE_RELEASE = 18, - FUSE_INVALIDATE = 19, /* user initiated */ + /*FUSE_INVALIDATE = 19,*/ FUSE_FSYNC = 20, FUSE_SETXATTR = 21, FUSE_GETXATTR = 22, @@ -212,14 +212,6 @@ struct fuse_out_header { int error; }; -struct fuse_user_header { - int unique; /* zero */ - enum fuse_opcode opcode; - unsigned long nodeid; - unsigned long ino; /* Needed only on 2.4.x where ino is also - used for inode lookup */ -}; - struct fuse_dirent { unsigned long ino; unsigned short namelen; diff --git a/kernel/inode.c b/kernel/inode.c index 5887605..ac80b63 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -1,9 +1,9 @@ /* - FUSE: Filesystem in Userspace - Copyright (C) 2001-2004 Miklos Szeredi + FUSE: Filesystem in Userspace + Copyright (C) 2001-2004 Miklos Szeredi - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. */ #include "fuse_i.h" @@ -72,8 +72,6 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) kmem_cache_free(fuse_inode_cachep, inode); return NULL; } - init_rwsem(&fi->write_sem); - INIT_LIST_HEAD(&fi->write_files); return inode; } @@ -81,7 +79,6 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) static void fuse_destroy_inode(struct inode *inode) { struct fuse_inode *fi = get_fuse_inode(inode); - BUG_ON(!list_empty(&fi->write_files)); if (fi->forget_req) fuse_request_free(fi->forget_req); kmem_cache_free(fuse_inode_cachep, inode); @@ -119,10 +116,8 @@ static void fuse_put_super(struct super_block *sb) { struct fuse_conn *fc = get_fuse_conn_super(sb); - down(&fc->sb_sem); spin_lock(&fuse_lock); fc->sb = NULL; - up(&fc->sb_sem); fc->uid = 0; fc->flags = 0; /* Flush all readers on this fs */ @@ -177,7 +172,7 @@ enum { OPT_ALLOW_OTHER, OPT_ALLOW_ROOT, OPT_KERNEL_CACHE, -#ifndef FUSE_MAINLINE +#ifndef KERNEL_2_6 OPT_LARGE_READ, #endif OPT_DIRECT_IO, @@ -193,7 +188,7 @@ static match_table_t tokens = { {OPT_ALLOW_OTHER, "allow_other"}, {OPT_ALLOW_ROOT, "allow_root"}, {OPT_KERNEL_CACHE, "kernel_cache"}, -#ifndef FUSE_MAINLINE +#ifndef KERNEL_2_6 {OPT_LARGE_READ, "large_read"}, #endif {OPT_DIRECT_IO, "direct_io"}, @@ -251,19 +246,9 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d) d->flags |= FUSE_KERNEL_CACHE; break; -#ifndef FUSE_MAINLINE - case OPT_LARGE_READ: #ifndef KERNEL_2_6 + case OPT_LARGE_READ: d->flags |= FUSE_LARGE_READ; -#else - { - static int warned = 0; - if (!warned) { - printk("fuse: large_read option is deprecated for 2.6 kernels\n"); - warned = 1; - } - } -#endif break; #endif case OPT_DIRECT_IO: @@ -345,7 +330,6 @@ static struct fuse_conn *new_conn(void) INIT_LIST_HEAD(&fc->processing); INIT_LIST_HEAD(&fc->unused_list); sema_init(&fc->unused_sem, FUSE_MAX_OUTSTANDING); - sema_init(&fc->sb_sem, 1); for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) { struct fuse_req *req = fuse_request_alloc(); if (!req) { @@ -368,18 +352,13 @@ static struct fuse_conn *get_conn(struct file *file, struct super_block *sb) { struct fuse_conn *fc; - if (file->f_op != &fuse_dev_operations) { - printk("FUSE: bad communication file descriptor\n"); + if (file->f_op != &fuse_dev_operations) return NULL; - } fc = new_conn(); - if (fc == NULL) { - printk("FUSE: failed to allocate connection data\n"); + if (fc == NULL) return NULL; - } spin_lock(&fuse_lock); if (file->private_data) { - printk("fuse_read_super: connection already mounted\n"); free_conn(fc); fc = NULL; } else { @@ -454,7 +433,6 @@ static int fuse_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len, return type; } - static struct export_operations fuse_export_operations = { .get_dentry = fuse_get_dentry, .encode_fh = fuse_encode_fh, @@ -516,10 +494,8 @@ static int fuse_read_super(struct super_block *sb, void *data, int silent) *get_fuse_conn_super_p(sb) = fc; root = get_root_inode(sb, d.rootmode); - if (root == NULL) { - printk("fuse_read_super: failed to get root inode\n"); + if (root == NULL) goto err; - } sb->s_root = d_alloc_root(root); if (!sb->s_root) { @@ -530,10 +506,8 @@ static int fuse_read_super(struct super_block *sb, void *data, int silent) return 0; err: - down(&fc->sb_sem); spin_lock(&fuse_lock); fc->sb = NULL; - up(&fc->sb_sem); fuse_release_conn(fc); spin_unlock(&fuse_lock); *get_fuse_conn_super_p(sb) = NULL; diff --git a/kernel/util.c b/kernel/util.c index 8dca167..57375f4 100644 --- a/kernel/util.c +++ b/kernel/util.c @@ -1,9 +1,9 @@ /* - FUSE: Filesystem in Userspace - Copyright (C) 2001-2004 Miklos Szeredi + FUSE: Filesystem in Userspace + Copyright (C) 2001-2004 Miklos Szeredi - This program can be distributed under the terms of the GNU GPL. - See the file COPYING. + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. */ #include "fuse_i.h" diff --git a/lib/fuse.c b/lib/fuse.c index 99bca56..1e120e8 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -235,43 +235,6 @@ static struct node *find_node(struct fuse *f, nodeid_t parent, char *name, return node; } -static int path_lookup(struct fuse *f, const char *path, nodeid_t *nodeidp, - unsigned long *inop) -{ - nodeid_t nodeid; - unsigned long ino; - int err; - char *s; - char *name; - char *tmp = strdup(path); - if (!tmp) - return -ENOMEM; - - pthread_mutex_lock(&f->lock); - nodeid = FUSE_ROOT_ID; - ino = nodeid; - err = 0; - for (s = tmp; (name = strsep(&s, "/")) != NULL; ) { - if (name[0]) { - struct node *node = lookup_node(f, nodeid, name); - if (node == NULL) { - err = -ENOENT; - break; - } - nodeid = node->nodeid; - ino = node->ino; - } - } - pthread_mutex_unlock(&f->lock); - free(tmp); - if (!err) { - *nodeidp = nodeid; - *inop = ino; - } - - return err; -} - static char *add_name(char *buf, char *s, const char *name) { size_t len = strlen(name); @@ -1664,7 +1627,7 @@ struct fuse_cmd *fuse_read_cmd(struct fuse *f) res = read(f->fd, cmd->buf, FUSE_MAX_IN); if (res == -1) { free_cmd(cmd); - if (fuse_exited(f) || errno == EINTR) + if (fuse_exited(f) || errno == EINTR || errno == ENOENT) return NULL; /* ENODEV means we got unmounted, so we silenty return failure */ @@ -1718,38 +1681,9 @@ int fuse_loop(struct fuse *f) int fuse_invalidate(struct fuse *f, const char *path) { - int res; - int err; - nodeid_t nodeid; - unsigned long ino; - struct fuse_user_header h; - - err = path_lookup(f, path, &nodeid, &ino); - if (err) { - if (err == -ENOENT) - return 0; - else - return err; - } - - memset(&h, 0, sizeof(struct fuse_user_header)); - h.opcode = FUSE_INVALIDATE; - h.nodeid = nodeid; - h.ino = ino; - - if ((f->flags & FUSE_DEBUG)) { - printf("INVALIDATE nodeid: %li\n", nodeid); - fflush(stdout); - } - - res = write(f->fd, &h, sizeof(struct fuse_user_header)); - if (res == -1) { - if (errno != ENOENT) { - perror("fuse: writing device"); - return -errno; - } - } - return 0; + (void) f; + (void) path; + return -EINVAL; } void fuse_exit(struct fuse *f) @@ -1829,8 +1763,10 @@ static int parse_lib_opts(struct fuse *f, const char *opts) char *s = xopts; char *opt; - if (xopts == NULL) + if (xopts == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); return -1; + } while((opt = strsep(&s, ","))) { if (strcmp(opt, "debug") == 0) @@ -1860,8 +1796,10 @@ struct fuse *fuse_new_common(int fd, const char *opts, } f = (struct fuse *) calloc(1, sizeof(struct fuse)); - if (f == NULL) + if (f == NULL) { + fprintf(stderr, "fuse: failed to allocate fuse object\n"); goto out; + } if (check_version(f) == -1) goto out_free; @@ -1876,14 +1814,18 @@ struct fuse *fuse_new_common(int fd, const char *opts, f->name_table_size = 14057; f->name_table = (struct node **) calloc(1, sizeof(struct node *) * f->name_table_size); - if (f->name_table == NULL) + if (f->name_table == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); goto out_free; + } f->id_table_size = 14057; f->id_table = (struct node **) calloc(1, sizeof(struct node *) * f->id_table_size); - if (f->id_table == NULL) + if (f->id_table == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); goto out_free_name_table; + } #ifndef USE_UCLIBC pthread_mutex_init(&f->lock, NULL); @@ -1903,14 +1845,18 @@ struct fuse *fuse_new_common(int fd, const char *opts, f->exited = 0; root = (struct node *) calloc(1, sizeof(struct node)); - if (root == NULL) + if (root == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); goto out_free_id_table; + } root->mode = 0; root->rdev = 0; root->name = strdup("/"); - if (root->name == NULL) + if (root->name == NULL) { + fprintf(stderr, "fuse: memory allocation failed\n"); goto out_free_root; + } root->parent = 0; root->nodeid = FUSE_ROOT_ID; @@ -1928,7 +1874,6 @@ struct fuse *fuse_new_common(int fd, const char *opts, out_free: free(f); out: - fprintf(stderr, "fuse: failed to allocate fuse object\n"); return NULL; } diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 329c4ad..edbed43 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -9,6 +9,7 @@ FUSE_2.2 { fuse_loop; fuse_loop_mt; fuse_loop_mt_proc; + fuse_main; fuse_main_compat1; fuse_main_compat2; fuse_main_real; diff --git a/lib/helper.c b/lib/helper.c index 1ac5aa2..c14248d 100644 --- a/lib/helper.c +++ b/lib/helper.c @@ -379,6 +379,13 @@ int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, return fuse_main_common(argc, argv, op, op_size, 0); } +#undef fuse_main +int fuse_main() +{ + fprintf(stderr, "This function does not exist\n"); + return -1; +} + void fuse_main_compat1(int argc, char *argv[], const struct fuse_operations_compat1 *op) { diff --git a/util/fusermount.c b/util/fusermount.c index 40df490..80808c8 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -33,6 +33,7 @@ #include #include #include +#include #define FUSE_COMMFD_ENV "_FUSE_COMMFD" @@ -463,15 +464,31 @@ static int do_mount(const char *mnt, const char *type, mode_t rootmode, !begins_with(s, "uid=")) { int on; int flag; - if (find_mount_flag(s, len, &on, &flag)) { - if (on) - flags |= flag; - else - flags &= ~flag; - } else { - memcpy(d, s, len); - d += len; - *d++ = ','; + int skip_option = 0; + const char *large_read_opt = "large_read"; + if (len == strlen(large_read_opt) && + strncmp(large_read_opt, s, len) == 0) { + struct utsname utsname; + unsigned kmaj, kmin; + res = uname(&utsname); + if (res == 0 && + sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 && + (kmaj > 2 || (kmaj == 2 && kmin > 4))) { + fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin); + skip_option = 1; + } + } + if (!skip_option) { + if (find_mount_flag(s, len, &on, &flag)) { + if (on) + flags |= flag; + else + flags &= ~flag; + } else { + memcpy(d, s, len); + d += len; + *d++ = ','; + } } } s += len; -- 2.30.2