|
@@ -8,7 +8,7 @@ mod private {
|
|
|
Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions,
|
|
|
},
|
|
|
};
|
|
|
- use log::{error, debug};
|
|
|
+ use log::{debug, error};
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
use std::{
|
|
|
collections::hash_map::{self, HashMap},
|
|
@@ -24,8 +24,8 @@ mod private {
|
|
|
};
|
|
|
|
|
|
use crate::{
|
|
|
- crypto::Creds,
|
|
|
- Block, BlockOpenOptions, BlockPath, BoxInIoErr, DirEntry, Directory, Error, Result, Epoch,
|
|
|
+ crypto::Creds, Block, BlockOpenOptions, BlockPath, BoxInIoErr, DirEntry, Directory, Epoch,
|
|
|
+ Error, Result,
|
|
|
};
|
|
|
|
|
|
type Inode = u64;
|
|
@@ -48,8 +48,8 @@ mod private {
|
|
|
}
|
|
|
|
|
|
impl FileType {
|
|
|
- const S_IFDIR: u32 = 0o040000;
|
|
|
- const S_IFREG: u32 = 0o100000;
|
|
|
+ const S_IFDIR: u32 = libc::S_IFDIR;
|
|
|
+ const S_IFREG: u32 = libc::S_IFREG;
|
|
|
|
|
|
/// Returns the underlying mode bits for this file type.
|
|
|
fn value(self) -> u32 {
|
|
@@ -85,6 +85,25 @@ mod private {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ trait SeekFromExt {
|
|
|
+ /// Converts the c-style `(whence, offset)` pair into a [SeekFrom] enum value.
|
|
|
+ /// See the POSIX man page for `lseek` for more details.
|
|
|
+ fn whence_offset(whence: u32, offset: u64) -> io::Result<SeekFrom> {
|
|
|
+ let whence = whence as i32;
|
|
|
+ match whence {
|
|
|
+ libc::SEEK_SET => Ok(SeekFrom::Start(offset)),
|
|
|
+ libc::SEEK_CUR => Ok(SeekFrom::Current(offset as i64)),
|
|
|
+ libc::SEEK_END => Ok(SeekFrom::End(-(offset as i64))),
|
|
|
+ _ => Err(io::Error::new(
|
|
|
+ io::ErrorKind::InvalidInput,
|
|
|
+ "`whence` was not one of `libc::{SEEK_SET, SEEK_CUR, SEEK_END}`",
|
|
|
+ )),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ impl SeekFromExt for SeekFrom {}
|
|
|
+
|
|
|
struct InodeTableValue {
|
|
|
block: Option<Box<dyn Block>>,
|
|
|
/// The path to the block. This is cloned from the block's metadata when the entry is
|
|
@@ -393,7 +412,7 @@ mod private {
|
|
|
.map_err(|err| Error::custom(err.to_string()))?;
|
|
|
let entry = inodes
|
|
|
.get_mut(&inode)
|
|
|
- .ok_or(Error::custom("inode table entry was removed unexpectedly"))?;
|
|
|
+ .ok_or_else(|| Error::custom("inode table entry was removed unexpectedly"))?;
|
|
|
entry.block = None;
|
|
|
}
|
|
|
Ok(())
|
|
@@ -502,7 +521,7 @@ mod private {
|
|
|
ctx: &Context,
|
|
|
inode: Self::Inode,
|
|
|
flags: u32,
|
|
|
- handle: Self::Handle,
|
|
|
+ _handle: Self::Handle,
|
|
|
flush: bool,
|
|
|
flock_release: bool,
|
|
|
lock_owner: Option<u64>,
|
|
@@ -626,8 +645,7 @@ mod private {
|
|
|
if written > 0 {
|
|
|
error!("error while reading: {err}");
|
|
|
return Ok(written);
|
|
|
- }
|
|
|
- else {
|
|
|
+ } else {
|
|
|
return Err(err);
|
|
|
}
|
|
|
}
|
|
@@ -641,8 +659,7 @@ mod private {
|
|
|
if written > 0 {
|
|
|
error!("error while writing: {err}");
|
|
|
return Ok(written);
|
|
|
- }
|
|
|
- else {
|
|
|
+ } else {
|
|
|
return Err(err);
|
|
|
}
|
|
|
}
|
|
@@ -686,8 +703,7 @@ mod private {
|
|
|
if read > 0 {
|
|
|
error!("error while reading from block: {err}");
|
|
|
return Ok(read);
|
|
|
- }
|
|
|
- else {
|
|
|
+ } else {
|
|
|
return Err(err);
|
|
|
}
|
|
|
}
|
|
@@ -701,8 +717,7 @@ mod private {
|
|
|
if read > 0 {
|
|
|
error!("error while writing: {err}");
|
|
|
return Ok(read);
|
|
|
- }
|
|
|
- else {
|
|
|
+ } else {
|
|
|
return Err(err);
|
|
|
}
|
|
|
}
|
|
@@ -771,6 +786,19 @@ mod private {
|
|
|
debug!("Blocktree::forget called for inode {inode}");
|
|
|
}
|
|
|
|
|
|
+ fn lseek(
|
|
|
+ &self,
|
|
|
+ ctx: &Context,
|
|
|
+ inode: Self::Inode,
|
|
|
+ _handle: Self::Handle,
|
|
|
+ offset: u64,
|
|
|
+ whence: u32,
|
|
|
+ ) -> io::Result<u64> {
|
|
|
+ debug!("Blocktree::lseek called for inode {inode}");
|
|
|
+ let seek_from = SeekFrom::whence_offset(whence, offset)?;
|
|
|
+ self.access_block(inode, |block| block.seek(seek_from))
|
|
|
+ }
|
|
|
+
|
|
|
//////////////////////////////////
|
|
|
// METHODS WHICH ARE NOT SUPPORTED
|
|
|
//////////////////////////////////
|
|
@@ -890,18 +918,6 @@ mod private {
|
|
|
Self::not_supported()
|
|
|
}
|
|
|
|
|
|
- fn lseek(
|
|
|
- &self,
|
|
|
- _ctx: &Context,
|
|
|
- inode: Self::Inode,
|
|
|
- _handle: Self::Handle,
|
|
|
- _offset: u64,
|
|
|
- _whence: u32,
|
|
|
- ) -> io::Result<u64> {
|
|
|
- debug!("Blocktree::lseek called for inode {inode}");
|
|
|
- Self::not_supported()
|
|
|
- }
|
|
|
-
|
|
|
fn mkdir(
|
|
|
&self,
|
|
|
_ctx: &Context,
|
|
@@ -966,7 +982,7 @@ mod private {
|
|
|
Self::not_supported()
|
|
|
}
|
|
|
|
|
|
- fn removexattr(&self, ctx: &Context, inode: Self::Inode, _name: &CStr) -> io::Result<()> {
|
|
|
+ fn removexattr(&self, _ctx: &Context, inode: Self::Inode, _name: &CStr) -> io::Result<()> {
|
|
|
debug!("Blocktree::removexattr called for inode {inode}");
|
|
|
Self::not_supported()
|
|
|
}
|
|
@@ -1062,27 +1078,53 @@ mod private {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[cfg(feature = "testing")]
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use std::ffi::CString;
|
|
|
|
|
|
- use fuse_backend_rs::api::filesystem::FileSystem;
|
|
|
+ use fuse_backend_rs::api::filesystem::{FileSystem, FsOptions};
|
|
|
use tempdir::TempDir;
|
|
|
|
|
|
use test_helpers::*;
|
|
|
|
|
|
- use crate::test_helpers;
|
|
|
+ use crate::{crypto::ConcreteCreds, test_helpers, Decompose};
|
|
|
|
|
|
use super::{private::SpecInodes, *};
|
|
|
|
|
|
+ struct BtTestCase {
|
|
|
+ dir: TempDir,
|
|
|
+ bt: Blocktree<ConcreteCreds>,
|
|
|
+ }
|
|
|
+
|
|
|
+ impl BtTestCase {
|
|
|
+ fn new_empty() -> BtTestCase {
|
|
|
+ let dir = TempDir::new("fuse").expect("failed to create temp dir");
|
|
|
+ let bt = Blocktree::new_empty(dir.path().to_owned(), 0, Self::creds())
|
|
|
+ .expect("failed to create empty blocktree");
|
|
|
+ bt.init(FsOptions::empty()).expect("init failed");
|
|
|
+ BtTestCase { dir, bt }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn new_existing(dir: TempDir) -> BtTestCase {
|
|
|
+ let bt = Blocktree::new_existing(dir.path().to_owned(), Self::creds())
|
|
|
+ .expect("failed to create blocktree from existing directory");
|
|
|
+ bt.init(FsOptions::empty()).expect("init failed");
|
|
|
+ BtTestCase { dir, bt }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn creds() -> ConcreteCreds {
|
|
|
+ test_helpers::NODE_CREDS.clone()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Tests that a new file can be created, written to and the written data can be read from it.
|
|
|
#[test]
|
|
|
- fn create_write_open_read() {
|
|
|
- let dir = TempDir::new("fuse").expect("failed to create temp dir");
|
|
|
- let creds = test_helpers::NODE_CREDS.clone();
|
|
|
- let bt = Blocktree::new_empty(dir.path().to_owned(), 0, creds)
|
|
|
- .expect("failed to create empty blocktree");
|
|
|
+ fn create_open_write_read() {
|
|
|
+ let case = BtTestCase::new_empty();
|
|
|
+ let bt = &case.bt;
|
|
|
+
|
|
|
let name = CString::new("README.md").unwrap();
|
|
|
+
|
|
|
let (entry, ..) = bt
|
|
|
.create(
|
|
|
&Default::default(),
|
|
@@ -1091,10 +1133,12 @@ mod tests {
|
|
|
Default::default(),
|
|
|
)
|
|
|
.expect("failed to create file");
|
|
|
+
|
|
|
bt.open(&Default::default(), entry.inode, 0, 0)
|
|
|
.expect("failed to open file");
|
|
|
const LEN: usize = 32;
|
|
|
let mut expected = BtCursor::new([1u8; LEN]);
|
|
|
+
|
|
|
let written = bt
|
|
|
.write(
|
|
|
&Default::default(),
|
|
@@ -1110,8 +1154,10 @@ mod tests {
|
|
|
)
|
|
|
.expect("write failed");
|
|
|
assert_eq!(LEN, written);
|
|
|
-
|
|
|
let mut actual = BtCursor::new([0u8; LEN]);
|
|
|
+ bt.lseek(&Default::default(), entry.inode, Default::default(), 0, 0)
|
|
|
+ .expect("lseek failed");
|
|
|
+
|
|
|
let read = bt
|
|
|
.read(
|
|
|
&Default::default(),
|
|
@@ -1131,10 +1177,9 @@ mod tests {
|
|
|
|
|
|
#[test]
|
|
|
fn lookup() {
|
|
|
- let dir = TempDir::new("fuse").expect("failed to create temp dir");
|
|
|
- let creds = test_helpers::NODE_CREDS.clone();
|
|
|
- let bt = Blocktree::new_empty(dir.path().to_owned(), 0, creds)
|
|
|
- .expect("failed to create empty blocktree");
|
|
|
+ let case = BtTestCase::new_empty();
|
|
|
+ let bt = &case.bt;
|
|
|
+
|
|
|
let name = CString::new("README.md").unwrap();
|
|
|
let (expected, ..) = bt
|
|
|
.create(
|
|
@@ -1152,4 +1197,77 @@ mod tests {
|
|
|
assert_eq!(expected.generation, actual.generation);
|
|
|
assert_eq!(expected.inode, actual.inode);
|
|
|
}
|
|
|
+
|
|
|
+ /// Tests that data written by one instance of [Blocktree] can be read by a subsequent
|
|
|
+ /// instance.
|
|
|
+ #[test]
|
|
|
+ fn new_existing() {
|
|
|
+ const EXPECTED: &[u8] = b"cool as cucumbers";
|
|
|
+ let name = CString::new("RESIGNATION.docx").unwrap();
|
|
|
+
|
|
|
+ let case = BtTestCase::new_empty();
|
|
|
+ let bt = &case.bt;
|
|
|
+
|
|
|
+ {
|
|
|
+ let (entry, ..) = bt
|
|
|
+ .create(
|
|
|
+ &Default::default(),
|
|
|
+ SpecInodes::RootDir.into(),
|
|
|
+ name.as_c_str(),
|
|
|
+ Default::default(),
|
|
|
+ )
|
|
|
+ .expect("failed to create file");
|
|
|
+
|
|
|
+ bt.open(&Default::default(), entry.inode, 0, 0)
|
|
|
+ .expect("open failed");
|
|
|
+
|
|
|
+ let mut vec = Vec::with_capacity(EXPECTED.len());
|
|
|
+ vec.extend_from_slice(EXPECTED);
|
|
|
+ let mut cursor = BtCursor::new(vec);
|
|
|
+ let written = bt
|
|
|
+ .write(
|
|
|
+ &Default::default(),
|
|
|
+ entry.inode,
|
|
|
+ Default::default(),
|
|
|
+ &mut cursor,
|
|
|
+ EXPECTED.len() as u32,
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ false,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ )
|
|
|
+ .expect("write failed");
|
|
|
+ assert_eq!(EXPECTED.len(), written);
|
|
|
+
|
|
|
+ bt.flush(&Default::default(), entry.inode, Default::default(), 0)
|
|
|
+ .expect("flush failed");
|
|
|
+ }
|
|
|
+
|
|
|
+ let case = BtTestCase::new_existing(case.dir);
|
|
|
+ let bt = &case.bt;
|
|
|
+
|
|
|
+ let entry = bt
|
|
|
+ .lookup(&Default::default(), SpecInodes::RootDir.into(), &name)
|
|
|
+ .expect("lookup failed");
|
|
|
+
|
|
|
+ bt.open(&Default::default(), entry.inode, 0, 0)
|
|
|
+ .expect("open failed");
|
|
|
+
|
|
|
+ let mut actual = BtCursor::new([0u8; EXPECTED.len()]);
|
|
|
+ let _ = bt
|
|
|
+ .read(
|
|
|
+ &Default::default(),
|
|
|
+ entry.inode,
|
|
|
+ Default::default(),
|
|
|
+ &mut actual,
|
|
|
+ EXPECTED.len() as u32,
|
|
|
+ 0,
|
|
|
+ None,
|
|
|
+ 0,
|
|
|
+ )
|
|
|
+ .expect("read failed");
|
|
|
+
|
|
|
+ assert_eq!(EXPECTED, actual.into_inner().as_slice())
|
|
|
+ }
|
|
|
}
|