feat: add crypt4ghfs-rust
This commit is contained in:
18
__fs/crypt4ghfs-rust/src/cli.rs
Normal file
18
__fs/crypt4ghfs-rust/src/cli.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Fuse layer exposing Crypt4GH-encrypted files, as if they were decrypted.
|
||||
#[derive(clap::Parser)]
|
||||
#[clap(about, version, author)]
|
||||
pub struct Args {
|
||||
/// Display debug information
|
||||
#[clap(short, long)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Path to the config file
|
||||
#[clap(short, long)]
|
||||
pub conf: PathBuf,
|
||||
|
||||
/// Path to the mountpoint
|
||||
#[clap()]
|
||||
pub mountpoint: PathBuf,
|
||||
}
|
||||
338
__fs/crypt4ghfs-rust/src/config.rs
Normal file
338
__fs/crypt4ghfs-rust/src/config.rs
Normal file
@@ -0,0 +1,338 @@
|
||||
use std::collections::HashSet;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use crypt4gh::error::Crypt4GHError;
|
||||
use crypt4gh::Keys;
|
||||
use fuser::MountOption;
|
||||
use itertools::Itertools;
|
||||
use rpassword::prompt_password;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::error::Crypt4GHFSError;
|
||||
|
||||
const PASSPHRASE: &str = "C4GH_PASSPHRASE";
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum LogLevel {
|
||||
#[serde(alias = "CRITICAL")]
|
||||
Critical,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
#[serde(alias = "NOTSET")]
|
||||
Trace,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Facility {
|
||||
Kern,
|
||||
User,
|
||||
Mail,
|
||||
Daemon,
|
||||
Auth,
|
||||
Syslog,
|
||||
Lpr,
|
||||
News,
|
||||
Uucp,
|
||||
Cron,
|
||||
Authpriv,
|
||||
Ftp,
|
||||
Local0,
|
||||
Local1,
|
||||
Local2,
|
||||
Local3,
|
||||
Local4,
|
||||
Local5,
|
||||
Local6,
|
||||
Local7,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Default {
|
||||
extensions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Fuse {
|
||||
options: Option<Vec<String>>,
|
||||
rootdir: String,
|
||||
cache_directories: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Crypt4GH {
|
||||
#[serde(rename = "seckey")]
|
||||
seckey_path: Option<String>,
|
||||
recipients: Option<Vec<String>>,
|
||||
include_myself_as_recipient: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct LoggerConfig {
|
||||
pub log_level: LogLevel,
|
||||
pub use_syslog: bool,
|
||||
pub log_facility: Option<Facility>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct Config {
|
||||
default: Default,
|
||||
pub logger: LoggerConfig,
|
||||
fuse: Fuse,
|
||||
crypt4gh: Crypt4GH,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new_with_defaults(rootdir: String, seckey_path: Option<String>) -> Self {
|
||||
Self {
|
||||
default: Default { extensions: vec![] },
|
||||
fuse: Fuse {
|
||||
rootdir,
|
||||
options: Some(vec!["ro".into(), "default_permissions".into(), "auto_unmount".into()]),
|
||||
cache_directories: Some(true),
|
||||
},
|
||||
crypt4gh: Crypt4GH {
|
||||
seckey_path,
|
||||
recipients: Some(vec![]),
|
||||
include_myself_as_recipient: Some(true),
|
||||
},
|
||||
logger: LoggerConfig {
|
||||
log_level: LogLevel::Info,
|
||||
use_syslog: false,
|
||||
log_facility: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_extensions(mut self, extensions: Vec<String>) -> Self {
|
||||
self.default.extensions = extensions;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn with_log_level(mut self, log_level: LogLevel) -> Self {
|
||||
self.logger.log_level = log_level;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_options(&self) -> Vec<MountOption> {
|
||||
self.fuse.options.clone().map_or_else(
|
||||
|| vec![MountOption::RW, MountOption::DefaultPermissions],
|
||||
|options| {
|
||||
options
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.map(str_to_mount_option)
|
||||
.inspect(|option| {
|
||||
log::info!("+ fuse option: {:?}", option);
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn get_cache(&self) -> bool {
|
||||
if let Some(cache_directories) = self.fuse.cache_directories {
|
||||
return cache_directories;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_extensions(&self) -> Vec<String> {
|
||||
self.default.extensions.clone()
|
||||
}
|
||||
|
||||
pub fn get_secret_key(&self) -> Result<Option<Vec<u8>>, Crypt4GHFSError> {
|
||||
match &self.crypt4gh.seckey_path {
|
||||
Some(seckey_path_str) => {
|
||||
let seckey_path = Path::new(&seckey_path_str);
|
||||
log::info!("Loading secret key from {}", seckey_path.display());
|
||||
|
||||
if !seckey_path.is_file() {
|
||||
return Err(Crypt4GHFSError::SecretNotFound(seckey_path.into()));
|
||||
}
|
||||
|
||||
let callback: Box<dyn Fn() -> Result<String, Crypt4GHError>> = match std::env::var(PASSPHRASE) {
|
||||
Ok(_) => {
|
||||
log::warn!("Warning: Using a passphrase in an environment variable is insecure");
|
||||
Box::new(|| std::env::var(PASSPHRASE).map_err(|e| Crypt4GHError::NoPassphrase(e.into())))
|
||||
},
|
||||
Err(_) => Box::new(|| {
|
||||
prompt_password(format!("Passphrase for {}: ", seckey_path.display()))
|
||||
.map_err(|e| Crypt4GHError::NoPassphrase(e.into()))
|
||||
}),
|
||||
};
|
||||
|
||||
let key = crypt4gh::keys::get_private_key(seckey_path, callback)
|
||||
.map_err(|e| Crypt4GHFSError::SecretKeyError(e.to_string()))?;
|
||||
|
||||
Ok(Some(key))
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_recipients(&self, seckey: &[u8]) -> HashSet<Keys> {
|
||||
let recipient_paths = &self.crypt4gh.recipients.clone().unwrap_or_default();
|
||||
|
||||
let mut recipient_pubkeys: HashSet<_> = recipient_paths
|
||||
.iter()
|
||||
.map(Path::new)
|
||||
.filter(|path| path.exists())
|
||||
.filter_map(|path| {
|
||||
log::debug!("Recipient pubkey path: {}", path.display());
|
||||
crypt4gh::keys::get_public_key(path).ok()
|
||||
})
|
||||
.unique()
|
||||
.map(|key| Keys {
|
||||
method: 0,
|
||||
privkey: seckey.to_vec(),
|
||||
recipient_pubkey: key,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if self.crypt4gh.include_myself_as_recipient.unwrap_or(true) {
|
||||
let k = crypt4gh::keys::get_public_key_from_private_key(seckey)
|
||||
.expect("Unable to extract public key from seckey");
|
||||
recipient_pubkeys.insert(Keys {
|
||||
method: 0,
|
||||
privkey: seckey.to_vec(),
|
||||
recipient_pubkey: k,
|
||||
});
|
||||
}
|
||||
|
||||
recipient_pubkeys
|
||||
}
|
||||
|
||||
pub const fn get_log_level(&self) -> LogLevel {
|
||||
self.logger.log_level
|
||||
}
|
||||
|
||||
pub fn get_rootdir(&self) -> String {
|
||||
self.fuse.rootdir.to_string()
|
||||
}
|
||||
|
||||
pub fn from_file(mut config_file: File) -> Result<Self, Crypt4GHFSError> {
|
||||
let mut config_string = String::new();
|
||||
config_file
|
||||
.read_to_string(&mut config_string)
|
||||
.map_err(|e| Crypt4GHFSError::BadConfig(e.to_string()))?;
|
||||
let config_toml = toml::from_str(config_string.as_str()).map_err(|e| Crypt4GHFSError::BadConfig(e.to_string()));
|
||||
config_toml
|
||||
}
|
||||
|
||||
pub fn get_facility(&self) -> syslog::Facility {
|
||||
match self.logger.log_facility.unwrap_or(Facility::User) {
|
||||
Facility::Kern => syslog::Facility::LOG_KERN,
|
||||
Facility::User => syslog::Facility::LOG_USER,
|
||||
Facility::Mail => syslog::Facility::LOG_MAIL,
|
||||
Facility::Daemon => syslog::Facility::LOG_DAEMON,
|
||||
Facility::Auth => syslog::Facility::LOG_AUTH,
|
||||
Facility::Syslog => syslog::Facility::LOG_SYSLOG,
|
||||
Facility::Lpr => syslog::Facility::LOG_LPR,
|
||||
Facility::News => syslog::Facility::LOG_NEWS,
|
||||
Facility::Uucp => syslog::Facility::LOG_UUCP,
|
||||
Facility::Cron => syslog::Facility::LOG_CRON,
|
||||
Facility::Authpriv => syslog::Facility::LOG_AUTHPRIV,
|
||||
Facility::Ftp => syslog::Facility::LOG_FTP,
|
||||
Facility::Local0 => syslog::Facility::LOG_LOCAL0,
|
||||
Facility::Local1 => syslog::Facility::LOG_LOCAL1,
|
||||
Facility::Local2 => syslog::Facility::LOG_LOCAL2,
|
||||
Facility::Local3 => syslog::Facility::LOG_LOCAL3,
|
||||
Facility::Local4 => syslog::Facility::LOG_LOCAL4,
|
||||
Facility::Local5 => syslog::Facility::LOG_LOCAL5,
|
||||
Facility::Local6 => syslog::Facility::LOG_LOCAL6,
|
||||
Facility::Local7 => syslog::Facility::LOG_LOCAL7,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_logger(&self) -> Result<(), Crypt4GHFSError> {
|
||||
let log_level: LogLevel = if let Ok(log_level_str) = std::env::var("RUST_LOG") {
|
||||
log_level_str
|
||||
.as_str()
|
||||
.try_into()
|
||||
.expect("Unable to parse RUST_LOG environment variable")
|
||||
}
|
||||
else {
|
||||
let log_level = self.get_log_level();
|
||||
let log_level_str = match log_level {
|
||||
LogLevel::Critical => "error",
|
||||
LogLevel::Warn => "warn",
|
||||
LogLevel::Info => "info",
|
||||
LogLevel::Debug => "debug",
|
||||
LogLevel::Trace => "trace",
|
||||
};
|
||||
std::env::set_var("RUST_LOG", log_level_str);
|
||||
log_level
|
||||
};
|
||||
|
||||
// Choose logger
|
||||
if self.logger.use_syslog {
|
||||
syslog::init(self.get_facility(), log_level.into(), None)?;
|
||||
}
|
||||
else {
|
||||
let _ = pretty_env_logger::try_init(); // Ignore error
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for LogLevel {
|
||||
type Error = Crypt4GHFSError;
|
||||
|
||||
fn try_from(level: &str) -> Result<Self, Self::Error> {
|
||||
match level {
|
||||
"error" => Ok(Self::Critical),
|
||||
"warn" => Ok(Self::Warn),
|
||||
"info" => Ok(Self::Info),
|
||||
"debug" => Ok(Self::Debug),
|
||||
"trace" => Ok(Self::Trace),
|
||||
_ => Err(Crypt4GHFSError::BadConfig("Wrong log level".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LogLevel> for log::LevelFilter {
|
||||
fn from(val: LogLevel) -> Self {
|
||||
match val {
|
||||
LogLevel::Critical => Self::Error,
|
||||
LogLevel::Warn => Self::Warn,
|
||||
LogLevel::Info => Self::Info,
|
||||
LogLevel::Debug => Self::Debug,
|
||||
LogLevel::Trace => Self::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_mount_option(s: &str) -> MountOption {
|
||||
match s {
|
||||
"auto_unmount" => MountOption::AutoUnmount,
|
||||
"allow_other" => MountOption::AllowOther,
|
||||
"allow_root" => MountOption::AllowRoot,
|
||||
"default_permissions" => MountOption::DefaultPermissions,
|
||||
"dev" => MountOption::Dev,
|
||||
"nodev" => MountOption::NoDev,
|
||||
"suid" => MountOption::Suid,
|
||||
"nosuid" => MountOption::NoSuid,
|
||||
"ro" => MountOption::RO,
|
||||
"rw" => MountOption::RW,
|
||||
"exec" => MountOption::Exec,
|
||||
"noexec" => MountOption::NoExec,
|
||||
"atime" => MountOption::Atime,
|
||||
"noatime" => MountOption::NoAtime,
|
||||
"dirsync" => MountOption::DirSync,
|
||||
"sync" => MountOption::Sync,
|
||||
"async" => MountOption::Async,
|
||||
x if x.starts_with("fsname=") => MountOption::FSName(x[7..].into()),
|
||||
x if x.starts_with("subtype=") => MountOption::Subtype(x[8..].into()),
|
||||
x => MountOption::CUSTOM(x.into()),
|
||||
}
|
||||
}
|
||||
74
__fs/crypt4ghfs-rust/src/directory.rs
Normal file
74
__fs/crypt4ghfs-rust/src/directory.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::egafile::EgaFile;
|
||||
use crate::error::{Crypt4GHFSError, Result};
|
||||
use crate::utils;
|
||||
|
||||
pub struct Directory {
|
||||
pub opened_files: HashMap<u64, Box<File>>,
|
||||
pub path: Box<Path>,
|
||||
}
|
||||
|
||||
impl EgaFile for Directory {
|
||||
fn fh(&self) -> Vec<u64> {
|
||||
self.opened_files.iter().map(|(&fh, _)| fh).collect()
|
||||
}
|
||||
|
||||
fn path(&self) -> Box<Path> {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn open(&mut self, flags: i32) -> Result<i32> {
|
||||
let path = self.path();
|
||||
let directory = utils::open(&path, flags)?;
|
||||
let fh = directory.as_raw_fd();
|
||||
self.opened_files.insert(fh as u64, Box::new(directory));
|
||||
Ok(fh)
|
||||
}
|
||||
|
||||
fn read(&mut self, _fh: u64, _offset: i64, _size: u32) -> Result<Vec<u8>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn flush(&mut self, _fh: u64) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn write(&mut self, _fh: u64, _data: &[u8]) -> Result<usize> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn truncate(&mut self, _fh: Option<u64>, _size: u64) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn close(&mut self, fh: u64) -> Result<()> {
|
||||
let f = self.opened_files.get(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
assert_eq!(f.as_raw_fd(), fh as i32);
|
||||
self.opened_files.remove(&fh);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename(&mut self, new_path: &Path) {
|
||||
self.path = new_path.into();
|
||||
}
|
||||
|
||||
fn attrs(&self, uid: u32, gid: u32) -> Result<fuser::FileAttr> {
|
||||
let stat = utils::lstat(&self.path)?;
|
||||
Ok(utils::stat_to_fileatr(stat, uid, gid))
|
||||
}
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn new(file: Option<Box<File>>, path: Box<Path>) -> Self {
|
||||
// Build open files
|
||||
let mut opened_files = HashMap::new();
|
||||
if let Some(f) = file {
|
||||
opened_files.insert(f.as_raw_fd() as u64, f);
|
||||
}
|
||||
Self { opened_files, path }
|
||||
}
|
||||
}
|
||||
21
__fs/crypt4ghfs-rust/src/egafile.rs
Normal file
21
__fs/crypt4ghfs-rust/src/egafile.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::path::Path;
|
||||
|
||||
use fuser::FileAttr;
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
pub trait EgaFile {
|
||||
// Attributes
|
||||
fn fh(&self) -> Vec<u64>;
|
||||
fn path(&self) -> Box<Path>;
|
||||
|
||||
// Filesystem
|
||||
fn open(&mut self, flags: i32) -> Result<i32>;
|
||||
fn read(&mut self, fh: u64, offset: i64, size: u32) -> Result<Vec<u8>>;
|
||||
fn flush(&mut self, fh: u64) -> Result<()>;
|
||||
fn write(&mut self, fh: u64, data: &[u8]) -> Result<usize>;
|
||||
fn truncate(&mut self, fh: Option<u64>, size: u64) -> Result<()>;
|
||||
fn close(&mut self, fh: u64) -> Result<()>;
|
||||
fn rename(&mut self, new_path: &Path);
|
||||
fn attrs(&self, uid: u32, gid: u32) -> Result<FileAttr>;
|
||||
}
|
||||
316
__fs/crypt4ghfs-rust/src/encrypted_file.rs
Normal file
316
__fs/crypt4ghfs-rust/src/encrypted_file.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
|
||||
use chacha20poly1305_ietf::{Key, Nonce};
|
||||
use crypt4gh::header::DecryptedHeaderPackets;
|
||||
use crypt4gh::{Keys, SEGMENT_SIZE};
|
||||
use itertools::Itertools;
|
||||
use sodiumoxide::crypto::aead::chacha20poly1305_ietf;
|
||||
use sodiumoxide::randombytes::randombytes;
|
||||
|
||||
use crate::egafile::EgaFile;
|
||||
use crate::error::{Crypt4GHFSError, Result};
|
||||
use crate::utils;
|
||||
|
||||
const CIPHER_SEGMENT_SIZE: usize = SEGMENT_SIZE + 28;
|
||||
|
||||
pub struct EncryptedFile {
|
||||
opened_files: HashMap<u64, Box<File>>,
|
||||
path: Box<Path>,
|
||||
session_key: [u8; 32],
|
||||
keys: Vec<Keys>,
|
||||
recipient_keys: HashSet<Keys>,
|
||||
write_buffer: Vec<u8>,
|
||||
only_read: bool,
|
||||
|
||||
// Optimization
|
||||
buffer: HashMap<u64, EncryptionBuffer>,
|
||||
session_keys: Vec<Vec<u8>>,
|
||||
header_len: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct EncryptionBuffer {
|
||||
data: Vec<u8>,
|
||||
pos: usize,
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
impl EgaFile for EncryptedFile {
|
||||
fn fh(&self) -> Vec<u64> {
|
||||
self.opened_files.iter().map(|(&fh, _)| fh).collect()
|
||||
}
|
||||
|
||||
fn path(&self) -> Box<std::path::Path> {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn open(&mut self, flags: i32) -> Result<i32> {
|
||||
// Get path
|
||||
let mut path_str = self.path().to_string_lossy().to_string();
|
||||
path_str.push_str(".c4gh");
|
||||
let path = Path::new(&path_str);
|
||||
|
||||
// Open file
|
||||
let mut file = utils::open(path, flags)?;
|
||||
let fh = file.as_raw_fd();
|
||||
|
||||
// Buffer
|
||||
self.buffer.insert(fh as u64, EncryptionBuffer::default());
|
||||
let (keys, header_length) = self.read_header(&mut file).unwrap();
|
||||
self.session_keys = keys;
|
||||
self.header_len = u64::from(header_length);
|
||||
|
||||
// Add to opened files
|
||||
self.opened_files.insert(fh as u64, Box::new(file));
|
||||
|
||||
// Return
|
||||
Ok(fh)
|
||||
}
|
||||
|
||||
fn read(&mut self, fh: u64, offset: i64, size: u32) -> Result<Vec<u8>> {
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
|
||||
let first_segment = offset as usize / SEGMENT_SIZE;
|
||||
let mut off = offset as usize % SEGMENT_SIZE;
|
||||
|
||||
let length = off + size as usize;
|
||||
let mut nsegments = length / SEGMENT_SIZE;
|
||||
if length % SEGMENT_SIZE != 0 {
|
||||
nsegments += 1;
|
||||
}
|
||||
|
||||
let start_pos = first_segment * SEGMENT_SIZE;
|
||||
|
||||
log::debug!("first_segment: {}", first_segment);
|
||||
log::debug!("off: {}", off);
|
||||
log::debug!("length: {}", length);
|
||||
log::debug!("length % SEGMENT_SIZE != 0: {}", length % SEGMENT_SIZE != 0);
|
||||
log::debug!("nsegments: {}", nsegments);
|
||||
log::debug!("start_pos: {}", start_pos);
|
||||
|
||||
let buf = self.buffer.get(&fh).expect("No buffer");
|
||||
|
||||
if buf.valid && buf.pos <= start_pos && ((start_pos - buf.pos) + length) <= buf.data.len() {
|
||||
log::debug!("Already have decrypted enough data");
|
||||
off += start_pos - buf.pos;
|
||||
|
||||
Ok(buf.data[off..off + size as usize].into())
|
||||
}
|
||||
else {
|
||||
f.seek(SeekFrom::Start(
|
||||
self.header_len + first_segment as u64 * CIPHER_SEGMENT_SIZE as u64,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let mut output = Vec::new();
|
||||
|
||||
for _ in 0..nsegments {
|
||||
let mut chunk = Vec::with_capacity(CIPHER_SEGMENT_SIZE);
|
||||
let n = f.take(CIPHER_SEGMENT_SIZE as u64).read_to_end(&mut chunk).unwrap();
|
||||
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let segment = Self::decrypt_block(&chunk, &self.session_keys);
|
||||
output.extend_from_slice(&segment);
|
||||
|
||||
if n < CIPHER_SEGMENT_SIZE {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Output: {}", output.len());
|
||||
|
||||
Ok(output[off..(off + size as usize).min(output.len())].into())
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self, fh: u64) -> Result<()> {
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
if !self.write_buffer.is_empty() {
|
||||
log::info!("Writing PARTIAL segment");
|
||||
let nonce = Nonce::from_slice(&randombytes(12)).expect("Unable to create nonce from randombytes");
|
||||
let key = Key::from_slice(&self.session_key).expect("Unable to create key from session_key");
|
||||
let encrypted_segment = crypt4gh::encrypt_segment(&self.write_buffer, nonce, &key);
|
||||
f.write_all(&encrypted_segment)?;
|
||||
self.write_buffer.clear();
|
||||
}
|
||||
f.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, fh: u64, data: &[u8]) -> Result<usize> {
|
||||
// Write header
|
||||
if self.only_read {
|
||||
// Build header
|
||||
log::debug!("Writing HEADER");
|
||||
let header_bytes = crypt4gh::encrypt_header(&self.recipient_keys, &Some(self.session_key))
|
||||
.map_err(|e| Crypt4GHFSError::Crypt4GHError(e.to_string()))?;
|
||||
log::debug!("Header size = {}", header_bytes.len());
|
||||
|
||||
// Write header
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
f.write_all(&header_bytes)?;
|
||||
|
||||
// Update status
|
||||
self.only_read = false;
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"write_buffer.len() = {}, data.len() = {}",
|
||||
self.write_buffer.len(),
|
||||
data.len()
|
||||
);
|
||||
|
||||
// Chain write buffer with data
|
||||
let last_segment = self.write_buffer.clone();
|
||||
let write_data = last_segment.into_iter().chain(data.iter().copied());
|
||||
|
||||
let mut new_last_segment = Vec::new();
|
||||
for segment in &write_data.chunks(SEGMENT_SIZE) {
|
||||
// Collect segment
|
||||
let segment_slice = segment.collect::<Vec<_>>();
|
||||
log::debug!("segment_slice.len() = {}", segment_slice.len());
|
||||
|
||||
// This is the last segment, add to the struct
|
||||
if segment_slice.len() < SEGMENT_SIZE {
|
||||
log::info!("Storing PARTIAL segment");
|
||||
new_last_segment = segment_slice;
|
||||
}
|
||||
else {
|
||||
log::info!("Writing FULL segment");
|
||||
// Full segment, write to the file
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
|
||||
// Build encrypted segment
|
||||
let nonce = Nonce::from_slice(&randombytes(12)).expect("Unable to create nonce from randombytes");
|
||||
let key = Key::from_slice(&self.session_key).expect("Unable to create key from session_key");
|
||||
let encrypted_segment = crypt4gh::encrypt_segment(&segment_slice, nonce, &key);
|
||||
|
||||
// Write segment
|
||||
f.write_all(&encrypted_segment)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace segment buffer
|
||||
self.write_buffer = new_last_segment;
|
||||
|
||||
// Return
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn truncate(&mut self, fh: Option<u64>, size: u64) -> Result<()> {
|
||||
log::debug!("Truncate: size = {}", size);
|
||||
self.opened_files
|
||||
.iter_mut()
|
||||
.filter(|(&ffh, _)| fh.is_none() || fh == Some(ffh))
|
||||
.try_for_each(|(_, f)| f.set_len(size))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close(&mut self, fh: u64) -> Result<()> {
|
||||
let f = self.opened_files.get(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
assert_eq!(f.as_raw_fd(), fh as i32);
|
||||
self.opened_files.remove(&fh);
|
||||
self.write_buffer.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename(&mut self, new_path: &Path) {
|
||||
self.path = new_path.into();
|
||||
}
|
||||
|
||||
fn attrs(&self, uid: u32, gid: u32) -> Result<fuser::FileAttr> {
|
||||
let mut path_str = self.path.display().to_string();
|
||||
path_str.push_str(".c4gh");
|
||||
let stat = utils::lstat(Path::new(&path_str))?;
|
||||
Ok(utils::stat_to_fileatr(stat, uid, gid))
|
||||
}
|
||||
}
|
||||
|
||||
impl EncryptedFile {
|
||||
pub fn new(file: Option<Box<File>>, path: Box<Path>, keys: &[Keys], recipient_keys: &HashSet<Keys>) -> Self {
|
||||
// Build session_key
|
||||
let mut session_key = [0_u8; 32];
|
||||
sodiumoxide::randombytes::randombytes_into(&mut session_key);
|
||||
|
||||
// Build open files
|
||||
let mut opened_files = HashMap::new();
|
||||
if let Some(f) = file {
|
||||
opened_files.insert(f.as_raw_fd() as u64, f);
|
||||
}
|
||||
|
||||
Self {
|
||||
opened_files,
|
||||
path,
|
||||
session_key,
|
||||
keys: keys.to_vec(),
|
||||
recipient_keys: recipient_keys.clone(),
|
||||
write_buffer: Vec::new(),
|
||||
only_read: true,
|
||||
buffer: HashMap::new(),
|
||||
session_keys: Vec::new(),
|
||||
header_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_header(&self, file: &mut File) -> Result<(Vec<Vec<u8>>, u32)> {
|
||||
// Get header info
|
||||
let mut header_length = 16;
|
||||
let mut temp_buf = [0_u8; 16]; // Size of the header
|
||||
file.read_exact(&mut temp_buf)?;
|
||||
|
||||
let header_info = crypt4gh::header::deconstruct_header_info(&temp_buf).unwrap();
|
||||
|
||||
// Calculate header packets
|
||||
let encrypted_packets = (0..header_info.packets_count)
|
||||
.map(|_| {
|
||||
// Get length
|
||||
let mut length_buffer = [0_u8; 4];
|
||||
file.read_exact(&mut length_buffer).unwrap();
|
||||
let length = u32::from_le_bytes(length_buffer);
|
||||
header_length += length;
|
||||
let length = length - 4;
|
||||
log::debug!("Packet length: {}", length);
|
||||
|
||||
// Get data
|
||||
let mut encrypted_data = vec![0_u8; length as usize];
|
||||
file.read_exact(&mut encrypted_data).unwrap();
|
||||
Ok(encrypted_data)
|
||||
})
|
||||
.collect::<Result<Vec<Vec<u8>>>>()?;
|
||||
|
||||
let DecryptedHeaderPackets {
|
||||
data_enc_packets: session_keys,
|
||||
edit_list_packet: edit_list_content,
|
||||
} = crypt4gh::header::deconstruct_header_body(encrypted_packets, &self.keys, &None).unwrap();
|
||||
|
||||
assert!(edit_list_content.is_none());
|
||||
|
||||
Ok((session_keys, header_length))
|
||||
}
|
||||
|
||||
fn decrypt_block(ciphersegment: &[u8], session_keys: &[Vec<u8>]) -> Vec<u8> {
|
||||
let (nonce_slice, data) = ciphersegment.split_at(12);
|
||||
let nonce = chacha20poly1305_ietf::Nonce::from_slice(nonce_slice).unwrap();
|
||||
|
||||
log::debug!("Nonce slice: {:02x?}", nonce_slice.iter().format(""));
|
||||
log::debug!("Data len = {}", data.len());
|
||||
for key in session_keys {
|
||||
log::debug!("Session keys: {:02x?}", key.iter().format(""));
|
||||
}
|
||||
|
||||
session_keys
|
||||
.iter()
|
||||
.find_map(|key| {
|
||||
chacha20poly1305_ietf::Key::from_slice(key)
|
||||
.and_then(|key| chacha20poly1305_ietf::open(data, None, &nonce, &key).ok())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
55
__fs/crypt4ghfs-rust/src/error.rs
Normal file
55
__fs/crypt4ghfs-rust/src/error.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Crypt4GHFSError>;
|
||||
const DEFAULT_LIBC_ERROR: Errno = Errno::ECANCELED;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Crypt4GHFSError {
|
||||
#[error("Path does not exist (path: {0})")]
|
||||
PathDoesNotExist(PathBuf),
|
||||
#[error("Mounting process failed (ERROR = {0})")]
|
||||
MountError(String),
|
||||
#[error("Fork failed")]
|
||||
ForkFailed,
|
||||
#[error("Secret key not found (path: {0})")]
|
||||
SecretNotFound(PathBuf),
|
||||
#[error("Error reading config (ERROR = {0})")]
|
||||
BadConfig(String),
|
||||
#[error("Unable to extract secret key (ERROR = {0})")]
|
||||
SecretKeyError(String),
|
||||
#[error("Connection url bad format")]
|
||||
BadConfigConnectionUrl,
|
||||
#[error("Invalid checksum format")]
|
||||
InvalidChecksumFormat,
|
||||
#[error("No checksum found")]
|
||||
NoChecksum,
|
||||
#[error("Invalid connection_url scheme: {0}")]
|
||||
InvalidScheme(String),
|
||||
#[error("IO Error: {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
#[error("CLI configuration failed (ERROR = {0})")]
|
||||
ConfigError(#[from] clap::Error),
|
||||
#[error("Config could not configure syslog (ERROR = {0})")]
|
||||
SysLogError(#[from] syslog::Error),
|
||||
#[error("Setting logger failed (ERROR = {0})")]
|
||||
LogError(#[from] log::SetLoggerError),
|
||||
#[error("File not opened")]
|
||||
FileNotOpened,
|
||||
#[error("Crypt4GH failed (ERROR = {0})")]
|
||||
Crypt4GHError(String),
|
||||
#[error("Libc failed (ERROR = {0})")]
|
||||
LibcError(#[from] nix::Error),
|
||||
}
|
||||
|
||||
impl Crypt4GHFSError {
|
||||
pub fn to_raw_os_error(&self) -> i32 {
|
||||
match self {
|
||||
Self::IoError(io_error) => io_error.raw_os_error().unwrap_or(DEFAULT_LIBC_ERROR as i32),
|
||||
_ => DEFAULT_LIBC_ERROR as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
70
__fs/crypt4ghfs-rust/src/file_admin.rs
Normal file
70
__fs/crypt4ghfs-rust/src/file_admin.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::directory::Directory;
|
||||
use crate::egafile::EgaFile;
|
||||
|
||||
type Inode = u64;
|
||||
|
||||
pub struct FileAdmin {
|
||||
pub inode2file: BTreeMap<Inode, Box<dyn EgaFile>>,
|
||||
path2inode: BTreeMap<Box<Path>, Inode>,
|
||||
}
|
||||
|
||||
impl FileAdmin {
|
||||
pub fn new(rootdir: &str) -> Self {
|
||||
// Create file admin
|
||||
let mut file_admin = Self {
|
||||
inode2file: BTreeMap::new(),
|
||||
path2inode: BTreeMap::new(),
|
||||
};
|
||||
|
||||
// Add rootdir
|
||||
file_admin.add(1, Box::new(Directory::new(None, Path::new(rootdir).into())));
|
||||
|
||||
// Return
|
||||
file_admin
|
||||
}
|
||||
|
||||
pub fn add(&mut self, ino: u64, file: Box<dyn EgaFile>) {
|
||||
self.path2inode.insert(file.path(), ino);
|
||||
self.inode2file.insert(ino, file);
|
||||
}
|
||||
|
||||
pub fn get_by_path(&self, path: &Path) -> Option<&dyn EgaFile> {
|
||||
self.path2inode.get(path).map(|ino| self.get_file(*ino))
|
||||
}
|
||||
|
||||
pub fn get_by_path_mut(&mut self, path: &Path) -> Option<&mut dyn EgaFile> {
|
||||
self.path2inode
|
||||
.get(path)
|
||||
.copied()
|
||||
.map(move |ino| self.get_file_mut(ino))
|
||||
}
|
||||
|
||||
pub fn get_file(&self, ino: u64) -> &dyn EgaFile {
|
||||
self.inode2file.get(&ino).expect("Unable to get file").as_ref()
|
||||
}
|
||||
|
||||
pub fn get_file_mut(&mut self, ino: u64) -> &mut dyn EgaFile {
|
||||
self.inode2file.get_mut(&ino).expect("Unable to get file").as_mut()
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, ino: u64) -> Option<Box<dyn EgaFile>> {
|
||||
self.inode2file.remove(&ino)
|
||||
}
|
||||
|
||||
pub fn remove_by_path(&mut self, path: &Path) -> Option<Box<dyn EgaFile>> {
|
||||
// Find inode+
|
||||
let mut inode = None;
|
||||
for (ino, file) in &mut self.inode2file {
|
||||
if file.path() == path.into() {
|
||||
inode = Some(*ino);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove inode
|
||||
self.remove(inode?)
|
||||
}
|
||||
}
|
||||
458
__fs/crypt4ghfs-rust/src/filesystem.rs
Normal file
458
__fs/crypt4ghfs-rust/src/filesystem.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
63
__fs/crypt4ghfs-rust/src/lib.rs
Normal file
63
__fs/crypt4ghfs-rust/src/lib.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
#![allow(
|
||||
clippy::upper_case_acronyms,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::must_use_candidate,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::similar_names
|
||||
)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use config::Config;
|
||||
use error::Crypt4GHFSError;
|
||||
|
||||
pub mod config;
|
||||
mod directory;
|
||||
mod egafile;
|
||||
mod encrypted_file;
|
||||
pub mod error;
|
||||
mod file_admin;
|
||||
mod filesystem;
|
||||
mod regular_file;
|
||||
mod utils;
|
||||
|
||||
pub fn run_with_config(conf: &Config, mountpoint: PathBuf) -> Result<(), Crypt4GHFSError> {
|
||||
// Set log level and logger
|
||||
conf.setup_logger()?;
|
||||
|
||||
let rootdir = conf.get_rootdir();
|
||||
if !Path::new(&rootdir).exists() {
|
||||
return Err(Crypt4GHFSError::PathDoesNotExist(Path::new(&rootdir).into()));
|
||||
}
|
||||
|
||||
// Encryption / Decryption keys
|
||||
let seckey = (conf.get_secret_key()?).map_or_else(
|
||||
|| {
|
||||
log::warn!("No seckey specified");
|
||||
vec![0_u8; 32]
|
||||
},
|
||||
|key| key,
|
||||
);
|
||||
|
||||
let recipients = conf.get_recipients(&seckey);
|
||||
|
||||
// Get options
|
||||
let options = conf.get_options();
|
||||
|
||||
let fs = filesystem::Crypt4ghFS::new(
|
||||
&rootdir,
|
||||
seckey,
|
||||
recipients,
|
||||
nix::unistd::getuid(),
|
||||
nix::unistd::getgid(),
|
||||
);
|
||||
|
||||
if !mountpoint.exists() {
|
||||
return Err(Crypt4GHFSError::PathDoesNotExist(mountpoint));
|
||||
}
|
||||
|
||||
fuser::mount2(fs, &mountpoint, &options).map_err(|e| Crypt4GHFSError::MountError(e.to_string()))
|
||||
}
|
||||
35
__fs/crypt4ghfs-rust/src/main.rs
Normal file
35
__fs/crypt4ghfs-rust/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::fs::File;
|
||||
|
||||
use clap::StructOpt;
|
||||
use crypt4ghfs::error::Crypt4GHFSError;
|
||||
use crypt4ghfs::{config, run_with_config};
|
||||
|
||||
use crate::cli::Args;
|
||||
|
||||
mod cli;
|
||||
|
||||
fn run() -> Result<(), Crypt4GHFSError> {
|
||||
// Init CLI
|
||||
let matches = Args::parse();
|
||||
|
||||
let mountpoint = matches.mountpoint;
|
||||
|
||||
// Read config
|
||||
let config_path = matches.conf;
|
||||
log::info!("Loading config: {:?}", config_path);
|
||||
let config_file = File::open(config_path)?;
|
||||
|
||||
let conf = config::Config::from_file(config_file)?;
|
||||
log::debug!("Config = {:?}", conf);
|
||||
|
||||
// Run
|
||||
run_with_config(&conf, mountpoint)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
log::error!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
99
__fs/crypt4ghfs-rust/src/regular_file.rs
Normal file
99
__fs/crypt4ghfs-rust/src/regular_file.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::egafile::EgaFile;
|
||||
use crate::error::{Crypt4GHFSError, Result};
|
||||
use crate::utils;
|
||||
|
||||
pub struct RegularFile {
|
||||
pub opened_files: HashMap<u64, Box<File>>,
|
||||
pub path: Box<Path>,
|
||||
pub only_read: bool,
|
||||
}
|
||||
|
||||
impl EgaFile for RegularFile {
|
||||
fn fh(&self) -> Vec<u64> {
|
||||
self.opened_files.iter().map(|(&fh, _)| fh).collect()
|
||||
}
|
||||
|
||||
fn path(&self) -> Box<Path> {
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
fn open(&mut self, flags: i32) -> Result<i32> {
|
||||
let path = self.path();
|
||||
let file = utils::open(&path, flags)?;
|
||||
let fh = file.as_raw_fd();
|
||||
self.opened_files.insert(fh as u64, Box::new(file));
|
||||
Ok(fh)
|
||||
}
|
||||
|
||||
fn read(&mut self, fh: u64, offset: i64, size: u32) -> Result<Vec<u8>> {
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
let mut data = Vec::new();
|
||||
f.seek(SeekFrom::Start(offset as u64))?;
|
||||
f.as_ref().take(u64::from(size)).read_to_end(&mut data)?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn flush(&mut self, fh: u64) -> Result<()> {
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
f.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, fh: u64, data: &[u8]) -> Result<usize> {
|
||||
self.only_read = false;
|
||||
let f = self.opened_files.get_mut(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
|
||||
// Write data
|
||||
f.write_all(data)?;
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn truncate(&mut self, fh: Option<u64>, size: u64) -> Result<()> {
|
||||
log::debug!("Truncate: size = {}", size);
|
||||
self.opened_files
|
||||
.iter_mut()
|
||||
.filter(|(&ffh, _)| fh.is_none() || fh == Some(ffh))
|
||||
.try_for_each(|(_, f)| f.set_len(size))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close(&mut self, fh: u64) -> Result<()> {
|
||||
let f = self.opened_files.get(&fh).ok_or(Crypt4GHFSError::FileNotOpened)?;
|
||||
assert_eq!(f.as_raw_fd(), fh as i32);
|
||||
self.opened_files.remove(&fh);
|
||||
self.only_read = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rename(&mut self, new_path: &Path) {
|
||||
self.path = new_path.into();
|
||||
}
|
||||
|
||||
fn attrs(&self, uid: u32, gid: u32) -> Result<fuser::FileAttr> {
|
||||
let stat = utils::lstat(&self.path)?;
|
||||
Ok(utils::stat_to_fileatr(stat, uid, gid))
|
||||
}
|
||||
}
|
||||
|
||||
impl RegularFile {
|
||||
pub fn new(file: Option<Box<File>>, path: Box<Path>) -> Self {
|
||||
// Build open files
|
||||
let mut opened_files = HashMap::new();
|
||||
if let Some(f) = file {
|
||||
opened_files.insert(f.as_raw_fd() as u64, f);
|
||||
}
|
||||
|
||||
// Build RegularFile object
|
||||
Self {
|
||||
opened_files,
|
||||
path,
|
||||
only_read: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
149
__fs/crypt4ghfs-rust/src/utils.rs
Normal file
149
__fs/crypt4ghfs-rust/src/utils.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fs::{DirEntry, File, OpenOptions};
|
||||
use std::io;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crypt4gh::Keys;
|
||||
use fuser::{FileAttr, FileType};
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::sys::stat::{FileStat, Mode, SFlag};
|
||||
use nix::sys::statvfs::Statvfs;
|
||||
|
||||
use crate::directory::Directory;
|
||||
use crate::egafile::EgaFile;
|
||||
use crate::encrypted_file::EncryptedFile;
|
||||
use crate::error::Result;
|
||||
use crate::regular_file::RegularFile;
|
||||
|
||||
pub fn lstat(path: &Path) -> Result<FileStat> {
|
||||
let stat = nix::sys::stat::lstat(path)?;
|
||||
Ok(stat)
|
||||
}
|
||||
|
||||
pub fn stat_to_fileatr(stat: FileStat, uid: u32, gid: u32) -> FileAttr {
|
||||
let mut perm = Mode::from_bits_truncate(stat.st_mode);
|
||||
perm.set(Mode::S_IRWXG, false);
|
||||
perm.set(Mode::S_IRWXO, false);
|
||||
|
||||
let kind = match SFlag::from_bits_truncate(stat.st_mode) & SFlag::S_IFMT {
|
||||
SFlag::S_IFDIR => FileType::Directory,
|
||||
SFlag::S_IFREG => FileType::RegularFile,
|
||||
SFlag::S_IFLNK => FileType::Symlink,
|
||||
SFlag::S_IFBLK => FileType::BlockDevice,
|
||||
SFlag::S_IFCHR => FileType::CharDevice,
|
||||
SFlag::S_IFIFO => FileType::NamedPipe,
|
||||
SFlag::S_IFSOCK => FileType::Socket,
|
||||
_ => panic!("Unknown file type"),
|
||||
};
|
||||
|
||||
FileAttr {
|
||||
ino: stat.st_ino,
|
||||
size: stat.st_size as u64,
|
||||
blocks: stat.st_blocks as u64,
|
||||
atime: SystemTime::UNIX_EPOCH
|
||||
+ Duration::from_secs(stat.st_atime as u64)
|
||||
+ Duration::from_nanos(stat.st_atime_nsec as u64),
|
||||
mtime: SystemTime::UNIX_EPOCH
|
||||
+ Duration::from_secs(stat.st_mtime as u64)
|
||||
+ Duration::from_nanos(stat.st_mtime_nsec as u64),
|
||||
ctime: SystemTime::UNIX_EPOCH
|
||||
+ Duration::from_secs(stat.st_ctime as u64)
|
||||
+ Duration::from_nanos(stat.st_ctime_nsec as u64),
|
||||
crtime: SystemTime::UNIX_EPOCH, // TODO: Is this one okay?
|
||||
kind,
|
||||
perm: perm.bits() as u16,
|
||||
#[cfg(target_os = "macos")]
|
||||
nlink: u32::from(stat.st_nlink),
|
||||
#[cfg(target_os = "linux")]
|
||||
nlink: stat.st_nlink as u32,
|
||||
uid,
|
||||
gid,
|
||||
rdev: stat.st_rdev as u32,
|
||||
blksize: stat.st_blksize as u32,
|
||||
flags: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(entry: &DirEntry) -> fuser::FileType {
|
||||
let kind = entry.file_type().expect("Unable to get file type");
|
||||
if kind.is_file() {
|
||||
fuser::FileType::RegularFile
|
||||
}
|
||||
else if kind.is_dir() {
|
||||
fuser::FileType::Directory
|
||||
}
|
||||
else if kind.is_symlink() {
|
||||
fuser::FileType::Symlink
|
||||
}
|
||||
else {
|
||||
panic!("Unknown file type");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open(path: &Path, flags: i32) -> io::Result<File> {
|
||||
let open_flags = OFlag::from_bits_truncate(flags);
|
||||
OpenOptions::new()
|
||||
.custom_flags(flags)
|
||||
.read(open_flags.contains(OFlag::O_RDONLY) || open_flags.contains(OFlag::O_RDWR))
|
||||
.write(open_flags.contains(OFlag::O_WRONLY) || open_flags.contains(OFlag::O_RDWR))
|
||||
.open(path)
|
||||
}
|
||||
|
||||
pub fn create(path: &Path, flags: i32, _mode: u32) -> io::Result<File> {
|
||||
let open_flags = OFlag::from_bits_truncate(flags);
|
||||
OpenOptions::new()
|
||||
.custom_flags(flags)
|
||||
//.mode(mode)
|
||||
.read(open_flags.contains(OFlag::O_RDONLY) || open_flags.contains(OFlag::O_RDWR))
|
||||
.write(open_flags.contains(OFlag::O_WRONLY) || open_flags.contains(OFlag::O_RDWR))
|
||||
.open(path)
|
||||
}
|
||||
|
||||
pub fn wrap_file(path: &Path, file: File, keys: &[Keys], recipient_keys: &HashSet<Keys>) -> Box<dyn EgaFile> {
|
||||
wrapper(path.into(), Some(Box::new(file)), keys, recipient_keys)
|
||||
}
|
||||
|
||||
pub fn wrap_path(path: &Path, keys: &[Keys], recipient_keys: &HashSet<Keys>) -> Box<dyn EgaFile> {
|
||||
wrapper(path.into(), None, keys, recipient_keys)
|
||||
}
|
||||
|
||||
fn wrapper(
|
||||
path: Box<Path>,
|
||||
file: Option<Box<File>>,
|
||||
keys: &[Keys],
|
||||
recipient_keys: &HashSet<Keys>,
|
||||
) -> Box<dyn EgaFile> {
|
||||
match path.extension() {
|
||||
Some(ext) if ext != "c4gh" => Box::new(RegularFile::new(file, path)),
|
||||
Some(_) => {
|
||||
let mut inbox_path = path.to_path_buf();
|
||||
let mut filename = inbox_path.file_name().unwrap().to_string_lossy().to_string();
|
||||
filename = filename.strip_suffix(".c4gh").unwrap().to_string();
|
||||
inbox_path.set_file_name(filename);
|
||||
Box::new(EncryptedFile::new(
|
||||
file,
|
||||
inbox_path.into_boxed_path(),
|
||||
keys,
|
||||
recipient_keys,
|
||||
))
|
||||
},
|
||||
None => {
|
||||
if path.is_file() {
|
||||
Box::new(EncryptedFile::new(file, path, keys, recipient_keys))
|
||||
}
|
||||
else if path.is_dir() {
|
||||
Box::new(Directory::new(file, path))
|
||||
}
|
||||
else {
|
||||
panic!("Unknown file: {:?}", path)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn statfs(path: &Path) -> Result<Statvfs> {
|
||||
let statvfs = nix::sys::statvfs::statvfs(path)?;
|
||||
Ok(statvfs)
|
||||
}
|
||||
Reference in New Issue
Block a user