use std::collections::BTreeMap;
use crate::{
error::Error,
fs::Fd,
runtime::{dir::Dir, file::File},
storage::types::Node,
};
pub const STDERR_FD: Fd = 2;
pub const ROOT_FD: Fd = 3;
const FIRST_AVAILABLE_FD: Fd = 4;
pub enum FdEntry {
File(File),
Dir(Dir),
}
pub struct FdTable {
table: BTreeMap<Fd, FdEntry>,
node_refcount: BTreeMap<Node, usize>,
next_fd: Fd,
free_fds: Vec<Fd>,
}
impl FdTable {
pub fn new() -> Self {
Self {
table: BTreeMap::default(),
node_refcount: BTreeMap::default(),
next_fd: FIRST_AVAILABLE_FD,
free_fds: vec![],
}
}
pub fn node_refcount(&self) -> &BTreeMap<Node, usize> {
&self.node_refcount
}
pub fn update(&mut self, fd: Fd, entry: FdEntry) {
self.insert(fd, entry);
}
pub fn insert(&mut self, fd: Fd, entry: FdEntry) -> Option<FdEntry> {
self.inc_node_refcount(&entry);
let prev_entry = self.table.insert(fd, entry);
if let Some(prev_entry) = prev_entry.as_ref() {
self.dec_node_refcount(prev_entry);
}
prev_entry
}
pub fn get(&self, fd: Fd) -> Option<&FdEntry> {
self.table.get(&fd)
}
pub fn open_root(&mut self, entry: FdEntry) -> Fd {
let fd = ROOT_FD;
let prev = self.insert(fd, entry);
assert!(prev.is_none());
fd
}
pub fn open(&mut self, entry: FdEntry) -> Fd {
let fd = match self.free_fds.pop() {
Some(fd) => fd,
None => {
let fd = self.next_fd;
self.next_fd += 1;
fd
}
};
let prev = self.insert(fd, entry);
assert!(prev.is_none());
fd
}
pub fn renumber(&mut self, src: Fd, dst: Fd) -> Result<(), Error> {
if src == dst {
return Ok(());
}
let src_entry: Option<&FdEntry> = self.table.get(&src);
let dst_entry: Option<&FdEntry> = self.table.get(&dst);
if let Some(FdEntry::Dir(_s)) = src_entry
&& let Some(FdEntry::File(_d)) = dst_entry
{
return Err(Error::BadFileDescriptor);
}
if let Some(FdEntry::File(_s)) = src_entry
&& let Some(FdEntry::Dir(_d)) = dst_entry
{
return Err(Error::BadFileDescriptor);
}
let old_entry = self.close(src).ok_or(Error::BadFileDescriptor)?;
if let Some(_old_dst_entry) = self.close(dst) {
self.free_fds.retain(|value| *value != dst);
}
self.insert(dst, old_entry);
Ok(())
}
pub fn close(&mut self, fd: Fd) -> Option<FdEntry> {
if fd == ROOT_FD {
return None;
}
let entry = self.table.remove(&fd);
if let Some(entry) = entry {
if fd >= FIRST_AVAILABLE_FD {
self.free_fds.push(fd);
}
self.dec_node_refcount(&entry);
Some(entry)
} else {
None
}
}
fn inc_node_refcount(&mut self, entry: &FdEntry) {
let node = match entry {
FdEntry::File(file) => file.node,
FdEntry::Dir(dir) => dir.node,
};
let refcount = self.node_refcount.entry(node).or_default();
*refcount += 1;
}
fn dec_node_refcount(&mut self, entry: &FdEntry) {
let node = match entry {
FdEntry::File(file) => file.node,
FdEntry::Dir(dir) => dir.node,
};
let refcount = self.node_refcount.remove(&node);
if let Some(mut refcount) = refcount {
refcount -= 1;
if refcount > 0 {
self.node_refcount.insert(node, refcount);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::fs::FdStat;
use super::*;
#[test]
fn test_fdtable_new() {
let fd_table = FdTable::new();
assert!(fd_table.table.is_empty());
assert!(fd_table.node_refcount.is_empty());
assert_eq!(fd_table.next_fd, FIRST_AVAILABLE_FD);
assert!(fd_table.free_fds.is_empty());
}
#[test]
fn test_fdtable_open_and_get() {
let mut fd_table = FdTable::new();
let file = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let fd = fd_table.open(FdEntry::File(file));
assert_eq!(fd, FIRST_AVAILABLE_FD);
let entry = fd_table.get(fd).unwrap();
match entry {
FdEntry::File(file) => assert_eq!(file.node, 1),
_ => panic!("Expected a file entry"),
}
}
#[test]
fn test_fdtable_close() {
let mut fd_table = FdTable::new();
let file = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let fd = fd_table.open(FdEntry::File(file));
let entry = fd_table.close(fd);
assert!(entry.is_some());
assert!(fd_table.get(fd).is_none());
assert!(fd_table.free_fds.contains(&fd));
}
#[test]
fn test_fdtable_renumber() {
let mut fd_table = FdTable::new();
let file = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let src_fd = fd_table.open(FdEntry::File(file));
let dst_fd = 10;
fd_table.renumber(src_fd, dst_fd).unwrap();
assert!(fd_table.get(src_fd).is_none());
let entry = fd_table.get(dst_fd).unwrap();
match entry {
FdEntry::File(file) => assert_eq!(file.node, 1),
_ => panic!("Expected a file entry"),
}
}
#[test]
fn test_renumber_different_types() {
let mut fd_table = FdTable::new();
let file = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let dir = Dir {
node: 2,
stat: FdStat::default(),
};
let fd1 = fd_table.open(FdEntry::File(file));
let fd2 = fd_table.open(FdEntry::Dir(dir));
let result = fd_table.renumber(fd1, fd2);
assert_eq!(result, Err(Error::BadFileDescriptor));
let result = fd_table.renumber(fd2, fd1);
assert_eq!(result, Err(Error::BadFileDescriptor));
}
#[test]
fn test_fdtable_node_refcount() {
let mut fd_table = FdTable::new();
let file1 = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let file2 = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let fd1 = fd_table.open(FdEntry::File(file1));
let fd2 = fd_table.open(FdEntry::File(file2));
assert_eq!(fd_table.node_refcount().get(&1), Some(&2));
fd_table.close(fd1);
assert_eq!(fd_table.node_refcount().get(&1), Some(&1));
fd_table.close(fd2);
assert!(fd_table.node_refcount().get(&1).is_none());
}
#[test]
fn test_fdtable_update() {
let mut fd_table = FdTable::new();
let file = File {
node: 1,
cursor: 0,
stat: FdStat::default(),
};
let fd = fd_table.open(FdEntry::File(file));
let updated_file = File {
node: 2,
cursor: 0,
stat: FdStat::default(),
};
fd_table.update(fd, FdEntry::File(updated_file));
let entry = fd_table.get(fd).unwrap();
match entry {
FdEntry::File(file) => assert_eq!(file.node, 2),
_ => panic!("Expected a file entry"),
}
assert!(fd_table.node_refcount().get(&1).is_none());
assert_eq!(fd_table.node_refcount().get(&2), Some(&1));
}
}