use crate::Time;
use crate::error::{NfsError, Result};
use async_trait::async_trait;
use bytes::Bytes;
use futures::TryStreamExt;
use futures::stream::Stream;
use std::pin::Pin;
pub(crate) fn block_on_compat<F: std::future::Future>(f: F) -> F::Output {
match tokio::runtime::Handle::try_current() {
Ok(handle) if handle.runtime_flavor() == tokio::runtime::RuntimeFlavor::MultiThread => {
tokio::task::block_in_place(|| handle.block_on(f))
}
_ => futures::executor::block_on(f),
}
}
pub type ReaddirStream<'a> = Pin<Box<dyn Stream<Item = Result<ReaddirEntry>> + Send + 'a>>;
pub type ReaddirplusStream<'a> = Pin<Box<dyn Stream<Item = Result<ReaddirplusEntry>> + Send + 'a>>;
pub const OPEN_READ: u32 = 1;
pub const OPEN_WRITE: u32 = 2;
pub const OPEN_BOTH: u32 = 3;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Nfs41ChannelLimits {
pub max_request_size: u32,
pub max_response_size: u32,
pub max_cached_response_size: u32,
pub max_operations: u32,
pub max_requests: u32,
pub effective_highest_slot_id: u32,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Nfs41CallbackStats {
pub layout_recalls_received: u64,
pub layout_returns_completed: u64,
}
#[async_trait]
pub trait Mount: std::fmt::Debug + Send + Sync {
fn get_max_read_size(&self) -> u32;
fn get_max_write_size(&self) -> u32;
async fn nfs41_channel_limits(&self) -> Option<Nfs41ChannelLimits> {
None
}
async fn nfs41_callback_stats(&self) -> Option<Nfs41CallbackStats> {
None
}
async fn null(&self) -> Result<()>;
async fn access(&self, fh: Bytes, mode: u32) -> Result<u32>;
async fn access_path(&self, path: &str, mode: u32) -> Result<u32> {
let res = self.lookup_path(path).await?;
self.access(res.fh, mode).await
}
async fn open(&self, dir_fh: Bytes, filename: &str, _access: u32) -> Result<ObjRes> {
self.lookup(dir_fh, filename).await
}
async fn open_path(&self, path: &str, _access: u32) -> Result<ObjRes> {
self.lookup_path(path).await
}
async fn close(&self, _fh: Bytes) -> Result<()> {
Ok(()) }
async fn commit(&self, fh: Bytes, offset: u64, count: u32) -> Result<()>;
async fn commit_path(&self, path: &str, offset: u64, count: u32) -> Result<()> {
let res = self.lookup_path(path).await?;
self.commit(res.fh, offset, count).await
}
async fn create(&self, dir_fh: Bytes, filename: &str, mode: Option<u32>) -> Result<ObjRes>;
async fn create_path(&self, path: &str, mode: Option<u32>) -> Result<ObjRes>;
async fn delegpurge(&self, _clientid: u64) -> Result<()> {
Err(NfsError::Unsupported(
"DELEGPURGE requires NFSv4".to_string(),
))
}
async fn delegreturn(&self, _stateid: u64) -> Result<()> {
Err(NfsError::Unsupported(
"DELEGRETURN requires NFSv4".to_string(),
))
}
async fn lock(&self, _fh: Bytes, _lock_type: u32, _offset: u64, _length: u64) -> Result<Bytes> {
Err(NfsError::Unsupported("LOCK requires NFSv4".to_string()))
}
async fn locku(
&self,
_fh: Bytes,
_lock_stateid: Bytes,
_lock_type: u32,
_offset: u64,
_length: u64,
) -> Result<()> {
Err(NfsError::Unsupported("LOCKU requires NFSv4".to_string()))
}
async fn getacl(&self, _fh: Bytes) -> Result<Acl> {
Err(NfsError::Unsupported("GETACL requires NFSv4".to_string()))
}
async fn getacl_path(&self, path: &str) -> Result<Acl> {
let res = self.lookup_path(path).await?;
self.getacl(res.fh).await
}
async fn setacl(&self, _fh: Bytes, _acl: &Acl) -> Result<()> {
Err(NfsError::Unsupported("SETACL requires NFSv4".to_string()))
}
async fn setacl_path(&self, path: &str, acl: &Acl) -> Result<()> {
let res = self.lookup_path(path).await?;
self.setacl(res.fh, acl).await
}
async fn aclsupport(&self, _fh: Bytes) -> Result<AclSupport> {
Err(NfsError::Unsupported(
"ACLSUPPORT requires NFSv4".to_string(),
))
}
async fn getxattr(&self, _fh: Bytes, _name: &str) -> Result<Bytes> {
Err(NfsError::Unsupported(
"Named attributes require NFSv4".to_string(),
))
}
async fn getxattr_path(&self, path: &str, name: &str) -> Result<Bytes> {
let res = self.lookup_path(path).await?;
self.getxattr(res.fh, name).await
}
async fn setxattr(&self, _fh: Bytes, _name: &str, _value: Bytes) -> Result<()> {
Err(NfsError::Unsupported(
"Named attributes require NFSv4".to_string(),
))
}
async fn setxattr_path(&self, path: &str, name: &str, value: Bytes) -> Result<()> {
let res = self.lookup_path(path).await?;
self.setxattr(res.fh, name, value).await
}
async fn listxattr(&self, _fh: Bytes) -> Result<Vec<String>> {
Err(NfsError::Unsupported(
"Named attributes require NFSv4".to_string(),
))
}
async fn listxattr_path(&self, path: &str) -> Result<Vec<String>> {
let res = self.lookup_path(path).await?;
self.listxattr(res.fh).await
}
async fn removexattr(&self, _fh: Bytes, _name: &str) -> Result<()> {
Err(NfsError::Unsupported(
"Named attributes require NFSv4".to_string(),
))
}
async fn removexattr_path(&self, path: &str, name: &str) -> Result<()> {
let res = self.lookup_path(path).await?;
self.removexattr(res.fh, name).await
}
async fn fsinfo(&self) -> Result<FSInfo>;
async fn fsstat(&self) -> Result<FSStat>;
async fn getattr(&self, fh: Bytes) -> Result<Attr>;
async fn getattr_path(&self, path: &str) -> Result<Attr> {
let res = self.lookup_path(path).await?;
self.getattr(res.fh).await
}
#[allow(clippy::too_many_arguments)]
async fn setattr(
&self,
fh: Bytes,
guard_ctime: Option<Time>,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<Time>,
mtime: Option<Time>,
) -> Result<()>;
#[allow(clippy::too_many_arguments)]
async fn setattr_path(
&self,
path: &str,
specify_guard: bool,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<Time>,
mtime: Option<Time>,
) -> Result<()>;
async fn getfh(&self) -> Bytes;
async fn link(&self, src_fh: Bytes, dst_dir_fh: Bytes, dst_filename: &str) -> Result<Attr>;
async fn link_path(&self, src_path: &str, dst_path: &str) -> Result<Attr>;
async fn symlink(
&self,
src_path: &str,
dst_dir_fh: Bytes,
dst_filename: &str,
) -> Result<ObjRes>;
async fn symlink_path(&self, src_path: &str, dst_path: &str) -> Result<ObjRes>;
#[allow(clippy::too_many_arguments)]
async fn symlink_with_attrs(
&self,
src_path: &str,
dst_dir_fh: Bytes,
dst_filename: &str,
uid: Option<u32>,
gid: Option<u32>,
atime: Option<Time>,
mtime: Option<Time>,
) -> Result<ObjRes> {
let obj = self.symlink(src_path, dst_dir_fh, dst_filename).await?;
if uid.is_some() || gid.is_some() || atime.is_some() || mtime.is_some() {
self.setattr(obj.fh.clone(), None, None, uid, gid, None, atime, mtime)
.await?;
}
Ok(obj)
}
async fn readlink(&self, fh: Bytes) -> Result<String>;
async fn readlink_path(&self, path: &str) -> Result<String> {
let res = self.lookup_path(path).await?;
self.readlink(res.fh).await
}
async fn lookup(&self, dir_fh: Bytes, filename: &str) -> Result<ObjRes>;
async fn lookup_path(&self, path: &str) -> Result<ObjRes>;
async fn pathconf(&self, fh: Bytes) -> Result<Pathconf>;
async fn pathconf_path(&self, path: &str) -> Result<Pathconf> {
let res = self.lookup_path(path).await?;
self.pathconf(res.fh).await
}
async fn read(&self, fh: Bytes, offset: u64, count: u32) -> Result<Bytes>;
async fn read_path(&self, path: &str, offset: u64, count: u32) -> Result<Bytes> {
let res = self.lookup_path(path).await?;
self.read(res.fh, offset, count).await
}
async fn write(&self, fh: Bytes, offset: u64, data: Bytes) -> Result<u32>;
async fn write_path(&self, path: &str, offset: u64, data: Bytes) -> Result<u32> {
let res = self.lookup_path(path).await?;
self.write(res.fh, offset, data).await
}
async fn readdir(&self, dir_fh: Bytes) -> ReaddirStream<'_>;
async fn readdir_path(&self, dir_path: &str) -> Result<ReaddirStream<'_>> {
let res = self.lookup_path(dir_path).await?;
Ok(self.readdir(res.fh).await)
}
async fn readdirplus(&self, dir_fh: Bytes) -> ReaddirplusStream<'_>;
async fn readdirplus_path(&self, dir_path: &str) -> Result<ReaddirplusStream<'_>> {
let res = self.lookup_path(dir_path).await?;
Ok(self.readdirplus(res.fh).await)
}
async fn mkdir(&self, dir_fh: Bytes, dirname: &str, mode: u32) -> Result<ObjRes>;
async fn mkdir_path(&self, path: &str, mode: u32) -> Result<ObjRes>;
async fn remove(&self, dir_fh: Bytes, filename: &str) -> Result<()>;
async fn remove_path(&self, path: &str) -> Result<()>;
async fn rmdir(&self, dir_fh: Bytes, dirname: &str) -> Result<()>;
async fn rmdir_path(&self, path: &str) -> Result<()>;
async fn rename(
&self,
from_dir_fh: Bytes,
from_filename: &str,
to_dir_fh: Bytes,
to_filename: &str,
) -> Result<()>;
async fn rename_path(&self, from_path: &str, to_path: &str) -> Result<()>;
async fn umount(&self) -> Result<()>;
fn version(&self) -> NFSVersion;
fn sync_null(&self) -> Result<()> {
block_on_compat(self.null())
}
fn sync_access(&self, fh: Bytes, mode: u32) -> Result<u32> {
block_on_compat(self.access(fh, mode))
}
fn sync_access_path(&self, path: &str, mode: u32) -> Result<u32> {
block_on_compat(self.access_path(path, mode))
}
fn sync_open(&self, dir_fh: Bytes, filename: &str, access: u32) -> Result<ObjRes> {
block_on_compat(self.open(dir_fh, filename, access))
}
fn sync_open_path(&self, path: &str, access: u32) -> Result<ObjRes> {
block_on_compat(self.open_path(path, access))
}
fn sync_close(&self, fh: Bytes) -> Result<()> {
block_on_compat(self.close(fh))
}
fn sync_commit(&self, fh: Bytes, offset: u64, count: u32) -> Result<()> {
block_on_compat(self.commit(fh, offset, count))
}
fn sync_commit_path(&self, path: &str, offset: u64, count: u32) -> Result<()> {
block_on_compat(self.commit_path(path, offset, count))
}
fn sync_create(&self, dir_fh: Bytes, filename: &str, mode: Option<u32>) -> Result<ObjRes> {
block_on_compat(self.create(dir_fh, filename, mode))
}
fn sync_create_path(&self, path: &str, mode: Option<u32>) -> Result<ObjRes> {
block_on_compat(self.create_path(path, mode))
}
fn sync_delegpurge(&self, clientid: u64) -> Result<()> {
block_on_compat(self.delegpurge(clientid))
}
fn sync_delegreturn(&self, stateid: u64) -> Result<()> {
block_on_compat(self.delegreturn(stateid))
}
fn sync_fsinfo(&self) -> Result<FSInfo> {
block_on_compat(self.fsinfo())
}
fn sync_fsstat(&self) -> Result<FSStat> {
block_on_compat(self.fsstat())
}
fn sync_getattr(&self, fh: Bytes) -> Result<Attr> {
block_on_compat(self.getattr(fh))
}
fn sync_getattr_path(&self, path: &str) -> Result<Attr> {
block_on_compat(self.getattr_path(path))
}
#[allow(clippy::too_many_arguments)]
fn sync_setattr(
&self,
fh: Bytes,
guard_ctime: Option<Time>,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<Time>,
mtime: Option<Time>,
) -> Result<()> {
block_on_compat(self.setattr(fh, guard_ctime, mode, uid, gid, size, atime, mtime))
}
#[allow(clippy::too_many_arguments)]
fn sync_setattr_path(
&self,
path: &str,
specify_guard: bool,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<Time>,
mtime: Option<Time>,
) -> Result<()> {
block_on_compat(self.setattr_path(path, specify_guard, mode, uid, gid, size, atime, mtime))
}
fn sync_getfh(&self) -> Bytes {
block_on_compat(self.getfh())
}
fn sync_link(&self, src_fh: Bytes, dst_dir_fh: Bytes, dst_filename: &str) -> Result<Attr> {
block_on_compat(self.link(src_fh, dst_dir_fh, dst_filename))
}
fn sync_link_path(&self, src_path: &str, dst_path: &str) -> Result<Attr> {
block_on_compat(self.link_path(src_path, dst_path))
}
fn sync_symlink(
&self,
src_path: &str,
dst_dir_fh: Bytes,
dst_filename: &str,
) -> Result<ObjRes> {
block_on_compat(self.symlink(src_path, dst_dir_fh, dst_filename))
}
fn sync_symlink_path(&self, src_path: &str, dst_path: &str) -> Result<ObjRes> {
block_on_compat(self.symlink_path(src_path, dst_path))
}
fn sync_readlink(&self, fh: Bytes) -> Result<String> {
block_on_compat(self.readlink(fh))
}
fn sync_readlink_path(&self, path: &str) -> Result<String> {
block_on_compat(self.readlink_path(path))
}
fn sync_lookup(&self, dir_fh: Bytes, filename: &str) -> Result<ObjRes> {
block_on_compat(self.lookup(dir_fh, filename))
}
fn sync_lookup_path(&self, path: &str) -> Result<ObjRes> {
block_on_compat(self.lookup_path(path))
}
fn sync_pathconf(&self, fh: Bytes) -> Result<Pathconf> {
block_on_compat(self.pathconf(fh))
}
fn sync_pathconf_path(&self, path: &str) -> Result<Pathconf> {
block_on_compat(self.pathconf_path(path))
}
fn sync_read(&self, fh: Bytes, offset: u64, count: u32) -> Result<Bytes> {
block_on_compat(self.read(fh, offset, count))
}
fn sync_read_path(&self, path: &str, offset: u64, count: u32) -> Result<Bytes> {
block_on_compat(self.read_path(path, offset, count))
}
fn sync_write(&self, fh: Bytes, offset: u64, data: Bytes) -> Result<u32> {
block_on_compat(self.write(fh, offset, data))
}
fn sync_write_path(&self, path: &str, offset: u64, data: Bytes) -> Result<u32> {
block_on_compat(self.write_path(path, offset, data))
}
fn sync_readdir(&self, dir_fh: Bytes) -> Result<Vec<ReaddirEntry>> {
block_on_compat(async { self.readdir(dir_fh).await.try_collect().await })
}
fn sync_readdir_path(&self, dir_path: &str) -> Result<Vec<ReaddirEntry>> {
block_on_compat(async { self.readdir_path(dir_path).await?.try_collect().await })
}
fn sync_readdirplus(&self, dir_fh: Bytes) -> Result<Vec<ReaddirplusEntry>> {
block_on_compat(async { self.readdirplus(dir_fh).await.try_collect().await })
}
fn sync_readdirplus_path(&self, dir_path: &str) -> Result<Vec<ReaddirplusEntry>> {
block_on_compat(async { self.readdirplus_path(dir_path).await?.try_collect().await })
}
fn sync_mkdir(&self, dir_fh: Bytes, dirname: &str, mode: u32) -> Result<ObjRes> {
block_on_compat(self.mkdir(dir_fh, dirname, mode))
}
fn sync_mkdir_path(&self, path: &str, mode: u32) -> Result<ObjRes> {
block_on_compat(self.mkdir_path(path, mode))
}
fn sync_remove(&self, dir_fh: Bytes, filename: &str) -> Result<()> {
block_on_compat(self.remove(dir_fh, filename))
}
fn sync_remove_path(&self, path: &str) -> Result<()> {
block_on_compat(self.remove_path(path))
}
fn sync_rmdir(&self, dir_fh: Bytes, dirname: &str) -> Result<()> {
block_on_compat(self.rmdir(dir_fh, dirname))
}
fn sync_rmdir_path(&self, path: &str) -> Result<()> {
block_on_compat(self.rmdir_path(path))
}
fn sync_rename(
&self,
from_dir_fh: Bytes,
from_filename: &str,
to_dir_fh: Bytes,
to_filename: &str,
) -> Result<()> {
block_on_compat(self.rename(from_dir_fh, from_filename, to_dir_fh, to_filename))
}
fn sync_rename_path(&self, from_path: &str, to_path: &str) -> Result<()> {
block_on_compat(self.rename_path(from_path, to_path))
}
fn sync_umount(&self) -> Result<()> {
block_on_compat(self.umount())
}
async fn exports(&self) -> Result<Vec<ExportEntry>>;
fn sync_exports(&self) -> Result<Vec<ExportEntry>> {
block_on_compat(self.exports())
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct ExportEntry {
pub path: String,
pub groups: Vec<String>,
}
#[derive(Debug, Eq, PartialEq)]
pub enum NFSVersion {
Unknown,
NFSv3,
NFSv4,
NFSv4p1,
NFSv4p2,
}
impl From<&str> for NFSVersion {
fn from(val: &str) -> Self {
match val {
"3" => NFSVersion::NFSv3,
"4" => NFSVersion::NFSv4,
"4.1" => NFSVersion::NFSv4p1,
"4.2" => NFSVersion::NFSv4p2,
_ => NFSVersion::Unknown,
}
}
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct Attr {
pub type_: u32,
pub file_mode: u32,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub filesize: u64,
pub used: u64,
pub spec_data: [u32; 2],
pub fsid: u64,
pub fileid: u64,
pub atime: Time,
pub mtime: Time,
pub ctime: Time,
pub acl: Option<Acl>,
pub owner: String,
pub owner_group: String,
pub filehandle: Bytes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AceType {
AccessAllowed = 0,
AccessDenied = 1,
SystemAudit = 2,
SystemAlarm = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AceFlags(pub u32);
impl AceFlags {
pub const FILE_INHERIT: u32 = 0x0000_0001;
pub const DIRECTORY_INHERIT: u32 = 0x0000_0002;
pub const NO_PROPAGATE_INHERIT: u32 = 0x0000_0004;
pub const INHERIT_ONLY: u32 = 0x0000_0008;
pub const SUCCESSFUL_ACCESS: u32 = 0x0000_0010;
pub const FAILED_ACCESS: u32 = 0x0000_0020;
pub const IDENTIFIER_GROUP: u32 = 0x0000_0040;
pub fn contains(self, flag: u32) -> bool {
self.0 & flag != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AceMask(pub u32);
impl AceMask {
pub const READ_DATA: u32 = 0x0000_0001;
pub const LIST_DIRECTORY: u32 = 0x0000_0001;
pub const WRITE_DATA: u32 = 0x0000_0002;
pub const ADD_FILE: u32 = 0x0000_0002;
pub const APPEND_DATA: u32 = 0x0000_0004;
pub const ADD_SUBDIRECTORY: u32 = 0x0000_0004;
pub const READ_NAMED_ATTRS: u32 = 0x0000_0008;
pub const WRITE_NAMED_ATTRS: u32 = 0x0000_0010;
pub const EXECUTE: u32 = 0x0000_0020;
pub const DELETE_CHILD: u32 = 0x0000_0040;
pub const READ_ATTRIBUTES: u32 = 0x0000_0080;
pub const WRITE_ATTRIBUTES: u32 = 0x0000_0100;
pub const DELETE: u32 = 0x0001_0000;
pub const READ_ACL: u32 = 0x0002_0000;
pub const WRITE_ACL: u32 = 0x0004_0000;
pub const WRITE_OWNER: u32 = 0x0008_0000;
pub const SYNCHRONIZE: u32 = 0x0010_0000;
pub fn contains(self, mask: u32) -> bool {
self.0 & mask != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NfsAce {
pub ace_type: AceType,
pub flags: AceFlags,
pub access_mask: AceMask,
pub who: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Acl {
pub aces: Vec<NfsAce>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AclSupport(pub u32);
impl AclSupport {
pub const ALLOW: u32 = 0x0000_0001;
pub const DENY: u32 = 0x0000_0002;
pub const AUDIT: u32 = 0x0000_0004;
pub const ALARM: u32 = 0x0000_0008;
pub fn supports(self, ace_type: u32) -> bool {
self.0 & ace_type != 0
}
}
#[derive(Debug, Default, PartialEq)]
pub struct FSInfo {
pub attr: Option<Attr>,
pub rtmax: u32,
pub rtpref: u32,
pub rtmult: u32,
pub wtmax: u32,
pub wtpref: u32,
pub wtmult: u32,
pub dtpref: u32,
pub maxfilesize: u64,
pub time_delta: Time,
pub properties: u32,
}
#[derive(Debug, Default, PartialEq)]
pub struct FSStat {
pub attr: Option<Attr>,
pub tbytes: u64,
pub fbytes: u64,
pub abytes: u64,
pub tfiles: u64,
pub ffiles: u64,
pub afiles: u64,
pub invarsec: u32,
}
#[derive(Debug, Default, PartialEq, Clone)]
pub struct ObjRes {
pub fh: Bytes,
pub attr: Option<Attr>,
}
#[derive(Debug, Default, PartialEq)]
pub struct Pathconf {
pub attr: Option<Attr>,
pub linkmax: u32,
pub name_max: u32,
pub no_trunc: bool,
pub chown_restricted: bool,
pub case_insensitive: bool,
pub case_preserving: bool,
}
#[derive(Debug)]
pub struct ReaddirEntry {
pub fileid: u64,
pub file_name: String,
}
#[derive(Debug)]
pub struct ReaddirplusEntry {
pub fileid: u64,
pub file_name: String,
pub attr: Option<Attr>,
pub handle: Bytes,
}