use std::{
collections::HashMap,
ffi::{OsStr, OsString},
io::{Read, Seek, SeekFrom},
path::Path,
sync::{
RwLock,
atomic::{AtomicU64, Ordering},
},
time::{Duration, SystemTime},
};
use chrono::{DateTime, Utc};
use fuse_mt::{
CallbackResult, DirectoryEntry as FuseDirectoryEntry, FileAttr, FileType, FilesystemMT,
RequestInfo, ResultData, ResultEmpty, ResultEntry, ResultOpen, ResultReaddir, ResultSlice,
ResultStatfs, ResultXattr, Statfs,
};
static NEXT_DIR_FD: AtomicU64 = AtomicU64::new(0x1000);
use crate::{
common::{CvmfsError, CvmfsResult, FileLike},
directory_entry::DirectoryEntry,
repository::Repository,
};
const TTL: Duration = Duration::from_secs(1);
fn map_dirent_type_to_fs_kind(dirent: &DirectoryEntry) -> FileType {
if dirent.is_directory() {
FileType::Directory
} else if dirent.is_symlink() {
FileType::Symlink
} else {
FileType::RegularFile
}
}
#[derive(Debug)]
pub struct CernvmFileSystem {
repository: RwLock<Repository>,
opened_files: RwLock<HashMap<String, Box<dyn FileLike>>>,
}
impl FilesystemMT for CernvmFileSystem {
fn destroy(&self) {
if let Ok(mut f) = self.opened_files.write() {
f.drain();
};
}
fn getattr(&self, _req: RequestInfo, path: &Path, _fh: Option<u64>) -> ResultEntry {
let path = path.to_str().ok_or(CvmfsError::FileNotFound)?;
log::info!("Getting attribute of path: {path}");
let mut repo =
self.repository.write().map_err(|e| CvmfsError::Generic(format!("{:?}", e)))?;
let result = repo.lookup(path)?;
let date_time: DateTime<Utc> =
DateTime::from_timestamp(result.mtime, 0).ok_or(CvmfsError::InvalidTimestamp)?;
let time = SystemTime::from(date_time);
let size = result.size as u64;
let nlink = if result.is_directory() { 2 } else { 1 };
let file_attr = FileAttr {
size,
blocks: 1 + size / 512,
atime: time,
mtime: time,
ctime: time,
crtime: time,
kind: map_dirent_type_to_fs_kind(&result),
perm: result.mode & 0o7777,
nlink,
uid: 0,
gid: 0,
rdev: 0,
flags: 0,
};
Ok((TTL, file_attr))
}
fn readlink(&self, _req: RequestInfo, path: &Path) -> ResultData {
let path = path.to_str().ok_or(CvmfsError::FileNotFound)?;
log::info!("Reading link: {path}");
let mut repo = self.repository.write().map_err(|_| CvmfsError::Sync)?;
let result = repo.lookup(path)?;
if !result.is_symlink() {
return Err(libc::ENOLINK);
}
Ok(result.symlink.ok_or(CvmfsError::FileNotFound)?.into_bytes())
}
fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
let path = path.to_str().ok_or(CvmfsError::FileNotFound)?;
log::info!("Opening file: {path}");
let mut repo = self.repository.write().map_err(|_| CvmfsError::Sync)?;
let result = repo.lookup(path)?;
if !result.is_file() {
return Err(libc::ENOENT);
}
let file = repo.get_file(path)?;
let fd = file.as_raw_fd() as u64;
self.opened_files
.write()
.map_err(|_| CvmfsError::Sync)?
.insert(path.into(), file);
Ok((fd, 0))
}
fn read(
&self,
_req: RequestInfo,
path: &Path,
_fh: u64,
offset: u64,
size: u32,
callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult,
) -> CallbackResult {
let path = match path.to_str() {
Some(p) => p,
None => return callback(Err(libc::ENOENT)),
};
log::info!("Reading file: {path}");
let mut opened_files = match self.opened_files.write() {
Ok(guard) => guard,
Err(e) => {
log::error!("{:?}", e);
return callback(Err(libc::EIO));
}
};
let file = match opened_files.get_mut(path) {
Some(f) => f,
None => return callback(Err(libc::ENOENT)),
};
let mut data = vec![0u8; size as usize];
if let Err(e) = file.seek(SeekFrom::Start(offset)) {
log::error!("{:?}", e);
return callback(Err(match e.raw_os_error() {
Some(code) => code,
None => libc::EIO,
}));
}
let bytes_read = match file.read(&mut data) {
Ok(n) => n,
Err(e) => {
log::error!("{:?}", e);
return callback(Err(match e.raw_os_error() {
Some(code) => code,
None => libc::EIO,
}));
}
};
callback(Ok(&data[0..bytes_read]))
}
fn flush(&self, _req: RequestInfo, path: &Path, _fh: u64, _lock_owner: u64) -> ResultEmpty {
let path = path.to_str().ok_or(libc::ENOENT)?;
log::info!("Flushing file: {path}");
Ok(())
}
fn release(
&self,
_req: RequestInfo,
path: &Path,
_fh: u64,
_flags: u32,
_lock_owner: u64,
_flush: bool,
) -> ResultEmpty {
let path = path.to_str().ok_or(libc::ENOENT)?;
log::info!("Releasing: {path}");
match self
.opened_files
.write()
.map_err(|e| {
log::error!("{:?}", e);
libc::EIO
})?
.remove(path)
{
None => Err(libc::ENOENT),
Some(_) => Ok(()),
}
}
fn opendir(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen {
let path = path.to_str().ok_or(libc::ENOENT)?;
log::info!("Opening directory: {path}");
let mut repo = match self.repository.write() {
Ok(repo) => repo,
Err(e) => {
log::error!("{:?}", e);
return Err(libc::EIO);
}
};
let result = repo.lookup(path)?;
if !result.is_directory() {
return Err(libc::ENOENT);
}
let fd = NEXT_DIR_FD.fetch_add(1, Ordering::Relaxed);
Ok((fd, 0))
}
fn readdir(&self, _req: RequestInfo, path: &Path, _fh: u64) -> ResultReaddir {
let path = path.to_str().ok_or(libc::ENOENT)?;
log::info!("Reading directory: {path}");
let mut repo = self.repository.write().map_err(|_| libc::EIO)?;
let result = repo.lookup(path)?;
if !result.is_directory() {
log::error!("Path '{path}' is not a directory");
return Err(libc::ENOENT);
}
match repo.list_directory(path) {
Ok(entries) => Ok(entries
.into_iter()
.map(|dirent| FuseDirectoryEntry {
kind: map_dirent_type_to_fs_kind(&dirent),
name: OsString::from(dirent.name),
})
.collect()),
Err(e) => {
log::error!("Could not list directory {path}: {:?}", e);
Err(e.into())
}
}
}
fn releasedir(&self, _req: RequestInfo, _path: &Path, _fh: u64, _flags: u32) -> ResultEmpty {
Ok(())
}
fn statfs(&self, _req: RequestInfo, _path: &Path) -> ResultStatfs {
log::info!("Getting FS statistics");
let mut repo = self.repository.write().map_err(|_| libc::EIO)?;
let statistics = repo.get_statistics()?;
Ok(Statfs {
blocks: 1 + statistics.file_size as u64 / 512,
bfree: 0,
bavail: 0,
files: statistics.regular as u64,
ffree: 0,
bsize: 512,
namelen: 255,
frsize: 512,
})
}
fn getxattr(&self, _req: RequestInfo, _path: &Path, _name: &OsStr, _size: u32) -> ResultXattr {
Err(libc::ENODATA)
}
fn access(&self, _req: RequestInfo, path: &Path, _mask: u32) -> ResultEmpty {
let path = path.to_str().ok_or(libc::ENOENT)?;
log::info!("Accessing: {path}");
let mut repo = self.repository.write().map_err(|_| libc::EIO)?;
Ok(repo.lookup(path).map(|_| ())?)
}
}
impl CernvmFileSystem {
pub fn new(repository: Repository) -> CvmfsResult<Self> {
Ok(Self { repository: RwLock::new(repository), opened_files: Default::default() })
}
}