Skip to content

Commit

Permalink
feat: file handlers by pid
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Oct 22, 2024
1 parent 6a01088 commit 80cb495
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 136 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ jobs:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo apt install -y libssh2-1-dev libssl-dev libfuse-dev libsmbclient-dev libsmbclient pkg-config
- name: Setup containers
run: docker compose -f "tests/docker-compose.yml" up -d --build
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
Expand Down
2 changes: 0 additions & 2 deletions remotefs-fuse/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ serde_json = "1"
[dev-dependencies]
env_logger = "^0.11"
pretty_assertions = "^1"
remotefs-ssh = "0.4"


[features]
default = []
Expand Down
4 changes: 2 additions & 2 deletions remotefs-fuse/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct Driver {
database: unix::InodeDb,
/// File handle database
#[cfg(target_family = "unix")]
file_handlers: unix::FileHandleDb,
file_handlers: unix::FileHandlersDb,
/// [`RemoteFs`] instance
remote: Box<dyn RemoteFs>,
}
Expand All @@ -37,7 +37,7 @@ impl Driver {
#[cfg(target_family = "unix")]
database: unix::InodeDb::load(),
#[cfg(target_family = "unix")]
file_handlers: unix::FileHandleDb::default(),
file_handlers: unix::FileHandlersDb::default(),
remote,
}
}
Expand Down
40 changes: 31 additions & 9 deletions remotefs-fuse/src/driver/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ use fuser::{
use libc::c_int;
use remotefs::fs::UnixPex;
use remotefs::{File, RemoteError, RemoteErrorType, RemoteResult};
use serde::de;

pub use self::file_handle::FileHandleDb;
pub use self::file_handle::FileHandlersDb;
pub use self::inode::InodeDb;
use super::Driver;

Expand Down Expand Up @@ -768,7 +769,7 @@ impl Filesystem for Driver {
}

// Set file handle and reply
let fh = self.file_handlers.put(ino, read, write);
let fh = self.file_handlers.put(req.pid(), ino, read, write);
reply.opened(fh, 0);
}

Expand All @@ -781,7 +782,7 @@ impl Filesystem for Driver {
/// if the open method didn't set any value.
fn read(
&mut self,
_req: &Request,
req: &Request,
ino: u64,
fh: u64,
offset: i64,
Expand All @@ -794,14 +795,15 @@ impl Filesystem for Driver {
// check access
if !self
.file_handlers
.get(fh)
.get(req.pid(), fh)
.map(|handler| handler.read)
.unwrap_or_default()
{
debug!("No read permission for fh {fh}");
reply.error(libc::EACCES);
return;
}
// check offset
if offset < 0 {
debug!("Invalid offset {offset}");
reply.error(libc::EINVAL);
Expand Down Expand Up @@ -847,6 +849,7 @@ impl Filesystem for Driver {
lock_owner: Option<u64>,
reply: ReplyWrite,
) {
debug!("write() called for {ino} {} bytes at {offset}", data.len());
todo!()
}

Expand All @@ -860,8 +863,17 @@ impl Filesystem for Driver {
/// 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, req: &Request, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) {
todo!()
fn flush(&mut self, req: &Request, ino: u64, fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
debug!("flush() called for {ino}");

// get fh
if self.file_handlers.get(req.pid(), fh).is_none() {
reply.error(libc::ENOENT);
return;
}

// nop and ok
reply.ok();
}

/// Release an open file.
Expand All @@ -876,19 +888,29 @@ impl Filesystem for Driver {
&mut self,
req: &Request,
_ino: u64,
_fh: u64,
fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
todo!()
// get fh
if self.file_handlers.get(req.pid(), fh).is_none() {
reply.error(libc::ENOENT);
return;
}

// remove fh and ok
self.file_handlers.close(req.pid(), fh);
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, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, _reply: ReplyEmpty) {}
fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
reply.ok();
}

/// Open a directory.
/// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and
Expand Down
128 changes: 118 additions & 10 deletions remotefs-fuse/src/driver/unix/file_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,58 @@ use std::collections::HashMap;

use super::inode::Inode;

/// FileHandleDb is a database of file handles. It is used to store file handles for open files.
/// Pid is a process identifier.
pub type Pid = u32;
/// Fh is a file handle number.
pub type Fh = u64;

/// FileHandlersDb is a database of file handles for each process.
#[derive(Debug, Default)]
pub struct FileHandlersDb {
/// Database of file handles for each process.
handlers: HashMap<Pid, ProcessFileHandlers>,
}

impl FileHandlersDb {
/// Put a new file handle into the database.
pub fn put(&mut self, pid: Pid, inode: Inode, read: bool, write: bool) -> u64 {
self.handlers
.entry(pid)
.or_insert_with(ProcessFileHandlers::default)
.put(inode, read, write)
}

/// Get a file handle from the database.
pub fn get(&self, pid: Pid, fh: u64) -> Option<&FileHandle> {
self.handlers
.get(&pid)
.and_then(|handlers| handlers.get(fh))
}

/// Close a file handle.
pub fn close(&mut self, pid: Pid, fh: u64) {
if let Some(handlers) = self.handlers.get_mut(&pid) {
handlers.close(fh);
}

// remove the process if it has no more file handles
if self
.handlers
.get(&pid)
.map(|handlers| handlers.handles.is_empty())
.unwrap_or_default()
{
self.handlers.remove(&pid);
}
}
}

/// ProcessFileHandlers is a database of file handles. It is used to store file handles for open files.
///
/// It is a map between the file handle number and the [`FileHandle`] struct.
#[derive(Debug, Default)]
pub struct FileHandleDb {
handles: HashMap<u64, FileHandle>,
struct ProcessFileHandlers {
handles: HashMap<Fh, FileHandle>,
/// Next file handle number
next: u64,
}
Expand All @@ -16,34 +62,34 @@ pub struct FileHandleDb {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileHandle {
/// Inode of the file
pub inode: u64,
pub inode: Inode,
/// Read permission
pub read: bool,
/// Write permission
pub write: bool,
}

impl FileHandleDb {
impl ProcessFileHandlers {
/// Put a new [`FileHandle`] into the database.
///
/// Returns the created file handle number.
pub fn put(&mut self, inode: Inode, read: bool, write: bool) -> u64 {
fn put(&mut self, inode: Inode, read: bool, write: bool) -> u64 {
let fh = self.next;
self.handles.insert(fh, FileHandle { inode, read, write });
self.next = self.handles.len() as u64;
fh
}

/// Get a [`FileHandle`] from the database.
pub fn get(&self, fh: u64) -> Option<&FileHandle> {
fn get(&self, fh: u64) -> Option<&FileHandle> {
self.handles.get(&fh)
}

/// Close a file handle.
///
/// This will remove the file handle from the database.
/// The file handle number will be reused next.
pub fn close(&mut self, fh: u64) {
fn close(&mut self, fh: u64) {
self.handles.remove(&fh);
self.next = fh;
}
Expand All @@ -56,9 +102,71 @@ mod test {

use super::*;

#[test]
fn test_should_store_handlers_for_pid() {
let mut db = FileHandlersDb::default();

let fh = db.put(1, 1, true, false);
assert_eq!(
db.get(1, fh),
Some(&FileHandle {
inode: 1,
read: true,
write: false
})
);

assert_eq!(db.get(2, fh), None);

let fh = db.put(1, 2, true, false);
assert_eq!(
db.get(1, fh),
Some(&FileHandle {
inode: 2,
read: true,
write: false
})
);

let fh = db.put(2, 3, true, false);

assert_eq!(
db.get(2, fh),
Some(&FileHandle {
inode: 3,
read: true,
write: false
})
);
}

#[test]
fn test_should_remove_pid_if_has_no_more_handles() {
let mut db = FileHandlersDb::default();

let fh = db.put(1, 1, true, false);
assert_eq!(
db.get(1, fh),
Some(&FileHandle {
inode: 1,
read: true,
write: false
})
);

db.close(1, fh);
assert_eq!(db.get(1, fh), None);

db.put(1, 2, true, false);
db.put(1, 3, true, false);
db.close(1, 2);

assert!(db.handlers.contains_key(&1));
}

#[test]
fn test_file_handle_db() {
let mut db = FileHandleDb::default();
let mut db = ProcessFileHandlers::default();

let fh = db.put(1, true, false);
assert_eq!(
Expand All @@ -76,7 +184,7 @@ mod test {

#[test]
fn test_should_reuse_fhs() {
let mut db = FileHandleDb::default();
let mut db = ProcessFileHandlers::default();

let _fh1 = db.put(1, true, false);
let fh2 = db.put(2, true, false);
Expand Down
Loading

0 comments on commit 80cb495

Please sign in to comment.