aboutsummaryrefslogtreecommitdiff
path: root/src/client/fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/fs')
-rw-r--r--src/client/fs/dcache.rs107
-rw-r--r--src/client/fs/fcache.rs31
-rw-r--r--src/client/fs/fh.rs110
-rw-r--r--src/client/fs/file.rs78
-rw-r--r--src/client/fs/ino.rs51
5 files changed, 377 insertions, 0 deletions
diff --git a/src/client/fs/dcache.rs b/src/client/fs/dcache.rs
new file mode 100644
index 0000000..b171abc
--- /dev/null
+++ b/src/client/fs/dcache.rs
@@ -0,0 +1,107 @@
+use super::ino::Ino;
+use std::{
+ collections::{
+ btree_map::{self, OccupiedError},
+ BTreeMap, HashMap,
+ },
+ ffi::{OsStr, OsString},
+};
+
+#[derive(Debug)]
+pub(super) struct Dcache {
+ inner: HashMap<Ino, Dentry>,
+}
+
+impl Dcache {
+ pub fn new() -> Self {
+ Self {
+ inner: HashMap::new(),
+ }
+ }
+
+ pub fn get_ino(&self, parent: Ino, name: &OsStr) -> Option<&Ino> {
+ self.get(parent).and_then(|dentry| dentry.get(name))
+ }
+
+ pub fn try_insert_name(
+ &mut self,
+ parent: Ino,
+ name: OsString,
+ ino: Ino,
+ ) -> Option<Result<(), ()>> {
+ match self
+ .get_mut(parent)
+ .map(|dentry| dentry.try_insert(name, ino))
+ {
+ Some(Ok(_)) => Some(Ok(())),
+ Some(Err(_)) => Some(Err(())),
+ None => None,
+ }
+ }
+
+ pub fn add_dentry(&mut self, ino: Ino, parent: Ino) -> Option<Dentry> {
+ self.insert(ino, Dentry::new(ino, parent))
+ }
+
+ // Map-like API
+
+ pub fn insert(&mut self, ino: Ino, dentry: Dentry) -> Option<Dentry> {
+ self.inner.insert(ino, dentry)
+ }
+
+ pub fn get(&self, ino: Ino) -> Option<&Dentry> {
+ self.inner.get(&ino)
+ }
+
+ pub fn get_mut(&mut self, ino: Ino) -> Option<&mut Dentry> {
+ self.inner.get_mut(&ino)
+ }
+
+ pub fn remove(&mut self, ino: Ino) -> Option<Dentry> {
+ self.inner.remove(&ino)
+ }
+}
+
+#[derive(Debug)]
+pub(super) struct Dentry {
+ inner: BTreeMap<OsString, Ino>,
+ pub loaded: bool,
+}
+
+impl Dentry {
+ pub fn new(ino: Ino, parent: Ino) -> Self {
+ let mut dentry = Self {
+ inner: BTreeMap::new(),
+ loaded: false,
+ };
+
+ dentry.insert(".".into(), ino);
+ dentry.insert("..".into(), parent);
+
+ dentry
+ }
+
+ pub fn insert(&mut self, k: OsString, v: Ino) -> Option<Ino> {
+ self.inner.insert(k, v)
+ }
+
+ pub fn try_insert(
+ &mut self,
+ k: OsString,
+ v: Ino,
+ ) -> Result<&mut Ino, btree_map::OccupiedError<'_, OsString, Ino>> {
+ self.inner.try_insert(k, v)
+ }
+
+ pub fn get(&self, k: &OsStr) -> Option<&Ino> {
+ self.inner.get(k)
+ }
+
+ pub fn remove(&mut self, k: &OsStr) -> Option<Ino> {
+ self.inner.remove(k)
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = (&OsString, &Ino)> {
+ self.inner.iter()
+ }
+}
diff --git a/src/client/fs/fcache.rs b/src/client/fs/fcache.rs
new file mode 100644
index 0000000..03785c0
--- /dev/null
+++ b/src/client/fs/fcache.rs
@@ -0,0 +1,31 @@
+use super::{file::File, ino::Ino};
+use std::collections::HashMap;
+
+#[derive(Debug)]
+pub struct Fcache {
+ inner: HashMap<Ino, File>,
+}
+
+impl Fcache {
+ pub fn new() -> Self {
+ Self {
+ inner: HashMap::new(),
+ }
+ }
+
+ pub fn insert(&mut self, ino: Ino, file: File) -> Option<File> {
+ self.inner.insert(ino, file)
+ }
+
+ pub fn get(&self, ino: Ino) -> Option<&File> {
+ self.inner.get(&ino)
+ }
+
+ pub fn get_mut(&mut self, ino: Ino) -> Option<&mut File> {
+ self.inner.get_mut(&ino)
+ }
+
+ pub fn remove(&mut self, ino: Ino) -> Option<File> {
+ self.inner.remove(&ino)
+ }
+}
diff --git a/src/client/fs/fh.rs b/src/client/fs/fh.rs
new file mode 100644
index 0000000..b1e8739
--- /dev/null
+++ b/src/client/fs/fh.rs
@@ -0,0 +1,110 @@
+use crate::{common::temp_file, server::Stream};
+use log::error;
+use std::{
+ fs::{self, File},
+ io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write},
+ ops::Index,
+ path::PathBuf,
+ slice::SliceIndex,
+};
+
+pub struct FileHandle {
+ buffer: Vec<u8>,
+ size: usize,
+ tmp: Option<(PathBuf, BufWriter<File>)>,
+ reader: Option<BufReader<File>>,
+ dirty: bool,
+}
+
+impl FileHandle {
+ pub fn empty() -> std::io::Result<Self> {
+ let (temp_path, temp_file) = temp_file()?;
+
+ Ok(Self {
+ buffer: Vec::new(),
+ size: 0,
+ tmp: Some((temp_path, BufWriter::new(temp_file))),
+ reader: None,
+ dirty: false,
+ })
+ }
+
+ pub fn new_rw(reader: BufReader<File>) -> std::io::Result<Self> {
+ // TODO: Copy read to tmp
+ let (temp_path, temp_file) = temp_file()?;
+
+ Ok(Self {
+ buffer: Vec::new(),
+ size: 0,
+ tmp: Some((temp_path, BufWriter::new(temp_file))),
+ reader: Some(reader),
+ dirty: false,
+ })
+ }
+
+ pub fn new_ro(reader: BufReader<File>) -> Self {
+ Self {
+ buffer: Vec::new(),
+ size: 0,
+ tmp: None,
+ reader: Some(reader),
+ dirty: false,
+ }
+ }
+
+ pub fn is_dirty(&self) -> bool {
+ self.dirty
+ }
+
+ pub fn write(&mut self, data: &[u8], offset: usize) -> Option<usize> {
+ let Some((_, ref mut tmp)) = self.tmp else {
+ error!("Tried to write to a RO file handle");
+ return None;
+ };
+
+ tmp.seek(SeekFrom::Start(offset as u64))
+ .inspect_err(|e| error!("Seek error: {e}"))
+ .ok()?;
+ let written = tmp
+ .write(data)
+ .inspect_err(|e| error!("Write error: {e}"))
+ .ok()?;
+
+ self.dirty = true;
+
+ Some(written)
+ }
+
+ pub fn read(&mut self, offset: usize, size: usize) -> Vec<u8> {
+ let Some(mut reader) = self.reader else {
+ error!("Tried to read from an empty file handle");
+ return Vec::new();
+ };
+
+ let mut buf = Vec::with_capacity(size);
+
+ // TODO: error handling...
+ reader.seek(SeekFrom::Start(offset as u64)).unwrap();
+ reader.read_exact(&mut buf).unwrap();
+
+ buf
+ }
+
+ pub fn clear(&mut self) {
+ self.tmp.as_mut().map(|b| b.1.flush());
+ self.buffer.clear();
+ self.dirty = false;
+ }
+}
+
+impl Drop for FileHandle {
+ fn drop(&mut self) {
+ let Some((ref temp_path, _)) = self.tmp else {
+ return;
+ };
+
+ if let Err(e) = fs::remove_file(temp_path) {
+ error!("Couldn't delete temp file {temp_path:?}: {e}");
+ }
+ }
+}
diff --git a/src/client/fs/file.rs b/src/client/fs/file.rs
new file mode 100644
index 0000000..5d51913
--- /dev/null
+++ b/src/client/fs/file.rs
@@ -0,0 +1,78 @@
+use super::ino::Ino;
+use crate::server::blobref::BlobRef;
+use fuser::{FileAttr, FileType};
+use std::{
+ ffi::{OsStr, OsString},
+ time::SystemTime,
+};
+
+const DEFAULT_PERMISSIONS: u16 = 0o644;
+
+#[derive(Debug)]
+pub struct File {
+ // Files only have a blobref if they were written to. No blob is created if the
+ // files is only `touch`ed. This means empty files will disappear on `umount`.
+ pub blobref: Option<BlobRef>,
+ parent: Option<Ino>,
+ pub attr: FileAttr,
+ name: OsString,
+}
+
+impl File {
+ fn new(ino: Ino, name: &OsStr, kind: FileType) -> Self {
+ let now = SystemTime::now();
+
+ let attr = FileAttr {
+ ino: ino.into(),
+ size: 0,
+ blocks: 0,
+ atime: now,
+ mtime: now,
+ ctime: now,
+ crtime: now,
+ kind,
+ perm: DEFAULT_PERMISSIONS,
+ nlink: 0,
+ uid: 0,
+ gid: 0,
+ rdev: 0,
+ flags: 0,
+ blksize: 0,
+ };
+
+ File {
+ blobref: None,
+ parent: None,
+ attr,
+ name: name.into(),
+ }
+ }
+
+ pub fn new_regular_file<T: Into<OsString>>(ino: Ino, name: T) -> Self {
+ Self::new(ino, &name.into(), FileType::RegularFile)
+ }
+
+ pub fn new_directory<T: Into<OsString>>(ino: Ino, name: T) -> Self {
+ Self::new(ino, &name.into(), FileType::Directory)
+ }
+
+ pub fn set_parent(&mut self, ino: Ino) {
+ self.parent = Some(ino);
+ }
+
+ pub fn name(&self) -> OsString {
+ self.name.clone()
+ }
+
+ pub fn parent(&self) -> Option<Ino> {
+ self.parent
+ }
+
+ pub fn ino(&self) -> Ino {
+ self.attr.ino.into()
+ }
+
+ pub fn size(&self) -> usize {
+ self.attr.size as usize
+ }
+}
diff --git a/src/client/fs/ino.rs b/src/client/fs/ino.rs
new file mode 100644
index 0000000..0b7628e
--- /dev/null
+++ b/src/client/fs/ino.rs
@@ -0,0 +1,51 @@
+use std::fmt::Display;
+
+const ROOT_INO: u64 = 1;
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash, Copy)]
+pub struct Ino(u64);
+
+impl Ino {
+ pub fn new() -> Self {
+ Self(ROOT_INO)
+ }
+
+ pub fn next(&mut self) -> &Self {
+ self.0 += 1;
+ self
+ }
+
+ pub fn prev(&mut self) {
+ self.0 -= 1;
+ }
+}
+
+impl From<Ino> for u64 {
+ fn from(ino: Ino) -> Self {
+ ino.0
+ }
+}
+
+impl From<u64> for Ino {
+ fn from(ino: u64) -> Self {
+ Self(ino)
+ }
+}
+
+impl From<u32> for Ino {
+ fn from(ino: u32) -> Self {
+ Self(u64::from(ino))
+ }
+}
+
+impl Display for Ino {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl Default for Ino {
+ fn default() -> Self {
+ Self::new()
+ }
+}