use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use super::error::{NfsError, NfsResult, NfsStatus};
use super::ops::StableHow;
use super::server::{NfsFilesystem, RequestContext};
use super::types::{DirEntry, FileHandle, FileType, FsInfo, FsStat, NfsAttr, SetAttr, Verifier};
use crate::FsError;
use crate::storage::zpl::{
S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, ZPL, Znode, ZnodePhys,
};
const DEFAULT_DATASET_ID: u64 = 1;
const ROOT_OBJECT_ID: u64 = 2;
const BLOCK_SIZE: u32 = 4096;
const MAX_RW_SIZE: u32 = 1024 * 1024;
const MAX_LINK: u32 = 65535;
fn fs_error_to_nfs_status(err: FsError) -> NfsStatus {
match err {
FsError::NotFound => NfsStatus::Noent,
FsError::PathNotFound { .. } => NfsStatus::Noent,
FsError::DiskFull { .. } => NfsStatus::Nospc,
FsError::IoError { .. } => NfsStatus::Io,
FsError::Corruption { .. } => NfsStatus::Io,
FsError::ChecksumMismatch { .. } => NfsStatus::Io,
FsError::EncryptionFailed => NfsStatus::Io,
FsError::DecryptionFailed => NfsStatus::Io,
FsError::CompressionFailed => NfsStatus::Io,
FsError::DecompressionFailed => NfsStatus::Io,
FsError::InvalidBlockPointer => NfsStatus::Io,
FsError::PoolNotImported => NfsStatus::Serverfault,
FsError::InvalidPoolConfig { .. } => NfsStatus::Serverfault,
FsError::TxgError { .. } => NfsStatus::Io,
FsError::ZapError { .. } => NfsStatus::Io,
FsError::DatasetError { .. } => NfsStatus::Serverfault,
FsError::DeviceTooSmall { .. } => NfsStatus::Nospc,
FsError::AlreadyExists => NfsStatus::Exist,
FsError::IsDirectory => NfsStatus::Isdir,
FsError::IsFile => NfsStatus::Notdir,
FsError::PermissionDenied => NfsStatus::Acces,
FsError::ResourceBusy => NfsStatus::Locked,
FsError::ReadOnly => NfsStatus::Rofs,
FsError::InvalidArgument { .. } => NfsStatus::Inval,
FsError::NotDirectory => NfsStatus::Notdir,
FsError::BadFileDescriptor => NfsStatus::Badhandle,
FsError::NotImplemented => NfsStatus::Notsupp,
FsError::DirectoryNotEmpty => NfsStatus::Notempty,
FsError::NoDevice => NfsStatus::Nxio,
FsError::SecurityViolation { .. } => NfsStatus::Acces,
}
}
fn fs_error_to_nfs(err: FsError) -> NfsError {
NfsError::new(fs_error_to_nfs_status(err))
}
fn mode_to_file_type(mode: u64) -> FileType {
match (mode as u32) & S_IFMT {
S_IFREG => FileType::Regular,
S_IFDIR => FileType::Directory,
S_IFLNK => FileType::Symlink,
S_IFBLK => FileType::Block,
S_IFCHR => FileType::Character,
S_IFIFO => FileType::Fifo,
S_IFSOCK => FileType::Socket,
_ => FileType::Regular, }
}
fn znode_to_nfs_attr(znode: &Znode) -> NfsAttr {
let phys = &znode.phys;
let file_type = mode_to_file_type(phys.mode);
NfsAttr {
file_type,
mode: (phys.mode & 0o7777) as u32,
nlink: phys.links as u32,
uid: phys.uid as u32,
gid: phys.gid as u32,
size: phys.size,
used: phys.size.div_ceil(512) * 512, rdev: phys.rdev,
fsid: DEFAULT_DATASET_ID,
fileid: znode.object_id,
atime_sec: phys.atime[0],
atime_nsec: phys.atime[1] as u32,
mtime_sec: phys.mtime[0],
mtime_nsec: phys.mtime[1] as u32,
ctime_sec: phys.ctime[0],
ctime_nsec: phys.ctime[1] as u32,
blksize: BLOCK_SIZE,
blocks: phys.size.div_ceil(512),
}
}
fn object_id_to_handle(object_id: u64, generation: u64) -> FileHandle {
FileHandle::new(DEFAULT_DATASET_ID, object_id, generation)
}
fn handle_to_object_id(fh: &FileHandle) -> u64 {
fh.object_id()
}
pub struct ZplNfsAdapter;
impl ZplNfsAdapter {
pub fn new() -> Self {
Self
}
}
impl Default for ZplNfsAdapter {
fn default() -> Self {
Self::new()
}
}
impl NfsFilesystem for ZplNfsAdapter {
fn lookup(&self, dir_fh: &FileHandle, name: &str) -> NfsResult<FileHandle> {
let dir_id = handle_to_object_id(dir_fh);
let zpl = ZPL.lock();
let child_id = zpl.lookup(dir_id, name).map_err(fs_error_to_nfs)?;
let generation = zpl
.get_znode(child_id)
.map(|z| z.phys.generation)
.unwrap_or(0);
Ok(object_id_to_handle(child_id, generation))
}
fn parent(&self, fh: &FileHandle) -> NfsResult<FileHandle> {
let object_id = handle_to_object_id(fh);
let zpl = ZPL.lock();
let znode = zpl
.get_znode(object_id)
.ok_or(NfsError::from(NfsStatus::Stale))?;
let parent_id = znode.phys.parent;
let parent_gen = zpl
.get_znode(parent_id)
.map(|z| z.phys.generation)
.unwrap_or(0);
Ok(object_id_to_handle(parent_id, parent_gen))
}
fn getattr(&self, fh: &FileHandle) -> NfsResult<NfsAttr> {
let object_id = handle_to_object_id(fh);
let zpl = ZPL.lock();
let znode = zpl
.get_znode(object_id)
.ok_or(NfsError::from(NfsStatus::Stale))?;
Ok(znode_to_nfs_attr(znode))
}
fn setattr(
&self,
fh: &FileHandle,
attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<NfsAttr> {
let object_id = handle_to_object_id(fh);
let mut zpl = ZPL.lock();
if let Some(mode) = attrs.mode {
zpl.setattr(object_id, Some(mode), None, None)
.map_err(fs_error_to_nfs)?;
}
if let Some(uid) = attrs.uid {
zpl.setattr(object_id, None, Some(uid), None)
.map_err(fs_error_to_nfs)?;
}
if let Some(gid) = attrs.gid {
zpl.setattr(object_id, None, None, Some(gid))
.map_err(fs_error_to_nfs)?;
}
if let Some(size) = attrs.size {
let handle = zpl.open(object_id, 0o2).map_err(fs_error_to_nfs)?; zpl.truncate(handle, size).map_err(fs_error_to_nfs)?;
let _ = zpl.close(handle);
}
let znode = zpl
.get_znode(object_id)
.ok_or(NfsError::from(NfsStatus::Stale))?;
Ok(znode_to_nfs_attr(znode))
}
fn read(&self, fh: &FileHandle, offset: u64, count: u32) -> NfsResult<(Vec<u8>, bool)> {
let object_id = handle_to_object_id(fh);
let mut zpl = ZPL.lock();
let handle = zpl.open(object_id, 0).map_err(fs_error_to_nfs)?;
zpl.seek(handle, offset as i64, 0)
.map_err(fs_error_to_nfs)?;
let mut buf = vec![0u8; count as usize];
let bytes_read = zpl.read(handle, &mut buf).map_err(fs_error_to_nfs)?;
buf.truncate(bytes_read);
let _ = zpl.close(handle);
let znode = zpl.get_znode(object_id);
let eof = match znode {
Some(z) => offset + bytes_read as u64 >= z.phys.size,
None => true,
};
Ok((buf, eof))
}
fn write(
&self,
fh: &FileHandle,
offset: u64,
data: &[u8],
stable: StableHow,
) -> NfsResult<(u32, StableHow)> {
let object_id = handle_to_object_id(fh);
let mut zpl = ZPL.lock();
let handle = zpl.open(object_id, 0o1).map_err(fs_error_to_nfs)?;
zpl.seek(handle, offset as i64, 0)
.map_err(fs_error_to_nfs)?;
let bytes_written = zpl.write(handle, data).map_err(fs_error_to_nfs)?;
let committed = match stable {
StableHow::Unstable => {
let _ = zpl.close(handle);
StableHow::Unstable
}
StableHow::DataSync | StableHow::FileSync => {
zpl.fsync(handle).map_err(fs_error_to_nfs)?;
let _ = zpl.close(handle);
StableHow::FileSync
}
};
Ok((bytes_written as u32, committed))
}
fn readdir(
&self,
fh: &FileHandle,
cookie: u64,
count: u32,
) -> NfsResult<(Vec<DirEntry>, bool)> {
let object_id = handle_to_object_id(fh);
let zpl = ZPL.lock();
let entries = zpl.readdir(object_id).map_err(fs_error_to_nfs)?;
let mut nfs_entries = Vec::new();
let mut current_cookie: u64 = 0;
let mut bytes_used: u32 = 0;
for entry in entries {
current_cookie += 1;
if current_cookie <= cookie {
continue;
}
let entry_size = 32 + entry.name.len() as u32;
if bytes_used + entry_size > count {
return Ok((nfs_entries, false));
}
let child_id = entry.object_id;
let (attrs, entry_fh) = {
let child_znode = zpl.get_znode(child_id);
match child_znode {
Some(z) => {
let attr = znode_to_nfs_attr(z);
let handle = object_id_to_handle(child_id, z.phys.generation);
(Some(attr), Some(handle))
}
None => (None, None),
}
};
nfs_entries.push(DirEntry {
cookie: current_cookie,
name: entry.name,
attrs,
fh: entry_fh,
});
bytes_used += entry_size;
}
Ok((nfs_entries, true))
}
fn create(
&self,
dir_fh: &FileHandle,
name: &str,
attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)> {
let dir_id = handle_to_object_id(dir_fh);
let mut zpl = ZPL.lock();
let mode = attrs.mode.unwrap_or(0o644);
let uid = attrs.uid.unwrap_or(0);
let gid = attrs.gid.unwrap_or(0);
let object_id = zpl
.create(dir_id, name, mode, uid, gid)
.map_err(fs_error_to_nfs)?;
let znode = zpl
.get_znode(object_id)
.ok_or(NfsError::from(NfsStatus::Serverfault))?;
let nfs_attr = znode_to_nfs_attr(znode);
let handle = object_id_to_handle(object_id, znode.phys.generation);
Ok((handle, nfs_attr))
}
fn mkdir(
&self,
dir_fh: &FileHandle,
name: &str,
attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)> {
let dir_id = handle_to_object_id(dir_fh);
let mut zpl = ZPL.lock();
let mode = attrs.mode.unwrap_or(0o755);
let uid = attrs.uid.unwrap_or(0);
let gid = attrs.gid.unwrap_or(0);
let object_id = zpl
.mkdir(dir_id, name, mode, uid, gid)
.map_err(fs_error_to_nfs)?;
let znode = zpl
.get_znode(object_id)
.ok_or(NfsError::from(NfsStatus::Serverfault))?;
let nfs_attr = znode_to_nfs_attr(znode);
let handle = object_id_to_handle(object_id, znode.phys.generation);
Ok((handle, nfs_attr))
}
fn remove(&self, dir_fh: &FileHandle, name: &str) -> NfsResult<()> {
let dir_id = handle_to_object_id(dir_fh);
let mut zpl = ZPL.lock();
let object_id = zpl.lookup(dir_id, name).map_err(fs_error_to_nfs)?;
if let Some(znode) = zpl.get_znode(object_id) {
if znode.is_dir() {
return Err(NfsError::from(NfsStatus::Isdir));
}
}
zpl.unlink(dir_id, name).map_err(fs_error_to_nfs)?;
Ok(())
}
fn rmdir(&self, dir_fh: &FileHandle, name: &str) -> NfsResult<()> {
let dir_id = handle_to_object_id(dir_fh);
let mut zpl = ZPL.lock();
let object_id = zpl.lookup(dir_id, name).map_err(fs_error_to_nfs)?;
if let Some(znode) = zpl.get_znode(object_id) {
if !znode.is_dir() {
return Err(NfsError::from(NfsStatus::Notdir));
}
}
zpl.rmdir(dir_id, name).map_err(fs_error_to_nfs)?;
Ok(())
}
fn rename(
&self,
from_dir: &FileHandle,
from_name: &str,
to_dir: &FileHandle,
to_name: &str,
) -> NfsResult<()> {
let from_dir_id = handle_to_object_id(from_dir);
let to_dir_id = handle_to_object_id(to_dir);
let mut zpl = ZPL.lock();
zpl.rename(from_dir_id, from_name, to_dir_id, to_name)
.map_err(fs_error_to_nfs)?;
Ok(())
}
fn symlink(
&self,
dir_fh: &FileHandle,
name: &str,
target: &str,
_attrs: &SetAttr,
_ctx: &RequestContext,
) -> NfsResult<(FileHandle, NfsAttr)> {
let dir_id = handle_to_object_id(dir_fh);
let mut zpl = ZPL.lock();
let object_id = zpl
.symlink(dir_id, name, target, 0, 0)
.map_err(fs_error_to_nfs)?;
let znode = zpl
.get_znode(object_id)
.ok_or(NfsError::from(NfsStatus::Serverfault))?;
let nfs_attr = znode_to_nfs_attr(znode);
let handle = object_id_to_handle(object_id, znode.phys.generation);
Ok((handle, nfs_attr))
}
fn readlink(&self, fh: &FileHandle) -> NfsResult<String> {
let object_id = handle_to_object_id(fh);
let zpl = ZPL.lock();
let target = zpl.readlink(object_id).map_err(fs_error_to_nfs)?;
Ok(target)
}
fn link(&self, fh: &FileHandle, dir_fh: &FileHandle, name: &str) -> NfsResult<()> {
let object_id = handle_to_object_id(fh);
let dir_id = handle_to_object_id(dir_fh);
let mut zpl = ZPL.lock();
zpl.link_create(dir_id, name, object_id)
.map_err(fs_error_to_nfs)?;
Ok(())
}
fn commit(&self, fh: &FileHandle, _offset: u64, _count: u32) -> NfsResult<Verifier> {
let object_id = handle_to_object_id(fh);
let mut zpl = ZPL.lock();
let _ = zpl.txg_sync(0);
let verifier = if let Some(znode) = zpl.get_znode(object_id) {
let mtime = znode.phys.mtime[0];
Verifier::from_u64(mtime)
} else {
Verifier::from_u64(0)
};
Ok(verifier)
}
fn fsinfo(&self, _fh: &FileHandle) -> NfsResult<FsInfo> {
Ok(FsInfo {
rtmax: MAX_RW_SIZE,
rtpref: MAX_RW_SIZE,
rtmult: BLOCK_SIZE,
wtmax: MAX_RW_SIZE,
wtpref: MAX_RW_SIZE,
wtmult: BLOCK_SIZE,
dtpref: BLOCK_SIZE,
maxfilesize: u64::MAX,
time_delta_sec: 0,
time_delta_nsec: 1,
properties: 0x001B, })
}
fn fsstat(&self, _fh: &FileHandle) -> NfsResult<FsStat> {
let zpl = ZPL.lock();
let used = zpl.used_bytes();
let quota = zpl.quota();
let total = if quota > 0 {
quota
} else {
1024 * 1024 * 1024 * 1024
};
let free = total.saturating_sub(used);
let files_total = total / BLOCK_SIZE as u64;
let files_free = free / BLOCK_SIZE as u64;
Ok(FsStat {
tbytes: total,
fbytes: free,
abytes: free, tfiles: files_total,
ffiles: files_free,
afiles: files_free,
invarsec: 0, })
}
}
use lazy_static::lazy_static;
use spin::Mutex;
use super::server::NfsServer;
lazy_static! {
pub static ref NFS_SERVER: Mutex<NfsServer<ZplNfsAdapter>> =
Mutex::new(NfsServer::new(ZplNfsAdapter::new()));
}
pub fn get_root_handle() -> FileHandle {
let generation = {
let zpl = ZPL.lock();
zpl.get_znode(ROOT_OBJECT_ID)
.map(|z| z.phys.generation)
.unwrap_or(0)
};
FileHandle::new(DEFAULT_DATASET_ID, ROOT_OBJECT_ID, generation)
}
pub fn process_request(
request: super::ops::CompoundRequest,
ctx: &mut RequestContext,
) -> super::ops::CompoundResponse {
let server = NFS_SERVER.lock();
server.process_compound(request, ctx)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_conversion() {
assert_eq!(fs_error_to_nfs_status(FsError::NotFound), NfsStatus::Noent);
assert_eq!(
fs_error_to_nfs_status(FsError::AlreadyExists),
NfsStatus::Exist
);
assert_eq!(
fs_error_to_nfs_status(FsError::IsDirectory),
NfsStatus::Isdir
);
assert_eq!(
fs_error_to_nfs_status(FsError::NotDirectory),
NfsStatus::Notdir
);
assert_eq!(
fs_error_to_nfs_status(FsError::PermissionDenied),
NfsStatus::Acces
);
assert_eq!(fs_error_to_nfs_status(FsError::ReadOnly), NfsStatus::Rofs);
assert_eq!(
fs_error_to_nfs_status(FsError::DirectoryNotEmpty),
NfsStatus::Notempty
);
}
#[test]
fn test_mode_to_file_type() {
assert_eq!(mode_to_file_type(S_IFREG as u64), FileType::Regular);
assert_eq!(mode_to_file_type(S_IFDIR as u64), FileType::Directory);
assert_eq!(mode_to_file_type(S_IFLNK as u64), FileType::Symlink);
assert_eq!(mode_to_file_type(S_IFBLK as u64), FileType::Block);
assert_eq!(mode_to_file_type(S_IFCHR as u64), FileType::Character);
assert_eq!(mode_to_file_type(S_IFIFO as u64), FileType::Fifo);
assert_eq!(mode_to_file_type(S_IFSOCK as u64), FileType::Socket);
}
#[test]
fn test_file_handle_conversion() {
let handle = object_id_to_handle(12345, 1);
assert_eq!(handle.dataset_id(), DEFAULT_DATASET_ID);
assert_eq!(handle.object_id(), 12345);
assert_eq!(handle.generation(), 1);
let object_id = handle_to_object_id(&handle);
assert_eq!(object_id, 12345);
}
#[test]
fn test_adapter_creation() {
let adapter = ZplNfsAdapter::new();
let _ = adapter;
let default_adapter: ZplNfsAdapter = Default::default();
let _ = default_adapter;
}
#[test]
fn test_root_handle() {
let handle = get_root_handle();
assert_eq!(handle.dataset_id(), DEFAULT_DATASET_ID);
assert_eq!(handle.object_id(), ROOT_OBJECT_ID);
}
#[test]
fn test_fsinfo() {
let adapter = ZplNfsAdapter::new();
let root = get_root_handle();
let info = adapter.fsinfo(&root).unwrap();
assert_eq!(info.rtmax, MAX_RW_SIZE);
assert_eq!(info.wtmax, MAX_RW_SIZE);
assert!(info.maxfilesize > 0);
}
#[test]
fn test_fsstat() {
let adapter = ZplNfsAdapter::new();
let root = get_root_handle();
let stat = adapter.fsstat(&root).unwrap();
assert!(stat.tbytes > 0);
assert!(stat.fbytes <= stat.tbytes);
}
#[test]
fn test_getattr_root() {
let adapter = ZplNfsAdapter::new();
let root = get_root_handle();
let result = adapter.getattr(&root);
assert!(result.is_ok() || result.err().unwrap().status == NfsStatus::Stale);
}
}