use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
pub const NFS4_PROGRAM: u32 = 100003;
pub const NFS4_VERSION: u32 = 4;
pub const NFS4_FHSIZE: usize = 128;
pub const NFS4_MAXNAMLEN: usize = 255;
pub const NFS4_MAXPATHLEN: usize = 4096;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct FileHandle {
data: [u8; NFS4_FHSIZE],
len: usize,
}
impl FileHandle {
pub fn new(dataset_id: u64, object_id: u64, generation: u64) -> Self {
let mut data = [0u8; NFS4_FHSIZE];
data[0..8].copy_from_slice(&dataset_id.to_le_bytes());
data[8..16].copy_from_slice(&object_id.to_le_bytes());
data[16..24].copy_from_slice(&generation.to_le_bytes());
Self { data, len: 24 }
}
pub fn root(dataset_id: u64) -> Self {
Self::new(dataset_id, 0, 0)
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() > NFS4_FHSIZE {
return None;
}
let mut data = [0u8; NFS4_FHSIZE];
data[..bytes.len()].copy_from_slice(bytes);
Some(Self {
data,
len: bytes.len(),
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.data[..self.len]
}
pub fn dataset_id(&self) -> u64 {
if self.len >= 8 {
u64::from_le_bytes(self.data[0..8].try_into().unwrap())
} else {
0
}
}
pub fn object_id(&self) -> u64 {
if self.len >= 16 {
u64::from_le_bytes(self.data[8..16].try_into().unwrap())
} else {
0
}
}
pub fn generation(&self) -> u64 {
if self.len >= 24 {
u64::from_le_bytes(self.data[16..24].try_into().unwrap())
} else {
0
}
}
pub fn is_root(&self) -> bool {
self.object_id() == 0
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl fmt::Debug for FileHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"FileHandle(ds={}, obj={}, gen={})",
self.dataset_id(),
self.object_id(),
self.generation()
)
}
}
impl Default for FileHandle {
fn default() -> Self {
Self {
data: [0u8; NFS4_FHSIZE],
len: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FileType {
Regular = 1,
Directory = 2,
Block = 3,
Character = 4,
Symlink = 5,
Socket = 6,
Fifo = 7,
AttrDir = 8,
NamedAttr = 9,
}
impl FileType {
pub fn from_u32(v: u32) -> Option<Self> {
match v {
1 => Some(Self::Regular),
2 => Some(Self::Directory),
3 => Some(Self::Block),
4 => Some(Self::Character),
5 => Some(Self::Symlink),
6 => Some(Self::Socket),
7 => Some(Self::Fifo),
8 => Some(Self::AttrDir),
9 => Some(Self::NamedAttr),
_ => None,
}
}
pub fn is_directory(&self) -> bool {
matches!(self, Self::Directory | Self::AttrDir)
}
pub fn is_regular(&self) -> bool {
matches!(self, Self::Regular)
}
}
#[derive(Debug, Clone)]
pub struct NfsAttr {
pub file_type: FileType,
pub mode: u32,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub size: u64,
pub used: u64,
pub blksize: u32,
pub blocks: u64,
pub fsid: u64,
pub fileid: u64,
pub atime_sec: u64,
pub atime_nsec: u32,
pub mtime_sec: u64,
pub mtime_nsec: u32,
pub ctime_sec: u64,
pub ctime_nsec: u32,
pub rdev: u64,
}
impl NfsAttr {
pub fn directory(fileid: u64, mode: u32, uid: u32, gid: u32) -> Self {
Self {
file_type: FileType::Directory,
mode: mode | 0o40000, nlink: 2,
uid,
gid,
size: 4096,
used: 4096,
blksize: 4096,
blocks: 8,
fsid: 0,
fileid,
atime_sec: 0,
atime_nsec: 0,
mtime_sec: 0,
mtime_nsec: 0,
ctime_sec: 0,
ctime_nsec: 0,
rdev: 0,
}
}
pub fn file(fileid: u64, size: u64, mode: u32, uid: u32, gid: u32) -> Self {
let blocks = size.div_ceil(512);
Self {
file_type: FileType::Regular,
mode: mode | 0o100000, nlink: 1,
uid,
gid,
size,
used: blocks * 512,
blksize: 4096,
blocks,
fsid: 0,
fileid,
atime_sec: 0,
atime_nsec: 0,
mtime_sec: 0,
mtime_nsec: 0,
ctime_sec: 0,
ctime_nsec: 0,
rdev: 0,
}
}
pub fn with_times(mut self, atime: u64, mtime: u64, ctime: u64) -> Self {
self.atime_sec = atime;
self.mtime_sec = mtime;
self.ctime_sec = ctime;
self
}
pub fn with_fsid(mut self, fsid: u64) -> Self {
self.fsid = fsid;
self
}
}
impl Default for NfsAttr {
fn default() -> Self {
Self {
file_type: FileType::Regular,
mode: 0o644,
nlink: 1,
uid: 0,
gid: 0,
size: 0,
used: 0,
blksize: 4096,
blocks: 0,
fsid: 0,
fileid: 0,
atime_sec: 0,
atime_nsec: 0,
mtime_sec: 0,
mtime_nsec: 0,
ctime_sec: 0,
ctime_nsec: 0,
rdev: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AttrBitmap {
pub words: [u32; 3],
}
impl AttrBitmap {
pub fn new() -> Self {
Self { words: [0; 3] }
}
pub fn all_supported() -> Self {
Self {
words: [
0x0011a7ff, 0x00b0a23a, 0x00000000, ],
}
}
pub fn set(&mut self, attr: u32) {
let word = (attr / 32) as usize;
let bit = attr % 32;
if word < 3 {
self.words[word] |= 1 << bit;
}
}
pub fn is_set(&self, attr: u32) -> bool {
let word = (attr / 32) as usize;
let bit = attr % 32;
word < 3 && (self.words[word] & (1 << bit)) != 0
}
pub fn from_words(words: &[u32]) -> Self {
let mut bitmap = Self::new();
for (i, &w) in words.iter().take(3).enumerate() {
bitmap.words[i] = w;
}
bitmap
}
}
pub const FATTR4_SUPPORTED_ATTRS: u32 = 0;
pub const FATTR4_TYPE: u32 = 1;
pub const FATTR4_FH_EXPIRE_TYPE: u32 = 2;
pub const FATTR4_CHANGE: u32 = 3;
pub const FATTR4_SIZE: u32 = 4;
pub const FATTR4_LINK_SUPPORT: u32 = 5;
pub const FATTR4_SYMLINK_SUPPORT: u32 = 6;
pub const FATTR4_NAMED_ATTR: u32 = 7;
pub const FATTR4_FSID: u32 = 8;
pub const FATTR4_FILEID: u32 = 20;
pub const FATTR4_FILES_AVAIL: u32 = 11;
pub const FATTR4_FILES_FREE: u32 = 12;
pub const FATTR4_FILES_TOTAL: u32 = 13;
pub const FATTR4_MAXFILESIZE: u32 = 27;
pub const FATTR4_MAXLINK: u32 = 28;
pub const FATTR4_MAXNAME: u32 = 29;
pub const FATTR4_MAXREAD: u32 = 30;
pub const FATTR4_MAXWRITE: u32 = 31;
pub const FATTR4_MODE: u32 = 33;
pub const FATTR4_NUMLINKS: u32 = 35;
pub const FATTR4_OWNER: u32 = 36;
pub const FATTR4_OWNER_GROUP: u32 = 37;
pub const FATTR4_SPACE_AVAIL: u32 = 42;
pub const FATTR4_SPACE_FREE: u32 = 43;
pub const FATTR4_SPACE_TOTAL: u32 = 44;
pub const FATTR4_SPACE_USED: u32 = 45;
pub const FATTR4_TIME_ACCESS: u32 = 47;
pub const FATTR4_TIME_METADATA: u32 = 52;
pub const FATTR4_TIME_MODIFY: u32 = 53;
#[derive(Debug, Clone, Copy, Default)]
pub struct AccessBits(pub u32);
impl AccessBits {
pub const READ: u32 = 0x00000001;
pub const LOOKUP: u32 = 0x00000002;
pub const MODIFY: u32 = 0x00000004;
pub const EXTEND: u32 = 0x00000008;
pub const DELETE: u32 = 0x00000010;
pub const EXECUTE: u32 = 0x00000020;
pub fn new(bits: u32) -> Self {
Self(bits)
}
pub fn read(&self) -> bool {
self.0 & Self::READ != 0
}
pub fn lookup(&self) -> bool {
self.0 & Self::LOOKUP != 0
}
pub fn modify(&self) -> bool {
self.0 & Self::MODIFY != 0
}
pub fn extend(&self) -> bool {
self.0 & Self::EXTEND != 0
}
pub fn delete(&self) -> bool {
self.0 & Self::DELETE != 0
}
pub fn execute(&self) -> bool {
self.0 & Self::EXECUTE != 0
}
pub fn all_file() -> Self {
Self(Self::READ | Self::MODIFY | Self::EXTEND | Self::EXECUTE)
}
pub fn all_dir() -> Self {
Self(Self::READ | Self::LOOKUP | Self::MODIFY | Self::EXTEND | Self::DELETE)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum OpenMode {
Unchecked = 0,
Guarded = 1,
Exclusive = 2,
Exclusive4_1 = 3,
}
#[derive(Debug, Clone, Copy)]
pub struct ShareAccess(pub u32);
impl ShareAccess {
pub const READ: u32 = 0x00000001;
pub const WRITE: u32 = 0x00000002;
pub const BOTH: u32 = 0x00000003;
pub fn new(bits: u32) -> Self {
Self(bits)
}
pub fn read(&self) -> bool {
self.0 & Self::READ != 0
}
pub fn write(&self) -> bool {
self.0 & Self::WRITE != 0
}
}
#[derive(Debug, Clone, Copy)]
pub struct ShareDeny(pub u32);
impl ShareDeny {
pub const NONE: u32 = 0x00000000;
pub const READ: u32 = 0x00000001;
pub const WRITE: u32 = 0x00000002;
pub const BOTH: u32 = 0x00000003;
pub fn new(bits: u32) -> Self {
Self(bits)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StateId {
pub seqid: u32,
pub other: [u8; 12],
}
impl StateId {
pub fn new(seqid: u32, other: [u8; 12]) -> Self {
Self { seqid, other }
}
pub fn anonymous() -> Self {
Self {
seqid: 0,
other: [0; 12],
}
}
pub fn current() -> Self {
Self {
seqid: 1,
other: [0xff; 12],
}
}
pub fn bypass() -> Self {
Self {
seqid: 0xffffffff,
other: [0xff; 12],
}
}
pub fn is_anonymous(&self) -> bool {
self.seqid == 0 && self.other == [0; 12]
}
pub fn is_current(&self) -> bool {
self.seqid == 1 && self.other == [0xff; 12]
}
}
impl Default for StateId {
fn default() -> Self {
Self::anonymous()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClientId {
pub id: u64,
}
impl ClientId {
pub fn new(id: u64) -> Self {
Self { id }
}
pub fn generate() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
Self {
id: NEXT_ID.fetch_add(1, Ordering::SeqCst),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct SessionId {
pub id: [u8; 16],
}
impl SessionId {
pub fn new(id: [u8; 16]) -> Self {
Self { id }
}
pub fn generate() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(1);
let count = COUNTER.fetch_add(1, Ordering::SeqCst);
let mut id = [0u8; 16];
id[0..8].copy_from_slice(&count.to_le_bytes());
id[8..16].copy_from_slice(&0x4C435046534E4653_u64.to_le_bytes());
Self { id }
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub cookie: u64,
pub name: String,
pub attrs: Option<NfsAttr>,
pub fh: Option<FileHandle>,
}
impl DirEntry {
pub fn new(cookie: u64, name: String) -> Self {
Self {
cookie,
name,
attrs: None,
fh: None,
}
}
pub fn with_attrs(mut self, attrs: NfsAttr) -> Self {
self.attrs = Some(attrs);
self
}
pub fn with_handle(mut self, fh: FileHandle) -> Self {
self.fh = Some(fh);
self
}
}
#[derive(Debug, Clone, Default)]
pub struct SetAttr {
pub mode: Option<u32>,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub size: Option<u64>,
pub atime: Option<TimeHow>,
pub mtime: Option<TimeHow>,
}
#[derive(Debug, Clone, Copy)]
pub enum TimeHow {
DontChange,
SetToServerTime,
SetToClientTime {
sec: u64,
nsec: u32,
},
}
impl SetAttr {
pub fn new() -> Self {
Self::default()
}
pub fn with_mode(mut self, mode: u32) -> Self {
self.mode = Some(mode);
self
}
pub fn with_size(mut self, size: u64) -> Self {
self.size = Some(size);
self
}
pub fn with_owner(mut self, uid: u32, gid: u32) -> Self {
self.uid = Some(uid);
self.gid = Some(gid);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum LockType {
Read = 1,
Write = 2,
ReadBlocking = 3,
WriteBlocking = 4,
}
#[derive(Debug, Clone, Copy)]
pub struct LockRange {
pub offset: u64,
pub length: u64,
}
impl LockRange {
pub fn new(offset: u64, length: u64) -> Self {
Self { offset, length }
}
pub fn all() -> Self {
Self {
offset: 0,
length: 0,
}
}
pub fn overlaps(&self, other: &Self) -> bool {
let end1 = if self.length == 0 {
u64::MAX
} else {
self.offset.saturating_add(self.length)
};
let end2 = if other.length == 0 {
u64::MAX
} else {
other.offset.saturating_add(other.length)
};
self.offset < end2 && other.offset < end1
}
}
#[derive(Debug, Clone)]
pub struct FsInfo {
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_sec: u32,
pub time_delta_nsec: u32,
pub properties: u32,
}
impl Default for FsInfo {
fn default() -> Self {
Self {
rtmax: 1024 * 1024, rtpref: 128 * 1024, rtmult: 4096, wtmax: 1024 * 1024, wtpref: 128 * 1024, wtmult: 4096, dtpref: 8192, maxfilesize: u64::MAX, time_delta_sec: 0,
time_delta_nsec: 1, properties: 0x0000001b, }
}
}
#[derive(Debug, Clone)]
pub struct FsStat {
pub tbytes: u64,
pub fbytes: u64,
pub abytes: u64,
pub tfiles: u64,
pub ffiles: u64,
pub afiles: u64,
pub invarsec: u32,
}
impl Default for FsStat {
fn default() -> Self {
Self {
tbytes: 1024 * 1024 * 1024 * 1024, fbytes: 512 * 1024 * 1024 * 1024, abytes: 512 * 1024 * 1024 * 1024, tfiles: 1_000_000_000,
ffiles: 500_000_000,
afiles: 500_000_000,
invarsec: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Verifier(pub [u8; 8]);
impl Verifier {
pub fn new(data: [u8; 8]) -> Self {
Self(data)
}
pub fn from_u64(v: u64) -> Self {
Self(v.to_le_bytes())
}
pub fn as_u64(&self) -> u64 {
u64::from_le_bytes(self.0)
}
pub fn generate() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(1);
Self::from_u64(COUNTER.fetch_add(1, Ordering::SeqCst))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_handle_creation() {
let fh = FileHandle::new(1, 100, 5);
assert_eq!(fh.dataset_id(), 1);
assert_eq!(fh.object_id(), 100);
assert_eq!(fh.generation(), 5);
assert!(!fh.is_root());
}
#[test]
fn test_file_handle_root() {
let fh = FileHandle::root(42);
assert_eq!(fh.dataset_id(), 42);
assert_eq!(fh.object_id(), 0);
assert!(fh.is_root());
}
#[test]
fn test_file_handle_from_bytes() {
let fh1 = FileHandle::new(1, 2, 3);
let fh2 = FileHandle::from_bytes(fh1.as_bytes()).unwrap();
assert_eq!(fh1.dataset_id(), fh2.dataset_id());
assert_eq!(fh1.object_id(), fh2.object_id());
assert_eq!(fh1.generation(), fh2.generation());
}
#[test]
fn test_file_type() {
assert!(FileType::Directory.is_directory());
assert!(FileType::AttrDir.is_directory());
assert!(!FileType::Regular.is_directory());
assert!(FileType::Regular.is_regular());
}
#[test]
fn test_nfs_attr_directory() {
let attr = NfsAttr::directory(100, 0o755, 1000, 1000);
assert_eq!(attr.file_type, FileType::Directory);
assert_eq!(attr.fileid, 100);
assert_eq!(attr.nlink, 2);
}
#[test]
fn test_nfs_attr_file() {
let attr = NfsAttr::file(200, 4096, 0o644, 1000, 1000);
assert_eq!(attr.file_type, FileType::Regular);
assert_eq!(attr.size, 4096);
assert_eq!(attr.blocks, 8); }
#[test]
fn test_attr_bitmap() {
let mut bitmap = AttrBitmap::new();
bitmap.set(FATTR4_SIZE);
assert!(bitmap.is_set(FATTR4_SIZE));
assert!(!bitmap.is_set(FATTR4_MODE));
}
#[test]
fn test_access_bits() {
let access = AccessBits::new(AccessBits::READ | AccessBits::MODIFY);
assert!(access.read());
assert!(!access.lookup());
assert!(access.modify()); }
#[test]
fn test_state_id() {
let anon = StateId::anonymous();
assert!(anon.is_anonymous());
assert!(!anon.is_current());
let current = StateId::current();
assert!(!current.is_anonymous());
assert!(current.is_current());
}
#[test]
fn test_lock_range_overlap() {
let r1 = LockRange::new(0, 100);
let r2 = LockRange::new(50, 100);
let r3 = LockRange::new(200, 100);
assert!(r1.overlaps(&r2));
assert!(!r1.overlaps(&r3));
let all = LockRange::all();
assert!(all.overlaps(&r1));
assert!(all.overlaps(&r3));
}
#[test]
fn test_verifier() {
let v1 = Verifier::generate();
let v2 = Verifier::generate();
assert_ne!(v1, v2);
let v3 = Verifier::from_u64(12345);
assert_eq!(v3.as_u64(), 12345);
}
#[test]
fn test_client_id_generate() {
let c1 = ClientId::generate();
let c2 = ClientId::generate();
assert_ne!(c1.id, c2.id);
}
#[test]
fn test_session_id_generate() {
let s1 = SessionId::generate();
let s2 = SessionId::generate();
assert_ne!(s1.id, s2.id);
}
#[test]
fn test_dir_entry() {
let entry = DirEntry::new(1, "test.txt".into())
.with_attrs(NfsAttr::file(100, 1024, 0o644, 0, 0))
.with_handle(FileHandle::new(1, 100, 1));
assert_eq!(entry.name, "test.txt");
assert!(entry.attrs.is_some());
assert!(entry.fh.is_some());
}
#[test]
fn test_fs_info_defaults() {
let info = FsInfo::default();
assert_eq!(info.rtmax, 1024 * 1024);
assert_eq!(info.maxfilesize, u64::MAX);
}
#[test]
fn test_fs_stat_defaults() {
let stat = FsStat::default();
assert!(stat.tbytes > 0);
assert!(stat.fbytes <= stat.tbytes);
}
}