diff --git a/Cargo.lock b/Cargo.lock index d79e0a9..d590e76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,9 +171,9 @@ dependencies = [ [[package]] name = "daemonize" -version = "0.2.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0239832c1b4ca406d5ec73728cf4c7336d25cf85dd32db9e047e9e706ee0e935" +checksum = "ab8bfdaacb3c887a54d41bdf48d3af8873b3f5566469f8ba21b92057509f116e" dependencies = [ "libc", ] @@ -295,9 +295,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.78" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "log" diff --git a/Cargo.toml b/Cargo.toml index 26d9ce3..d5f3c72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" backtrace = "0.3" chan-signal = "0.2" clap = "2.29.0" -daemonize = "0.2" +daemonize = "0.5" env_logger = "0.4" fd = { git = "https://github.com/stemjail/fd-rs.git", rev = "3bc3e3587f8904cce8bf29163a2021c2f5906557" } fuse = "0.3.0" diff --git a/src/catfs/file.rs b/src/catfs/file.rs index 6e781eb..7a81742 100644 --- a/src/catfs/file.rs +++ b/src/catfs/file.rs @@ -168,7 +168,7 @@ impl Handle { if !valid && (flags & rlibc::O_TRUNC) == 0 { debug!("read ahead {:?}", path.as_ref()); handle.has_page_in_thread = true; - let mut h = handle.clone(); + let h = handle.clone(); let path = path.as_ref().to_path_buf(); tp.lock().unwrap().execute(move || { if let Err(e) = h.copy(true, disable_splice) { @@ -190,10 +190,6 @@ impl Handle { debug!("read ahead {:?} canceled", path); } } - // the files are always closed in the main IO path, consume - // the fds to prevent closing - h.src_file.into_raw(); - h.cache_file.into_raw(); }); } @@ -248,20 +244,19 @@ impl Handle { Err(e) => { return Err(RError::from(e)); } - Ok(mut cache) => { - let mut src = File::openat(src_dir, path, rlibc::O_RDONLY, 0)?; + Ok(cache) => { + let src = File::openat(src_dir, path, rlibc::O_RDONLY, 0)?; cache.set_xattr( "user.catfs.src_chksum", Handle::src_chksum(&src)?.as_slice(), )?; - src.close()?; - cache.close()?; } } return Ok(()); } + #[cfg(target_os = "linux")] pub fn set_pristine(&self, pristine: bool) -> error::Result<()> { if pristine { self.cache_file.set_xattr( @@ -272,7 +267,26 @@ impl Handle { } else { if let Err(e) = self.cache_file.remove_xattr("user.catfs.src_chksum") { let my_errno = e.raw_os_error().unwrap(); - if my_errno != libc::ENODATA && my_errno != libc::ENOATTR { + if my_errno != libc::ENODATA { + return Err(RError::from(e)); + } + } + } + return Ok(()); + } + + #[cfg(not(target_os = "linux"))] + pub fn set_pristine(&self, pristine: bool) -> error::Result<()> { + if pristine { + self.cache_file.set_xattr( + "user.catfs.src_chksum", + Handle::src_chksum(&self.src_file)? + .as_slice(), + )?; + } else { + if let Err(e) = self.cache_file.remove_xattr("user.catfs.src_chksum") { + let my_errno = e.raw_os_error().unwrap(); + if my_errno != libc::ENODATA { return Err(RError::from(e)); } } @@ -308,9 +322,9 @@ impl Handle { check_only: bool, ) -> error::Result { match File::openat(src_dir, path, rlibc::O_RDONLY, 0) { - Ok(mut src_file) => { + Ok(src_file) => { match File::openat(cache_dir, path, rlibc::O_RDONLY, 0) { - Ok(mut cache_file) => { + Ok(cache_file) => { let valid: bool; if cache_valid_if_present || Handle::is_pristine(&src_file, &cache_file)? { valid = true; @@ -321,12 +335,9 @@ impl Handle { rlibc::unlinkat(cache_dir, path, 0)?; } } - src_file.close()?; - cache_file.close()?; return Ok(valid); } Err(e) => { - src_file.close()?; if error::try_enoent(e)? { return Ok(false); } @@ -476,7 +487,7 @@ impl Handle { if let Err(e) = self.src_file.flush() { error!("!flush(src) = {}", e); // flush failed, now the fd is invalid, get rid of it - self.src_file.into_raw(); + std::mem::drop(&self.src_file); // we couldn't flush the src_file, because of some // linux vfs oddity the file would appear to be @@ -569,7 +580,7 @@ impl Handle { path: &dyn AsRef, create: bool, ) -> error::Result<()> { - let _ = self.page_in_res.0.lock().unwrap(); + let _unused = self.page_in_res.0.lock().unwrap(); let mut buf = [0u8; 0]; let mut flags = rlibc::O_RDWR; @@ -589,13 +600,6 @@ impl Handle { flags |= rlibc::O_CREAT; } - if let Err(e) = self.src_file.close() { - // normal for this close to fail - if e.raw_os_error().unwrap() != libc::ENOTSUP { - return Err(RError::from(e)); - } - } - self.src_file = File::openat(dir, path, flags, mode)?; return Ok(()); } @@ -603,18 +607,23 @@ impl Handle { fn copy_user(&self, rh: &File, wh: &File) -> error::Result { let mut buf = [0u8; 32 * 1024]; let mut offset = 0; - loop { - let nread = rh.read_at(&mut buf, offset)?; - if nread == 0 { - break; - } - wh.write_at(&buf[..nread], offset)?; - offset += nread as i64; + match wh.write_lock() { + Ok(write_fd) => { + loop { + let nread = rh.read_at(&mut buf, offset)?; + if nread == 0 { + break; + } + wh.write_at_nolock(&buf[..nread], offset, *write_fd)?; + offset += nread as i64; - self.notify_offset(Ok(offset), false)?; + self.notify_offset(Ok(offset), false)?; + } + return Ok(offset); + }, + Err(_) => return Err(RError::from(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!"))), } - return Ok(offset); } #[cfg(not(target_os = "macos"))] @@ -622,23 +631,33 @@ impl Handle { let (pin, pout) = rlibc::pipe()?; let pin = fd::FileDesc::new(pin, /*close_on_drop=*/ true); let pout = fd::FileDesc::new(pout, /*close_on_drop=*/ true); - let mut offset = 0; - loop { - let nread = rlibc::splice(rh.as_raw_fd(), offset, pout.as_raw_fd(), -1, 128 * 1024)?; - if nread == 0 { - break; - } - let mut written = 0; - while written < nread { - let nxfer = rlibc::splice(pin.as_raw_fd(), -1, wh.as_raw_fd(), offset, 128 * 1024)?; + match wh.write_lock() { + Ok( write_fd ) => { + match rh.read_lock() { + Ok(read_fd) => { + loop { + let nread = rlibc::splice(*read_fd, offset, pout.as_raw_fd(), -1, 128 * 1024)?; + if nread == 0 { + break; + } - written += nxfer; - offset += nxfer as i64; + let mut written = 0; + while written < nread { + let nxfer = rlibc::splice(pin.as_raw_fd(), -1, *write_fd, offset, 128 * 1024)?; - self.notify_offset(Ok(offset), false)?; - } + written += nxfer; + offset += nxfer as i64; + + self.notify_offset(Ok(offset), false)?; + } + } + }, + Err(_) => return Err(RError::from(io::Error::new(io::ErrorKind::Other, "couldn't get read lock!"))), + } + }, + Err(_) => return Err(RError::from(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!"))), } if let Err(e) = rlibc::close(pin.into_raw_fd()) { @@ -696,25 +715,14 @@ impl Handle { impl Drop for Handle { fn drop(&mut self) { - if self.cache_file.valid() { - if let Err(e) = self.cache_file.close() { - error!("!close(cache) = {}", RError::from(e)); - } - } - - if self.src_file.valid() { - if let Err(e) = self.src_file.close() { - error!("!close(src) = {}", RError::from(e)); - } - } } } impl Clone for Handle { fn clone(&self) -> Self { return Handle { - src_file: File::with_fd(self.src_file.as_raw_fd()), - cache_file: File::with_fd(self.cache_file.as_raw_fd()), + src_file: self.src_file.clone(), + cache_file: self.cache_file.clone(), dirty: self.dirty, write_through_failed: self.write_through_failed, has_page_in_thread: false, diff --git a/src/catfs/flags.rs b/src/catfs/flags.rs index 8d739d1..8d073e1 100644 --- a/src/catfs/flags.rs +++ b/src/catfs/flags.rs @@ -74,6 +74,8 @@ pub struct FlagStorage { pub free_space: DiskSpace, pub uid: libc::uid_t, pub gid: libc::gid_t, + pub catfs_threadpool_size: usize, + pub pcatfs_threadpool_size: usize } #[cfg(test)] diff --git a/src/catfs/inode.rs b/src/catfs/inode.rs index 705ab3c..7b8ad78 100644 --- a/src/catfs/inode.rs +++ b/src/catfs/inode.rs @@ -255,14 +255,13 @@ impl Inode { } pub fn truncate(&mut self, size: u64) -> error::Result<()> { - let mut f = File::openat(self.src_dir, &self.path, rlibc::O_WRONLY, 0)?; + let f = File::openat(self.src_dir, &self.path, rlibc::O_WRONLY, 0)?; f.set_size(size)?; - f.close()?; + std::mem::drop(&f); match File::openat(self.cache_dir, &self.path, rlibc::O_WRONLY, 0) { - Ok(mut f) => { + Ok(f) => { f.set_size(size)?; - f.close()?; } Err(e) => { error::try_enoent(e)?; diff --git a/src/catfs/mod.rs b/src/catfs/mod.rs index fbd7145..f536fe1 100644 --- a/src/catfs/mod.rs +++ b/src/catfs/mod.rs @@ -110,7 +110,7 @@ pub fn make_self(s: &mut T) -> &'static T { } impl CatFS { - pub fn new(from: &dyn AsRef, to: &dyn AsRef) -> error::Result { + pub fn new(from: &dyn AsRef, to: &dyn AsRef, n_threads : usize) -> error::Result { let src_dir = rlibc::open(from, rlibc::O_RDONLY, 0)?; let cache_dir = rlibc::open(to, rlibc::O_RDONLY, 0)?; @@ -123,7 +123,7 @@ impl CatFS { store: Mutex::new(Default::default()), dh_store: Mutex::new(Default::default()), fh_store: Mutex::new(Default::default()), - tp: Mutex::new(ThreadPool::new(5)), + tp: Mutex::new(ThreadPool::new(n_threads)), }; catfs.make_root()?; diff --git a/src/catfs/rlibc.rs b/src/catfs/rlibc.rs index 984ce43..881cb7b 100644 --- a/src/catfs/rlibc.rs +++ b/src/catfs/rlibc.rs @@ -14,11 +14,13 @@ use std::ptr; use std::os::unix::io::AsRawFd; use std::os::unix::io::RawFd; use std::os::unix::fs::FileExt; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, LockResult}; use self::fuse::FileType; use self::time::Timespec; use self::xattr::FileExt as XattrFileExt; + #[cfg(not(target_os = "macos"))] use self::libc::{fstat64, fstatvfs64, ftruncate64, open64, openat64, pread64, pwrite64, stat64, statvfs64}; #[cfg(target_os = "macos")] @@ -48,6 +50,7 @@ pub fn to_cstring(path: &dyn AsRef) -> CString { return CString::new(bytes).unwrap(); } + macro_rules! libc_wrap { ($( pub fn $name:ident($($arg:ident : $argtype:ty),*) $body:block )*) => ( $( @@ -400,7 +403,7 @@ pub fn fchmodat(dir: RawFd, path: &dyn AsRef, mode: libc::mode_t, flags: u } pub struct File { - fd: libc::c_int, + fd: Arc>, } fn as_void_ptr(s: &[T]) -> *const libc::c_void { @@ -431,7 +434,15 @@ impl File { mode, fd ); - return Ok(File { fd: fd }); + return Ok(File { fd: Arc::new( RwLock::new( fd ) ) }); + } + + pub fn write_lock(&self) -> LockResult> { + return Ok(self.fd.write()?); + } + + pub fn read_lock(&self) -> LockResult> { + return Ok(self.fd.read()?); } #[allow(dead_code)] @@ -444,42 +455,65 @@ impl File { mode, fd ); - return Ok(File { fd: fd }); + return Ok(File { fd: Arc::new(RwLock::new(fd)) }); } - pub fn with_fd(fd: libc::c_int) -> File { - return File { fd: fd }; + pub fn clone(&self) -> File { + return File { fd: self.fd.clone() }; } pub fn valid(&self) -> bool { - return self.fd != -1; + match self.fd.read() { + Ok(fd) => *fd != -1, + Err(_) => false + } } pub fn filesize(&self) -> io::Result { - let st = fstat(self.fd)?; - return Ok(st.st_size as u64); + match self.fd.read() { + Ok(fd) => { + let st = fstat(*fd)?; + return Ok(st.st_size as u64); + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get read lock!")), + } } pub fn stat(&self) -> io::Result { - fstat(self.fd) + match self.fd.read() { + Ok(fd) => { + fstat(*fd) + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get read lock!")), + } } pub fn truncate(&self, size: u64) -> io::Result<()> { - let res = unsafe { ftruncate64(self.fd, size as i64) }; - if res < 0 { - return Err(io::Error::last_os_error()); - } else { - return Ok(()); + match self.fd.write() { + Ok(fd) => { + let res = unsafe { ftruncate64(*fd, size as i64) }; + if res < 0 { + return Err(io::Error::last_os_error()); + } else { + return Ok(()); + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!")), } } #[cfg(not(target_os = "macos"))] pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { - let res = unsafe { libc::posix_fallocate64(self.fd, offset as i64, len as i64) }; - if res == 0 { - return Ok(()); - } else { - return Err(io::Error::from_raw_os_error(res)); + match self.fd.write() { + Ok(fd) => { + let res = unsafe { libc::posix_fallocate64(*fd, offset as i64, len as i64) }; + if res == 0 { + return Ok(()); + } else { + return Err(io::Error::from_raw_os_error(res)); + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!")), } } @@ -504,26 +538,36 @@ impl File { } pub fn chmod(&self, mode: libc::mode_t) -> io::Result<()> { - let res = unsafe { libc::fchmod(self.fd, mode) }; - if res == 0 { - return Ok(()); - } else { - return Err(io::Error::from_raw_os_error(res)); + match self.fd.write() { + Ok(fd) => { + let res = unsafe { libc::fchmod(*fd, mode) }; + if res == 0 { + return Ok(()); + } else { + return Err(io::Error::from_raw_os_error(res)); + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!")), } } pub fn read_at(&self, buf: &mut [u8], offset: i64) -> io::Result { - let nbytes = - unsafe { pread64(self.fd, as_mut_void_ptr(buf), buf.len(), offset) }; - if nbytes < 0 { - return Err(io::Error::last_os_error()); - } else { - return Ok(nbytes as usize); + match self.fd.read() { + Ok(fd) => { + let nbytes = + unsafe { pread64(*fd, as_mut_void_ptr(buf), buf.len(), offset) }; + if nbytes < 0 { + return Err(io::Error::last_os_error()); + } else { + return Ok(nbytes as usize); + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get read lock!")), } } - pub fn write_at(&self, buf: &[u8], offset: i64) -> io::Result { - let nbytes = unsafe { pwrite64(self.fd, as_void_ptr(buf), buf.len(), offset) }; + pub fn write_at_nolock(&self, buf: &[u8], offset: i64, fd : i32 ) -> io::Result { + let nbytes = unsafe { pwrite64(fd, as_void_ptr(buf), buf.len(), offset) }; if nbytes < 0 { return Err(io::Error::last_os_error()); } else { @@ -531,38 +575,47 @@ impl File { } } - pub fn flush(&self) -> io::Result<()> { - debug!("flush {}", self.fd); - // trigger a flush for the underly fd, this could be called - // multiple times, for ex: - // - // int fd2 = dup(fd); close(fd2); close(fd) - // - // so the fd needs to stay valid. Note that this means when an - // application sends close(), kernel will send us - // flush()/release(), and we will send close()/close(), which - // will be translated to flush()/flush()/release() to the - // underlining filesystem - let fd = unsafe { libc::dup(self.fd) }; - if fd < 0 { - return Err(io::Error::last_os_error()); - } else { - let res = unsafe { libc::close(fd) }; - if res < 0 { - return Err(io::Error::last_os_error()); - } else { - return Ok(()); - } + pub fn write_at(&self, buf: &[u8], offset: i64) -> io::Result { + match self.fd.write() { + Ok(fd) => { + let nbytes = unsafe { pwrite64(*fd, as_void_ptr(buf), buf.len(), offset) }; + if nbytes < 0 { + return Err(io::Error::last_os_error()); + } else { + return Ok(nbytes as usize); + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!")), } } - pub fn close(&mut self) -> io::Result<()> { - let res = unsafe { libc::close(self.fd) }; - self.fd = -1; - if res < 0 { - return Err(io::Error::last_os_error()); - } else { - return Ok(()); + pub fn flush(&self) -> io::Result<()> { + match self.fd.write() { + Ok(wfd) => { + debug!("flush {}", *wfd); + // trigger a flush for the underly fd, this could be called + // multiple times, for ex: + // + // int fd2 = dup(fd); close(fd2); close(fd) + // + // so the fd needs to stay valid. Note that this means when an + // application sends close(), kernel will send us + // flush()/release(), and we will send close()/close(), which + // will be translated to flush()/flush()/release() to the + // underlining filesystem + let fd = unsafe { libc::dup(*wfd) }; + if fd < 0 { + return Err(io::Error::last_os_error()); + } else { + let res = unsafe { libc::close(fd) }; + if res < 0 { + return Err(io::Error::last_os_error()); + } else { + return Ok(()); + } + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "couldn't get write lock!")), } } @@ -570,33 +623,35 @@ impl File { if !self.valid() { error!("as_raw_fd called on invalid fd"); } - - return self.fd; - } - - pub fn into_raw(&mut self) -> RawFd { - let fd = self.fd; - self.fd = -1; - fd + match self.fd.read() { + Ok(fd) => { + return *fd; + }, + Err(_) => return -1, + } } } impl Default for File { fn default() -> File { - File { fd: -1 } + File { fd: Arc::new( RwLock::new(-1)) } } } impl Drop for File { fn drop(&mut self) { - if self.fd != -1 { - error!( - "{} dropped but not closed: {}", - self.fd, - RError::from(io::Error::from_raw_os_error(libc::EIO)) - ); - if let Err(e) = self.close() { - error!("!close({}) = {}", self.fd, RError::from(e)); + if Arc::strong_count(&self.fd) == 1 { + match self.fd.write() { + Ok(fd) => { + if *fd >= 0 { + let res = unsafe { libc::close(*fd) }; + debug!( "<-- closed {} result: {}", *fd, res); + if res < 0 { + error!("!close({}) = {}", *fd, res); + } + } + }, + Err(e) => error!("!close() = {}", e.to_string()), } } } diff --git a/src/flags.rs b/src/flags.rs index cc8ac00..eafe4aa 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -86,6 +86,11 @@ pub fn parse_options<'a, 'b>(mut app: clap::App<'a, 'a>, flags: &'b mut [Flag<'a *v = String::from(s); continue; } + if let Some(v) = f.value.downcast_mut::() { + let s = matches.value_of(name).unwrap(); + *v = s.parse().unwrap(); + continue; + } if let Some(v) = f.value.downcast_mut::() { let s = matches.value_of_os(name).unwrap(); *v = s.to_os_string(); diff --git a/src/main.rs b/src/main.rs index 1276016..54ef907 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,6 +101,8 @@ fn main_internal() -> error::Result<()> { flags.mount_options.push( OsString::from("default_permissions"), ); + flags.catfs_threadpool_size = 5; + flags.pcatfs_threadpool_size = 100; let app = App::new("catfs") .about("Cache Anything FileSystem") @@ -193,6 +195,20 @@ fn main_internal() -> error::Result<()> { .validator(path_validator), value: &mut flags.mount_point, }, + flags::Flag{ + arg: Arg::with_name("catfs-threadpool-size") + .short("t") + .takes_value(true) + .help("Catfs thread pool size"), + value: &mut flags.catfs_threadpool_size, + }, + flags::Flag{ + arg: Arg::with_name("pcatfs-threadpool-size") + .short("p") + .takes_value(true) + .help("PCatfs thread pool size"), + value: &mut flags.pcatfs_threadpool_size, + }, ]; @@ -233,14 +249,14 @@ fn main_internal() -> error::Result<()> { let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); let path_from = Path::new(&flags.cat_from).canonicalize()?; let path_to = Path::new(&flags.cat_to).canonicalize()?; - let fs = catfs::CatFS::new(&path_from, &path_to)?; - let fs = pcatfs::PCatFS::new(fs); + let fs = catfs::CatFS::new(&path_from, &path_to, flags.catfs_threadpool_size)?; + let fs = pcatfs::PCatFS::new(fs, flags.pcatfs_threadpool_size); let cache_dir = fs.get_cache_dir()?; let mut options: Vec<&OsStr> = Vec::new(); for i in 0..flags.mount_options.len() { options.push(&flags.mount_options[i]); } - + debug!("threadpool options are catfs:{:?} pcatfs:{:?}", flags.catfs_threadpool_size, flags.pcatfs_threadpool_size); debug!("options are {:?}", flags.mount_options); { diff --git a/src/pcatfs/mod.rs b/src/pcatfs/mod.rs index a6b8433..b4f2da8 100644 --- a/src/pcatfs/mod.rs +++ b/src/pcatfs/mod.rs @@ -28,9 +28,9 @@ pub fn make_self(s: &mut T) -> &'static mut T { } impl PCatFS { - pub fn new(fs: CatFS) -> PCatFS { + pub fn new(fs: CatFS, n_threads : usize) -> PCatFS { PCatFS { - tp: ThreadPool::new(100), + tp: ThreadPool::new(n_threads), fs: fs, } } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 34ff05c..a8d4a61 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -67,8 +67,8 @@ impl<'a> CatFSTests<'a> { } fn mount(&self) -> error::Result<(fuse::BackgroundSession<'a>, Evicter)> { - let fs = CatFS::new(&self.src, &self.cache)?; - let fs = PCatFS::new(fs); + let fs = CatFS::new(&self.src, &self.cache, 5)?; + let fs = PCatFS::new(fs, 100); let cache_dir = fs.get_cache_dir()?; // essentially no-op, but ensures that it starts and terminates @@ -374,10 +374,9 @@ unit_tests!{ let foo = f.src.join("file1"); xattr::set(&foo, "user.catfs.random", b"hello").unwrap(); rlibc::utimes(&foo, 0, 100000000).unwrap(); - let mut fh = rlibc::File::open(&foo, rlibc::O_RDONLY, 0).unwrap(); + let fh = rlibc::File::open(&foo, rlibc::O_RDONLY, 0).unwrap(); let s = file::Handle::src_str_to_checksum(&fh).unwrap(); assert_eq!(s, OsStr::new("100000000\n6\n")); - fh.close().unwrap(); } fn check_dirty(f: &CatFSTests) {