use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use lazy_static::lazy_static;
use spin::Mutex;
use super::error::{NfsError, NfsResult, NfsStatus};
use super::export::{
ClientState, ExportOptions, confirm_client, create_session, destroy_session,
find_client_by_owner, find_export_by_handle, get_client, get_export, get_lease_duration,
get_session, register_client, renew_client_lease,
};
use super::ops::*;
use super::types::*;
lazy_static! {
static ref SERVER_STATE: Mutex<ServerState> = Mutex::new(ServerState::new());
}
#[derive(Debug)]
struct ServerState {
boot_verifier: Verifier,
impl_id: String,
scope: Vec<u8>,
owner_major_id: Vec<u8>,
minor_id: u64,
stats: ServerStats,
}
impl ServerState {
fn new() -> Self {
Self {
boot_verifier: Verifier::generate(),
impl_id: "LCPFS NFS Server".into(),
scope: b"lcpfs.local".to_vec(),
owner_major_id: b"lcpfs-server".to_vec(),
minor_id: 0,
stats: ServerStats::default(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct ServerStats {
pub total_ops: u64,
pub reads: u64,
pub writes: u64,
pub lookups: u64,
pub errors: u64,
pub bytes_read: u64,
pub bytes_written: u64,
}
pub fn get_stats() -> ServerStats {
SERVER_STATE.lock().stats.clone()
}
pub fn reset_stats() {
SERVER_STATE.lock().stats = ServerStats::default();
}
#[derive(Debug)]
pub struct RequestContext {
pub client_addr: [u8; 4],
pub client_id: Option<ClientId>,
pub session_id: Option<SessionId>,
pub current_fh: Option<FileHandle>,
pub saved_fh: Option<FileHandle>,
pub uid: u32,
pub gid: u32,
pub gids: Vec<u32>,
pub export_options: Option<ExportOptions>,
pub timestamp: u64,
}
impl RequestContext {
pub fn new(client_addr: [u8; 4]) -> Self {
Self {
client_addr,
client_id: None,
session_id: None,
current_fh: None,
saved_fh: None,
uid: 65534, gid: 65534, gids: Vec::new(),
export_options: None,
timestamp: 0,
}
}
pub fn with_credentials(mut self, uid: u32, gid: u32, gids: Vec<u32>) -> Self {
self.uid = uid;
self.gid = gid;
self.gids = gids;
self
}
pub fn effective_uid(&self) -> u32 {
if let Some(ref opts) = self.export_options {
if opts.all_squash {
return opts.anon_uid;
}
if self.uid == 0 && !opts.no_root_squash {
return opts.anon_uid;
}
}
self.uid
}
pub fn effective_gid(&self) -> u32 {
if let Some(ref opts) = self.export_options {
if opts.all_squash {
return opts.anon_gid;
}
if self.gid == 0 && !opts.no_root_squash {
return opts.anon_gid;
}
}
self.gid
}
pub fn can_write(&self) -> bool {
self.export_options
.as_ref()
.map(|o| !o.read_only)
.unwrap_or(false)
}
}
pub trait NfsFilesystem: Send + Sync {
fn lookup(&self, dir_fh: &FileHandle, name: &str) -> NfsResult<FileHandle>;
fn getattr(&self, fh: &FileHandle) -> NfsResult<NfsAttr>;
fn setattr(&self, fh: &FileHandle, attrs: &SetAttr, ctx: &RequestContext)
-> NfsResult<NfsAttr>;
fn read(&self, fh: &FileHandle, offset: u64, count: u32) -> NfsResult<(Vec<u8>, bool)>;
fn write(
&self,
fh: &FileHandle,
offset: u64,
data: &[u8],
stable: StableHow,
) -> NfsResult<(u32, StableHow)>;
fn readdir(&self, fh: &FileHandle, cookie: u64, count: u32)
-> NfsResult<(Vec<DirEntry>, bool)>;
fn create(
&self,
dir_fh: &FileHandle,
name: &str,
attrs: &SetAttr,
ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)>;
fn mkdir(
&self,
dir_fh: &FileHandle,
name: &str,
attrs: &SetAttr,
ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)>;
fn remove(&self, dir_fh: &FileHandle, name: &str) -> NfsResult<()>;
fn rmdir(&self, dir_fh: &FileHandle, name: &str) -> NfsResult<()>;
fn rename(
&self,
from_dir: &FileHandle,
from_name: &str,
to_dir: &FileHandle,
to_name: &str,
) -> NfsResult<()>;
fn symlink(
&self,
dir_fh: &FileHandle,
name: &str,
target: &str,
attrs: &SetAttr,
ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)>;
fn readlink(&self, fh: &FileHandle) -> NfsResult<String>;
fn link(&self, fh: &FileHandle, dir_fh: &FileHandle, name: &str) -> NfsResult<()>;
fn commit(&self, fh: &FileHandle, offset: u64, count: u32) -> NfsResult<Verifier>;
fn fsinfo(&self, fh: &FileHandle) -> NfsResult<FsInfo>;
fn fsstat(&self, fh: &FileHandle) -> NfsResult<FsStat>;
fn parent(&self, fh: &FileHandle) -> NfsResult<FileHandle>;
}
pub struct NfsServer<F: NfsFilesystem> {
fs: F,
}
impl<F: NfsFilesystem> NfsServer<F> {
pub fn new(fs: F) -> Self {
Self { fs }
}
pub fn process_compound(
&self,
request: CompoundRequest,
ctx: &mut RequestContext,
) -> CompoundResponse {
let mut results = Vec::with_capacity(request.operations.len());
let mut overall_status = NfsStatus::Ok;
for op in request.operations {
let result = self.process_operation(&op, ctx);
let status = result.status();
results.push(result);
{
let mut state = SERVER_STATE.lock();
state.stats.total_ops += 1;
if status != NfsStatus::Ok {
state.stats.errors += 1;
}
}
if status != NfsStatus::Ok {
overall_status = status;
break;
}
}
CompoundResponse {
status: overall_status,
tag: request.tag,
results,
}
}
fn process_operation(&self, op: &Operation, ctx: &mut RequestContext) -> OperationResult {
match op {
Operation::Putfh(args) => self.op_putfh(args, ctx),
Operation::Putrootfh => self.op_putrootfh(ctx),
Operation::Putpubfh => self.op_putpubfh(ctx),
Operation::Getfh => self.op_getfh(ctx),
Operation::Savefh => self.op_savefh(ctx),
Operation::Restorefh => self.op_restorefh(ctx),
Operation::Lookup(args) => self.op_lookup(args, ctx),
Operation::Lookupp => self.op_lookupp(ctx),
Operation::Getattr(args) => self.op_getattr(args, ctx),
Operation::Setattr(args) => self.op_setattr(args, ctx),
Operation::Access(args) => self.op_access(args, ctx),
Operation::Read(args) => self.op_read(args, ctx),
Operation::Write(args) => self.op_write(args, ctx),
Operation::Readdir(args) => self.op_readdir(args, ctx),
Operation::Readlink => self.op_readlink(ctx),
Operation::Create(args) => self.op_create(args, ctx),
Operation::Remove(args) => self.op_remove(args, ctx),
Operation::Rename(args) => self.op_rename(args, ctx),
Operation::Link(args) => self.op_link(args, ctx),
Operation::Commit(args) => self.op_commit(args, ctx),
Operation::Open(args) => self.op_open(args, ctx),
Operation::Close(args) => self.op_close(args, ctx),
Operation::Setclientid(args) => self.op_setclientid(args, ctx),
Operation::SetclientidConfirm(args) => self.op_setclientid_confirm(args, ctx),
Operation::Renew(args) => self.op_renew(args, ctx),
Operation::ExchangeId(args) => self.op_exchange_id(args, ctx),
Operation::CreateSession(args) => self.op_create_session(args, ctx),
Operation::DestroySession(args) => self.op_destroy_session(args, ctx),
Operation::Sequence(args) => self.op_sequence(args, ctx),
Operation::ReclaimComplete(args) => self.op_reclaim_complete(args, ctx),
Operation::Lock(args) => self.op_lock(args, ctx),
Operation::Lockt(args) => self.op_lockt(args, ctx),
Operation::Locku(args) => self.op_locku(args, ctx),
}
}
fn op_putfh(&self, args: &PutfhArgs, ctx: &mut RequestContext) -> OperationResult {
if let Some(export) = find_export_by_handle(&args.fh) {
ctx.export_options = export.check_access(ctx.client_addr).cloned();
if ctx.export_options.is_none() {
return OperationResult::Putfh(Err(NfsStatus::Acces));
}
}
ctx.current_fh = Some(args.fh.clone());
OperationResult::Putfh(Ok(()))
}
fn op_putrootfh(&self, ctx: &mut RequestContext) -> OperationResult {
ctx.current_fh = Some(FileHandle::root(0));
OperationResult::Putrootfh(Ok(()))
}
fn op_putpubfh(&self, ctx: &mut RequestContext) -> OperationResult {
ctx.current_fh = Some(FileHandle::root(0));
OperationResult::Putpubfh(Ok(()))
}
fn op_getfh(&self, ctx: &mut RequestContext) -> OperationResult {
match &ctx.current_fh {
Some(fh) => OperationResult::Getfh(Ok(GetfhResult { fh: fh.clone() })),
None => OperationResult::Getfh(Err(NfsStatus::Nofilehandle)),
}
}
fn op_savefh(&self, ctx: &mut RequestContext) -> OperationResult {
match &ctx.current_fh {
Some(fh) => {
ctx.saved_fh = Some(fh.clone());
OperationResult::Savefh(Ok(()))
}
None => OperationResult::Savefh(Err(NfsStatus::Nofilehandle)),
}
}
fn op_restorefh(&self, ctx: &mut RequestContext) -> OperationResult {
match &ctx.saved_fh {
Some(fh) => {
ctx.current_fh = Some(fh.clone());
OperationResult::Restorefh(Ok(()))
}
None => OperationResult::Restorefh(Err(NfsStatus::Nofilehandle)),
}
}
fn op_lookup(&self, args: &LookupArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Lookup(Err(NfsStatus::Nofilehandle)),
};
{
let mut state = SERVER_STATE.lock();
state.stats.lookups += 1;
}
match self.fs.lookup(fh, &args.name) {
Ok(new_fh) => {
ctx.current_fh = Some(new_fh);
OperationResult::Lookup(Ok(LookupResult))
}
Err(e) => OperationResult::Lookup(Err(e.status)),
}
}
fn op_lookupp(&self, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Lookupp(Err(NfsStatus::Nofilehandle)),
};
match self.fs.parent(fh) {
Ok(parent_fh) => {
ctx.current_fh = Some(parent_fh);
OperationResult::Lookupp(Ok(LookupResult))
}
Err(e) => OperationResult::Lookupp(Err(e.status)),
}
}
fn op_getattr(&self, args: &GetattrArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Getattr(Err(NfsStatus::Nofilehandle)),
};
match self.fs.getattr(fh) {
Ok(attrs) => OperationResult::Getattr(Ok(GetattrResult {
attrs,
attrmask: args.attr_request.clone(),
})),
Err(e) => OperationResult::Getattr(Err(e.status)),
}
}
fn op_setattr(&self, args: &SetattrArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Setattr(Err(NfsStatus::Nofilehandle)),
};
if !ctx.can_write() {
return OperationResult::Setattr(Err(NfsStatus::Rofs));
}
match self.fs.setattr(&fh, &args.attrs, ctx) {
Ok(_) => OperationResult::Setattr(Ok(SetattrResult {
attrsset: AttrBitmap::all_supported(),
})),
Err(e) => OperationResult::Setattr(Err(e.status)),
}
}
fn op_access(&self, args: &AccessArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Access(Err(NfsStatus::Nofilehandle)),
};
let attrs = match self.fs.getattr(fh) {
Ok(a) => a,
Err(e) => return OperationResult::Access(Err(e.status)),
};
let mut access = AccessBits(0);
let supported;
if attrs.file_type.is_directory() {
supported = AccessBits::all_dir();
if (attrs.mode & 0o400) != 0 || ctx.effective_uid() == attrs.uid {
access.0 |= AccessBits::READ | AccessBits::LOOKUP;
}
if ctx.can_write() && ((attrs.mode & 0o200) != 0 || ctx.effective_uid() == attrs.uid) {
access.0 |= AccessBits::MODIFY | AccessBits::EXTEND | AccessBits::DELETE;
}
} else {
supported = AccessBits::all_file();
if (attrs.mode & 0o400) != 0 || ctx.effective_uid() == attrs.uid {
access.0 |= AccessBits::READ;
}
if ctx.can_write() && ((attrs.mode & 0o200) != 0 || ctx.effective_uid() == attrs.uid) {
access.0 |= AccessBits::MODIFY | AccessBits::EXTEND;
}
if (attrs.mode & 0o100) != 0 || ctx.effective_uid() == attrs.uid {
access.0 |= AccessBits::EXECUTE;
}
}
access.0 &= args.access.0;
OperationResult::Access(Ok(AccessResult { supported, access }))
}
fn op_read(&self, args: &ReadArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Read(Err(NfsStatus::Nofilehandle)),
};
match self.fs.read(fh, args.offset, args.count) {
Ok((data, eof)) => {
{
let mut state = SERVER_STATE.lock();
state.stats.reads += 1;
state.stats.bytes_read += data.len() as u64;
}
OperationResult::Read(Ok(ReadResult { eof, data }))
}
Err(e) => OperationResult::Read(Err(e.status)),
}
}
fn op_write(&self, args: &WriteArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Write(Err(NfsStatus::Nofilehandle)),
};
if !ctx.can_write() {
return OperationResult::Write(Err(NfsStatus::Rofs));
}
match self.fs.write(&fh, args.offset, &args.data, args.stable) {
Ok((count, committed)) => {
{
let mut state = SERVER_STATE.lock();
state.stats.writes += 1;
state.stats.bytes_written += count as u64;
}
OperationResult::Write(Ok(WriteResult {
count,
committed,
verifier: SERVER_STATE.lock().boot_verifier,
}))
}
Err(e) => OperationResult::Write(Err(e.status)),
}
}
fn op_commit(&self, args: &CommitArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Commit(Err(NfsStatus::Nofilehandle)),
};
match self.fs.commit(fh, args.offset, args.count) {
Ok(verifier) => OperationResult::Commit(Ok(CommitResult { verifier })),
Err(e) => OperationResult::Commit(Err(e.status)),
}
}
fn op_readdir(&self, args: &ReaddirArgs, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Readdir(Err(NfsStatus::Nofilehandle)),
};
match self.fs.readdir(fh, args.cookie, args.maxcount) {
Ok((entries, eof)) => OperationResult::Readdir(Ok(ReaddirResult {
cookieverf: args.cookieverf,
entries,
eof,
})),
Err(e) => OperationResult::Readdir(Err(e.status)),
}
}
fn op_readlink(&self, ctx: &mut RequestContext) -> OperationResult {
let fh = match &ctx.current_fh {
Some(fh) => fh,
None => return OperationResult::Readlink(Err(NfsStatus::Nofilehandle)),
};
match self.fs.readlink(fh) {
Ok(link) => OperationResult::Readlink(Ok(ReadlinkResult { link })),
Err(e) => OperationResult::Readlink(Err(e.status)),
}
}
fn op_create(&self, args: &CreateArgs, ctx: &mut RequestContext) -> OperationResult {
let dir_fh = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Create(Err(NfsStatus::Nofilehandle)),
};
if !ctx.can_write() {
return OperationResult::Create(Err(NfsStatus::Rofs));
}
let result = match &args.obj_type {
CreateType::Directory => self.fs.mkdir(&dir_fh, &args.name, &args.attrs, ctx),
CreateType::Symlink(target) => {
self.fs
.symlink(&dir_fh, &args.name, target, &args.attrs, ctx)
}
CreateType::Regular => self.fs.create(&dir_fh, &args.name, &args.attrs, ctx),
_ => return OperationResult::Create(Err(NfsStatus::Notsupp)),
};
match result {
Ok((new_fh, _)) => {
ctx.current_fh = Some(new_fh);
OperationResult::Create(Ok(CreateResult {
cinfo: ChangeInfo {
atomic: true,
before: 0,
after: 1,
},
attrset: AttrBitmap::all_supported(),
}))
}
Err(e) => OperationResult::Create(Err(e.status)),
}
}
fn op_remove(&self, args: &RemoveArgs, ctx: &mut RequestContext) -> OperationResult {
let dir_fh = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Remove(Err(NfsStatus::Nofilehandle)),
};
if !ctx.can_write() {
return OperationResult::Remove(Err(NfsStatus::Rofs));
}
let result = self
.fs
.remove(&dir_fh, &args.target)
.or_else(|_| self.fs.rmdir(&dir_fh, &args.target));
match result {
Ok(()) => OperationResult::Remove(Ok(RemoveResult {
cinfo: ChangeInfo {
atomic: true,
before: 0,
after: 1,
},
})),
Err(e) => OperationResult::Remove(Err(e.status)),
}
}
fn op_rename(&self, args: &RenameArgs, ctx: &mut RequestContext) -> OperationResult {
let src_dir = match &ctx.saved_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Rename(Err(NfsStatus::Nofilehandle)),
};
let dst_dir = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Rename(Err(NfsStatus::Nofilehandle)),
};
if !ctx.can_write() {
return OperationResult::Rename(Err(NfsStatus::Rofs));
}
match self
.fs
.rename(&src_dir, &args.oldname, &dst_dir, &args.newname)
{
Ok(()) => OperationResult::Rename(Ok(RenameResult {
source_cinfo: ChangeInfo {
atomic: true,
before: 0,
after: 1,
},
target_cinfo: ChangeInfo {
atomic: true,
before: 0,
after: 1,
},
})),
Err(e) => OperationResult::Rename(Err(e.status)),
}
}
fn op_link(&self, args: &LinkArgs, ctx: &mut RequestContext) -> OperationResult {
let src_fh = match &ctx.saved_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Link(Err(NfsStatus::Nofilehandle)),
};
let dir_fh = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Link(Err(NfsStatus::Nofilehandle)),
};
if !ctx.can_write() {
return OperationResult::Link(Err(NfsStatus::Rofs));
}
match self.fs.link(&src_fh, &dir_fh, &args.newname) {
Ok(()) => OperationResult::Link(Ok(LinkResult {
cinfo: ChangeInfo {
atomic: true,
before: 0,
after: 1,
},
})),
Err(e) => OperationResult::Link(Err(e.status)),
}
}
fn op_open(&self, args: &OpenArgs, ctx: &mut RequestContext) -> OperationResult {
let dir_fh = match &ctx.current_fh {
Some(fh) => fh.clone(),
None => return OperationResult::Open(Err(NfsStatus::Nofilehandle)),
};
if matches!(args.open_type, OpenType::Create { .. }) && !ctx.can_write() {
return OperationResult::Open(Err(NfsStatus::Rofs));
}
let (file_fh, _attrs) = match &args.open_type {
OpenType::NoCreate => {
let name = args.name.as_ref().ok_or(NfsStatus::Inval);
match name {
Ok(n) => match self.fs.lookup(&dir_fh, n) {
Ok(fh) => (fh, None),
Err(e) => return OperationResult::Open(Err(e.status)),
},
Err(e) => return OperationResult::Open(Err(e)),
}
}
OpenType::Create { attrs, .. } => {
let name = args.name.as_ref().ok_or(NfsStatus::Inval);
match name {
Ok(n) => {
match self.fs.lookup(&dir_fh, n) {
Ok(fh) => (fh, None),
Err(_) => {
match self.fs.create(&dir_fh, n, attrs, ctx) {
Ok((fh, a)) => (fh, Some(a)),
Err(e) => return OperationResult::Open(Err(e.status)),
}
}
}
}
Err(e) => return OperationResult::Open(Err(e)),
}
}
};
ctx.current_fh = Some(file_fh);
let stateid = StateId::new(1, [0; 12]);
OperationResult::Open(Ok(OpenResult {
stateid,
cinfo: ChangeInfo {
atomic: true,
before: 0,
after: 1,
},
rflags: 0,
attrset: AttrBitmap::all_supported(),
delegation: Delegation::None,
}))
}
fn op_close(&self, args: &CloseArgs, _ctx: &mut RequestContext) -> OperationResult {
OperationResult::Close(Ok(CloseResult {
stateid: StateId::new(args.seqid + 1, args.stateid.other),
}))
}
fn op_setclientid(&self, args: &SetclientidArgs, ctx: &mut RequestContext) -> OperationResult {
if let Some(existing) = find_client_by_owner(&args.id) {
if existing.verifier == args.verifier.0 {
return OperationResult::Setclientid(Ok(SetclientidResult {
clientid: existing.id,
verifier: Verifier::new(existing.verifier),
}));
}
}
let state = ClientState::new(args.id.clone(), args.verifier.0, ctx.client_addr);
let id = register_client(state);
let verifier = Verifier::generate();
OperationResult::Setclientid(Ok(SetclientidResult {
clientid: id,
verifier,
}))
}
fn op_setclientid_confirm(
&self,
args: &SetclientidConfirmArgs,
_ctx: &mut RequestContext,
) -> OperationResult {
match confirm_client(args.clientid) {
Ok(()) => OperationResult::SetclientidConfirm(Ok(())),
Err(e) => OperationResult::SetclientidConfirm(Err(e.status)),
}
}
fn op_renew(&self, args: &RenewArgs, ctx: &mut RequestContext) -> OperationResult {
match renew_client_lease(args.clientid, ctx.timestamp) {
Ok(()) => OperationResult::Renew(Ok(())),
Err(e) => OperationResult::Renew(Err(e.status)),
}
}
fn op_exchange_id(&self, args: &ExchangeIdArgs, ctx: &mut RequestContext) -> OperationResult {
let existing = find_client_by_owner(&args.client_owner.ownerid);
let (client_id, sequence_id) = if let Some(client) = existing {
(client.id, 1u32)
} else {
let state = ClientState::new(
args.client_owner.ownerid.clone(),
args.client_owner.verifier.0,
ctx.client_addr,
);
let id = register_client(state);
(id, 1u32)
};
let state = SERVER_STATE.lock();
OperationResult::ExchangeId(Ok(ExchangeIdResult {
clientid: client_id,
sequenceid: sequence_id,
flags: args.flags,
state_protect: StateProtect4R::None,
server_owner: ServerOwner4 {
minor_id: state.minor_id,
major_id: state.owner_major_id.clone(),
},
server_scope: state.scope.clone(),
impl_id: vec![NfsImplId4 {
domain: "lcpfs.io".into(),
name: state.impl_id.clone(),
date: 0,
}],
}))
}
fn op_create_session(
&self,
args: &CreateSessionArgs,
_ctx: &mut RequestContext,
) -> OperationResult {
if get_client(args.clientid).is_none() {
return OperationResult::CreateSession(Err(NfsStatus::Stale));
}
let session = create_session(args.clientid);
OperationResult::CreateSession(Ok(CreateSessionResult {
sessionid: session.id,
sequenceid: args.seqid + 1,
flags: args.flags,
fore_chan_attrs: args.fore_chan_attrs.clone(),
back_chan_attrs: args.back_chan_attrs.clone(),
}))
}
fn op_destroy_session(
&self,
args: &DestroySessionArgs,
_ctx: &mut RequestContext,
) -> OperationResult {
match destroy_session(args.sessionid) {
Some(_) => OperationResult::DestroySession(Ok(())),
None => OperationResult::DestroySession(Err(NfsStatus::BadSession)),
}
}
fn op_sequence(&self, args: &SequenceArgs, ctx: &mut RequestContext) -> OperationResult {
let session = match get_session(args.sessionid) {
Some(s) => s,
None => return OperationResult::Sequence(Err(NfsStatus::BadSession)),
};
ctx.session_id = Some(args.sessionid);
ctx.client_id = Some(session.client_id);
let _ = renew_client_lease(session.client_id, ctx.timestamp);
OperationResult::Sequence(Ok(SequenceResult {
sessionid: args.sessionid,
sequenceid: args.sequenceid,
slotid: args.slotid,
highest_slotid: args.highest_slotid,
target_highest_slotid: session.fore_slots,
status_flags: 0,
}))
}
fn op_reclaim_complete(
&self,
_args: &ReclaimCompleteArgs,
_ctx: &mut RequestContext,
) -> OperationResult {
OperationResult::ReclaimComplete(Ok(()))
}
fn op_lock(&self, _args: &LockArgs, _ctx: &mut RequestContext) -> OperationResult {
let stateid = StateId::new(1, [0; 12]);
OperationResult::Lock(Ok(LockResult {
lock_stateid: stateid,
}))
}
fn op_lockt(&self, _args: &LocktArgs, _ctx: &mut RequestContext) -> OperationResult {
OperationResult::Lockt(Ok(()))
}
fn op_locku(&self, args: &LockuArgs, _ctx: &mut RequestContext) -> OperationResult {
let stateid = StateId::new(args.seqid + 1, args.lock_stateid.other);
OperationResult::Locku(Ok(LockuResult {
lock_stateid: stateid,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockFs;
impl NfsFilesystem for MockFs {
fn lookup(&self, _dir_fh: &FileHandle, name: &str) -> NfsResult<FileHandle> {
if name == "test.txt" {
Ok(FileHandle::new(1, 100, 1))
} else {
Err(NfsError::noent())
}
}
fn getattr(&self, fh: &FileHandle) -> NfsResult<NfsAttr> {
Ok(NfsAttr::file(fh.object_id(), 1024, 0o644, 1000, 1000))
}
fn setattr(
&self,
_fh: &FileHandle,
_attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<NfsAttr> {
Ok(NfsAttr::default())
}
fn read(&self, _fh: &FileHandle, _offset: u64, count: u32) -> NfsResult<(Vec<u8>, bool)> {
let data = vec![0u8; count.min(1024) as usize];
Ok((data, true))
}
fn write(
&self,
_fh: &FileHandle,
_offset: u64,
data: &[u8],
stable: StableHow,
) -> NfsResult<(u32, StableHow)> {
Ok((data.len() as u32, stable))
}
fn readdir(
&self,
_fh: &FileHandle,
_cookie: u64,
_count: u32,
) -> NfsResult<(Vec<DirEntry>, bool)> {
let entries = vec![
DirEntry::new(1, ".".into()),
DirEntry::new(2, "..".into()),
DirEntry::new(3, "test.txt".into()),
];
Ok((entries, true))
}
fn create(
&self,
_dir_fh: &FileHandle,
_name: &str,
_attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)> {
Ok((
FileHandle::new(1, 200, 1),
NfsAttr::file(200, 0, 0o644, 0, 0),
))
}
fn mkdir(
&self,
_dir_fh: &FileHandle,
_name: &str,
_attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)> {
Ok((
FileHandle::new(1, 201, 1),
NfsAttr::directory(201, 0o755, 0, 0),
))
}
fn remove(&self, _dir_fh: &FileHandle, _name: &str) -> NfsResult<()> {
Ok(())
}
fn rmdir(&self, _dir_fh: &FileHandle, _name: &str) -> NfsResult<()> {
Ok(())
}
fn rename(
&self,
_from_dir: &FileHandle,
_from_name: &str,
_to_dir: &FileHandle,
_to_name: &str,
) -> NfsResult<()> {
Ok(())
}
fn symlink(
&self,
_dir_fh: &FileHandle,
_name: &str,
_target: &str,
_attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)> {
Ok((FileHandle::new(1, 202, 1), NfsAttr::default()))
}
fn readlink(&self, _fh: &FileHandle) -> NfsResult<String> {
Ok("/target".into())
}
fn link(&self, _fh: &FileHandle, _dir_fh: &FileHandle, _name: &str) -> NfsResult<()> {
Ok(())
}
fn commit(&self, _fh: &FileHandle, _offset: u64, _count: u32) -> NfsResult<Verifier> {
Ok(Verifier::generate())
}
fn fsinfo(&self, _fh: &FileHandle) -> NfsResult<FsInfo> {
Ok(FsInfo::default())
}
fn fsstat(&self, _fh: &FileHandle) -> NfsResult<FsStat> {
Ok(FsStat::default())
}
fn parent(&self, _fh: &FileHandle) -> NfsResult<FileHandle> {
Ok(FileHandle::root(1))
}
}
#[test]
fn test_request_context() {
let ctx = RequestContext::new([127, 0, 0, 1]).with_credentials(1000, 1000, vec![]);
assert_eq!(ctx.uid, 1000);
assert_eq!(ctx.effective_uid(), 1000);
}
#[test]
fn test_root_squash() {
let mut ctx = RequestContext::new([127, 0, 0, 1]).with_credentials(0, 0, vec![]);
assert_eq!(ctx.effective_uid(), 0);
ctx.export_options = Some(ExportOptions::default());
assert_eq!(ctx.effective_uid(), 65534);
ctx.export_options = Some(ExportOptions::default().with_no_root_squash());
assert_eq!(ctx.effective_uid(), 0);
}
#[test]
fn test_server_putfh() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
let fh = FileHandle::new(1, 100, 1);
let result = server.op_putfh(&PutfhArgs { fh: fh.clone() }, &mut ctx);
assert!(result.is_ok());
assert_eq!(ctx.current_fh.as_ref().unwrap().object_id(), 100);
}
#[test]
fn test_server_putrootfh() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
let result = server.op_putrootfh(&mut ctx);
assert!(result.is_ok());
assert!(ctx.current_fh.is_some());
}
#[test]
fn test_server_lookup() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::root(1));
let result = server.op_lookup(
&LookupArgs {
name: "test.txt".into(),
},
&mut ctx,
);
assert!(result.is_ok());
assert_eq!(ctx.current_fh.as_ref().unwrap().object_id(), 100);
}
#[test]
fn test_server_lookup_noent() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::root(1));
let result = server.op_lookup(
&LookupArgs {
name: "nonexistent".into(),
},
&mut ctx,
);
assert_eq!(result.status(), NfsStatus::Noent);
}
#[test]
fn test_server_getattr() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::new(1, 100, 1));
let result = server.op_getattr(
&GetattrArgs {
attr_request: AttrBitmap::all_supported(),
},
&mut ctx,
);
assert!(result.is_ok());
}
#[test]
fn test_server_read() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::new(1, 100, 1));
let result = server.op_read(
&ReadArgs {
stateid: StateId::anonymous(),
offset: 0,
count: 4096,
},
&mut ctx,
);
assert!(result.is_ok());
}
#[test]
fn test_server_write_read_only() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::new(1, 100, 1));
ctx.export_options = Some(ExportOptions::read_only());
let result = server.op_write(
&WriteArgs {
stateid: StateId::anonymous(),
offset: 0,
stable: StableHow::FileSync,
data: vec![0; 100],
},
&mut ctx,
);
assert_eq!(result.status(), NfsStatus::Rofs);
}
#[test]
fn test_server_write_read_write() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::new(1, 100, 1));
ctx.export_options = Some(ExportOptions::read_write());
let result = server.op_write(
&WriteArgs {
stateid: StateId::anonymous(),
offset: 0,
stable: StableHow::FileSync,
data: vec![0; 100],
},
&mut ctx,
);
assert!(result.is_ok());
}
#[test]
fn test_server_compound() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.export_options = Some(ExportOptions::read_write());
let request = CompoundRequest {
tag: "test".into(),
minorversion: 0,
operations: vec![
Operation::Putrootfh,
Operation::Lookup(LookupArgs {
name: "test.txt".into(),
}),
Operation::Getfh,
Operation::Getattr(GetattrArgs {
attr_request: AttrBitmap::all_supported(),
}),
],
};
let response = server.process_compound(request, &mut ctx);
assert_eq!(response.status, NfsStatus::Ok);
assert_eq!(response.results.len(), 4);
}
#[test]
fn test_server_savefh_restorefh() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::new(1, 100, 1));
let result = server.op_savefh(&mut ctx);
assert!(result.is_ok());
assert!(ctx.saved_fh.is_some());
ctx.current_fh = Some(FileHandle::new(1, 200, 1));
let result = server.op_restorefh(&mut ctx);
assert!(result.is_ok());
assert_eq!(ctx.current_fh.as_ref().unwrap().object_id(), 100);
}
#[test]
fn test_server_readdir() {
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::root(1));
let result = server.op_readdir(
&ReaddirArgs {
cookie: 0,
cookieverf: Verifier::default(),
dircount: 4096,
maxcount: 8192,
attr_request: AttrBitmap::new(),
},
&mut ctx,
);
assert!(result.is_ok());
}
#[test]
fn test_server_stats() {
reset_stats();
let server = NfsServer::new(MockFs);
let mut ctx = RequestContext::new([127, 0, 0, 1]);
ctx.current_fh = Some(FileHandle::new(1, 100, 1));
let _ = server.op_read(
&ReadArgs {
stateid: StateId::anonymous(),
offset: 0,
count: 1024,
},
&mut ctx,
);
let stats = get_stats();
assert!(stats.reads >= 1);
assert!(stats.bytes_read >= 1024);
}
}