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

Issue:
rust-fuse uses removed scoped threads API
Scoped threads were removed from Rust, because they were deemed unsound.

This of course makes rust-fuse unable to compile on up to date versions of Rust.

We need to find a suitable replacement.

If nothing else, in the meantime we can use https://crates.io/crates/thread-scoped, which is the same thing that the standard library used to provide.



Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index a03e16d..550689b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,10 @@ os:
   - linux
   - osx
 language: rust
-rust: nightly
+rust:
+  - nightly
+  - beta
+  - stable
 env:
   global:
     - secure: atCJyliV0MQp8aNBrDc1iymeAxNJWFZPU0OmyhK94+/1FjU1pw7MU3cdSCN1dHY82/4bmKGsewRwCnpj5SPYPG2BbOSAuKobLvGO/mlagqFrdnJtmCmsGda0F2wZ3PNC30zfx0L+TsngB34URcgWGCfzPvyDS0JuHTIwl25YiX8=
diff --git a/Cargo.toml b/Cargo.toml
index 45adefb..e32a400 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,8 +14,10 @@ license = "MIT"
 pkg-config = "*"
 
 [dependencies]
+libc = "*"
 log = "*"
 time = "*"
+thread-scoped = "*"
 
 [lib]
 name = "fuse"
diff --git a/examples/hello.rs b/examples/hello.rs
index a8c9481..0d99aff 100644
--- a/examples/hello.rs
+++ b/examples/hello.rs
@@ -1,5 +1,3 @@
-#![feature(libc)]
-
 extern crate fuse;
 extern crate libc;
 extern crate time;
diff --git a/src/channel.rs b/src/channel.rs
index d08b14d..ec9e557 100644
--- a/src/channel.rs
+++ b/src/channel.rs
@@ -8,6 +8,7 @@ use std::os::unix::ffi::OsStrExt;
 use std::path::{PathBuf, Path};
 use libc::{c_char, c_int, c_void, size_t};
 use fuse::{fuse_args, fuse_mount_compat25};
+use reply::ReplySender;
 
 // Libc provides iovec based I/O using readv and writev functions
 #[allow(dead_code, non_camel_case_types)]
@@ -40,7 +41,7 @@ mod libc {
 }
 
 /// Wrapper around libc's realpath.  Returns the errno value if the real path cannot be obtained.
-/// FIXME: Use Rust's realpath method once available in std (see also https://github.com/mozilla/rust/issues/11857)
+/// FIXME: Use std::fs::canonicalize() once stable (rust-lang/rust#27706)
 fn real_path (path: &CStr) -> io::Result<CString> {
     let mut resolved: Vec<c_char> = Vec::with_capacity(libc::PATH_MAX);
     unsafe {
@@ -58,7 +59,7 @@ fn real_path (path: &CStr) -> io::Result<CString> {
 /// (which contains an argc count and an argv pointer)
 fn with_fuse_args<T, F: FnOnce(&fuse_args) -> T> (options: &[&OsStr], f: F) -> T {
     let mut args: Vec<CString> = vec![CString::new("rust-fuse").unwrap()];
-    args.extend(options.iter().map(|s| s.to_cstring().unwrap() ));
+    args.extend(options.iter().map(|s| CString::new(s.as_bytes()).unwrap() ));
     let argptrs: Vec<*const i8> = args.iter().map(|s| s.as_ptr()).collect();
     f(&fuse_args { argc: argptrs.len() as i32, argv: argptrs.as_ptr(), allocated: 0 })
 }
@@ -75,8 +76,7 @@ impl Channel {
     /// the given path to the channel. If the channel is dropped, the path is
     /// unmounted.
     pub fn new (mountpoint: &Path, options: &[&OsStr]) -> io::Result<Channel> {
-        let mnt = try!(mountpoint.as_os_str().to_cstring().ok_or(
-                io::Error::new(io::ErrorKind::InvalidInput, "invalid path")));
+        let mnt = try!(CString::new(mountpoint.as_os_str().as_bytes()));
         real_path(&mnt).and_then(|mnt| {
             with_fuse_args(options, |args| {
                 let fd = unsafe { fuse_mount_compat25(mnt.as_ptr(), args) };
@@ -149,6 +149,14 @@ impl ChannelSender {
     }
 }
 
+impl ReplySender for ChannelSender {
+    fn send(&self, data: &[&[u8]]) {
+        if let Err(err) = ChannelSender::send(self, data) {
+            error!("Failed to send FUSE reply: {}", err);
+        }
+    }
+}
+
 /// Unmount an arbitrary mount point
 pub fn unmount (mountpoint: &Path) -> io::Result<()> {
     // fuse_unmount_compat22 unfortunately doesn't return a status. Additionally,
@@ -177,8 +185,7 @@ pub fn unmount (mountpoint: &Path) -> io::Result<()> {
         }
     }
 
-    let mnt = try!(mountpoint.as_os_str().to_cstring().ok_or(
-            io::Error::new(io::ErrorKind::InvalidInput, "invalid path")));
+    let mnt = try!(CString::new(mountpoint.as_os_str().as_bytes()));
     let rc = libc_umount(&mnt);
     if rc < 0 {
         Err(io::Error::last_os_error())
diff --git a/src/lib.rs b/src/lib.rs
index 84fb754..5b2b31e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,18 +7,13 @@
 //! needed to establish a fd to talk to the kernel driver.
 //!
 
-#![feature(convert)]
-#![feature(fnbox)]
-#![feature(libc)]
-#![feature(scoped)]
-#![feature(vec_push_all)]
-
 #![warn(missing_docs, bad_style, unused, unused_extern_crates, unused_import_braces, unused_qualifications)]
 
 extern crate libc;
 #[macro_use]
 extern crate log;
 extern crate time;
+extern crate thread_scoped;
 
 use std::convert::AsRef;
 use std::io;
@@ -385,6 +380,6 @@ pub fn mount<FS: Filesystem, P: AsRef<Path>> (filesystem: FS, mountpoint: &P, op
 /// and therefore returns immediately. The returned handle should be stored
 /// to reference the mounted filesystem. If it's dropped, the filesystem will
 /// be unmounted.
-pub fn spawn_mount<'a, FS: Filesystem+Send+'a, P: AsRef<Path>> (filesystem: FS, mountpoint: &P, options: &[&OsStr]) -> io::Result<BackgroundSession<'a>> {
+pub unsafe fn spawn_mount<'a, FS: Filesystem+Send+'a, P: AsRef<Path>> (filesystem: FS, mountpoint: &P, options: &[&OsStr]) -> io::Result<BackgroundSession<'a>> {
     Session::new(filesystem, mountpoint.as_ref(), options).spawn()
 }
diff --git a/src/reply.rs b/src/reply.rs
index 0e3cb1c..46ff4d3 100644
--- a/src/reply.rs
+++ b/src/reply.rs
@@ -12,7 +12,6 @@ use std::{mem, ptr, slice};
 use std::convert::AsRef;
 use std::ffi::OsStr;
 use std::marker::PhantomData;
-use std::boxed::FnBox;
 use std::os::unix::ffi::OsStrExt;
 use libc::{c_int, S_IFIFO, S_IFCHR, S_IFBLK, S_IFDIR, S_IFREG, S_IFLNK};
 use libc::consts::os::posix88::EIO;
@@ -24,10 +23,16 @@ use fuse::fuse_getxtimes_out;
 use fuse::{fuse_out_header, fuse_dirent};
 use {FileType, FileAttr};
 
+/// Generic reply callback to send data
+pub trait ReplySender: Send + 'static {
+    /// Send data.
+    fn send(&self, data: &[&[u8]]);
+}
+
 /// Generic reply trait
 pub trait Reply {
     /// Create a new reply for the given request
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> Self;
+    fn new<S: ReplySender> (unique: u64, sender: S) -> Self;
 }
 
 /// Serialize an arbitrary type to bytes (memory copy, useful for fuse_*_out types)
@@ -110,15 +115,15 @@ pub struct ReplyRaw<T> {
     /// Unique id of the request to reply to
     unique: u64,
     /// Closure to call for sending the reply
-    sender: Option<Box<for<'a> FnBox(&'a [&'a [u8]]) + Send>>,
+    sender: Option<Box<ReplySender>>,
     /// Marker for being able to have T on this struct (which enforces
     /// reply types to send the correct type of data)
     marker: PhantomData<T>,
 }
 
 impl<T> Reply for ReplyRaw<T> {
-    fn new<F: FnBox(&[&[u8]])+Send+'static> (unique: u64, sender: F) -> ReplyRaw<T> {
-        let sender: Box<for<'a> FnBox(&'a [&'a [u8]]) + Send> = Box::new(sender);
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyRaw<T> {
+        let sender = Box::new(sender);
         ReplyRaw { unique: unique, sender: Some(sender), marker: PhantomData }
     }
 }
@@ -137,8 +142,8 @@ impl<T> ReplyRaw<T> {
         as_bytes(&header, |headerbytes| {
             let sender = self.sender.take().unwrap();
             let mut sendbytes = headerbytes.to_vec();
-            sendbytes.push_all(bytes);
-            sender.call_box((&sendbytes,));
+            sendbytes.extend(bytes);
+            sender.send(&sendbytes);
         });
     }
 
@@ -172,7 +177,7 @@ pub struct ReplyEmpty {
 }
 
 impl Reply for ReplyEmpty {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyEmpty {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyEmpty {
         ReplyEmpty { reply: Reply::new(unique, sender) }
     }
 }
@@ -197,7 +202,7 @@ pub struct ReplyData {
 }
 
 impl Reply for ReplyData {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyData {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyData {
         ReplyData { reply: Reply::new(unique, sender) }
     }
 }
@@ -222,7 +227,7 @@ pub struct ReplyEntry {
 }
 
 impl Reply for ReplyEntry {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyEntry {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyEntry {
         ReplyEntry { reply: Reply::new(unique, sender) }
     }
 }
@@ -255,7 +260,7 @@ pub struct ReplyAttr {
 }
 
 impl Reply for ReplyAttr {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyAttr {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyAttr {
         ReplyAttr { reply: Reply::new(unique, sender) }
     }
 }
@@ -287,7 +292,7 @@ pub struct ReplyXTimes {
 
 #[cfg(target_os = "macos")]
 impl Reply for ReplyXTimes {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyXTimes {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyXTimes {
         ReplyXTimes { reply: Reply::new(unique, sender) }
     }
 }
@@ -318,7 +323,7 @@ pub struct ReplyOpen {
 }
 
 impl Reply for ReplyOpen {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyOpen {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyOpen {
         ReplyOpen { reply: Reply::new(unique, sender) }
     }
 }
@@ -347,7 +352,7 @@ pub struct ReplyWrite {
 }
 
 impl Reply for ReplyWrite {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyWrite {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyWrite {
         ReplyWrite { reply: Reply::new(unique, sender) }
     }
 }
@@ -375,7 +380,7 @@ pub struct ReplyStatfs {
 }
 
 impl Reply for ReplyStatfs {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyStatfs {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyStatfs {
         ReplyStatfs { reply: Reply::new(unique, sender) }
     }
 }
@@ -413,7 +418,7 @@ pub struct ReplyCreate {
 }
 
 impl Reply for ReplyCreate {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyCreate {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyCreate {
         ReplyCreate { reply: Reply::new(unique, sender) }
     }
 }
@@ -450,7 +455,7 @@ pub struct ReplyLock {
 }
 
 impl Reply for ReplyLock {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyLock {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyLock {
         ReplyLock { reply: Reply::new(unique, sender) }
     }
 }
@@ -482,7 +487,7 @@ pub struct ReplyBmap {
 }
 
 impl Reply for ReplyBmap {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyBmap {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyBmap {
         ReplyBmap { reply: Reply::new(unique, sender) }
     }
 }
@@ -511,7 +516,7 @@ pub struct ReplyDirectory {
 }
 
 impl Reply for ReplyDirectory {
-    fn new<F: FnOnce(&[&[u8]])+Send> (unique: u64, sender: F) -> ReplyDirectory {
+    fn new<S: ReplySender> (unique: u64, sender: S) -> ReplyDirectory {
         ReplyDirectory { reply: Reply::new(unique, sender), size: 0, data: Vec::with_capacity(4096) }
     }
 }
@@ -565,7 +570,7 @@ impl ReplyDirectory {
 #[cfg(test)]
 mod test {
     use std::thread;
-    use std::sync::mpsc::channel;
+    use std::sync::mpsc::{channel, Sender};
     use time::Timespec;
     use super::as_bytes;
     use super::{Reply, ReplyRaw, ReplyEmpty, ReplyData, ReplyEntry, ReplyAttr, ReplyOpen};
@@ -609,54 +614,69 @@ mod test {
         });
     }
 
+
+    struct AssertSender {
+        expected: Vec<Vec<u8>>,
+    }
+
+    impl super::ReplySender for AssertSender {
+        fn send(&self, data: &[&[u8]]) {
+            assert_eq!(self.expected, data);
+        }
+    }
+
     #[test]
     fn reply_raw () {
         let data = Data { a: 0x12, b: 0x34, c: 0x5678 };
-        let reply: ReplyRaw<Data> = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x12, 0x34, 0x78, 0x56],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyRaw<Data> = Reply::new(0xdeadbeef, sender);
         reply.ok(&data);
     }
 
     #[test]
     fn reply_error () {
-        let reply: ReplyRaw<Data> = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x10, 0x00, 0x00, 0x00, 0xbe, 0xff, 0xff, 0xff,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyRaw<Data> = Reply::new(0xdeadbeef, sender);
         reply.error(66);
     }
 
     #[test]
     fn reply_empty () {
-        let reply: ReplyEmpty = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyEmpty = Reply::new(0xdeadbeef, sender);
         reply.ok();
     }
 
     #[test]
     fn reply_data () {
-        let reply: ReplyData = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0xde, 0xad, 0xbe, 0xef],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyData = Reply::new(0xdeadbeef, sender);
         reply.data(&[0xde, 0xad, 0xbe, 0xef]);
     }
 
     #[test]
     fn reply_entry () {
-        let reply: ReplyEntry = Reply::new(0xdeadbeef, |bytes| {
-            if cfg!(target_os = "macos") {
-                assert_eq!([
+        let sender = AssertSender {
+            expected: if cfg!(target_os = "macos") {
+                vec![
                     vec![0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                     vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -667,9 +687,9 @@ mod test {
                          0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,  0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,
                          0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,  0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
                          0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00],
-                ], bytes);
+                ]
             } else {
-                assert_eq!([
+                vec![
                     vec![0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                     vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -679,9 +699,10 @@ mod test {
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,
                          0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00,  0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
                          0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00],
-                ], bytes);
+                ]
             }
-        });
+        };
+        let reply: ReplyEntry = Reply::new(0xdeadbeef, sender);
         let time = Timespec::new(0x1234, 0x5678);
         let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time,
             kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 };
@@ -690,9 +711,9 @@ mod test {
 
     #[test]
     fn reply_attr () {
-        let reply: ReplyAttr = Reply::new(0xdeadbeef, |bytes| {
-            if cfg!(target_os = "macos") {
-                assert_eq!([
+        let sender = AssertSender {
+            expected: if cfg!(target_os = "macos") {
+                vec![
                     vec![0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                     vec![0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x78, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -701,9 +722,9 @@ mod test {
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,
                          0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,  0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,
                          0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,  0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00],
-                ], bytes);
+                ]
             } else {
-                assert_eq!([
+                vec![
                     vec![0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                     vec![0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x78, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -711,9 +732,10 @@ mod test {
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,  0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00,
                          0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,  0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00],
-                ], bytes);
+                ]
             }
-        });
+        };
+        let reply: ReplyAttr = Reply::new(0xdeadbeef, sender);
         let time = Timespec::new(0x1234, 0x5678);
         let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time,
             kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 };
@@ -723,59 +745,63 @@ mod test {
     #[test]
     #[cfg(target_os = "macos")]
     fn reply_xtimes () {
-        let reply: ReplyXTimes = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                      0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyXTimes = Reply::new(0xdeadbeef, sender);
         let time = Timespec::new(0x1234, 0x5678);
         reply.xtimes(time, time);
     }
 
     #[test]
     fn reply_open () {
-        let reply: ReplyOpen = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyOpen = Reply::new(0xdeadbeef, sender);
         reply.opened(0x1122, 0x33);
     }
 
     #[test]
     fn reply_write () {
-        let reply: ReplyWrite = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyWrite = Reply::new(0xdeadbeef, sender);
         reply.written(0x1122);
     }
 
     #[test]
     fn reply_statfs () {
-        let reply: ReplyStatfs = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                      0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                      0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
                      0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyStatfs = Reply::new(0xdeadbeef, sender);
         reply.statfs(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88);
     }
 
     #[test]
     fn reply_create () {
-        let reply: ReplyCreate = Reply::new(0xdeadbeef, |bytes| {
-            if cfg!(target_os = "macos") {
-                assert_eq!([
+        let sender = AssertSender {
+            expected: if cfg!(target_os = "macos") {
+                vec![
                     vec![0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                     vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -787,9 +813,9 @@ mod test {
                          0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,  0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
                          0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00,  0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-                ], bytes);
+                ]
             } else {
-                assert_eq!([
+                vec![
                     vec![0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                     vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -800,9 +826,10 @@ mod test {
                          0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00,  0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
                          0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,  0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-                ], bytes);
+                ]
             }
-        });
+        };
+        let reply: ReplyCreate = Reply::new(0xdeadbeef, sender);
         let time = Timespec::new(0x1234, 0x5678);
         let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time,
             kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 };
@@ -811,49 +838,56 @@ mod test {
 
     #[test]
     fn reply_lock () {
-        let reply: ReplyLock = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                     0x33, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+                  0x33, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00],
+            ]
+        };
+        let reply: ReplyLock = Reply::new(0xdeadbeef, sender);
         reply.locked(0x11, 0x22, 0x33, 0x44);
     }
 
     #[test]
     fn reply_bmap () {
-        let reply: ReplyBmap = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-            ], bytes);
-        });
+            ]
+        };
+        let reply: ReplyBmap = Reply::new(0xdeadbeef, sender);
         reply.bmap(0x1234);
     }
 
     #[test]
     fn reply_directory () {
-        let mut reply: ReplyDirectory = Reply::new(0xdeadbeef, |bytes| {
-            assert_eq!([
+        let sender = AssertSender {
+            expected: vec![
                 vec![0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00],
                 vec![0xbb, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                      0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,  0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 ,0x00, 0x00,
                      0xdd, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                      0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,  0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x72, 0x73],
-            ], bytes);
-        });
+            ]
+        };
+        let mut reply: ReplyDirectory = Reply::new(0xdeadbeef, sender);
         reply.add(0xaabb, 1, FileType::Directory, "hello");
         reply.add(0xccdd, 2, FileType::RegularFile, "world.rs");
         reply.ok();
     }
 
+    impl super::ReplySender for Sender<()> {
+        fn send(&self, _: &[&[u8]]) {
+            Sender::send(self, ()).unwrap()
+        }
+    }
+
     #[test]
     fn async_reply () {
         let (tx, rx) = channel::<()>();
-        let reply: ReplyEmpty = Reply::new(0xdeadbeef, move |_| {
-            tx.send(()).unwrap();
-        });
+        let reply: ReplyEmpty = Reply::new(0xdeadbeef, tx);
         thread::spawn(move || {
             reply.ok();
         });
diff --git a/src/request.rs b/src/request.rs
index 7874e50..e3e4cd5 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -363,13 +363,8 @@ impl<'a> Request<'a> {
     /// Create a reply object for this request that can be passed to the filesystem
     /// implementation and makes sure that a request is replied exactly once
     fn reply<T: Reply> (&self) -> T {
-        let ch = self.ch;
-        Reply::new(self.header.unique, move |buffer| {
-            match ch.send(buffer) {
-                Ok(()) => (),
-                Err(err) => error!("Failed to send FUSE reply: {}", err),
-            }
-        })
+        let reply: T = Reply::new(self.header.unique, self.ch);
+        reply
     }
 
     /// Returns the unique identifier of this request
diff --git a/src/session.rs b/src/session.rs
index 60f1159..b103ad2 100644
--- a/src/session.rs
+++ b/src/session.rs
@@ -9,7 +9,7 @@
 use std::io;
 use std::ffi::OsStr;
 use std::path::{PathBuf, Path};
-use std::thread::{Builder, JoinGuard};
+use thread_scoped::{scoped, JoinGuard};
 use libc::{EAGAIN, EINTR, ENODEV, ENOENT};
 use channel;
 use channel::Channel;
@@ -101,7 +101,7 @@ impl<FS: Filesystem> Session<FS> {
 
 impl<'a, FS: Filesystem+Send+'a> Session<FS> {
     /// Run the session loop in a background thread
-    pub fn spawn (self) -> io::Result<BackgroundSession<'a>> {
+    pub unsafe fn spawn (self) -> io::Result<BackgroundSession<'a>> {
         BackgroundSession::new(self)
     }
 }
@@ -124,13 +124,12 @@ impl<'a> BackgroundSession<'a> {
     /// Create a new background session for the given session by running its
     /// session loop in a background thread. If the returned handle is dropped,
     /// the filesystem is unmounted and the given session ends.
-    pub fn new<FS: Filesystem+Send+'a> (se: Session<FS>) -> io::Result<BackgroundSession<'a>> {
+    pub unsafe fn new<FS: Filesystem+Send+'a> (se: Session<FS>) -> io::Result<BackgroundSession<'a>> {
         let mountpoint = se.mountpoint().to_path_buf();
-        let builder = Builder::new().name(format!("FUSE {}", mountpoint.display()));
-        let guard = try!(builder.scoped(move || {
+        let guard = scoped(move || {
             let mut se = se;
             se.run();
-        }));
+        });
         Ok(BackgroundSession { mountpoint: mountpoint, guard: guard })
     }
 }


Test patch: