use std::sync::Arc;
use libc::{O_RDONLY, O_RDWR};
use crate::nfsserve::nfs::{
fattr3, fileid3, filename3, ftype3, nfspath3, nfsstat3, nfstime3, sattr3, set_atime, set_gid3,
set_mode3, set_mtime, set_size3, set_uid3, specdata3,
};
use crate::nfsserve::vfs::{auth_unix, DirEntry, NFSFileSystem, ReadDirResult, VFSCapabilities};
use secafs_sdk::error::Error as SdkError;
use secafs_sdk::filesystem::FsError;
use secafs_sdk::{
FileSystem, Stats, TimeChange, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG,
S_IFSOCK,
};
use async_trait::async_trait;
use tokio::sync::Mutex;
const ROOT_INO: fileid3 = 1;
fn id_to_fs_ino(id: fileid3) -> i64 {
id as i64
}
fn error_to_nfsstat(e: SdkError) -> nfsstat3 {
match e {
SdkError::Fs(ref fs_err) => match fs_err {
FsError::NotFound => nfsstat3::NFS3ERR_NOENT,
FsError::AlreadyExists => nfsstat3::NFS3ERR_EXIST,
FsError::NotEmpty => nfsstat3::NFS3ERR_NOTEMPTY,
FsError::NotADirectory => nfsstat3::NFS3ERR_NOTDIR,
FsError::IsADirectory => nfsstat3::NFS3ERR_ISDIR,
FsError::NameTooLong => nfsstat3::NFS3ERR_NAMETOOLONG,
FsError::RootOperation => nfsstat3::NFS3ERR_ACCES,
_ => nfsstat3::NFS3ERR_IO,
},
SdkError::ConnectionPoolTimeout => nfsstat3::NFS3ERR_JUKEBOX,
_ => nfsstat3::NFS3ERR_IO,
}
}
pub struct AgentNFS {
fs: Arc<Mutex<dyn FileSystem>>,
}
impl AgentNFS {
pub fn new(fs: Arc<Mutex<dyn FileSystem>>) -> Self {
AgentNFS { fs }
}
fn stats_to_fattr(&self, stats: &Stats) -> fattr3 {
let ftype = match stats.mode & S_IFMT {
S_IFREG => ftype3::NF3REG,
S_IFDIR => ftype3::NF3DIR,
S_IFLNK => ftype3::NF3LNK,
S_IFIFO => ftype3::NF3FIFO,
S_IFCHR => ftype3::NF3CHR,
S_IFBLK => ftype3::NF3BLK,
S_IFSOCK => ftype3::NF3SOCK,
_ => ftype3::NF3REG,
};
let rdev = specdata3 {
specdata1: libc::major(stats.rdev as libc::dev_t) as u32,
specdata2: libc::minor(stats.rdev as libc::dev_t) as u32,
};
fattr3 {
ftype,
mode: stats.mode & 0o7777,
nlink: stats.nlink,
uid: stats.uid,
gid: stats.gid,
size: stats.size as u64,
used: stats.size as u64,
rdev,
fsid: 0,
fileid: stats.ino as fileid3,
atime: nfstime3 {
seconds: stats.atime as u32,
nseconds: stats.atime_nsec,
},
mtime: nfstime3 {
seconds: stats.mtime as u32,
nseconds: stats.mtime_nsec,
},
ctime: nfstime3 {
seconds: stats.ctime as u32,
nseconds: stats.ctime_nsec,
},
}
}
}
#[async_trait]
impl NFSFileSystem for AgentNFS {
fn root_dir(&self) -> fileid3 {
ROOT_INO
}
fn capabilities(&self) -> VFSCapabilities {
VFSCapabilities::ReadWrite
}
async fn lookup(&self, dirid: fileid3, filename: &filename3) -> Result<fileid3, nfsstat3> {
let name = std::str::from_utf8(filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
if name == "." {
return Ok(dirid);
}
let fs = self.fs.lock().await;
if name == ".." {
let stats = fs
.lookup(id_to_fs_ino(dirid), "..")
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
return Ok(stats.ino as fileid3);
}
let dir_stats = fs
.getattr(id_to_fs_ino(dirid))
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
if !dir_stats.is_directory() {
return Err(nfsstat3::NFS3ERR_NOTDIR);
}
let stats = fs
.lookup(id_to_fs_ino(dirid), name)
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
Ok(stats.ino as fileid3)
}
async fn getattr(&self, id: fileid3) -> Result<fattr3, nfsstat3> {
let fs = self.fs.lock().await;
let stats = fs
.getattr(id_to_fs_ino(id))
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
Ok(self.stats_to_fattr(&stats))
}
async fn setattr(&self, id: fileid3, setattr: sattr3) -> Result<fattr3, nfsstat3> {
let fs_ino = id_to_fs_ino(id);
let fs = self.fs.lock().await;
if let set_mode3::mode(mode) = setattr.mode {
fs.chmod(fs_ino, mode).await.map_err(error_to_nfsstat)?;
}
let new_uid = if let set_uid3::uid(uid) = setattr.uid {
Some(uid)
} else {
None
};
let new_gid = if let set_gid3::gid(gid) = setattr.gid {
Some(gid)
} else {
None
};
if new_uid.is_some() || new_gid.is_some() {
fs.chown(fs_ino, new_uid, new_gid)
.await
.map_err(error_to_nfsstat)?;
}
if let set_size3::size(size) = setattr.size {
let file = fs.open(fs_ino, O_RDWR).await.map_err(error_to_nfsstat)?;
file.truncate(size).await.map_err(error_to_nfsstat)?;
}
let new_atime = match setattr.atime {
set_atime::SET_TO_CLIENT_TIME(t) => TimeChange::Set(t.seconds as i64, t.nseconds),
set_atime::SET_TO_SERVER_TIME => TimeChange::Now,
set_atime::DONT_CHANGE => TimeChange::Omit,
};
let new_mtime = match setattr.mtime {
set_mtime::SET_TO_CLIENT_TIME(t) => TimeChange::Set(t.seconds as i64, t.nseconds),
set_mtime::SET_TO_SERVER_TIME => TimeChange::Now,
set_mtime::DONT_CHANGE => TimeChange::Omit,
};
if !matches!(new_atime, TimeChange::Omit) || !matches!(new_mtime, TimeChange::Omit) {
fs.utimens(fs_ino, new_atime, new_mtime)
.await
.map_err(error_to_nfsstat)?;
}
let stats = fs
.getattr(fs_ino)
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
Ok(self.stats_to_fattr(&stats))
}
async fn read(
&self,
id: fileid3,
offset: u64,
count: u32,
) -> Result<(Vec<u8>, bool), nfsstat3> {
let fs = self.fs.lock().await;
let file = fs
.open(id_to_fs_ino(id), O_RDONLY)
.await
.map_err(|_| nfsstat3::NFS3ERR_NOENT)?;
let data = file
.pread(offset, count as u64)
.await
.map_err(error_to_nfsstat)?;
let stats = file.fstat().await.map_err(error_to_nfsstat)?;
let eof = offset + data.len() as u64 >= stats.size as u64;
Ok((data, eof))
}
async fn write(&self, id: fileid3, offset: u64, data: &[u8]) -> Result<fattr3, nfsstat3> {
let fs = self.fs.lock().await;
let file = fs
.open(id_to_fs_ino(id), O_RDWR)
.await
.map_err(error_to_nfsstat)?;
file.pwrite(offset, data).await.map_err(error_to_nfsstat)?;
let stats = fs
.getattr(id_to_fs_ino(id))
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
Ok(self.stats_to_fattr(&stats))
}
async fn create(
&self,
dirid: fileid3,
filename: &filename3,
attr: sattr3,
auth: &auth_unix,
) -> Result<(fileid3, fattr3), nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let mode = match attr.mode {
set_mode3::mode(m) => m & 0o7777,
set_mode3::Void => 0o644,
};
let fs = self.fs.lock().await;
let (stats, _file) = fs
.create_file(dir_fs_ino, name, S_IFREG | mode, auth.uid, auth.gid)
.await
.map_err(error_to_nfsstat)?;
let ino = stats.ino as fileid3;
let fattr = self.stats_to_fattr(&stats);
Ok((ino, fattr))
}
async fn create_exclusive(
&self,
dirid: fileid3,
filename: &filename3,
auth: &auth_unix,
) -> Result<fileid3, nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let fs = self.fs.lock().await;
if fs
.lookup(dir_fs_ino, name)
.await
.map_err(error_to_nfsstat)?
.is_some()
{
return Err(nfsstat3::NFS3ERR_EXIST);
}
let (stats, _file) = fs
.create_file(dir_fs_ino, name, S_IFREG | 0o644, auth.uid, auth.gid)
.await
.map_err(error_to_nfsstat)?;
Ok(stats.ino as fileid3)
}
async fn mkdir(
&self,
dirid: fileid3,
dirname: &filename3,
attr: sattr3,
auth: &auth_unix,
) -> Result<(fileid3, fattr3), nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(dirname).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let mode = match attr.mode {
set_mode3::mode(m) => m & 0o7777,
set_mode3::Void => 0o755,
};
let fs = self.fs.lock().await;
let stats = fs
.mkdir(dir_fs_ino, name, mode, auth.uid, auth.gid)
.await
.map_err(error_to_nfsstat)?;
let ino = stats.ino as fileid3;
let fattr = self.stats_to_fattr(&stats);
Ok((ino, fattr))
}
async fn mknod(
&self,
dirid: fileid3,
filename: &filename3,
ftype: ftype3,
attr: sattr3,
rdev: specdata3,
auth: &auth_unix,
) -> Result<(fileid3, fattr3), nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let perm_mode = match attr.mode {
set_mode3::mode(m) => m & 0o7777,
set_mode3::Void => 0o644,
};
let type_mode = match ftype {
ftype3::NF3CHR => S_IFCHR,
ftype3::NF3BLK => S_IFBLK,
ftype3::NF3SOCK => S_IFSOCK,
ftype3::NF3FIFO => S_IFIFO,
_ => return Err(nfsstat3::NFS3ERR_BADTYPE),
};
let rdev_val = libc::makedev(rdev.specdata1 as _, rdev.specdata2 as _) as u64;
let fs = self.fs.lock().await;
let stats = fs
.mknod(
dir_fs_ino,
name,
type_mode | perm_mode,
rdev_val,
auth.uid,
auth.gid,
)
.await
.map_err(error_to_nfsstat)?;
let ino = stats.ino as fileid3;
let fattr = self.stats_to_fattr(&stats);
Ok((ino, fattr))
}
async fn remove(&self, dirid: fileid3, filename: &filename3) -> Result<(), nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let fs = self.fs.lock().await;
let stats = fs
.lookup(dir_fs_ino, name)
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
if stats.is_directory() {
fs.rmdir(dir_fs_ino, name).await.map_err(error_to_nfsstat)?;
} else {
fs.unlink(dir_fs_ino, name)
.await
.map_err(error_to_nfsstat)?;
}
Ok(())
}
async fn rename(
&self,
from_dirid: fileid3,
from_filename: &filename3,
to_dirid: fileid3,
to_filename: &filename3,
) -> Result<(), nfsstat3> {
let from_dir_fs_ino = id_to_fs_ino(from_dirid);
let to_dir_fs_ino = id_to_fs_ino(to_dirid);
let from_name = std::str::from_utf8(from_filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let to_name = std::str::from_utf8(to_filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let fs = self.fs.lock().await;
fs.rename(from_dir_fs_ino, from_name, to_dir_fs_ino, to_name)
.await
.map_err(error_to_nfsstat)?;
Ok(())
}
async fn link(
&self,
id: fileid3,
dirid: fileid3,
filename: &filename3,
) -> Result<fattr3, nfsstat3> {
let fs_ino = id_to_fs_ino(id);
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(filename).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let fs = self.fs.lock().await;
let stats = fs
.link(fs_ino, dir_fs_ino, name)
.await
.map_err(error_to_nfsstat)?;
Ok(self.stats_to_fattr(&stats))
}
async fn readdir(
&self,
dirid: fileid3,
start_after: fileid3,
max_entries: usize,
) -> Result<ReadDirResult, nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let fs = self.fs.lock().await;
let entries = fs
.readdir_plus(dir_fs_ino)
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
drop(fs);
let mut result = ReadDirResult {
entries: Vec::new(),
end: false,
};
let mut skip = start_after > 0;
let mut skipped_count = 0;
for entry in entries.iter() {
let ino = entry.stats.ino as fileid3;
if skip {
if ino == start_after {
skip = false;
}
skipped_count += 1;
continue;
}
if result.entries.len() >= max_entries {
break;
}
result.entries.push(DirEntry {
fileid: ino,
name: entry.name.as_bytes().into(),
attr: self.stats_to_fattr(&entry.stats),
});
}
result.end = result.entries.len() + skipped_count >= entries.len();
Ok(result)
}
async fn symlink(
&self,
dirid: fileid3,
linkname: &filename3,
symlink: &nfspath3,
_attr: &sattr3,
auth: &auth_unix,
) -> Result<(fileid3, fattr3), nfsstat3> {
let dir_fs_ino = id_to_fs_ino(dirid);
let name = std::str::from_utf8(linkname).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let target = std::str::from_utf8(symlink).map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
let fs = self.fs.lock().await;
let stats = fs
.symlink(dir_fs_ino, name, target, auth.uid, auth.gid)
.await
.map_err(error_to_nfsstat)?;
let ino = stats.ino as fileid3;
let fattr = self.stats_to_fattr(&stats);
Ok((ino, fattr))
}
async fn readlink(&self, id: fileid3) -> Result<nfspath3, nfsstat3> {
let fs = self.fs.lock().await;
let target = fs
.readlink(id_to_fs_ino(id))
.await
.map_err(error_to_nfsstat)?
.ok_or(nfsstat3::NFS3ERR_NOENT)?;
Ok(target.into_bytes().into())
}
}