Repository: zargony/fuse-rs (language: rust)

Issue:
Filesystem's methods connot refer request header
We sometimes want to make it dynamic for invoking process.(e.g. pid, uid or any more)
For example, in 'go-fuse', [each method's argument contain request header](https://github.com/hanwen/go-fuse/blob/df17f4147024da8ad215f0b101fd62b27e63e026/fuse/types.go).

Thank you.



Discussion / hints:
It would be indeed useful have the request passed to every method and make some information available through it. Passing the request is necessary anyway for supporting concurrent operations like discussed in #1 and #6.


Fix patch:
diff --git a/README.md b/README.md
index 8218375..e61cb87 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Except for a single setup (mount) function call and a final teardown (umount) fu
 
 ## How to
 
-To create a new filesystem, implement the trait `Filesystem`. All methods have default implementations that do nothing, so if you implement no method at all, you still get a mountable filesystem that does nothing.
+To create a new filesystem, implement the trait `Filesystem`. Filesystem operations from the kernel are dispatched to the methods of the `Filesystem` trait. Most methods get a `reply` parameter that must be used to eventually answer the request. All methods have default implementations that reply with neutral answers, so if you implement no method at all, you still get a mountable filesystem that does nothing.
 
 To actually mount the filesystem, pass an object that implements `Filesystem` and the path of an (existing) mountpoint to the `mount` function. `mount` will not return until the filesystem is unmounted.
 
@@ -35,7 +35,6 @@ See the examples directory for some basic examples.
 
 There's still a lot of stuff to be done. Feel free to contribute.
 
-- Filesystem operations are currently always dispatched in the same task. Running multiple operations concurrently is probably desirable, especially for read/write operations.
 - Interrupting a filesystem operation isn't handled yet.
 - Using `fuse_*_out` in the results of `Filesystem` methods doesn't look right. These native structs should be hidden from the public interface.
 - An additional more high level API would be nice. It should provide pathnames instead inode numbers and automatically handle concurrency and interruption (like the FUSE C library's high level API).
diff --git a/examples/hello.rs b/examples/hello.rs
index d51a902..d16323a 100644
--- a/examples/hello.rs
+++ b/examples/hello.rs
@@ -4,7 +4,7 @@ use std::default::Default;
 use std::libc::{ENOENT, S_IFDIR, S_IFREG};
 use std::io::{TypeFile, TypeDirectory};
 use std::os;
-use fuse::{Filesystem, FuseResult, fuse_attr, fuse_entry_out, fuse_attr_out, DirBuffer};
+use fuse::{Filesystem, Request, Reply, fuse_attr, fuse_entry_out, fuse_attr_out, DirBuffer};
 
 struct HelloFS;
 
@@ -23,40 +23,40 @@ fn hello_txt_attr () -> fuse_attr {
 }
 
 impl Filesystem for HelloFS {
-	fn lookup (&mut self, parent: u64, name: &PosixPath) -> FuseResult<fuse_entry_out> {
+	fn lookup (&mut self, _req: &Request, parent: u64, name: &PosixPath, mut reply: Reply<fuse_entry_out>) {
 		if parent == 1 && name.as_str() == Some("hello.txt") {
-			Ok(fuse_entry_out { nodeid: 2, generation: 0, attr: hello_txt_attr(), entry_valid: 1, entry_valid_nsec: 0, attr_valid: 1, attr_valid_nsec: 0 })
+			reply.ok(&fuse_entry_out { nodeid: 2, generation: 0, attr: hello_txt_attr(), entry_valid: 1, entry_valid_nsec: 0, attr_valid: 1, attr_valid_nsec: 0 });
 		} else {
-			Err(ENOENT)
+			reply.error(ENOENT);
 		}
 	}
 
-	fn getattr (&mut self, ino: u64) -> FuseResult<fuse_attr_out> {
+	fn getattr (&mut self, _req: &Request, ino: u64, mut reply: Reply<fuse_attr_out>) {
 		match ino {
-			1 => Ok(fuse_attr_out { attr_valid: 1, attr_valid_nsec: 0, dummy: 0, attr: hello_dir_attr() }),
-			2 => Ok(fuse_attr_out { attr_valid: 1, attr_valid_nsec: 0, dummy: 0, attr: hello_txt_attr() }),
-			_ => Err(ENOENT),
+			1 => reply.ok(&fuse_attr_out { attr_valid: 1, attr_valid_nsec: 0, dummy: 0, attr: hello_dir_attr() }),
+			2 => reply.ok(&fuse_attr_out { attr_valid: 1, attr_valid_nsec: 0, dummy: 0, attr: hello_txt_attr() }),
+			_ => reply.error(ENOENT),
 		}
 	}
 
-	fn read (&mut self, ino: u64, _fh: u64, offset: u64, _size: uint) -> FuseResult<Vec<u8>> {
+	fn read (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, _size: uint, mut reply: Reply<Vec<u8>>) {
 		if ino == 2 {
-			Ok(Vec::from_slice(HELLO_WORLD.as_bytes().tailn(offset as uint)))
+			reply.ok(&Vec::from_slice(HELLO_WORLD.as_bytes().tailn(offset as uint)));
 		} else {
-			Err(ENOENT)
+			reply.error(ENOENT);
 		}
 	}
 
-	fn readdir (&mut self, ino: u64, _fh: u64, offset: u64, mut buffer: DirBuffer) -> FuseResult<DirBuffer> {
+	fn readdir (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, mut buffer: DirBuffer, mut reply: Reply<DirBuffer>) {
 		if ino == 1 {
 			if offset == 0 {
 				buffer.fill(1, 0, TypeDirectory, &PosixPath::new("."));
 				buffer.fill(1, 1, TypeDirectory, &PosixPath::new(".."));
 				buffer.fill(2, 2, TypeFile, &PosixPath::new("hello.txt"));
 			}
-			Ok(buffer)
+			reply.ok(&buffer);
 		} else {
-			Err(ENOENT)
+			reply.error(ENOENT);
 		}
 	}
 }
diff --git a/src/channel.rs b/src/channel.rs
index ca04a71..8724dc3 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -79,6 +79,34 @@ impl Channel {
 		}
 	}
 
+	/// Returns a sender object for this channel. The sender object can be
+	/// used to send to the channel. Multiple sender objects can be used
+	/// and they can safely be sent to other tasks.
+	pub fn sender (&self) -> ChannelSender {
+		// Since write/writev syscalls are threadsafe, we can simply create
+		// a sender by using the same fd and use it in other threads. Only
+		// the channel closes the fd when dropped. If any sender is used after
+		// dropping the channel, it'll return an EBADF error.
+		ChannelSender { fd: self.fd }
+	}
+}
+
+impl Drop for Channel {
+	fn drop (&mut self) {
+		// TODO: send ioctl FUSEDEVIOCSETDAEMONDEAD on OS X before closing the fd
+		// Close the communication channel to the kernel driver
+		// (closing it before unnmount prevents sync unmount deadlock)
+		unsafe { ::std::libc::close(self.fd); }
+		// Unmount this channel's mount point
+		unmount(&self.mountpoint);
+	}
+}
+
+pub struct ChannelSender {
+	priv fd: c_int,
+}
+
+impl ChannelSender {
 	/// Send all data in the slice of slice of bytes in a single write.
 	/// Note: Can block natively, so it should be called from a separate thread
 	pub fn send (&self, buffer: &[&[u8]]) -> Result<(), c_int> {
@@ -94,17 +122,6 @@ impl Channel {
 	}
 }
 
-impl Drop for Channel {
-	fn drop (&mut self) {
-		// TODO: send ioctl FUSEDEVIOCSETDAEMONDEAD on OS X before closing the fd
-		// Close the communication channel to the kernel driver
-		// (closing it before unnmount prevents sync unmount deadlock)
-		unsafe { ::std::libc::close(self.fd); }
-		// Unmount this channel's mount point
-		unmount(&self.mountpoint);
-	}
-}
-
 /// Unmount an arbitrary mount point
 pub fn unmount (mountpoint: &Path) {
 	mountpoint.with_c_str(|mnt| {
diff --git a/src/lib.rs b/src/lib.rs
index 5b2b5d4..5a014c8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,7 +7,7 @@
 //! needed to establish a fd to talk to the kernel driver.
 //!
 
-#[crate_id = "github.com/zargony/rust-fuse#fuse:0.1"];
+#[crate_id = "github.com/zargony/rust-fuse#fuse:0.2"];
 #[desc = "Rust FUSE - Filesystem in Userspace"];
 #[license = "MIT"];
 #[crate_type = "dylib"];
@@ -30,23 +30,19 @@ pub use fuse::{fuse_statfs_out, fuse_getxattr_out, fuse_lk_out, fuse_bmap_out};
 pub use fuse::{fuse_getxtimes_out};
 pub use fuse::FUSE_ROOT_ID;
 pub use fuse::consts;
+pub use reply::Reply;
+pub use request::Request;
 pub use sendable::DirBuffer;
 pub use session::{Session, BackgroundSession};
 
 mod argument;
 mod channel;
 mod fuse;
+mod reply;
 mod request;
 mod sendable;
 mod session;
 
-/// Result type for filesystem operations.
-///
-/// All filesystem operations (methods of the Filesystem trait) usually return this
-/// type to indicate success or failure. In case of an error, it contains a Posix
-/// error code (errno). Error constants are defined in std::libc.
-pub type FuseResult<T> = Result<T, c_int>;
-
 /// Filesystem trait.
 ///
 /// This trait must be implemented to provide a userspace filesystem via FUSE.
@@ -56,18 +52,18 @@ pub type FuseResult<T> = Result<T, c_int>;
 pub trait Filesystem {
 	/// Initialize filesystem
 	/// Called before any other filesystem method.
-	fn init (&mut self) -> FuseResult<()> {
+	fn init (&mut self, _req: &Request) -> Result<(), c_int> {
 		Ok(())
 	}
 
 	/// Clean up filesystem
 	/// Called on filesystem exit.
-	fn destroy (&mut self) {
+	fn destroy (&mut self, _req: &Request) {
 	}
 
 	/// Look up a directory entry by name and get its attributes.
-	fn lookup (&mut self, _parent: u64, _name: &PosixPath) -> FuseResult<fuse_entry_out> {
-		Err(ENOSYS)
+	fn lookup (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, mut reply: Reply<fuse_entry_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Forget about an inode
@@ -77,60 +73,60 @@ pub trait Filesystem {
 	/// each forget. The filesystem may ignore forget calls, if the inodes don't need to
 	/// have a limited lifetime. On unmount it is not guaranteed, that all referenced
 	/// inodes will receive a forget message.
-	fn forget (&mut self, _ino: u64, _nlookup: uint) {
+	fn forget (&mut self, _req: &Request, _ino: u64, _nlookup: uint) {
 	}
 
 	/// Get file attributes
-	fn getattr (&mut self, _ino: u64) -> FuseResult<fuse_attr_out> {
-		Err(ENOSYS)
+	fn getattr (&mut self, _req: &Request, _ino: u64, mut reply: Reply<fuse_attr_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Set file attributes
 	/// In the 'attr' argument only members indicated by the 'valid' bitmask contain
 	/// valid values. Other members contain undefined values.
-	fn setattr (&mut self, _ino: u64, _attr: &fuse_setattr_in) -> FuseResult<fuse_attr_out> {
-		Err(ENOSYS)
+	fn setattr (&mut self, _req: &Request, _ino: u64, _attr: &fuse_setattr_in, mut reply: Reply<fuse_attr_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Read symbolic link
-	fn readlink (&mut self, _ino: u64) -> FuseResult<Vec<u8>> {
-		Err(ENOSYS)
+	fn readlink (&mut self, _req: &Request, _ino: u64, mut reply: Reply<Vec<u8>>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Create file node
 	/// Create a regular file, character device, block device, fifo or socket node.
-	fn mknod (&mut self, _parent: u64, _name: &PosixPath, _mode: u32, _rdev: u32) -> FuseResult<fuse_entry_out> {
-		Err(ENOSYS)
+	fn mknod (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, _mode: u32, _rdev: u32, mut reply: Reply<fuse_entry_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Create a directory
-	fn mkdir (&mut self, _parent: u64, _name: &PosixPath, _mode: u32) -> FuseResult<fuse_entry_out> {
-		Err(ENOSYS)
+	fn mkdir (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, _mode: u32, mut reply: Reply<fuse_entry_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Remove a file
-	fn unlink (&mut self, _parent: u64, _name: &PosixPath) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn unlink (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Remove a directory
-	fn rmdir (&mut self, _parent: u64, _name: &PosixPath) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn rmdir (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Create a symbolic link
-	fn symlink (&mut self, _parent: u64, _name: &PosixPath, _link: &PosixPath) -> FuseResult<fuse_entry_out> {
-		Err(ENOSYS)
+	fn symlink (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, _link: &PosixPath, mut reply: Reply<fuse_entry_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Rename a file
-	fn rename (&mut self, _parent: u64, _name: &PosixPath, _newparent: u64, _newname: &PosixPath) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn rename (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, _newparent: u64, _newname: &PosixPath, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Create a hard link
-	fn link (&mut self, _ino: u64, _newparent: u64, _newname: &PosixPath) -> FuseResult<fuse_entry_out> {
-		Err(ENOSYS)
+	fn link (&mut self, _req: &Request, _ino: u64, _newparent: u64, _newname: &PosixPath, mut reply: Reply<fuse_entry_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Open a file
@@ -141,8 +137,8 @@ pub trait Filesystem {
 	/// anything in fh. There are also some flags (direct_io, keep_cache) which the
 	/// filesystem may set, to change the way the file is opened. See fuse_file_info
 	/// structure in <fuse_common.h> for more details.
-	fn open (&mut self, _ino: u64, _flags: uint) -> FuseResult<fuse_open_out> {
-		Ok(fuse_open_out { fh: 0, open_flags: 0, padding: 0 })
+	fn open (&mut self, _req: &Request, _ino: u64, _flags: uint, mut reply: Reply<fuse_open_out>) {
+		reply.ok(&fuse_open_out { fh: 0, open_flags: 0, padding: 0 });
 	}
 
 	/// Read data
@@ -152,8 +148,8 @@ pub trait Filesystem {
 	/// return value of the read system call will reflect the return value of this
 	/// operation. fh will contain the value set by the open method, or will be undefined
 	/// if the open method didn't set any value.
-	fn read (&mut self, _ino: u64, _fh: u64, _offset: u64, _size: uint) -> FuseResult<Vec<u8>> {
-		Err(ENOSYS)
+	fn read (&mut self, _req: &Request, _ino: u64, _fh: u64, _offset: u64, _size: uint, mut reply: Reply<Vec<u8>>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Write data
@@ -162,8 +158,8 @@ pub trait Filesystem {
 	/// which case the return value of the write system call will reflect the return
 	/// value of this operation. fh will contain the value set by the open method, or
 	/// will be undefined if the open method didn't set any value.
-	fn write (&mut self, _ino: u64, _fh: u64, _offset: u64, _data: &[u8], _flags: uint) -> FuseResult<uint> {
-		Err(ENOSYS)
+	fn write (&mut self, _req: &Request, _ino: u64, _fh: u64, _offset: u64, _data: &[u8], _flags: uint, mut reply: Reply<fuse_write_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Flush method
@@ -176,8 +172,8 @@ pub trait Filesystem {
 	/// is not forced to flush pending writes. One reason to flush data, is if the
 	/// filesystem wants to return write errors. If the filesystem supports file locking
 	/// operations (setlk, getlk) it should remove all locks belonging to 'lock_owner'.
-	fn flush (&mut self, _ino: u64, _fh: u64, _lock_owner: u64) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn flush (&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Release an open file
@@ -188,15 +184,15 @@ pub trait Filesystem {
 	/// the release. fh will contain the value set by the open method, or will be undefined
 	/// if the open method didn't set any value. flags will contain the same flags as for
 	/// open.
-	fn release (&mut self, _ino: u64, _fh: u64, _flags: uint, _lock_owner: u64, _flush: bool) -> FuseResult<()> {
-		Ok(())
+	fn release (&mut self, _req: &Request, _ino: u64, _fh: u64, _flags: uint, _lock_owner: u64, _flush: bool, mut reply: Reply<()>) {
+		reply.ok(&());
 	}
 
 	/// Synchronize file contents
 	/// If the datasync parameter is non-zero, then only the user data should be flushed,
 	/// not the meta data.
-	fn fsync (&mut self, _ino: u64, _fh: u64, _datasync: bool) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn fsync (&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Open a directory
@@ -206,8 +202,8 @@ pub trait Filesystem {
 	/// anything in fh, though that makes it impossible to implement standard conforming
 	/// directory stream operations in case the contents of the directory can change
 	/// between opendir and releasedir.
-	fn opendir (&mut self, _ino: u64, _flags: uint) -> FuseResult<fuse_open_out> {
-		Ok(fuse_open_out { fh: 0, open_flags: 0, padding: 0 })
+	fn opendir (&mut self, _req: &Request, _ino: u64, _flags: uint, mut reply: Reply<fuse_open_out>) {
+		reply.ok(&fuse_open_out { fh: 0, open_flags: 0, padding: 0 });
 	}
 
 	/// Read directory
@@ -215,57 +211,61 @@ pub trait Filesystem {
 	/// requested size. Send an empty buffer on end of stream. fh will contain the
 	/// value set by the opendir method, or will be undefined if the opendir method
 	/// didn't set any value.
-	fn readdir (&mut self, _ino: u64, _fh: u64, _offset: u64, _buffer: DirBuffer) -> FuseResult<DirBuffer> {
-		Err(ENOSYS)
+	fn readdir (&mut self, _req: &Request, _ino: u64, _fh: u64, _offset: u64, _buffer: DirBuffer, mut reply: Reply<DirBuffer>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Release an open directory
 	/// For every opendir call there will be exactly one releasedir call. fh will
 	/// contain the value set by the opendir method, or will be undefined if the
 	/// opendir method didn't set any value.
-	fn releasedir (&mut self, _ino: u64, _fh: u64, _flags: uint) -> FuseResult<()> {
-		Ok(())
+	fn releasedir (&mut self, _req: &Request, _ino: u64, _fh: u64, _flags: uint, mut reply: Reply<()>) {
+		reply.ok(&());
 	}
 
 	/// Synchronize directory contents
 	/// If the datasync parameter is set, then only the directory contents should
 	/// be flushed, not the meta data. fh will contain the value set by the opendir
 	/// method, or will be undefined if the opendir method didn't set any value.
-	fn fsyncdir (&mut self, _ino: u64, _fh: u64, _datasync: bool) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn fsyncdir (&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Get file system statistics
-	fn statfs (&mut self, _ino: u64) -> FuseResult<fuse_statfs_out> {
-		Ok(fuse_statfs_out { st: fuse_kstatfs { blocks: 0, bfree: 0, bavail: 0, files: 0, ffree: 0, bsize: 512, namelen: 255, frsize: 0, padding: 0, spare: [0, ..6] }})
+	fn statfs (&mut self, _req: &Request, _ino: u64, mut reply: Reply<fuse_statfs_out>) {
+		reply.ok(&fuse_statfs_out { st: fuse_kstatfs { blocks: 0, bfree: 0, bavail: 0, files: 0, ffree: 0, bsize: 512, namelen: 255, frsize: 0, padding: 0, spare: [0, ..6] }});
 	}
 
 	/// Set an extended attribute
-	fn setxattr (&mut self, _ino: u64, _name: &[u8], _value: &[u8], _flags: uint, _position: u32) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn setxattr (&mut self, _req: &Request, _ino: u64, _name: &[u8], _value: &[u8], _flags: uint, _position: u32, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Get an extended attribute
-	fn getxattr (&mut self, _ino: u64, _name: &[u8]) -> FuseResult<Vec<u8>> {
-		Err(ENOSYS)
+	fn getxattr (&mut self, _req: &Request, _ino: u64, _name: &[u8], mut reply: Reply<Vec<u8>>) {
+		// FIXME: If arg.size is zero, the size of the value should be sent with fuse_getxattr_out
+		// FIXME: If arg.size is non-zero, send the value if it fits, or ERANGE otherwise
+		reply.error(ENOSYS);
 	}
 
 	/// List extended attribute names
-	fn listxattr (&mut self, _ino: u64) -> FuseResult<Vec<Vec<u8>>> {
-		Err(ENOSYS)
+	fn listxattr (&mut self, _req: &Request, _ino: u64, mut reply: Reply<()>) {
+		// FIXME: If arg.size is zero, the size of the attribute list should be sent with fuse_getxattr_out
+		// FIXME: If arg.size is non-zero, send the attribute list if it fits, or ERANGE otherwise
+		reply.error(ENOSYS);
 	}
 
 	/// Remove an extended attribute
-	fn removexattr (&mut self, _ino: u64, _name: &[u8]) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn removexattr (&mut self, _req: &Request, _ino: u64, _name: &[u8], mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Check file access permissions
 	/// This will be called for the access() system call. If the 'default_permissions'
 	/// mount option is given, this method is not called. This method is not called
 	/// under Linux kernel versions 2.4.x
-	fn access (&mut self, _ino: u64, _mask: uint) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn access (&mut self, _req: &Request, _ino: u64, _mask: uint, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Create and open a file
@@ -278,13 +278,13 @@ pub trait Filesystem {
 	/// structure in <fuse_common.h> for more details. If this method is not
 	/// implemented or under Linux kernel versions earlier than 2.6.15, the mknod()
 	/// and open() methods will be called instead.
-	fn create (&mut self, _parent: u64, _name: &PosixPath, _mode: u32, _flags: uint) -> FuseResult<(fuse_entry_out,fuse_open_out)> {
-		Err(ENOSYS)
+	fn create (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, _mode: u32, _flags: uint, mut reply: Reply<(fuse_entry_out, fuse_open_out)>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Test for a POSIX file lock
-	fn getlk (&mut self, _ino: u64, _fh: u64, _lock_owner: u64, _lock: &fuse_file_lock) -> FuseResult<fuse_file_lock> {
-		Err(ENOSYS)
+	fn getlk (&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, _lock: &fuse_file_lock, mut reply: Reply<fuse_file_lock>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Acquire, modify or release a POSIX file lock
@@ -294,35 +294,35 @@ pub trait Filesystem {
 	/// used to fill in this field in getlk(). Note: if the locking methods are not
 	/// implemented, the kernel will still allow file locking to work locally.
 	/// Hence these are only interesting for network filesystems and similar.
-	fn setlk (&mut self, _ino: u64, _fh: u64, _lock_owner: u64, _lock: &fuse_file_lock, _sleep: bool) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn setlk (&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, _lock: &fuse_file_lock, _sleep: bool, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// Map block index within file to block index within device
 	/// Note: This makes sense only for block device backed filesystems mounted
 	/// with the 'blkdev' option
-	fn bmap (&mut self, _ino: u64, _blocksize: uint, _idx: u64) -> FuseResult<fuse_bmap_out> {
-		Err(ENOSYS)
+	fn bmap (&mut self, _req: &Request, _ino: u64, _blocksize: uint, _idx: u64, mut reply: Reply<fuse_bmap_out>) {
+		reply.error(ENOSYS);
 	}
 
 	/// OS X only: Rename the volume. Set fuse_init_out.flags during init to
 	/// FUSE_VOL_RENAME to enable
 	#[cfg(target_os = "macos")]
-	fn setvolname (&mut self, _name: &[u8]) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn setvolname (&mut self, _req: &Request, _name: &[u8], mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// OS X only (undocumented)
 	#[cfg(target_os = "macos")]
-	fn exchange (&mut self, _parent: u64, _name: &PosixPath, _newparent: u64, _newname: &PosixPath, _options: uint) -> FuseResult<()> {
-		Err(ENOSYS)
+	fn exchange (&mut self, _req: &Request, _parent: u64, _name: &PosixPath, _newparent: u64, _newname: &PosixPath, _options: uint, mut reply: Reply<()>) {
+		reply.error(ENOSYS);
 	}
 
 	/// OS X only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags
 	/// during init to FUSE_XTIMES to enable
 	#[cfg(target_os = "macos")]
-	fn getxtimes (&mut self, _ino: u64) -> FuseResult<fuse_getxtimes_out> {
-		Err(ENOSYS)
+	fn getxtimes (&mut self, _req: &Request, _ino: u64, mut reply: Reply<fuse_getxtimes_out>) {
+		reply.error(ENOSYS);
 	}
 }
 
diff --git a/src/reply.rs b/src/reply.rs
new file mode 100644
index 0000000..b42fca1
--- /dev/null
+++ b/src/reply.rs
@@ -0,0 +1,77 @@
+//!
+//! A reply is passed to filesystem operation implementations and must
+//! be used to send back the result of an operation.
+//! The reply can optionally be sent to another task to asynchronously
+//! work on an operation and provide the result later. Also it allows
+//! replying with a block of data without cloning the data.
+//! A reply *must always* be used (by calling either ok() or error()
+//! exactly once).
+//!
+
+use std::mem;
+use std::libc::{c_int, EIO};
+use channel::ChannelSender;
+use fuse::fuse_out_header;
+use sendable::Sendable;
+
+/// Create a new reply for the given request
+pub fn reply<T: Sendable> (ch: ChannelSender, unique: u64) -> Reply<T> {
+	Reply::new(ch, unique)
+}
+
+/// Reply data structure
+pub struct Reply<T> {
+	/// Channel sender for sending the reply
+	priv ch: ChannelSender,
+	/// Unique id of the request to reply to
+	priv unique: u64,
+	/// Flag whether the reply was sent
+	priv replied: bool,
+}
+
+impl<T: Sendable> Reply<T> {
+	/// Create a new reply for the given request
+	fn new (ch: ChannelSender, unique: u64) -> Reply<T> {
+		Reply { ch: ch, unique: unique, replied: false }
+	}
+
+	/// Reply to a request with the given error code and data
+	fn send<T: Sendable> (&mut self, err: c_int, data: &T) {
+		if self.replied {
+			warn!("Ignoring duplicate reply for operation {:u}", self.unique);
+			return;
+		}
+		data.as_bytegroups(|databytes| {
+			let datalen = databytes.iter().fold(0, |l, b| { l +  b.len()});
+			let outheader = fuse_out_header {
+				len: mem::size_of::<fuse_out_header>() as u32 + datalen as u32,
+				error: err,
+				unique: self.unique,
+			};
+			outheader.as_bytegroups(|headbytes| {
+				let _ = self.ch.send(headbytes + databytes);
+				self.replied = true;
+			});
+		});
+	}
+
+	/// Reply to a request with the given data
+	pub fn ok (&mut self, data: &T) {
+		self.send(0, data);
+	}
+
+	/// Reply to a request with the given error code
+	pub fn error (&mut self, err: c_int) {
+		self.send(-err, &());
+	}
+}
+
+#[unsafe_destructor]
+impl<T: Sendable> Drop for Reply<T> {
+	fn drop (&mut self) {
+		if !self.replied {
+			warn!("Reply not sent for operation {:u}, replying with I/O error", self.unique);
+			self.error(EIO);
+		}
+	}
+}
diff --git a/src/request.rs b/src/request.rs
index bbb2c33..d6785a6 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -4,21 +4,18 @@
 //!
 
 use std::{mem, str};
-use std::libc::{c_int, EIO, ENOSYS, EPROTO, ERANGE};
+use std::libc::{c_int, EIO, ENOSYS, EPROTO};
 use argument::ArgumentIterator;
-use channel::Channel;
+use channel::ChannelSender;
 use Filesystem;
 use fuse::*;
 use fuse::consts::*;
+use reply::{reply, Reply};
 use sendable::{Sendable, DirBuffer};
 use session::Session;
 
 /// Maximum write size. FUSE recommends at least 128k, max 16M. Default on OS X is 16M.
-static MAX_WRITE_SIZE: u32 = 16*1024*1024;
-
-/// Size of the buffer for reading a request from the kernel. Since the kernel may send
-/// up to MAX_WRITE_SIZE bytes in a write request, we use that value plus some extra space.
-static BUFFER_SIZE: uint = MAX_WRITE_SIZE as uint + 4096;
+pub static MAX_WRITE_SIZE: u32 = 16*1024*1024;
 
 #[cfg(target_os = "macos")]
 /// We support async reads and our filesystems are usually case-insensitive
@@ -29,66 +26,79 @@ static INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_CASE_INSENSITIVE;
 /// We support async reads
 static INIT_FLAGS: u32 = FUSE_ASYNC_READ;
 
-/// Request data structure
-pub struct Request {
-	priv data: Vec<u8>,
+/// Create a new request from the given buffer
+pub fn request<'a> (ch: ChannelSender, buffer: &'a [u8]) -> Option<Request<'a>> {
+	Request::new(ch, buffer)
 }
 
-impl Request {
-	/// Create a new request
-	pub fn new () -> Request {
-		Request { data: Vec::with_capacity(BUFFER_SIZE) }
-	}
+/// Dispatch request to the given filesystem
+pub fn dispatch<FS: Filesystem> (req: &Request, se: &mut Session<FS>) {
+	req.dispatch(se);
+}
 
-	/// Read the next request from the given channel to kernel driver
-	pub fn read<FS: Filesystem> (&mut self, se: &Session<FS>) -> Result<(), c_int> {
-		assert!(self.data.capacity() >= BUFFER_SIZE);
-		// The kernel driver makes sure that we get exactly one request per read
-		let res = se.ch.receive(&mut self.data);
-		if res.is_ok() && self.data.len() < mem::size_of::<fuse_in_header>() {
-			error!("Short read on FUSE device");
-			Err(EIO)
-		} else {
-			res
+/// Request data structure
+pub struct Request<'a> {
+	/// Channel sender for sending the reply
+	priv ch: ChannelSender,
+	/// Header of the FUSE request
+	priv header: &'a fuse_in_header,
+	/// Operation-specific data payload
+	priv data: &'a [u8],
+}
+
+impl<'a> Request<'a> {
+	/// Create a new request from the given buffer
+	fn new (ch: ChannelSender, buffer: &'a [u8]) -> Option<Request<'a>> {
+		// Every request always begins with a fuse_in_header struct
+		// followed by arbitrary data depending on which opcode it contains
+		if buffer.len() < mem::size_of::<fuse_in_header>() {
+			error!("Short read of FUSE request ({:u} < {:u})", buffer.len(), mem::size_of::<fuse_in_header>());
+			return None;
+		}
+		let mut data = ArgumentIterator::new(buffer);
+		let req = Request {
+			ch: ch,
+			header: data.fetch(),
+			data: data.fetch_data(),
+		};
+		if buffer.len() < req.header.len as uint {
+			error!("Short read of FUSE request ({:u} < {:u})", buffer.len(), req.header.len);
+			return None;
 		}
+		Some(req)
 	}
 
 	/// Dispatch request to the given filesystem.
-	/// This parses a previously read request, calls the appropriate
-	/// filesystem operation method and sends back the returned reply
-	/// to the kernel
-	pub fn dispatch<FS: Filesystem> (&self, se: &mut Session<FS>) {
-		// Every request begins with a fuse_in_header struct followed by arbitrary
-		// data depending on which opcode it contains
-		assert!(self.data.len() >= mem::size_of::<fuse_in_header>());
-		let mut data = ArgumentIterator::new(self.data.as_slice());
-		let header: &fuse_in_header = data.fetch();
-		let opcode: fuse_opcode = match FromPrimitive::from_u32(header.opcode) {
+	/// This calls the appropriate filesystem operation method for the
+	/// request and sends back the returned reply to the kernel
+	fn dispatch<FS: Filesystem> (&self, se: &mut Session<FS>) {
+		let opcode: fuse_opcode = match FromPrimitive::from_u32(self.header.opcode) {
 			Some(op) => op,
 			None => {
-				warn!("Ignoring unknown FUSE operation {:u}", header.opcode)
-				self.reply_error(&se.ch, ENOSYS);
+				warn!("Ignoring unknown FUSE operation {:u}", self.header.opcode)
+				self.reply_error(ENOSYS);
 				return;
 			},
 		};
+		let mut data = ArgumentIterator::new(self.data);
 		match opcode {
 			// Filesystem initialization
 			FUSE_INIT => {
 				let arg: &fuse_init_in = data.fetch();
-				debug!("INIT({:u})   kernel: ABI {:u}.{:u}, flags {:#x}, max readahead {:u}", header.unique, arg.major, arg.minor, arg.flags, arg.max_readahead);
+				debug!("INIT({:u})   kernel: ABI {:u}.{:u}, flags {:#x}, max readahead {:u}", self.header.unique, arg.major, arg.minor, arg.flags, arg.max_readahead);
 				// We don't support ABI versions before 7.6
 				if arg.major < 7 || (arg.major == 7 && arg.minor < 6) {
 					error!("Unsupported FUSE ABI version {:u}.{:u}", arg.major, arg.minor);
-					self.reply_error(&se.ch, EPROTO);
+					self.reply_error(EPROTO);
 					return;
 				}
 				// Remember ABI version supported by kernel
 				se.proto_major = arg.major as uint;
 				se.proto_minor = arg.minor as uint;
 				// Call filesystem init method and give it a chance to return an error
-				let res = se.filesystem.init();
+				let res = se.filesystem.init(self);
 				if res.is_err() {
-					self.reply_error(&se.ch, res.unwrap_err());
+					self.reply_error(res.unwrap_err());
 					return;
 				}
 				// Reply with our desired version and settings. If the kernel supports a
@@ -102,251 +112,238 @@ impl Request {
 					unused: 0,
 					max_write: MAX_WRITE_SIZE,
 				};
-				debug!("INIT({:u}) response: ABI {:u}.{:u}, flags {:#x}, max readahead {:u}, max write {:u}", header.unique, reply.major, reply.minor, reply.flags, reply.max_readahead, reply.max_write);
+				debug!("INIT({:u}) response: ABI {:u}.{:u}, flags {:#x}, max readahead {:u}, max write {:u}", self.header.unique, reply.major, reply.minor, reply.flags, reply.max_readahead, reply.max_write);
 				se.initialized = true;
-				self.reply(&se.ch, Ok(reply));
+				self.reply().ok(&reply);
 			},
 			// Any operation is invalid before initialization
 			_ if !se.initialized => {
-				warn!("Ignoring FUSE operation {:u} before init", header.opcode);
-				self.reply_error(&se.ch, EIO);
+				warn!("Ignoring FUSE operation {:u} before init", self.header.opcode);
+				self.reply_error(EIO);
 			},
 			// Filesystem destroyed
 			FUSE_DESTROY => {
-				debug!("DESTROY({:u})", header.unique);
-				se.filesystem.destroy();
+				debug!("DESTROY({:u})", self.header.unique);
+				se.filesystem.destroy(self);
 				se.destroyed = true;
-				self.reply(&se.ch, Ok(()));
+				self.reply().ok(&());
 			}
 			// Any operation is invalid after destroy
 			_ if se.destroyed => {
-				warn!("Ignoring FUSE operation {:u} after destroy", header.opcode);
-				self.reply_error(&se.ch, EIO);
+				warn!("Ignoring FUSE operation {:u} after destroy", self.header.opcode);
+				self.reply_error(EIO);
 			}
 
 			FUSE_INTERRUPT => {
 				let arg: &fuse_interrupt_in = data.fetch();
-				debug!("INTERRUPT({:u}) unique {:u}", header.unique, arg.unique);
+				debug!("INTERRUPT({:u}) unique {:u}", self.header.unique, arg.unique);
 				// TODO: handle FUSE_INTERRUPT
-				self.reply_error(&se.ch, ENOSYS);
+				self.reply_error(ENOSYS);
 			},
 
 			FUSE_LOOKUP => {
 				let name = data.fetch_path();
-				debug!("LOOKUP({:u}) parent {:#018x}, name {}", header.unique, header.nodeid, name.display());
-				self.reply(&se.ch, se.filesystem.lookup(header.nodeid, &name));
+				debug!("LOOKUP({:u}) parent {:#018x}, name {}", self.header.unique, self.header.nodeid, name.display());
+				se.filesystem.lookup(self, self.header.nodeid, &name, self.reply());
 			},
 			FUSE_FORGET => {
 				let arg: &fuse_forget_in = data.fetch();
-				debug!("FORGET({:u}) ino {:#018x}, nlookup {:u}", header.unique, header.nodeid, arg.nlookup);
-				se.filesystem.forget(header.nodeid, arg.nlookup as uint);	// no reply
+				debug!("FORGET({:u}) ino {:#018x}, nlookup {:u}", self.header.unique, self.header.nodeid, arg.nlookup);
+				se.filesystem.forget(self, self.header.nodeid, arg.nlookup as uint);	// no reply
 			},
 			FUSE_GETATTR => {
-				debug!("GETATTR({:u}) ino {:#018x}", header.unique, header.nodeid);
-				self.reply(&se.ch, se.filesystem.getattr(header.nodeid));
+				debug!("GETATTR({:u}) ino {:#018x}", self.header.unique, self.header.nodeid);
+				se.filesystem.getattr(self, self.header.nodeid, self.reply());
 			},
 			FUSE_SETATTR => {
 				let arg: &fuse_setattr_in = data.fetch();
-				debug!("SETATTR({:u}) ino {:#018x}, valid {:#x}", header.unique, header.nodeid, arg.valid);
-				self.reply(&se.ch, se.filesystem.setattr(header.nodeid, arg));
+				debug!("SETATTR({:u}) ino {:#018x}, valid {:#x}", self.header.unique, self.header.nodeid, arg.valid);
+				se.filesystem.setattr(self, self.header.nodeid, arg, self.reply());
 			},
 			FUSE_READLINK => {
-				debug!("READLINK({:u}) ino {:#018x}", header.unique, header.nodeid);
-				self.reply(&se.ch, se.filesystem.readlink(header.nodeid));
+				debug!("READLINK({:u}) ino {:#018x}", self.header.unique, self.header.nodeid);
+				se.filesystem.readlink(self, self.header.nodeid, self.reply());
 			},
 			FUSE_MKNOD => {
 				let arg: &fuse_mknod_in = data.fetch();
 				let name = data.fetch_path();
-				debug!("MKNOD({:u}) parent {:#018x}, name {}, mode {:#05o}, rdev {:u}", header.unique, header.nodeid, name.display(), arg.mode, arg.rdev);
-				self.reply(&se.ch, se.filesystem.mknod(header.nodeid, &name, arg.mode, arg.rdev));
+				debug!("MKNOD({:u}) parent {:#018x}, name {}, mode {:#05o}, rdev {:u}", self.header.unique, self.header.nodeid, name.display(), arg.mode, arg.rdev);
+				se.filesystem.mknod(self, self.header.nodeid, &name, arg.mode, arg.rdev, self.reply());
 			},
 			FUSE_MKDIR => {
 				let arg: &fuse_mkdir_in = data.fetch();
 				let name = data.fetch_path();
-				debug!("MKDIR({:u}) parent {:#018x}, name {}, mode {:#05o}", header.unique, header.nodeid, name.display(), arg.mode);
-				self.reply(&se.ch, se.filesystem.mkdir(header.nodeid, &name, arg.mode));
+				debug!("MKDIR({:u}) parent {:#018x}, name {}, mode {:#05o}", self.header.unique, self.header.nodeid, name.display(), arg.mode);
+				se.filesystem.mkdir(self, self.header.nodeid, &name, arg.mode, self.reply());
 			},
 			FUSE_UNLINK => {
 				let name = data.fetch_path();
-				debug!("UNLINK({:u}) parent {:#018x}, name {}", header.unique, header.nodeid, name.display());
-				self.reply(&se.ch, se.filesystem.unlink(header.nodeid, &name));
+				debug!("UNLINK({:u}) parent {:#018x}, name {}", self.header.unique, self.header.nodeid, name.display());
+				se.filesystem.unlink(self, self.header.nodeid, &name, self.reply());
 			},
 			FUSE_RMDIR => {
 				let name = data.fetch_path();
-				debug!("RMDIR({:u}) parent {:#018x}, name {}", header.unique, header.nodeid, name.display());
-				self.reply(&se.ch, se.filesystem.rmdir(header.nodeid, &name));
+				debug!("RMDIR({:u}) parent {:#018x}, name {}", self.header.unique, self.header.nodeid, name.display());
+				se.filesystem.rmdir(self, self.header.nodeid, &name, self.reply());
 			},
 			FUSE_SYMLINK => {
 				let name = data.fetch_path();
 				let link = data.fetch_path();
-				debug!("SYMLINK({:u}) parent {:#018x}, name {}, link {}", header.unique, header.nodeid, name.display(), link.display());
-				self.reply(&se.ch, se.filesystem.symlink(header.nodeid, &name, &link));
+				debug!("SYMLINK({:u}) parent {:#018x}, name {}, link {}", self.header.unique, self.header.nodeid, name.display(), link.display());
+				se.filesystem.symlink(self, self.header.nodeid, &name, &link, self.reply());
 			},
 			FUSE_RENAME => {
 				let arg: &fuse_rename_in = data.fetch();
 				let name = data.fetch_path();
 				let newname = data.fetch_path();
-				debug!("RENAME({:u}) parent {:#018x}, name {}, newparent {:#018x}, newname {}", header.unique, header.nodeid, name.display(), arg.newdir, newname.display());
-				self.reply(&se.ch, se.filesystem.rename(header.nodeid, &name, arg.newdir, &newname));
+				debug!("RENAME({:u}) parent {:#018x}, name {}, newparent {:#018x}, newname {}", self.header.unique, self.header.nodeid, name.display(), arg.newdir, newname.display());
+				se.filesystem.rename(self, self.header.nodeid, &name, arg.newdir, &newname, self.reply());
 			},
 			FUSE_LINK => {
 				let arg: &fuse_link_in = data.fetch();
 				let newname = data.fetch_path();
-				debug!("LINK({:u}) ino {:#018x}, newparent {:#018x}, newname {}", header.unique, arg.oldnodeid, header.nodeid, newname.display());
-				self.reply(&se.ch, se.filesystem.link(arg.oldnodeid, header.nodeid, &newname));
+				debug!("LINK({:u}) ino {:#018x}, newparent {:#018x}, newname {}", self.header.unique, arg.oldnodeid, self.header.nodeid, newname.display());
+				se.filesystem.link(self, arg.oldnodeid, self.header.nodeid, &newname, self.reply());
 			},
 			FUSE_OPEN => {
 				let arg: &fuse_open_in = data.fetch();
-				debug!("OPEN({:u}) ino {:#018x}, flags {:#x}", header.unique, header.nodeid, arg.flags);
-				self.reply(&se.ch, se.filesystem.open(header.nodeid, arg.flags as uint));
+				debug!("OPEN({:u}) ino {:#018x}, flags {:#x}", self.header.unique, self.header.nodeid, arg.flags);
+				se.filesystem.open(self, self.header.nodeid, arg.flags as uint, self.reply());
 			},
 			FUSE_READ => {
 				let arg: &fuse_read_in = data.fetch();
-				debug!("READ({:u}) ino {:#018x}, fh {:u}, offset {:u}, size {:u}", header.unique, header.nodeid, arg.fh, arg.offset, arg.size);
-				self.reply(&se.ch, se.filesystem.read(header.nodeid, arg.fh, arg.offset, arg.size as uint));
+				debug!("READ({:u}) ino {:#018x}, fh {:u}, offset {:u}, size {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.offset, arg.size);
+				se.filesystem.read(self, self.header.nodeid, arg.fh, arg.offset, arg.size as uint, self.reply());
 			},
 			FUSE_WRITE => {
 				let arg: &fuse_write_in = data.fetch();
 				let data = data.fetch_data();
 				assert!(data.len() == arg.size as uint);
-				debug!("WRITE({:u}) ino {:#018x}, fh {:u}, offset {:u}, size {:u}, flags {:#x}", header.unique, header.nodeid, arg.fh, arg.offset, arg.size, arg.write_flags);
-				self.reply(&se.ch, se.filesystem.write(header.nodeid, arg.fh, arg.offset, data, arg.write_flags as uint).and_then(|written| {
-					Ok(fuse_write_out { size: written as u32, padding: 0 })
-				}));
+				debug!("WRITE({:u}) ino {:#018x}, fh {:u}, offset {:u}, size {:u}, flags {:#x}", self.header.unique, self.header.nodeid, arg.fh, arg.offset, arg.size, arg.write_flags);
+				se.filesystem.write(self, self.header.nodeid, arg.fh, arg.offset, data, arg.write_flags as uint, self.reply());
 			},
 			FUSE_FLUSH => {
 				let arg: &fuse_flush_in = data.fetch();
-				debug!("FLUSH({:u}) ino {:#018x}, fh {:u}, lock owner {:u}", header.unique, header.nodeid, arg.fh, arg.lock_owner);
-				self.reply(&se.ch, se.filesystem.flush(header.nodeid, arg.fh, arg.lock_owner));
+				debug!("FLUSH({:u}) ino {:#018x}, fh {:u}, lock owner {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.lock_owner);
+				se.filesystem.flush(self, self.header.nodeid, arg.fh, arg.lock_owner, self.reply());
 			},
 			FUSE_RELEASE => {
 				let arg: &fuse_release_in = data.fetch();
 				let flush = match arg.release_flags & FUSE_RELEASE_FLUSH { 0 => false, _ => true };
-				debug!("RELEASE({:u}) ino {:#018x}, fh {:u}, flags {:#x}, release flags {:#x}, lock owner {:u}", header.unique, header.nodeid, arg.fh, arg.flags, arg.release_flags, arg.lock_owner);
-				self.reply(&se.ch, se.filesystem.release(header.nodeid, arg.fh, arg.flags as uint, arg.lock_owner, flush));
+				debug!("RELEASE({:u}) ino {:#018x}, fh {:u}, flags {:#x}, release flags {:#x}, lock owner {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.flags, arg.release_flags, arg.lock_owner);
+				se.filesystem.release(self, self.header.nodeid, arg.fh, arg.flags as uint, arg.lock_owner, flush, self.reply());
 			},
 			FUSE_FSYNC => {
 				let arg: &fuse_fsync_in = data.fetch();
 				let datasync = match arg.fsync_flags & 1 { 0 => false, _ => true };
-				debug!("FSYNC({:u}) ino {:#018x}, fh {:u}, flags {:#x}", header.unique, header.nodeid, arg.fh, arg.fsync_flags);
-				self.reply(&se.ch, se.filesystem.fsync(header.nodeid, arg.fh, datasync));
+				debug!("FSYNC({:u}) ino {:#018x}, fh {:u}, flags {:#x}", self.header.unique, self.header.nodeid, arg.fh, arg.fsync_flags);
+				se.filesystem.fsync(self, self.header.nodeid, arg.fh, datasync, self.reply());
 			},
 			FUSE_OPENDIR => {
 				let arg: &fuse_open_in = data.fetch();
-				debug!("OPENDIR({:u}) ino {:#018x}, flags {:#x}", header.unique, header.nodeid, arg.flags);
-				self.reply(&se.ch, se.filesystem.opendir(header.nodeid, arg.flags as uint));
+				debug!("OPENDIR({:u}) ino {:#018x}, flags {:#x}", self.header.unique, self.header.nodeid, arg.flags);
+				se.filesystem.opendir(self, self.header.nodeid, arg.flags as uint, self.reply());
 			},
 			FUSE_READDIR => {
 				let arg: &fuse_read_in = data.fetch();
-				debug!("READDIR({:u}) ino {:#018x}, fh {:u}, offset {:u}, size {:u}", header.unique, header.nodeid, arg.fh, arg.offset, arg.size);
-				self.reply(&se.ch, se.filesystem.readdir(header.nodeid, arg.fh, arg.offset, DirBuffer::new(arg.size as uint)));
+				debug!("READDIR({:u}) ino {:#018x}, fh {:u}, offset {:u}, size {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.offset, arg.size);
+				se.filesystem.readdir(self, self.header.nodeid, arg.fh, arg.offset, DirBuffer::new(arg.size as uint), self.reply());
 			},
 			FUSE_RELEASEDIR => {
 				let arg: &fuse_release_in = data.fetch();
-				debug!("RELEASEDIR({:u}) ino {:#018x}, fh {:u}, flags {:#x}, release flags {:#x}, lock owner {:u}", header.unique, header.nodeid, arg.fh, arg.flags, arg.release_flags, arg.lock_owner);
-				self.reply(&se.ch, se.filesystem.releasedir(header.nodeid, arg.fh, arg.flags as uint));
+				debug!("RELEASEDIR({:u}) ino {:#018x}, fh {:u}, flags {:#x}, release flags {:#x}, lock owner {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.flags, arg.release_flags, arg.lock_owner);
+				se.filesystem.releasedir(self, self.header.nodeid, arg.fh, arg.flags as uint, self.reply());
 			},
 			FUSE_FSYNCDIR => {
 				let arg: &fuse_fsync_in = data.fetch();
 				let datasync = match arg.fsync_flags & 1 { 0 => false, _ => true };
-				debug!("FSYNCDIR({:u}) ino {:#018x}, fh {:u}, flags {:#x}", header.unique, header.nodeid, arg.fh, arg.fsync_flags);
-				self.reply(&se.ch, se.filesystem.fsyncdir(header.nodeid, arg.fh, datasync));
+				debug!("FSYNCDIR({:u}) ino {:#018x}, fh {:u}, flags {:#x}", self.header.unique, self.header.nodeid, arg.fh, arg.fsync_flags);
+				se.filesystem.fsyncdir(self, self.header.nodeid, arg.fh, datasync, self.reply());
 			},
 			FUSE_STATFS => {
-				debug!("STATFS({:u}) ino {:#018x}", header.unique, header.nodeid);
-				self.reply(&se.ch, se.filesystem.statfs(header.nodeid));
+				debug!("STATFS({:u}) ino {:#018x}", self.header.unique, self.header.nodeid);
+				se.filesystem.statfs(self, self.header.nodeid, self.reply());
 			},
 			FUSE_SETXATTR => {
 				let arg: &fuse_setxattr_in = data.fetch();
 				let name = data.fetch_str();
 				let value = data.fetch_data();
 				assert!(value.len() == arg.size as uint);
-				debug!("SETXATTR({:u}) ino {:#018x}, name {:s}, size {:u}, flags {:#x}", header.unique, header.nodeid, str::from_utf8_lossy(name), arg.size, arg.flags);
+				debug!("SETXATTR({:u}) ino {:#018x}, name {:s}, size {:u}, flags {:#x}", self.header.unique, self.header.nodeid, str::from_utf8_lossy(name), arg.size, arg.flags);
 				#[cfg(target_os = "macos")]
 				fn get_position(arg: &fuse_setxattr_in) -> u32 { arg.position }
 				#[cfg(not(target_os = "macos"))]
 				fn get_position(_arg: &fuse_setxattr_in) -> u32 { 0 }
-				self.reply(&se.ch, se.filesystem.setxattr(header.nodeid, name, value, arg.flags as uint, get_position(arg)));
+				se.filesystem.setxattr(self, self.header.nodeid, name, value, arg.flags as uint, get_position(arg), self.reply());
 			},
 			FUSE_GETXATTR => {
 				let arg: &fuse_getxattr_in = data.fetch();
 				let name = data.fetch_str();
-				debug!("GETXATTR({:u}) ino {:#018x}, name {:s}, size {:u}", header.unique, header.nodeid, str::from_utf8_lossy(name), arg.size);
-				match se.filesystem.getxattr(header.nodeid, name) {
-					// If arg.size is zero, the size of the value should be sent with fuse_getxattr_out
-					Ok(ref value) if arg.size == 0 => self.reply(&se.ch, Ok(fuse_getxattr_out { size: value.len() as u32, padding: 0 })),
-					// If arg.size is non-zero, send the value if it fits, or ERANGE otherwise
-					Ok(ref value) if value.len() > arg.size as uint => self.reply_error(&se.ch, ERANGE),
-					Ok(value) => self.reply(&se.ch, Ok(value)),
-					Err(err) => self.reply_error(&se.ch, err),
-				}
+				debug!("GETXATTR({:u}) ino {:#018x}, name {:s}, size {:u}", self.header.unique, self.header.nodeid, str::from_utf8_lossy(name), arg.size);
+				se.filesystem.getxattr(self, self.header.nodeid, name, self.reply());
 			},
 			FUSE_LISTXATTR => {
 				let arg: &fuse_getxattr_in = data.fetch();
-				debug!("LISTXATTR({:u}) ino {:#018x}, size {:u}", header.unique, header.nodeid, arg.size);
-				match se.filesystem.listxattr(header.nodeid) {
-					// TODO: If arg.size is zero, the size of the attribute list should be sent with fuse_getxattr_out
-					// TODO: If arg.size is non-zero, send the attribute list if it fits, or ERANGE otherwise
-					Ok(..) => self.reply_error(&se.ch, ENOSYS),
-					Err(err) => self.reply_error(&se.ch, err),
-				}
+				debug!("LISTXATTR({:u}) ino {:#018x}, size {:u}", self.header.unique, self.header.nodeid, arg.size);
+				se.filesystem.listxattr(self, self.header.nodeid, self.reply());
 			},
 			FUSE_REMOVEXATTR => {
 				let name = data.fetch_str();
-				debug!("REMOVEXATTR({:u}) ino {:#018x}, name {:s}", header.unique, header.nodeid, str::from_utf8_lossy(name));
-				self.reply(&se.ch, se.filesystem.removexattr(header.nodeid, name));
+				debug!("REMOVEXATTR({:u}) ino {:#018x}, name {:s}", self.header.unique, self.header.nodeid, str::from_utf8_lossy(name));
+				se.filesystem.removexattr(self, self.header.nodeid, name, self.reply());
 			},
 			FUSE_ACCESS => {
 				let arg: &fuse_access_in = data.fetch();
-				debug!("ACCESS({:u}) ino {:#018x}, mask {:#05o}", header.unique, header.nodeid, arg.mask);
-				self.reply(&se.ch, se.filesystem.access(header.nodeid, arg.mask as uint));
+				debug!("ACCESS({:u}) ino {:#018x}, mask {:#05o}", self.header.unique, self.header.nodeid, arg.mask);
+				se.filesystem.access(self, self.header.nodeid, arg.mask as uint, self.reply());
 			},
 			FUSE_CREATE => {
 				let arg: &fuse_open_in = data.fetch();
 				let name = data.fetch_path();
-				debug!("CREATE({:u}) parent {:#018x}, name {}, mode {:#05o}, flags {:#x}", header.unique, header.nodeid, name.display(), arg.mode, arg.flags);
-				self.reply(&se.ch, se.filesystem.create(header.nodeid, &name, arg.mode, arg.flags as uint));
+				debug!("CREATE({:u}) parent {:#018x}, name {}, mode {:#05o}, flags {:#x}", self.header.unique, self.header.nodeid, name.display(), arg.mode, arg.flags);
+				se.filesystem.create(self, self.header.nodeid, &name, arg.mode, arg.flags as uint, self.reply());
 			},
 			FUSE_GETLK => {
 				let arg: &fuse_lk_in = data.fetch();
-				debug!("GETLK({:u}) ino {:#018x}, fh {:u}, lock owner {:u}", header.unique, header.nodeid, arg.fh, arg.owner);
-				self.reply(&se.ch, se.filesystem.getlk(header.nodeid, arg.fh, arg.owner, &arg.lk));
+				debug!("GETLK({:u}) ino {:#018x}, fh {:u}, lock owner {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.owner);
+				se.filesystem.getlk(self, self.header.nodeid, arg.fh, arg.owner, &arg.lk, self.reply());
 			},
 			FUSE_SETLK | FUSE_SETLKW => {
 				let arg: &fuse_lk_in = data.fetch();
 				let sleep = match opcode { FUSE_SETLKW => true, _ => false };
-				debug!("SETLK({:u}) ino {:#018x}, fh {:u}, lock owner {:u}", header.unique, header.nodeid, arg.fh, arg.owner);
-				self.reply(&se.ch, se.filesystem.setlk(header.nodeid, arg.fh, arg.owner, &arg.lk, sleep));
+				debug!("SETLK({:u}) ino {:#018x}, fh {:u}, lock owner {:u}", self.header.unique, self.header.nodeid, arg.fh, arg.owner);
+				se.filesystem.setlk(self, self.header.nodeid, arg.fh, arg.owner, &arg.lk, sleep, self.reply());
 			},
 			FUSE_BMAP => {
 				let arg: &fuse_bmap_in = data.fetch();
-				debug!("BMAP({:u}) ino {:#018x}, blocksize {:u}, ids {:u}", header.unique, header.nodeid, arg.blocksize, arg.block);
-				self.reply(&se.ch, se.filesystem.bmap(header.nodeid, arg.blocksize as uint, arg.block));
+				debug!("BMAP({:u}) ino {:#018x}, blocksize {:u}, ids {:u}", self.header.unique, self.header.nodeid, arg.blocksize, arg.block);
+				se.filesystem.bmap(self, self.header.nodeid, arg.blocksize as uint, arg.block, self.reply());
 			},
 			// OS X only
-			FUSE_SETVOLNAME | FUSE_EXCHANGE | FUSE_GETXTIMES => self.dispatch_macos_only(opcode, se, header, &mut data),
+			FUSE_SETVOLNAME | FUSE_EXCHANGE | FUSE_GETXTIMES => self.dispatch_macos_only(opcode, se),
 		}
 	}
 
 	/// Handle OS X operation
 	#[cfg(target_os = "macos")]
-	fn dispatch_macos_only<FS: Filesystem> (&self, opcode: fuse_opcode, se: &mut Session<FS>, header: &fuse_in_header, data: &mut ArgumentIterator) {
+	fn dispatch_macos_only<FS: Filesystem> (&self, opcode: fuse_opcode, se: &mut Session<FS>) {
+		let mut data = ArgumentIterator::new(self.data);
 		match opcode {
 			FUSE_SETVOLNAME => {
 				let name = data.fetch_str();
-				debug!("SETVOLNAME({:u}) name {:s}", header.unique, str::from_utf8_lossy(name));
-				self.reply(&se.ch, se.filesystem.setvolname(name));
+				debug!("SETVOLNAME({:u}) name {:s}", self.header.unique, str::from_utf8_lossy(name));
+				se.filesystem.setvolname(self, name, self.reply());
 			},
 			FUSE_EXCHANGE => {
 				let arg: &fuse_exchange_in = data.fetch();
 				let oldname = data.fetch_path();
 				let newname = data.fetch_path();
-				debug!("EXCHANGE({:u}) parent {:#018x}, name {}, newparent {:#018x}, newname {}, options {:#x}", header.unique, arg.olddir, oldname.display(), arg.newdir, newname.display(), arg.options);
-				self.reply(&se.ch, se.filesystem.exchange(arg.olddir, &oldname, arg.newdir, &newname, arg.options as uint));
+				debug!("EXCHANGE({:u}) parent {:#018x}, name {}, newparent {:#018x}, newname {}, options {:#x}", self.header.unique, arg.olddir, oldname.display(), arg.newdir, newname.display(), arg.options);
+				se.filesystem.exchange(self, arg.olddir, &oldname, arg.newdir, &newname, arg.options as uint, self.reply());
 			},
 			FUSE_GETXTIMES => {
-				debug!("GETXTIMES({:u}) ino {:#018x}", header.unique, header.nodeid);
-				self.reply(&se.ch, se.filesystem.getxtimes(header.nodeid));
+				debug!("GETXTIMES({:u}) ino {:#018x}", self.header.unique, self.header.nodeid);
+				se.filesystem.getxtimes(self, self.header.nodeid, self.reply());
 			},
 			_ => unreachable!(),
 		}
@@ -354,37 +351,43 @@ impl Request {
 
 	/// Warn about unsupported OS X operation on other os
 	#[cfg(not(target_os = "macos"))]
-	fn dispatch_macos_only<FS: Filesystem> (&self, _opcode: fuse_opcode, se: &mut Session<FS>, header: &fuse_in_header, _data: &mut ArgumentIterator) {
-		warn!("Ignoring unsupported FUSE operation {:u}", header.opcode)
-		self.reply_error(&se.ch, ENOSYS);
+	fn dispatch_macos_only<FS: Filesystem> (&self, _opcode: fuse_opcode, se: &mut Session<FS>) {
+		warn!("Ignoring unsupported FUSE operation {:u}", self.header.opcode)
+		self.reply_error(ENOSYS);
 	}
 
-	/// Reply to a request with the given error code and data
-	fn send<T: Sendable> (&self, ch: &Channel, err: c_int, data: &T) {
-		let inheader: &fuse_in_header = ArgumentIterator::new(self.data.as_slice()).fetch();
-		data.as_bytegroups(|databytes| {
-			let datalen = databytes.iter().fold(0, |l, b| { l +  b.len()});
-			let outheader = fuse_out_header {
-				len: mem::size_of::<fuse_out_header>() as u32 + datalen as u32,
-				error: err,
-				unique: inheader.unique,
-			};
-			outheader.as_bytegroups(|headbytes| {
-				let _ = ch.send(headbytes + databytes);
-			});
-		});
+	/// Create a reply object for this request that can be passed to the filesystem
+	/// implementation and makes sure that a request is replied eventually
+	fn reply<T: Sendable> (&self) -> Reply<T> {
+		reply(self.ch, self.header.unique)
 	}
 
-	/// Reply to a request with the given data or error code
-	fn reply<T: Sendable> (&self, ch: &Channel, result: Result<T, c_int>) {
-		match result {
-			Ok(reply) => self.send(ch, 0, &reply),
-			Err(err) => self.send(ch, -err, &()),
-		}
+	/// Shortcut to immediately reply to a request with an error code
+	fn reply_error (&self, err: c_int) {
+		self.reply::<()>().error(err);
+	}
+
+	/// Returns the unique identifier of this request
+	#[inline] #[allow(dead_code)]
+	pub fn unique (&self) -> u64 {
+		self.header.unique
+	}
+
+	/// Returns the uid of this request
+	#[inline] #[allow(dead_code)]
+	pub fn uid (&self) -> u32 {
+		self.header.uid
+	}
+
+	/// Returns the gid of this request
+	#[inline] #[allow(dead_code)]
+	pub fn gid (&self) -> u32 {
+		self.header.gid
 	}
 
-	/// Reply to a request with the given error code
-	fn reply_error (&self, ch: &Channel, err: c_int) {
-		self.send(ch, -err, &());
+	/// Returns the pid of this request
+	#[inline] #[allow(dead_code)]
+	pub fn pid (&self) -> u32 {
+		self.header.pid
 	}
 }
diff --git a/src/session.rs b/src/session.rs
index 9f3bf66..d110754 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -6,13 +6,17 @@
 //! operations under its mount point.
 //!
 
-use std::libc::{EAGAIN, EINTR, ENODEV, ENOENT};
 use std::task;
+use std::libc::{EAGAIN, EINTR, ENODEV, ENOENT};
 use native;
 use channel;
 use channel::Channel;
 use Filesystem;
-use request::Request;
+use request::{MAX_WRITE_SIZE, request, dispatch};
+
+/// Size of the buffer for reading a request from the kernel. Since the kernel may send
+/// up to MAX_WRITE_SIZE bytes in a write request, we use that value plus some extra space.
+static BUFFER_SIZE: uint = MAX_WRITE_SIZE as uint + 4096;
 
 /// The session data structure
 pub struct Session<FS> {
@@ -21,8 +25,7 @@ pub struct Session<FS> {
 	/// Path of the mounted filesystem
 	mountpoint: Path,
 	/// Communication channel to the kernel driver
-	/// FIXME: should be private
-	ch: Channel,
+	priv ch: Channel,
 	/// FUSE protocol major version
 	proto_major: uint,
 	/// FUSE protocol minor version
@@ -56,15 +59,21 @@ impl<FS: Filesystem+Send> Session<FS> {
 	/// Make sure to run it on a new single threaded scheduler since the I/O in the
 	/// session loop can block.
 	pub fn run (&mut self) {
-		let mut req = Request::new();
+		let mut buffer = Vec::with_capacity(BUFFER_SIZE);
 		loop {
-			match req.read(self) {
-				Err(ENOENT) => continue,		// Operation interrupted. Accordingly to FUSE, this is safe to retry
-				Err(EINTR) => continue,			// Interrupted system call, retry
-				Err(EAGAIN) => continue,		// Explicitly try again
-				Err(ENODEV) => break,			// Filesystem was unmounted, quit the loop
+			// Read the next request from the given channel to kernel driver
+			// The kernel driver makes sure that we get exactly one request per read
+			assert!(buffer.capacity() >= BUFFER_SIZE);
+			match self.ch.receive(&mut buffer) {
+				Err(ENOENT) => continue,				// Operation interrupted. Accordingly to FUSE, this is safe to retry
+				Err(EINTR) => continue,					// Interrupted system call, retry
+				Err(EAGAIN) => continue,				// Explicitly try again
+				Err(ENODEV) => break,					// Filesystem was unmounted, quit the loop
 				Err(err) => fail!("Lost connection to FUSE device. Error {:i}", err),
-				Ok(..) => req.dispatch(self),
+				Ok(..) => match request(self.ch.sender(), buffer.as_slice()) {
+					None => break,						// Illegal request, quit the loop
+					Some(req) => dispatch(&req, self),
+				},
 			}
 		}
 	}


Test patch: