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::collections::HashSet;
use std::pin::Pin;
pub(crate) struct DirectoryCursor {
pub cookie: u64,
pub verifier: [u8; 8],
seen: HashSet<u64>,
}
impl Default for DirectoryCursor {
fn default() -> Self {
Self {
cookie: 0,
verifier: [0; 8],
seen: HashSet::from([0]),
}
}
}
impl DirectoryCursor {
pub fn advance(
&mut self,
cookie: u64,
verifier: [u8; 8],
entries: usize,
eof: bool,
) -> Result<bool> {
if (!eof && entries == 0) || (entries > 0 && !self.seen.insert(cookie)) {
return Err(NfsError::Xdr(
"READDIR page made no progress or repeated a cookie".into(),
));
}
self.cookie = cookie;
self.verifier = verifier;
Ok(!eof)
}
}
pub(crate) const MAX_IO_SIZE: u32 = 4 * 1024 * 1024;
pub(crate) fn negotiated_io_size(server_max: u64) -> Result<u32> {
let size = server_max.min(u64::from(MAX_IO_SIZE)) as u32;
if size == 0 {
return Err(NfsError::Xdr(
"server reported a zero maximum I/O size".into(),
));
}
Ok(size)
}
pub(crate) fn read_reply(data: Bytes, eof: bool) -> Result<Bytes> {
if data.is_empty() && !eof {
return Err(NfsError::Rpc("READ made no progress without EOF".into()));
}
Ok(data)
}
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, Ord, PartialOrd)]
#[repr(u32)]
pub enum WriteCommitted {
Unstable = 0,
DataSync = 1,
FileSync = 2,
}
impl TryFrom<u32> for WriteCommitted {
type Error = NfsError;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(Self::Unstable),
1 => Ok(Self::DataSync),
2 => Ok(Self::FileSync),
_ => Err(NfsError::Xdr(format!(
"invalid WRITE committed value: {value}"
))),
}
}
}
#[derive(Clone, Debug)]
pub struct WriteOutcome {
pub count: u32,
pub committed: WriteCommitted,
pub verifier: Option<[u8; 8]>,
pub(crate) pnfs: Option<std::sync::Arc<crate::nfs41::pnfs_io::PendingWrite>>,
}
impl WriteOutcome {
pub fn new(count: u32, committed: WriteCommitted, verifier: Option<[u8; 8]>) -> Self {
Self {
count,
committed,
verifier,
pnfs: None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum WriteStability {
Unstable,
}
impl WriteStability {
pub(crate) fn stable_how(self) -> u32 {
match self {
WriteStability::Unstable => 0,
}
}
}
pub(crate) fn write_verifier_changed(protocol: NFSVersion) -> NfsError {
NfsError::OperationOutcome(Box::new(crate::error::OperationOutcomeError::new(
crate::error::OperationOutcome::Uncertain,
crate::error::OperationClass::ReplaySensitive,
crate::error::RecoveryAction::VerifyThenResume,
crate::error::RequestContext {
operation: "write_verifier".into(),
protocol,
request_id: None,
},
NfsError::Rpc("WRITE verifier changed before COMMIT".into()),
)))
}
#[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,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MountCapabilities {
pub acl: bool,
pub named_attributes: bool,
pub locks: bool,
pub callbacks: bool,
pub delegation_retention: bool,
pub pnfs: bool,
pub session_diagnostics: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum MountLifecycleState {
#[default]
Ready,
Reconnecting,
Suspect,
Recovering,
Reclaiming,
LostState,
Closing,
Closed,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MountHealth {
pub lifecycle: MountLifecycleState,
pub generation: u64,
pub lease_healthy: Option<bool>,
pub lease_seconds: Option<u32>,
pub lease_renewals: u64,
pub callback_healthy: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CallbackStats {
pub grants_received: u64,
pub recalls_received: u64,
pub returns_completed: u64,
pub returns_failed: u64,
}
#[derive(Debug, PartialEq)]
pub struct OpenFile {
pub object: ObjRes,
state: Option<Bytes>,
}
impl OpenFile {
pub(crate) fn from_object(object: ObjRes) -> Self {
Self {
object,
state: None,
}
}
pub(crate) fn with_protocol_state(object: ObjRes, state: Bytes) -> Self {
Self {
object,
state: Some(state),
}
}
pub(crate) fn into_parts(self) -> (ObjRes, Option<Bytes>) {
(self.object, self.state)
}
pub(crate) fn protocol_state(&self) -> Option<&Bytes> {
self.state.as_ref()
}
pub(crate) fn file_handle(&self) -> Bytes {
self.object.fh.clone()
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct LockToken {
pub(crate) fh: Bytes,
pub(crate) stateid: Bytes,
pub(crate) lock_type: u32,
pub(crate) offset: u64,
pub(crate) length: u64,
pub(crate) issuer: u64,
pub(crate) generation: u64,
}
impl LockToken {
pub(crate) fn new(
fh: Bytes,
stateid: Bytes,
lock_type: u32,
offset: u64,
length: u64,
issuer: u64,
generation: u64,
) -> Self {
Self {
fh,
stateid,
lock_type,
offset,
length,
issuer,
generation,
}
}
}
#[async_trait]
pub trait Mount: std::fmt::Debug + Send + Sync {
fn capabilities(&self) -> MountCapabilities {
MountCapabilities::default()
}
fn health(&self) -> MountHealth {
MountHealth::default()
}
async fn callback_stats(&self) -> CallbackStats {
let stats = self.nfs41_callback_stats().await.unwrap_or_default();
CallbackStats {
grants_received: 0,
recalls_received: stats.layout_recalls_received,
returns_completed: stats.layout_returns_completed,
returns_failed: 0,
}
}
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 open_stateful(&self, dir_fh: Bytes, filename: &str, access: u32) -> Result<OpenFile> {
Ok(OpenFile {
object: self.open(dir_fh, filename, access).await?,
state: None,
})
}
async fn open_path_stateful(&self, path: &str, access: u32) -> Result<OpenFile> {
Ok(OpenFile {
object: self.open_path(path, access).await?,
state: None,
})
}
async fn close(&self, _fh: Bytes) -> Result<()> {
Ok(()) }
async fn close_stateful(&self, file: OpenFile) -> Result<()> {
let _protocol_state = file.state;
self.close(file.object.fh).await
}
async fn commit(&self, fh: Bytes, offset: u64, count: u32) -> Result<()>;
#[doc(hidden)]
async fn commit_with_verifier(
&self,
fh: Bytes,
offset: u64,
count: u32,
) -> Result<Option<[u8; 8]>> {
self.commit(fh, offset, count).await?;
Ok(None)
}
async fn commit_write_batch(
&self,
fh: Bytes,
offset: u64,
count: u32,
writes: &[WriteOutcome],
) -> Result<()> {
if writes
.iter()
.all(|w| w.committed == crate::WriteCommitted::FileSync)
{
return Ok(());
}
let actual = self.commit_with_verifier(fh, offset, count).await?;
verify_write_batch(self.version(), writes, actual)
}
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 create_path_stateful(&self, path: &str, mode: Option<u32>) -> Result<OpenFile> {
Ok(OpenFile::from_object(self.create_path(path, mode).await?))
}
async fn create_path_stateful_with_access(
&self,
path: &str,
mode: Option<u32>,
access: u32,
) -> Result<OpenFile> {
if !matches!(access, OPEN_READ | OPEN_WRITE | OPEN_BOTH) {
return Err(NfsError::InvalidInput(format!(
"invalid create OPEN access {access}"
)));
}
self.create_path_stateful(path, mode).await
}
#[deprecated(note = "delegation lifecycle is managed internally by the mount")]
async fn delegpurge(&self, _clientid: u64) -> Result<()> {
Err(NfsError::Unsupported(
"DELEGPURGE requires NFSv4".to_string(),
))
}
#[deprecated(note = "delegation lifecycle is managed internally by the mount")]
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.0 or NFSv4.1".to_string(),
))
}
async fn lock_test(
&self,
_fh: Bytes,
_lock_type: u32,
_offset: u64,
_length: u64,
) -> Result<()> {
Err(NfsError::Unsupported(
"LOCKT requires NFSv4.0 or NFSv4.1".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 lock_stateful(
&self,
fh: Bytes,
lock_type: u32,
offset: u64,
length: u64,
) -> Result<LockToken> {
let stateid = self.lock(fh.clone(), lock_type, offset, length).await?;
Ok(LockToken::new(fh, stateid, lock_type, offset, length, 0, 0))
}
async fn lock_open_stateful(
&self,
opened: &OpenFile,
lock_type: u32,
offset: u64,
length: u64,
) -> Result<LockToken> {
self.lock_stateful(opened.object.fh.clone(), lock_type, offset, length)
.await
}
async fn unlock_stateful(&self, token: LockToken) -> Result<()> {
self.locku(
token.fh,
token.stateid,
token.lock_type,
token.offset,
token.length,
)
.await
}
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 getdacl(&self, _fh: Bytes) -> Result<NfsAcl41> {
Err(NfsError::Unsupported("DACL requires NFSv4.1".to_string()))
}
async fn getdacl_path(&self, path: &str) -> Result<NfsAcl41> {
let res = self.lookup_path(path).await?;
self.getdacl(res.fh).await
}
async fn setdacl(&self, _fh: Bytes, _acl: &NfsAcl41) -> Result<()> {
Err(NfsError::Unsupported("DACL requires NFSv4.1".to_string()))
}
async fn setdacl_path(&self, path: &str, acl: &NfsAcl41) -> Result<()> {
let res = self.lookup_path(path).await?;
self.setdacl(res.fh, acl).await
}
async fn getsacl(&self, _fh: Bytes) -> Result<NfsAcl41> {
Err(NfsError::Unsupported("SACL requires NFSv4.1".to_string()))
}
async fn getsacl_path(&self, path: &str) -> Result<NfsAcl41> {
let res = self.lookup_path(path).await?;
self.getsacl(res.fh).await
}
async fn setsacl(&self, _fh: Bytes, _acl: &NfsAcl41) -> Result<()> {
Err(NfsError::Unsupported("SACL requires NFSv4.1".to_string()))
}
async fn setsacl_path(&self, path: &str, acl: &NfsAcl41) -> Result<()> {
let res = self.lookup_path(path).await?;
self.setsacl(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_with_support(&self, fh: Bytes) -> Result<SupportedPathconf> {
Ok(SupportedPathconf {
values: self.pathconf(fh).await?,
available: PathconfSupport::all(),
fsid: None,
})
}
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<WriteOutcome>;
async fn write_path(&self, path: &str, offset: u64, data: Bytes) -> Result<WriteOutcome> {
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))
}
#[allow(deprecated)]
fn sync_delegpurge(&self, clientid: u64) -> Result<()> {
block_on_compat(self.delegpurge(clientid))
}
#[allow(deprecated)]
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<WriteOutcome> {
block_on_compat(self.write(fh, offset, data))
}
fn sync_write_path(&self, path: &str, offset: u64, data: Bytes) -> Result<WriteOutcome> {
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(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NFSVersion {
Unknown,
NFSv3,
NFSv4p0,
#[deprecated(note = "use NFSVersion::NFSv4p0 and the exact URL selector version=4.0")]
NFSv4,
NFSv4p1,
NFSv4p2,
}
impl From<&str> for NFSVersion {
fn from(val: &str) -> Self {
match val {
"3" => NFSVersion::NFSv3,
"4.0" => NFSVersion::NFSv4p0,
"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 const INHERITED: u32 = 0x0000_0080;
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 Acl41Flags(pub u32);
impl Acl41Flags {
pub const AUTO_INHERIT: u32 = 0x0000_0001;
pub const PROTECTED: u32 = 0x0000_0002;
pub const DEFAULTED: u32 = 0x0000_0004;
pub fn contains(self, flag: u32) -> bool {
self.0 & flag != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct NfsAcl41 {
pub flags: Acl41Flags,
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, Default, PartialEq, Eq, Clone, Copy)]
pub struct PathconfSupport {
pub linkmax: bool,
pub name_max: bool,
pub no_trunc: bool,
pub chown_restricted: bool,
pub case_insensitive: bool,
pub case_preserving: bool,
}
impl PathconfSupport {
pub const fn all() -> Self {
Self {
linkmax: true,
name_max: true,
no_trunc: true,
chown_restricted: true,
case_insensitive: true,
case_preserving: true,
}
}
}
#[derive(Debug, Default, PartialEq)]
pub struct SupportedPathconf {
pub values: Pathconf,
pub available: PathconfSupport,
pub fsid: Option<(u64, u64)>,
}
#[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,
}
pub(crate) fn verify_write_batch(
protocol: NFSVersion,
writes: &[WriteOutcome],
actual: Option<[u8; 8]>,
) -> Result<()> {
for write in writes
.iter()
.filter(|w| w.committed != crate::WriteCommitted::FileSync)
{
if let Some(expected) = write.verifier
&& actual != Some(expected)
{
return Err(write_verifier_changed(protocol));
}
}
Ok(())
}
#[cfg(test)]
mod negotiated_size_tests {
use super::*;
#[test]
fn empty_read_requires_eof() {
assert!(read_reply(Bytes::new(), false).is_err());
assert!(read_reply(Bytes::new(), true).unwrap().is_empty());
assert_eq!(
read_reply(Bytes::from_static(b"short"), false).unwrap(),
b"short"[..]
);
}
#[test]
fn automatic_sizes_respect_server_and_client_limits() {
assert_eq!(negotiated_io_size(4096).unwrap(), 4096);
assert_eq!(
negotiated_io_size(2 * 1024 * 1024).unwrap(),
2 * 1024 * 1024
);
assert_eq!(negotiated_io_size(u64::MAX).unwrap(), MAX_IO_SIZE);
assert!(negotiated_io_size(0).is_err());
}
}
#[cfg(test)]
mod write_committed_tests {
use super::*;
#[test]
fn response_levels_are_distinct_and_invalid_wire_values_fail() {
for (wire, expected) in [
(0, WriteCommitted::Unstable),
(1, WriteCommitted::DataSync),
(2, WriteCommitted::FileSync),
] {
let outcome =
WriteOutcome::new(7, WriteCommitted::try_from(wire).unwrap(), Some([9; 8]));
assert_eq!(outcome.committed, expected);
assert_eq!(outcome.count, 7);
assert_eq!(outcome.verifier, Some([9; 8]));
let verified = verify_write_batch(NFSVersion::NFSv3, &[outcome], Some([8; 8]));
assert_eq!(verified.is_ok(), expected == WriteCommitted::FileSync);
}
assert!(WriteCommitted::try_from(3).is_err());
assert!(WriteCommitted::try_from(u32::MAX).is_err());
}
}