feat: add crypt4ghfs-rust

This commit is contained in:
2023-01-08 11:48:40 +08:00
parent c8837b21f6
commit 8f6805f3e3
30 changed files with 2959 additions and 1 deletions

View File

@@ -0,0 +1,458 @@
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::fs::DirEntry;
use std::os::unix::fs::DirEntryExt;
use std::time::{Duration, SystemTime};
use crypt4gh::Keys;
use fuser::{
Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs,
ReplyWrite, Request, TimeOrNow,
};
use nix::errno::Errno;
use nix::unistd::{Gid, Uid};
use crate::file_admin::FileAdmin;
use crate::utils;
// use std::os::linux::fs::MetadataExt;
const TTL: Duration = Duration::from_secs(300);
pub struct Crypt4ghFS {
file_admin: FileAdmin,
keys: Vec<Keys>,
recipients: HashSet<Keys>,
uid: Uid,
gid: Gid,
entries: HashMap<u64, Vec<Result<DirEntry, std::io::Error>>>,
duration1: std::time::Duration,
// TODO: implement cache directories functionality
}
impl Crypt4ghFS {
pub fn new(rootdir: &str, seckey: Vec<u8>, recipients: HashSet<Keys>, uid: Uid, gid: Gid) -> Self {
Self {
file_admin: FileAdmin::new(rootdir),
keys: vec![Keys {
method: 0,
privkey: seckey,
recipient_pubkey: vec![],
}],
recipients,
uid,
gid,
entries: HashMap::new(),
duration1: Duration::from_secs(0),
}
}
}
impl Filesystem for Crypt4ghFS {
// FILESYSTEM
fn destroy(&mut self) {
log::info!("1 - Elapsed: {:?}", self.duration1);
}
fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
let file = self.file_admin.get_file(ino);
match file.attrs(self.uid.as_raw(), self.gid.as_raw()) {
Ok(attrs) => reply.attr(&TTL, &attrs),
Err(_) => reply.error(1000),
}
}
fn setattr(
&mut self,
req: &Request<'_>,
ino: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
fh: Option<u64>,
crtime: Option<SystemTime>,
chgtime: Option<SystemTime>,
bkuptime: Option<SystemTime>,
flags: Option<u32>,
reply: ReplyAttr,
) {
let mut err = None;
if mode.is_some() {
reply.error(Errno::EPERM as i32);
return;
}
if uid.is_some() || gid.is_some() {
reply.error(Errno::EPERM as i32);
return;
}
if atime.is_some() || mtime.is_some() {
reply.error(Errno::EOPNOTSUPP as i32);
return;
}
if crtime.is_some() || chgtime.is_some() || bkuptime.is_some() || flags.is_some() {
reply.error(Errno::EOPNOTSUPP as i32);
return;
}
let file = self.file_admin.get_file_mut(ino);
if let Some(size) = size {
if let Err(e) = file.truncate(fh, size) {
err = Some(e);
}
}
match err {
None => match file.attrs(req.uid(), req.gid()) {
Ok(attrs) => reply.attr(&TTL, &attrs),
Err(e) => reply.error(e.to_raw_os_error()),
},
Some(e) => reply.error(e.to_raw_os_error()),
}
}
fn lookup(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
let parent_file = self.file_admin.get_file(parent);
let name_string = name.to_string_lossy().to_string();
let new_name = name_string.strip_suffix(".c4gh").unwrap_or(&name_string);
let path = parent_file.path().join(new_name);
match self.file_admin.get_by_path(path.as_path()) {
Some(child_file) => match child_file.attrs(req.uid(), req.gid()) {
Ok(attr) => reply.entry(&TTL, &attr, 0),
Err(e) => reply.error(e.to_raw_os_error()),
},
None => {
reply.error(Errno::ENOENT as i32);
},
}
}
fn statfs(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyStatfs) {
let file = self.file_admin.get_file(ino);
match utils::statfs(&file.path()) {
Ok(statfs) => reply.statfs(
u64::from(statfs.blocks()),
u64::from(statfs.blocks_free()),
u64::from(statfs.blocks_available()),
u64::from(statfs.files()),
u64::from(statfs.files_free()),
statfs.block_size() as u32,
statfs.name_max() as u32,
statfs.fragment_size() as u32,
),
Err(e) => reply.error(e.to_raw_os_error()),
}
}
// FILE
fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
let file = self.file_admin.get_file_mut(ino);
match file.open(flags) {
Ok(fh) => reply.opened(fh as u64, flags as u32),
Err(e) => reply.error(e.to_raw_os_error()),
}
}
fn read(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyData,
) {
let file = self.file_admin.get_file_mut(ino);
match file.read(fh, offset, size) {
Ok(data) => reply.data(&data),
Err(e) => {
log::error!("{:?}", e);
reply.error(e.to_raw_os_error());
},
}
}
fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
let file = self.file_admin.get_file_mut(ino);
match file.flush(fh) {
Ok(_) => reply.ok(),
Err(e) => reply.error(e.to_raw_os_error()),
}
}
fn create(
&mut self,
req: &Request<'_>,
parent: u64,
name: &OsStr,
mode: u32,
umask: u32,
flags: i32,
reply: ReplyCreate,
) {
// Get path
let parent_file = self.file_admin.get_file(parent);
let parent_path = parent_file.path();
let path = parent_path.join(name).into_boxed_path();
let mut inbox_path = path.to_path_buf();
if path.extension().is_none() || path.extension().unwrap() != "c4gh" {
let mut filename = inbox_path.file_name().unwrap().to_string_lossy().to_string();
filename.push_str(".c4gh");
inbox_path.set_file_name(filename);
}
// Create file
log::debug!("Create new file with path: {:?}", inbox_path);
let file = utils::create(&inbox_path, flags, mode & umask).unwrap();
// Build file admin entry
let ino = utils::lstat(&inbox_path).unwrap().st_ino;
let egafile = utils::wrap_file(&path, file, &self.keys, &self.recipients);
// Build reply
let attrs = egafile.attrs(req.uid(), req.gid()).unwrap();
let fh = *egafile.fh().last().unwrap();
// Add and reply
self.file_admin.add(ino, egafile);
reply.created(&TTL, &attrs, 0, fh, flags as u32);
}
fn write(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
_offset: i64,
data: &[u8],
_write_flags: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyWrite,
) {
let file = self.file_admin.get_file_mut(ino);
// TODO: Warn if offset != 0 => not allowed
match file.write(fh, data) {
Ok(size) => reply.written(size as u32),
Err(e) => reply.error(e.to_raw_os_error()),
}
}
fn release(
&mut self,
_req: &Request<'_>,
ino: u64,
fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
let file = self.file_admin.get_file_mut(ino);
file.close(fh).unwrap();
reply.ok();
}
fn rename(
&mut self,
_req: &Request<'_>,
parent: u64,
name: &OsStr,
newparent: u64,
newname: &OsStr,
_flags: u32,
reply: ReplyEmpty,
) {
// Build paths
let old_parent_file = self.file_admin.get_file(parent).path();
let new_parent_path = self.file_admin.get_file(newparent).path();
let old_path = old_parent_file.join(name);
let new_path = new_parent_path.join(newname);
// Change paths
let file = self.file_admin.get_by_path_mut(&old_path).unwrap();
file.rename(new_path.as_path());
// Rename
match std::fs::rename(&old_path, new_path) {
Ok(_) => reply.ok(),
Err(e) => reply.error(e.raw_os_error().unwrap()),
}
}
fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
// Build paths
let parent_file = self.file_admin.get_file(parent);
let parent_path = parent_file.path();
let path = parent_path.join(name);
// Remove file
match std::fs::remove_file(path) {
Ok(_) => reply.ok(),
Err(e) => reply.error(e.raw_os_error().unwrap()),
}
}
// DIRECTORY
fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, skipped: i64, mut reply: ReplyDirectory) {
let _dir = self.file_admin.get_file(ino);
let entries = self.entries.get_mut(&ino).unwrap();
// TODO: Make sure that they have always the same order
let mut last_error = None;
for (offset, entry) in entries[skipped as usize..].iter().enumerate() {
log::debug!("Entry {} - {:?}", offset, entry);
match entry {
Ok(dir_entry) => {
// Track file
let egafile = utils::wrap_path(dir_entry.path().as_path(), &self.keys, &self.recipients);
self.file_admin.add(dir_entry.ino(), egafile);
// Kind
let kind = utils::get_type(dir_entry);
// Name
let name = dir_entry.file_name().clone();
let name_str = name.to_string_lossy().to_string();
let new_name = name_str.strip_suffix(".c4gh").unwrap_or(&name_str);
// Add entry
let buffer_full = reply.add(dir_entry.ino(), (skipped + offset as i64 + 1) as i64, kind, new_name);
if buffer_full {
break;
}
},
Err(e) => {
log::error!("Error on entry {} (ERROR = {:?})", offset, e);
last_error = Some(e.raw_os_error().expect("Unable to convert to error"));
},
}
}
match last_error {
None => reply.ok(),
Some(e) => reply.error(e),
}
}
// fn readdirplus(&mut self, req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectoryPlus) {
// let _dir = self.file_admin.get_file(ino);
// // TODO: Make sure that they have always the same order
// let entries = self.entries.get_mut(&ino).unwrap();
// let mut last_error = None;
// for (offset, entry) in entries.enumerate().skip(offset as usize) {
// match entry {
// Ok(dir_entry) => {
// let egafile = utils::wrap_path(dir_entry.path().as_path(), &self.keys, &self.recipients);
// self.file_admin.add(dir_entry.ino(), egafile);
// let kind = utils::get_type(&dir_entry);
// let name = dir_entry.file_name().clone();
// let metadata = dir_entry.metadata().unwrap();
// let attrs = FileAttr {
// ino: dir_entry.ino(),
// size: dir_entry.metadata().unwrap().len(),
// blocks: metadata.blocks(),
// atime: metadata.accessed().unwrap_or(std::time::UNIX_EPOCH),
// mtime: metadata.modified().unwrap_or(std::time::UNIX_EPOCH),
// ctime: metadata.created().unwrap_or(std::time::UNIX_EPOCH),
// crtime: std::time::UNIX_EPOCH,
// kind,
// perm: metadata.permissions().mode() as u16,
// #[cfg(target_os = "linux")]
// nlink: metadata.st_nlink() as u32,
// #[cfg(target_os = "macos")]
// nlink: 0,
// uid: req.uid(),
// gid: req.gid(),
// #[cfg(target_os = "linux")]
// rdev: metadata.st_rdev() as u32,
// #[cfg(target_os = "macos")]
// rdev: metadata.rdev() as u32,
// #[cfg(target_os = "linux")]
// blksize: metadata.st_blksize() as u32,
// #[cfg(target_os = "macos")]
// blksize: metadata.blksize() as u32,
// padding: 0,
// flags: 0,
// };
// let buffer_full = reply.add(dir_entry.ino(), (offset + 1) as i64, name, &TTL, &attrs, 0);
// if buffer_full {
// break;
// }
// },
// Err(e) => {
// last_error = Some(e.raw_os_error().expect("Unable to convert to error"));
// },
// }
// }
// match last_error {
// None => reply.ok(),
// Some(e) => reply.error(e),
// }
// }
fn mkdir(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, _mode: u32, _umask: u32, reply: ReplyEntry) {
let parent_file = self.file_admin.get_file(parent);
let parent_path = parent_file.path();
let path = parent_path.join(name);
match std::fs::create_dir(&path) {
Ok(_) => {
let stat = utils::lstat(&path).unwrap();
let attrs = utils::stat_to_fileatr(stat, req.uid(), req.gid());
reply.entry(&TTL, &attrs, 0);
},
Err(e) => reply.error(e.raw_os_error().expect("Unable to retrieve raw OS error")),
}
}
fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
let parent_file = self.file_admin.get_file(parent);
let parent_path = parent_file.path();
let path = parent_path.join(name);
self.file_admin.remove_by_path(&path);
match std::fs::remove_dir(path) {
Ok(_) => reply.ok(),
Err(e) => reply.error(e.raw_os_error().expect("Unable to retrieve raw OS error")),
}
}
fn opendir(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
let file = self.file_admin.get_file_mut(ino);
self.entries.insert(
ino,
std::fs::read_dir(file.path())
.expect("Unable to read directory")
.collect(),
);
match file.open(flags) {
Ok(fh) => reply.opened(fh as u64, flags as u32),
Err(e) => reply.error(e.to_raw_os_error()),
}
}
fn releasedir(&mut self, _req: &Request<'_>, ino: u64, fh: u64, _flags: i32, reply: ReplyEmpty) {
let file = self.file_admin.get_file_mut(ino);
self.entries.remove(&ino);
match file.close(fh) {
Ok(_) => reply.ok(),
Err(e) => reply.error(e.to_raw_os_error()),
}
}
}