use crate::fscore::structs::{DmuObjectType, DnodePhys, Dva, Hyperblock};
use crate::integrity::anomaly::AnomalyEngine;
use crate::io::pipeline::Pipeline;
use crate::storage::dmu::{
DmuTx, Objset, TxWaitType, dmu_object_alloc, dmu_read, dmu_tx_assign, dmu_tx_commit,
dmu_tx_create, dmu_tx_hold_write, dmu_write, get_block_size,
};
use crate::{FileStat, FsError, FsResult};
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use hashbrown::HashMap;
use spin::Mutex;
pub const S_IFMT: u32 = 0o170000; pub const S_IFREG: u32 = 0o100000; pub const S_IFDIR: u32 = 0o040000; pub const S_IFLNK: u32 = 0o120000; pub const S_IFBLK: u32 = 0o060000; pub const S_IFCHR: u32 = 0o020000; pub const S_IFIFO: u32 = 0o010000; pub const S_IFSOCK: u32 = 0o140000;
pub const S_ISUID: u32 = 0o4000; pub const S_ISGID: u32 = 0o2000; pub const S_ISVTX: u32 = 0o1000; pub const S_IRWXU: u32 = 0o0700; pub const S_IRUSR: u32 = 0o0400; pub const S_IWUSR: u32 = 0o0200; pub const S_IXUSR: u32 = 0o0100; pub const S_IRWXG: u32 = 0o0070; pub const S_IRWXO: u32 = 0o0007;
pub const O_RDONLY: u32 = 0;
pub const O_WRONLY: u32 = 1;
pub const O_RDWR: u32 = 2;
pub const O_CREAT: u32 = 0o100;
pub const O_EXCL: u32 = 0o200;
pub const O_TRUNC: u32 = 0o1000;
pub const O_APPEND: u32 = 0o2000;
pub const O_DIRECTORY: u32 = 0o200000;
pub const SEEK_SET: i32 = 0;
pub const SEEK_CUR: i32 = 1;
pub const SEEK_END: i32 = 2;
pub const DIRENT_TYPE_SHIFT: u64 = 60;
pub const DIRENT_OBJ_MASK: u64 = (1 << 48) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockType {
Shared,
Exclusive,
}
#[derive(Debug, Clone)]
pub struct FileLock {
pub lock_type: LockType,
pub start: u64,
pub length: u64,
pub pid: u32,
}
impl FileLock {
pub fn conflicts_with(&self, other: &FileLock) -> bool {
if self.lock_type == LockType::Shared && other.lock_type == LockType::Shared {
return false;
}
let self_end = if self.length == 0 {
u64::MAX
} else {
self.start + self.length
};
let other_end = if other.length == 0 {
u64::MAX
} else {
other.start + other.length
};
self.start < other_end && other.start < self_end
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ZnodePhys {
pub atime: [u64; 2], pub mtime: [u64; 2], pub ctime: [u64; 2], pub crtime: [u64; 2], pub generation: u64, pub mode: u64, pub size: u64, pub parent: u64, pub links: u64, pub xattr: u64, pub rdev: u64, pub flags: u64, pub uid: u64, pub gid: u64, pub pad: [u64; 4], }
impl Default for ZnodePhys {
fn default() -> Self {
Self {
atime: [0, 0],
mtime: [0, 0],
ctime: [0, 0],
crtime: [0, 0],
generation: 1,
mode: 0,
size: 0,
parent: 0,
links: 1,
xattr: 0,
rdev: 0,
flags: 0,
uid: 0,
gid: 0,
pad: [0; 4],
}
}
}
pub struct Znode {
pub object_id: u64,
pub phys: ZnodePhys,
pub master_node: Dva,
pub dirty: bool,
pub unlinked: bool,
pub data_cache: Option<Vec<u8>>,
pub flock: Option<(LockType, u32)>,
pub record_locks: Vec<FileLock>,
}
impl Znode {
pub fn new(object_id: u64, mode: u32, uid: u32, gid: u32) -> Self {
let now = get_current_time();
Self {
object_id,
master_node: Dva { vdev: 0, offset: 0 },
phys: ZnodePhys {
atime: now,
mtime: now,
ctime: now,
crtime: now,
generation: 1,
mode: mode as u64,
size: 0,
parent: 0,
links: 1,
xattr: 0,
rdev: 0,
flags: 0,
uid: uid as u64,
gid: gid as u64,
pad: [0; 4],
},
dirty: true,
unlinked: false,
data_cache: None,
flock: None,
record_locks: Vec::new(),
}
}
pub fn new_dir(object_id: u64, parent_id: u64, mode: u32, uid: u32, gid: u32) -> Self {
let mut znode = Self::new(object_id, S_IFDIR | (mode & 0o7777), uid, gid);
znode.phys.parent = parent_id;
znode.phys.links = 2; znode
}
pub fn new_file(object_id: u64, parent_id: u64, mode: u32, uid: u32, gid: u32) -> Self {
let mut znode = Self::new(object_id, S_IFREG | (mode & 0o7777), uid, gid);
znode.phys.parent = parent_id;
znode
}
pub fn new_symlink(object_id: u64, parent_id: u64, uid: u32, gid: u32) -> Self {
let mut znode = Self::new(object_id, S_IFLNK | 0o777, uid, gid);
znode.phys.parent = parent_id;
znode
}
#[inline]
pub fn is_dir(&self) -> bool {
(self.phys.mode as u32 & S_IFMT) == S_IFDIR
}
#[inline]
pub fn is_file(&self) -> bool {
(self.phys.mode as u32 & S_IFMT) == S_IFREG
}
#[inline]
pub fn is_symlink(&self) -> bool {
(self.phys.mode as u32 & S_IFMT) == S_IFLNK
}
pub fn dirent_type(&self) -> u8 {
match self.phys.mode as u32 & S_IFMT {
S_IFREG => 8, S_IFDIR => 4, S_IFLNK => 10, S_IFBLK => 6, S_IFCHR => 2, S_IFIFO => 1, S_IFSOCK => 12, _ => 0, }
}
pub fn getattr(&self) -> FileStat {
FileStat {
st_dev: 0,
st_ino: self.object_id,
st_nlink: self.phys.links,
st_mode: self.phys.mode as u32,
st_uid: self.phys.uid as u32,
st_gid: self.phys.gid as u32,
__pad0: 0,
st_rdev: self.phys.rdev,
st_size: self.phys.size as i64,
st_blksize: 131072, st_blocks: self.phys.size.div_ceil(512) as i64,
st_atime: self.phys.atime[0] as i64,
st_atime_nsec: self.phys.atime[1] as i64,
st_mtime: self.phys.mtime[0] as i64,
st_mtime_nsec: self.phys.mtime[1] as i64,
st_ctime: self.phys.ctime[0] as i64,
st_ctime_nsec: self.phys.ctime[1] as i64,
__reserved: [0; 3],
}
}
pub fn touch_mtime(&mut self) {
let now = get_current_time();
self.phys.mtime = now;
self.phys.ctime = now;
self.dirty = true;
}
pub fn touch_atime(&mut self) {
self.phys.atime = get_current_time();
self.dirty = true;
}
pub fn touch_ctime(&mut self) {
self.phys.ctime = get_current_time();
self.dirty = true;
}
pub fn check_access(&self, uid: u32, gid: u32, mode: u32) -> bool {
if uid == 0 {
return true;
}
let file_mode = self.phys.mode as u32;
let perm = if uid == self.phys.uid as u32 {
(file_mode >> 6) & 0o7
} else if gid == self.phys.gid as u32 {
(file_mode >> 3) & 0o7
} else {
file_mode & 0o7
};
(perm & mode) == mode
}
pub fn flock_acquire(&mut self, lock_type: LockType, pid: u32) -> FsResult<()> {
if let Some((existing_type, existing_pid)) = self.flock {
if existing_pid == pid {
self.flock = Some((lock_type, pid));
return Ok(());
}
if lock_type == LockType::Shared && existing_type == LockType::Shared {
return Ok(());
}
return Err(FsError::ResourceBusy);
}
self.flock = Some((lock_type, pid));
Ok(())
}
pub fn flock_release(&mut self, pid: u32) -> FsResult<()> {
if let Some((_, existing_pid)) = self.flock {
if existing_pid == pid {
self.flock = None;
return Ok(());
}
}
Err(FsError::PermissionDenied)
}
pub fn fcntl_lock(&mut self, lock: FileLock) -> FsResult<()> {
for existing_lock in &self.record_locks {
if existing_lock.pid == lock.pid {
continue;
}
if lock.conflicts_with(existing_lock) {
return Err(FsError::ResourceBusy);
}
}
self.record_locks
.retain(|l| l.pid != lock.pid || !l.conflicts_with(&lock));
self.record_locks.push(lock);
Ok(())
}
pub fn fcntl_unlock(&mut self, start: u64, length: u64, pid: u32) -> FsResult<()> {
let unlock_end = if length == 0 {
u64::MAX
} else {
start + length
};
self.record_locks.retain(|lock| {
if lock.pid != pid {
return true; }
let lock_end = if lock.length == 0 {
u64::MAX
} else {
lock.start + lock.length
};
!(lock.start < unlock_end && start < lock_end)
});
Ok(())
}
pub fn fcntl_test_lock(&self, test_lock: &FileLock) -> Option<FileLock> {
for existing_lock in &self.record_locks {
if existing_lock.pid == test_lock.pid {
continue;
}
if test_lock.conflicts_with(existing_lock) {
return Some(existing_lock.clone());
}
}
None
}
pub fn release_all_locks(&mut self, pid: u32) {
if let Some((_, existing_pid)) = self.flock {
if existing_pid == pid {
self.flock = None;
}
}
self.record_locks.retain(|lock| lock.pid != pid);
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub object_id: u64,
pub file_type: u8,
}
impl DirEntry {
pub fn encode(object_id: u64, file_type: u8) -> u64 {
((file_type as u64) << DIRENT_TYPE_SHIFT) | (object_id & DIRENT_OBJ_MASK)
}
pub fn decode(value: u64) -> (u64, u8) {
let file_type = (value >> DIRENT_TYPE_SHIFT) as u8;
let object_id = value & DIRENT_OBJ_MASK;
(object_id, file_type)
}
}
pub struct FileHandle {
pub object_id: u64,
pub offset: u64,
pub flags: u32,
pub znode_id: u64,
}
impl FileHandle {
pub fn new(object_id: u64, flags: u32) -> Self {
Self {
object_id,
offset: 0,
flags,
znode_id: object_id,
}
}
pub fn can_read(&self) -> bool {
let access = self.flags & 3;
access == O_RDONLY || access == O_RDWR
}
pub fn can_write(&self) -> bool {
let access = self.flags & 3;
access == O_WRONLY || access == O_RDWR
}
pub fn is_append(&self) -> bool {
(self.flags & O_APPEND) != 0
}
}
pub struct Zpl {
objset: Objset,
znodes: HashMap<u64, Znode>,
dir_entries: HashMap<u64, HashMap<String, u64>>,
root_id: u64,
next_object_id: u64,
file_handles: HashMap<u64, FileHandle>,
next_handle_id: u64,
quota: u64,
used_bytes: u64,
}
impl Default for Zpl {
fn default() -> Self {
Self::new()
}
}
impl Zpl {
pub fn new() -> Self {
let mut objset = Objset::new(1);
let mut tx = dmu_tx_create(&objset);
let _ = dmu_tx_hold_write(&mut tx, 0, 0, 512);
let _ = dmu_tx_assign(&mut tx, &objset, TxWaitType::Wait);
let _ = objset.object_claim(
2, DmuObjectType::DirectoryContents,
get_block_size(),
DmuObjectType::Znode,
64,
&tx,
);
dmu_tx_commit(tx);
let mut zpl = Self {
objset,
znodes: HashMap::new(),
dir_entries: HashMap::new(),
root_id: 2, next_object_id: 100, file_handles: HashMap::new(),
next_handle_id: 1,
quota: 0, used_bytes: 0, };
let root = Znode::new_dir(2, 2, 0o755, 0, 0);
zpl.znodes.insert(2, root);
zpl.dir_entries.insert(2, HashMap::new());
zpl
}
pub fn import_from_hyperblock(
hyperblock: &Hyperblock,
encryption_key: Option<[u8; 32]>,
) -> FsResult<Self> {
let rootbp = &hyperblock.rootbp;
if rootbp.is_hole() {
crate::lcpfs_println!("[ ZPL ] Import: rootbp is hole, creating fresh ZPL");
return Ok(Self::new());
}
let key = encryption_key.unwrap_or([0u8; 32]);
let meta_block = Pipeline::read_block_auto_nonce(rootbp, &key).map_err(|e| {
crate::lcpfs_println!("[ ZPL ] Import: Failed to read meta-block: {:?}", e);
e
})?;
crate::lcpfs_println!(
"[ ZPL ] Import: Read meta-block ({} bytes)",
meta_block.len()
);
let dnode_size = core::mem::size_of::<DnodePhys>();
let entry_size = 8 + dnode_size;
let mut objset = Objset::new(1); objset
.os_txg
.store(hyperblock.txg, core::sync::atomic::Ordering::Release);
objset.os_rootbp = *rootbp;
let mut znodes: HashMap<u64, Znode> = HashMap::new();
let mut dir_entries: HashMap<u64, HashMap<String, u64>> = HashMap::new();
let mut max_object_id: u64 = 100;
let mut offset = 0;
while offset + entry_size <= meta_block.len() {
let object_id = u64::from_le_bytes(
meta_block[offset..offset + 8]
.try_into()
.unwrap_or([0u8; 8]),
);
offset += 8;
let dnode_phys: DnodePhys = unsafe {
core::ptr::read_unaligned(meta_block[offset..].as_ptr() as *const DnodePhys)
};
offset += dnode_size;
if object_id > max_object_id {
max_object_id = object_id;
}
crate::lcpfs_println!(
"[ ZPL ] Import: Found object {} type {}",
object_id,
dnode_phys.object_type
);
match dnode_phys.object_type {
t if t == DmuObjectType::Znode as u8 => {
let znode = Self::reconstruct_znode(object_id, &dnode_phys, &key)?;
znodes.insert(object_id, znode);
}
t if t == DmuObjectType::DirectoryContents as u8 => {
dir_entries.entry(object_id).or_default();
if !znodes.contains_key(&object_id) {
let znode = Znode::new_dir(object_id, 2, 0o755, 0, 0);
znodes.insert(object_id, znode);
}
}
_ => {
}
}
}
if !znodes.contains_key(&2) {
let root = Znode::new_dir(2, 2, 0o755, 0, 0);
znodes.insert(2, root);
}
dir_entries.entry(2).or_default();
for (&object_id, znode) in znodes.iter() {
if object_id == 2 {
continue; }
let parent_id = znode.phys.parent;
if parent_id > 0 && parent_id != object_id {
let name = format!("obj_{}", object_id);
dir_entries
.entry(parent_id)
.or_default()
.insert(name, object_id);
}
}
crate::lcpfs_println!(
"[ ZPL ] Import complete: {} znodes, {} directories",
znodes.len(),
dir_entries.len()
);
Ok(Self {
objset,
znodes,
dir_entries,
root_id: 2,
next_object_id: max_object_id + 1,
file_handles: HashMap::new(),
next_handle_id: 1,
quota: 0,
used_bytes: 0,
})
}
fn reconstruct_znode(
object_id: u64,
dnode_phys: &DnodePhys,
key: &[u8; 32],
) -> FsResult<Znode> {
let mut data_cache: Option<Vec<u8>> = None;
let bp = &dnode_phys.blkptr[0];
if !bp.is_hole() {
match Pipeline::read_block_auto_nonce(bp, key) {
Ok(data) => {
data_cache = Some(data);
}
Err(e) => {
crate::lcpfs_println!(
"[ ZPL ] Warning: Failed to read data for object {}: {:?}",
object_id,
e
);
}
}
}
let size = if let Some(ref data) = data_cache {
data.len() as u64
} else {
dnode_phys.used_bytes
};
let is_dir = dnode_phys.object_type == DmuObjectType::DirectoryContents as u8;
let mut znode = if is_dir {
Znode::new_dir(object_id, 2, 0o755, 0, 0)
} else {
Znode::new_file(object_id, 2, 0o644, 0, 0)
};
znode.phys.size = size;
znode.data_cache = data_cache;
znode.dirty = false;
Ok(znode)
}
fn alloc_object_id(&mut self, tx: &DmuTx) -> FsResult<u64> {
dmu_object_alloc(
&mut self.objset,
DmuObjectType::Znode,
get_block_size(),
DmuObjectType::None,
0,
tx,
)
}
fn alloc_object_id_legacy(&mut self) -> u64 {
let id = self.next_object_id;
self.next_object_id += 1;
id
}
fn alloc_handle_id(&mut self) -> u64 {
let id = self.next_handle_id;
self.next_handle_id += 1;
id
}
pub fn root_id(&self) -> u64 {
self.root_id
}
pub fn set_quota(&mut self, quota: u64) {
self.quota = quota;
}
pub fn get_quota(&self) -> u64 {
self.quota
}
pub fn get_used_bytes(&self) -> u64 {
self.used_bytes
}
pub fn used_bytes(&self) -> u64 {
self.used_bytes
}
pub fn quota(&self) -> u64 {
self.quota
}
pub fn is_over_quota(&self) -> bool {
self.quota > 0 && self.used_bytes > self.quota
}
pub fn get_znode(&self, object_id: u64) -> Option<&Znode> {
self.znodes.get(&object_id)
}
pub fn get_znode_mut(&mut self, object_id: u64) -> Option<&mut Znode> {
self.znodes.get_mut(&object_id)
}
pub fn stat_by_id(&self, object_id: u64) -> FsResult<crate::FileStat> {
let znode = self.znodes.get(&object_id).ok_or(FsError::NotFound)?;
Ok(znode.getattr())
}
pub fn lookup(&self, dir_id: u64, name: &str) -> FsResult<u64> {
let dir = self.znodes.get(&dir_id).ok_or(FsError::NotFound)?;
if !dir.is_dir() {
return Err(FsError::NotDirectory);
}
match name {
"." => return Ok(dir_id),
".." => return Ok(dir.phys.parent),
_ => {}
}
self.dir_entries
.get(&dir_id)
.and_then(|entries| entries.get(name))
.copied()
.ok_or(FsError::NotFound)
}
pub fn link_create(&mut self, dir_id: u64, name: &str, znode_id: u64) -> FsResult<()> {
if let Some(entries) = self.dir_entries.get(&dir_id) {
if entries.contains_key(name) {
return Err(FsError::AlreadyExists);
}
}
let child_is_dir = self
.znodes
.get(&znode_id)
.map(|z| z.is_dir())
.unwrap_or(false);
self.dir_entries
.entry(dir_id)
.or_default()
.insert(String::from(name), znode_id);
if let Some(znode) = self.znodes.get_mut(&znode_id) {
znode.phys.links += 1;
znode.touch_ctime();
}
if let Some(dir) = self.znodes.get_mut(&dir_id) {
dir.phys.size += (name.len() + 8) as u64; dir.touch_mtime();
if child_is_dir {
dir.phys.links += 1;
}
}
if child_is_dir {
self.dir_entries.entry(znode_id).or_default();
}
Ok(())
}
pub fn link_destroy(&mut self, dir_id: u64, name: &str, znode_id: u64) -> FsResult<()> {
if let Some(entries) = self.dir_entries.get_mut(&dir_id) {
entries.remove(name);
}
let is_dir = self
.znodes
.get(&znode_id)
.map(|z| z.is_dir())
.unwrap_or(false);
let should_delete = if let Some(znode) = self.znodes.get_mut(&znode_id) {
znode.phys.links = znode.phys.links.saturating_sub(1);
znode.touch_ctime();
znode.phys.links == 0
} else {
false
};
if let Some(dir) = self.znodes.get_mut(&dir_id) {
dir.phys.size = dir.phys.size.saturating_sub((name.len() + 8) as u64);
dir.touch_mtime();
if is_dir {
dir.phys.links = dir.phys.links.saturating_sub(1);
}
}
if should_delete {
if let Some(znode) = self.znodes.get_mut(&znode_id) {
znode.unlinked = true;
}
if is_dir {
self.dir_entries.remove(&znode_id);
}
self.znodes.remove(&znode_id);
}
Ok(())
}
pub fn create(
&mut self,
dir_id: u64,
name: &str,
mode: u32,
uid: u32,
gid: u32,
) -> FsResult<u64> {
if let Err(reason) = AnomalyEngine::check_create(uid as u64, self.root_id, 0) {
return Err(FsError::SecurityViolation { reason });
}
let dir = self.znodes.get(&dir_id).ok_or(FsError::NotFound)?;
if !dir.is_dir() {
return Err(FsError::NotDirectory);
}
let mut tx = dmu_tx_create(&self.objset);
let _ = dmu_tx_hold_write(&mut tx, dir_id, 0, 512); dmu_tx_assign(&mut tx, &self.objset, TxWaitType::Wait)?;
let object_id = self.alloc_object_id(&tx)?;
let znode = Znode::new_file(object_id, dir_id, mode, uid, gid);
self.znodes.insert(object_id, znode);
self.link_create(dir_id, name, object_id)?;
dmu_tx_commit(tx);
Ok(object_id)
}
pub fn mkdir(
&mut self,
parent_id: u64,
name: &str,
mode: u32,
uid: u32,
gid: u32,
) -> FsResult<u64> {
let parent = self.znodes.get(&parent_id).ok_or(FsError::NotFound)?;
if !parent.is_dir() {
return Err(FsError::NotDirectory);
}
let mut tx = dmu_tx_create(&self.objset);
let _ = dmu_tx_hold_write(&mut tx, parent_id, 0, 512);
dmu_tx_assign(&mut tx, &self.objset, TxWaitType::Wait)?;
let object_id = self.alloc_object_id(&tx)?;
let znode = Znode::new_dir(object_id, parent_id, mode, uid, gid);
self.znodes.insert(object_id, znode);
self.link_create(parent_id, name, object_id)?;
dmu_tx_commit(tx);
Ok(object_id)
}
pub fn unlink(&mut self, dir_id: u64, name: &str) -> FsResult<()> {
if name == "." || name == ".." {
return Err(FsError::InvalidArgument {
reason: "cannot unlink . or ..",
});
}
if let Err(reason) = AnomalyEngine::check_delete(0, self.root_id, 0) {
return Err(FsError::SecurityViolation { reason });
}
let object_id = self.lookup(dir_id, name)?;
let is_dir = self
.znodes
.get(&object_id)
.map(|z| z.is_dir())
.unwrap_or(false);
if is_dir {
return Err(FsError::IsDirectory);
}
let has_open_handles = self.file_handles.values().any(|h| h.object_id == object_id);
if has_open_handles {
if let Some(znode) = self.znodes.get_mut(&object_id) {
znode.unlinked = true;
}
}
self.link_destroy(dir_id, name, object_id)
}
pub fn rmdir(&mut self, parent_id: u64, name: &str) -> FsResult<()> {
if name == "." || name == ".." {
return Err(FsError::InvalidArgument {
reason: "cannot remove . or ..",
});
}
let object_id = self.lookup(parent_id, name)?;
let is_dir = self
.znodes
.get(&object_id)
.map(|z| z.is_dir())
.unwrap_or(false);
if !is_dir {
return Err(FsError::NotDirectory);
}
let is_empty = self
.dir_entries
.get(&object_id)
.map(|entries| entries.is_empty())
.unwrap_or(true);
if !is_empty {
return Err(FsError::DirectoryNotEmpty);
}
self.link_destroy(parent_id, name, object_id)
}
pub fn rename(
&mut self,
src_dir: u64,
src_name: &str,
dst_dir: u64,
dst_name: &str,
) -> FsResult<()> {
if src_name == "." || src_name == ".." || dst_name == "." || dst_name == ".." {
return Err(FsError::InvalidArgument {
reason: "cannot rename . or ..",
});
}
let src_object_id = self.lookup(src_dir, src_name)?;
let (src_is_dir, src_parent) = {
let src_znode = self.znodes.get(&src_object_id).ok_or(FsError::NotFound)?;
(src_znode.is_dir(), src_znode.phys.parent)
};
{
let src_dir_znode = self.znodes.get(&src_dir).ok_or(FsError::NotFound)?;
if !src_dir_znode.is_dir() {
return Err(FsError::NotDirectory);
}
}
{
let dst_dir_znode = self.znodes.get(&dst_dir).ok_or(FsError::NotFound)?;
if !dst_dir_znode.is_dir() {
return Err(FsError::NotDirectory);
}
}
if src_is_dir && src_dir != dst_dir {
let mut check_dir = dst_dir;
while check_dir != self.root_id {
if check_dir == src_object_id {
return Err(FsError::InvalidArgument {
reason: "cannot move directory into itself",
});
}
check_dir = self
.znodes
.get(&check_dir)
.map(|z| z.phys.parent)
.unwrap_or(self.root_id);
}
}
let dst_exists = self.lookup(dst_dir, dst_name).ok();
if let Some(dst_object_id) = dst_exists {
let dst_is_dir = self
.znodes
.get(&dst_object_id)
.map(|z| z.is_dir())
.unwrap_or(false);
if src_is_dir != dst_is_dir {
if dst_is_dir {
return Err(FsError::IsDirectory);
} else {
return Err(FsError::NotDirectory);
}
}
if dst_is_dir {
let is_empty = self
.dir_entries
.get(&dst_object_id)
.map(|entries| entries.is_empty())
.unwrap_or(true);
if !is_empty {
return Err(FsError::DirectoryNotEmpty);
}
}
if src_object_id == dst_object_id {
return Ok(()); }
self.link_destroy(dst_dir, dst_name, dst_object_id)?;
}
let mut tx = dmu_tx_create(&self.objset);
let _ = dmu_tx_hold_write(&mut tx, src_dir, 0, 512);
if src_dir != dst_dir {
let _ = dmu_tx_hold_write(&mut tx, dst_dir, 0, 512);
}
dmu_tx_assign(&mut tx, &self.objset, TxWaitType::Wait)?;
if let Some(entries) = self.dir_entries.get_mut(&src_dir) {
entries.remove(src_name);
}
if let Some(src_dir_znode) = self.znodes.get_mut(&src_dir) {
src_dir_znode.phys.size = src_dir_znode
.phys
.size
.saturating_sub((src_name.len() + 8) as u64);
src_dir_znode.touch_mtime();
if src_is_dir && src_dir != dst_dir {
src_dir_znode.phys.links = src_dir_znode.phys.links.saturating_sub(1);
}
}
self.dir_entries
.entry(dst_dir)
.or_default()
.insert(String::from(dst_name), src_object_id);
if let Some(dst_dir_znode) = self.znodes.get_mut(&dst_dir) {
dst_dir_znode.phys.size += (dst_name.len() + 8) as u64;
dst_dir_znode.touch_mtime();
if src_is_dir && src_dir != dst_dir {
dst_dir_znode.phys.links += 1;
}
}
if src_is_dir && src_dir != dst_dir {
if let Some(src_znode) = self.znodes.get_mut(&src_object_id) {
src_znode.phys.parent = dst_dir;
src_znode.touch_ctime();
}
}
if let Some(src_znode) = self.znodes.get_mut(&src_object_id) {
src_znode.touch_ctime();
}
dmu_tx_commit(tx);
Ok(())
}
pub fn open(&mut self, object_id: u64, flags: u32) -> FsResult<u64> {
let (is_dir, is_file) = {
let znode = self.znodes.get(&object_id).ok_or(FsError::NotFound)?;
(znode.is_dir(), znode.is_file())
};
if (flags & O_DIRECTORY) != 0 && !is_dir {
return Err(FsError::NotDirectory);
}
if is_dir && (flags & O_WRONLY != 0 || flags & O_RDWR != 0) {
return Err(FsError::IsDirectory);
}
let handle_id = self.alloc_handle_id();
let handle = FileHandle::new(object_id, flags);
self.file_handles.insert(handle_id, handle);
if (flags & O_TRUNC) != 0 && is_file {
if let Some(z) = self.znodes.get_mut(&object_id) {
z.phys.size = 0;
z.data_cache = Some(Vec::new());
z.touch_mtime();
}
}
Ok(handle_id)
}
pub fn close(&mut self, handle_id: u64) -> FsResult<()> {
self.file_handles
.remove(&handle_id)
.ok_or(FsError::BadFileDescriptor)?;
Ok(())
}
pub fn flock(&mut self, handle_id: u64, lock_type: LockType, pid: u32) -> FsResult<()> {
let object_id = self
.file_handles
.get(&handle_id)
.ok_or(FsError::BadFileDescriptor)?
.object_id;
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
znode.flock_acquire(lock_type, pid)
}
pub fn funlock(&mut self, handle_id: u64, pid: u32) -> FsResult<()> {
let object_id = self
.file_handles
.get(&handle_id)
.ok_or(FsError::BadFileDescriptor)?
.object_id;
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
znode.flock_release(pid)
}
pub fn fcntl_setlk(
&mut self,
handle_id: u64,
lock_type: LockType,
start: u64,
length: u64,
pid: u32,
) -> FsResult<()> {
let object_id = self
.file_handles
.get(&handle_id)
.ok_or(FsError::BadFileDescriptor)?
.object_id;
let lock = FileLock {
lock_type,
start,
length,
pid,
};
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
znode.fcntl_lock(lock)
}
pub fn fcntl_unlk(
&mut self,
handle_id: u64,
start: u64,
length: u64,
pid: u32,
) -> FsResult<()> {
let object_id = self
.file_handles
.get(&handle_id)
.ok_or(FsError::BadFileDescriptor)?
.object_id;
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
znode.fcntl_unlock(start, length, pid)
}
pub fn fcntl_getlk(
&self,
handle_id: u64,
lock_type: LockType,
start: u64,
length: u64,
pid: u32,
) -> FsResult<Option<FileLock>> {
let object_id = self
.file_handles
.get(&handle_id)
.ok_or(FsError::BadFileDescriptor)?
.object_id;
let test_lock = FileLock {
lock_type,
start,
length,
pid,
};
let znode = self.znodes.get(&object_id).ok_or(FsError::NotFound)?;
Ok(znode.fcntl_test_lock(&test_lock))
}
pub fn read(&mut self, handle_id: u64, buf: &mut [u8]) -> FsResult<usize> {
let handle = self
.file_handles
.get_mut(&handle_id)
.ok_or(FsError::BadFileDescriptor)?;
if !handle.can_read() {
return Err(FsError::PermissionDenied);
}
let object_id = handle.object_id;
let offset = handle.offset;
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
if offset >= znode.phys.size {
return Ok(0);
}
let available = (znode.phys.size - offset) as usize;
let to_read = buf.len().min(available);
if let Some(ref data) = znode.data_cache {
let start = offset as usize;
let end = start + to_read;
if end <= data.len() {
buf[..to_read].copy_from_slice(&data[start..end]);
} else {
let copy_len = data.len().saturating_sub(start);
if copy_len > 0 {
buf[..copy_len].copy_from_slice(&data[start..]);
}
return Ok(copy_len);
}
} else {
match dmu_read(&mut self.objset, object_id, offset, to_read) {
Ok(data) => {
let copy_len = data.len().min(to_read);
buf[..copy_len].copy_from_slice(&data[..copy_len]);
}
Err(_) => {
buf[..to_read].fill(0);
}
}
}
if let Some(h) = self.file_handles.get_mut(&handle_id) {
h.offset += to_read as u64;
}
if let Some(z) = self.znodes.get_mut(&object_id) {
z.touch_atime();
}
Ok(to_read)
}
pub fn write(&mut self, handle_id: u64, buf: &[u8]) -> FsResult<usize> {
let handle = self
.file_handles
.get_mut(&handle_id)
.ok_or(FsError::BadFileDescriptor)?;
if !handle.can_write() {
return Err(FsError::PermissionDenied);
}
let object_id = handle.object_id;
let offset = if handle.is_append() {
self.znodes
.get(&object_id)
.map(|z| z.phys.size)
.unwrap_or(0)
} else {
handle.offset
};
if self.quota > 0 {
let new_usage = self.used_bytes.saturating_add(buf.len() as u64);
if new_usage > self.quota {
return Err(FsError::DiskFull {
needed_bytes: buf.len() as u64,
});
}
}
if let Err(reason) = AnomalyEngine::check_write(0, self.root_id, 0, buf) {
return Err(FsError::SecurityViolation { reason });
}
let mut tx = dmu_tx_create(&self.objset);
dmu_tx_hold_write(&mut tx, object_id, offset, buf.len() as u64)?;
dmu_tx_assign(&mut tx, &self.objset, TxWaitType::Wait)?;
dmu_write(&mut self.objset, object_id, offset, buf, &tx)?;
dmu_tx_commit(tx);
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
let data = znode.data_cache.get_or_insert_with(Vec::new);
let write_end = offset as usize + buf.len();
if data.len() < write_end {
data.resize(write_end, 0);
}
data[offset as usize..write_end].copy_from_slice(buf);
if write_end as u64 > znode.phys.size {
znode.phys.size = write_end as u64;
}
znode.touch_mtime();
znode.dirty = true;
if let Some(h) = self.file_handles.get_mut(&handle_id) {
h.offset = write_end as u64;
}
self.used_bytes = self.used_bytes.saturating_add(buf.len() as u64);
Ok(buf.len())
}
pub fn seek(&mut self, handle_id: u64, offset: i64, whence: i32) -> FsResult<u64> {
let handle = self
.file_handles
.get_mut(&handle_id)
.ok_or(FsError::BadFileDescriptor)?;
let size = self
.znodes
.get(&handle.object_id)
.map(|z| z.phys.size)
.unwrap_or(0);
let new_offset = match whence {
SEEK_SET => offset as u64,
SEEK_CUR => (handle.offset as i64 + offset) as u64,
SEEK_END => (size as i64 + offset) as u64,
_ => {
return Err(FsError::InvalidArgument {
reason: "invalid seek whence",
});
}
};
handle.offset = new_offset;
Ok(new_offset)
}
pub fn truncate(&mut self, object_id: u64, length: u64) -> FsResult<()> {
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
if !znode.is_file() {
return Err(FsError::InvalidArgument {
reason: "truncate requires a file",
});
}
if let Some(ref mut data) = znode.data_cache {
if length as usize > data.len() {
data.resize(length as usize, 0);
} else {
data.truncate(length as usize);
}
}
znode.phys.size = length;
znode.touch_mtime();
znode.dirty = true;
Ok(())
}
pub fn getattr(&self, object_id: u64) -> FsResult<FileStat> {
let znode = self.znodes.get(&object_id).ok_or(FsError::NotFound)?;
Ok(znode.getattr())
}
pub fn setattr(
&mut self,
object_id: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
) -> FsResult<()> {
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
if let Some(m) = mode {
let file_type = znode.phys.mode as u32 & S_IFMT;
znode.phys.mode = (file_type | (m & 0o7777)) as u64;
}
if let Some(u) = uid {
znode.phys.uid = u as u64;
}
if let Some(g) = gid {
znode.phys.gid = g as u64;
}
znode.touch_ctime();
Ok(())
}
pub fn reflink_data(&mut self, src_id: u64, dst_id: u64) -> FsResult<()> {
let src_data = {
let src = self.znodes.get(&src_id).ok_or(FsError::NotFound)?;
if !src.is_file() {
return Err(FsError::InvalidArgument {
reason: "source must be a regular file",
});
}
(src.data_cache.clone(), src.phys.size, src.master_node)
};
let dst = self.znodes.get_mut(&dst_id).ok_or(FsError::NotFound)?;
if !dst.is_file() {
return Err(FsError::InvalidArgument {
reason: "destination must be a regular file",
});
}
dst.data_cache = src_data.0;
dst.phys.size = src_data.1;
dst.master_node = src_data.2;
dst.dirty = true;
use crate::dedup::dedup::DDT;
if src_data.2.offset != 0 || src_data.2.vdev != 0 {
let mut ddt = DDT.lock();
if let Some(entry) = ddt.table.values_mut().find(|e| e.dva == src_data.2) {
entry.ref_count += 1;
}
}
Ok(())
}
pub fn symlink(
&mut self,
dir_id: u64,
name: &str,
target: &str,
uid: u32,
gid: u32,
) -> FsResult<u64> {
let dir = self.znodes.get(&dir_id).ok_or(FsError::NotFound)?;
if !dir.is_dir() {
return Err(FsError::NotDirectory);
}
let mut tx = dmu_tx_create(&self.objset);
let _ = dmu_tx_hold_write(&mut tx, dir_id, 0, 512);
dmu_tx_assign(&mut tx, &self.objset, TxWaitType::Wait)?;
let object_id = self.alloc_object_id(&tx)?;
let mut znode = Znode::new_symlink(object_id, dir_id, uid, gid);
znode.data_cache = Some(target.as_bytes().to_vec());
znode.phys.size = target.len() as u64;
self.znodes.insert(object_id, znode);
self.link_create(dir_id, name, object_id)?;
dmu_tx_commit(tx);
Ok(object_id)
}
pub fn readlink(&self, object_id: u64) -> FsResult<String> {
let znode = self.znodes.get(&object_id).ok_or(FsError::NotFound)?;
if !znode.is_symlink() {
return Err(FsError::InvalidArgument {
reason: "not a symbolic link",
});
}
let target_bytes = znode.data_cache.as_ref().ok_or(FsError::InvalidArgument {
reason: "symlink has no target",
})?;
String::from_utf8(target_bytes.clone()).map_err(|_| FsError::InvalidArgument {
reason: "symlink target is not valid UTF-8",
})
}
pub fn readdir(&self, dir_id: u64) -> FsResult<Vec<DirEntry>> {
let dir = self.znodes.get(&dir_id).ok_or(FsError::NotFound)?;
if !dir.is_dir() {
return Err(FsError::NotDirectory);
}
let mut entries = Vec::new();
entries.push(DirEntry {
name: String::from("."),
object_id: dir_id,
file_type: 4, });
entries.push(DirEntry {
name: String::from(".."),
object_id: dir.phys.parent,
file_type: 4, });
if let Some(dir_map) = self.dir_entries.get(&dir_id) {
for (name, &object_id) in dir_map {
let file_type = self
.znodes
.get(&object_id)
.map(|z| z.dirent_type())
.unwrap_or(0);
entries.push(DirEntry {
name: name.clone(),
object_id,
file_type,
});
}
}
Ok(entries)
}
pub fn fsync(&mut self, object_id: u64) -> FsResult<()> {
use crate::storage::zil::{ZilEngine, ZilOpcode};
let znode = self.znodes.get_mut(&object_id).ok_or(FsError::NotFound)?;
if znode.dirty {
if let Some(data) = &znode.data_cache {
let txg = self
.objset
.os_txg
.load(core::sync::atomic::Ordering::Acquire);
let _ = ZilEngine::log_operation(txg, ZilOpcode::Write, object_id, 0, data);
}
ZilEngine::flush_to_slog().map_err(|e| FsError::InvalidPoolConfig { reason: e })?;
znode.dirty = false;
}
Ok(())
}
pub fn txg_sync(&mut self, dev_id: usize) -> FsResult<u64> {
self.objset.txg_sync(dev_id)
}
pub fn send_to_stream(&self, stream: &mut crate::net::send_recv::SendStream) -> FsResult<()> {
use crate::fscore::structs::DnodePhys;
crate::lcpfs_println!("[ SEND ] Serializing {} znodes", self.znodes.len());
for (&object_id, znode) in self.znodes.iter() {
let dnode = DnodePhys::zero();
stream.write_object(object_id, znode.phys.mode as u8, &dnode);
if znode.is_file() {
if let Some(ref data) = znode.data_cache {
stream.write_block(object_id, 0, data);
}
}
}
for (&dir_id, entries) in self.dir_entries.iter() {
for (name, &child_id) in entries.iter() {
let entry_data = format!("{}:{}", name, child_id);
stream.write_block(dir_id, child_id, entry_data.as_bytes());
}
}
crate::lcpfs_println!("[ SEND ] Stream complete: {} bytes", stream.size());
Ok(())
}
pub fn send_incremental_to_stream(
&self,
stream: &mut crate::net::send_recv::SendStream,
from_txg: u64,
) -> FsResult<()> {
use crate::fscore::structs::DnodePhys;
crate::lcpfs_println!("[ SEND ] Incremental send from TXG {}", from_txg);
let mut sent_count = 0;
for (&object_id, znode) in self.znodes.iter() {
if !znode.dirty {
continue;
}
let dnode = DnodePhys::zero();
stream.write_object(object_id, znode.phys.mode as u8, &dnode);
sent_count += 1;
if znode.is_file() {
if let Some(ref data) = znode.data_cache {
stream.write_block(object_id, 0, data);
}
}
}
for (&dir_id, entries) in self.dir_entries.iter() {
if let Some(dir_znode) = self.znodes.get(&dir_id) {
if dir_znode.dirty {
for (name, &child_id) in entries.iter() {
let entry_data = format!("{}:{}", name, child_id);
stream.write_block(dir_id, child_id, entry_data.as_bytes());
}
}
}
}
crate::lcpfs_println!(
"[ SEND ] Incremental send complete: {} objects, {} bytes",
sent_count,
stream.size()
);
Ok(())
}
pub fn receive_from_stream(
&mut self,
recv: &mut crate::net::send_recv::ReceiveStream,
) -> FsResult<()> {
crate::lcpfs_println!("[ RECV ] Processing stream...");
let mut received_objects = 0;
let mut received_blocks = 0;
while let Some((record, payload)) = recv
.read_next_record()
.map_err(|e| FsError::InvalidPoolConfig { reason: e })?
{
match record.record_type {
3 => {
let object_id = record.object_id;
let znode = Znode::new_file(object_id, 0, 0o644, 0, 0);
self.znodes.insert(object_id, znode);
received_objects += 1;
}
5 => {
let object_id = record.object_id;
if let Some(znode) = self.znodes.get_mut(&object_id) {
let payload_len = payload.len() as u64;
znode.data_cache = Some(payload);
znode.phys.size = payload_len;
}
received_blocks += 1;
}
_ => {
}
}
}
crate::lcpfs_println!(
"[ RECV ] Received {} objects, {} blocks",
received_objects,
received_blocks
);
Ok(())
}
}
use lazy_static::lazy_static;
lazy_static! {
pub static ref ZPL: Mutex<Zpl> = Mutex::new(Zpl::new());
}
fn get_current_time() -> [u64; 2] {
let secs = crate::time::now();
let ns = crate::time::high_resolution() % 1_000_000_000; [secs, ns]
}
pub fn zpl_create(path: &str, mode: u32) -> FsResult<u64> {
let mut zpl = ZPL.lock();
let (parent_id, name) = parse_path(&zpl, path)?;
zpl.create(parent_id, name, mode, 0, 0)
}
pub fn zpl_mkdir(path: &str, mode: u32) -> FsResult<u64> {
let mut zpl = ZPL.lock();
let (parent_id, name) = parse_path(&zpl, path)?;
zpl.mkdir(parent_id, name, mode, 0, 0)
}
pub fn zpl_open(path: &str, flags: u32) -> FsResult<u64> {
let mut zpl = ZPL.lock();
let object_id = resolve_path(&zpl, path)?;
zpl.open(object_id, flags)
}
pub fn zpl_close(handle: u64) -> FsResult<()> {
ZPL.lock().close(handle)
}
pub fn zpl_read(handle: u64, buf: &mut [u8]) -> FsResult<usize> {
ZPL.lock().read(handle, buf)
}
pub fn zpl_write(handle: u64, buf: &[u8]) -> FsResult<usize> {
ZPL.lock().write(handle, buf)
}
pub fn zpl_stat(path: &str) -> FsResult<FileStat> {
let zpl = ZPL.lock();
let object_id = resolve_path(&zpl, path)?;
zpl.getattr(object_id)
}
pub fn zpl_readdir(path: &str) -> FsResult<Vec<DirEntry>> {
let zpl = ZPL.lock();
let object_id = resolve_path(&zpl, path)?;
zpl.readdir(object_id)
}
pub fn zpl_unlink(path: &str) -> FsResult<()> {
let mut zpl = ZPL.lock();
let (parent_id, name) = parse_path(&zpl, path)?;
zpl.unlink(parent_id, name)
}
pub fn zpl_rmdir(path: &str) -> FsResult<()> {
let mut zpl = ZPL.lock();
let (parent_id, name) = parse_path(&zpl, path)?;
zpl.rmdir(parent_id, name)
}
pub fn zpl_rename(src_path: &str, dst_path: &str) -> FsResult<()> {
let mut zpl = ZPL.lock();
let (src_parent_id, src_name) = parse_path(&zpl, src_path)?;
let (dst_parent_id, dst_name) = parse_path(&zpl, dst_path)?;
zpl.rename(src_parent_id, src_name, dst_parent_id, dst_name)
}
fn parse_path<'a>(zpl: &Zpl, path: &'a str) -> FsResult<(u64, &'a str)> {
let path = path.trim_start_matches('/');
if path.is_empty() {
return Err(FsError::InvalidArgument {
reason: "empty path",
});
}
let mut current = zpl.root_id;
let parts: Vec<&str> = path.split('/').collect();
for part in parts.iter().take(parts.len().saturating_sub(1)) {
if part.is_empty() {
continue;
}
return Err(FsError::NotFound);
}
let filename = parts.last().ok_or(FsError::InvalidArgument {
reason: "no filename",
})?;
Ok((current, filename))
}
fn resolve_path(zpl: &Zpl, path: &str) -> FsResult<u64> {
let path = path.trim_start_matches('/');
if path.is_empty() {
return Ok(zpl.root_id);
}
let mut current = zpl.root_id;
for part in path.split('/') {
if part.is_empty() {
continue;
}
current = zpl.lookup(current, part)?;
}
Ok(current)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flock_acquire_exclusive() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
assert!(znode.flock_acquire(LockType::Exclusive, 1).is_ok());
assert_eq!(
znode.flock_acquire(LockType::Exclusive, 2),
Err(FsError::ResourceBusy)
);
assert_eq!(
znode.flock_acquire(LockType::Shared, 2),
Err(FsError::ResourceBusy)
);
}
#[test]
fn test_flock_acquire_shared() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
assert!(znode.flock_acquire(LockType::Shared, 1).is_ok());
assert!(znode.flock_acquire(LockType::Shared, 2).is_ok());
assert_eq!(
znode.flock_acquire(LockType::Exclusive, 3),
Err(FsError::ResourceBusy)
);
}
#[test]
fn test_flock_release() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
znode
.flock_acquire(LockType::Exclusive, 1)
.expect("test: operation should succeed");
assert!(znode.flock_release(1).is_ok());
assert!(znode.flock_acquire(LockType::Exclusive, 2).is_ok());
}
#[test]
fn test_fcntl_lock_exclusive() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
let lock1 = FileLock {
lock_type: LockType::Exclusive,
start: 0,
length: 100,
pid: 1,
};
assert!(znode.fcntl_lock(lock1.clone()).is_ok());
let lock2 = FileLock {
lock_type: LockType::Exclusive,
start: 50,
length: 100,
pid: 2,
};
assert_eq!(znode.fcntl_lock(lock2), Err(FsError::ResourceBusy));
}
#[test]
fn test_fcntl_lock_non_overlapping() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
let lock1 = FileLock {
lock_type: LockType::Exclusive,
start: 0,
length: 100,
pid: 1,
};
let lock2 = FileLock {
lock_type: LockType::Exclusive,
start: 200,
length: 100,
pid: 2,
};
assert!(znode.fcntl_lock(lock1).is_ok());
assert!(znode.fcntl_lock(lock2).is_ok());
}
#[test]
fn test_fcntl_lock_shared() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
let lock1 = FileLock {
lock_type: LockType::Shared,
start: 0,
length: 100,
pid: 1,
};
let lock2 = FileLock {
lock_type: LockType::Shared,
start: 50,
length: 100,
pid: 2,
};
assert!(znode.fcntl_lock(lock1).is_ok());
assert!(znode.fcntl_lock(lock2).is_ok());
}
#[test]
fn test_fcntl_unlock() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
let lock = FileLock {
lock_type: LockType::Exclusive,
start: 0,
length: 100,
pid: 1,
};
znode
.fcntl_lock(lock)
.expect("test: operation should succeed");
assert_eq!(znode.record_locks.len(), 1);
znode
.fcntl_unlock(0, 100, 1)
.expect("test: operation should succeed");
assert_eq!(znode.record_locks.len(), 0);
}
#[test]
fn test_fcntl_test_lock() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
let lock1 = FileLock {
lock_type: LockType::Exclusive,
start: 0,
length: 100,
pid: 1,
};
znode
.fcntl_lock(lock1.clone())
.expect("test: operation should succeed");
let test_lock = FileLock {
lock_type: LockType::Exclusive,
start: 50,
length: 100,
pid: 2,
};
let conflict = znode.fcntl_test_lock(&test_lock);
assert!(conflict.is_some());
assert_eq!(conflict.expect("test: operation should succeed").pid, 1);
}
#[test]
fn test_release_all_locks() {
let mut znode = Znode::new_file(1, 0, 0o644, 1000, 1000);
znode
.flock_acquire(LockType::Exclusive, 1)
.expect("test: operation should succeed");
let lock = FileLock {
lock_type: LockType::Exclusive,
start: 0,
length: 100,
pid: 1,
};
znode
.fcntl_lock(lock)
.expect("test: operation should succeed");
assert!(znode.flock.is_some());
assert_eq!(znode.record_locks.len(), 1);
znode.release_all_locks(1);
assert!(znode.flock.is_none());
assert_eq!(znode.record_locks.len(), 0);
}
#[test]
fn test_file_lock_conflicts() {
let lock1 = FileLock {
lock_type: LockType::Exclusive,
start: 0,
length: 100,
pid: 1,
};
let lock2 = FileLock {
lock_type: LockType::Exclusive,
start: 50,
length: 100,
pid: 2,
};
let lock3 = FileLock {
lock_type: LockType::Shared,
start: 0,
length: 100,
pid: 1,
};
let lock4 = FileLock {
lock_type: LockType::Shared,
start: 50,
length: 100,
pid: 2,
};
assert!(lock1.conflicts_with(&lock2));
assert!(lock1.conflicts_with(&lock4));
assert!(!lock3.conflicts_with(&lock4));
}
}