use crate::virtio_block::BLOCK_DEVICE;
use alloc::{
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use spin::{Lazy, Mutex};
use tg_easy_fs::{EasyFileSystem, FSManager, FileHandle, Inode, OpenFlags, UserBuffer};
use tg_syscall::StatMode;
pub static FS: Lazy<FileSystem> = Lazy::new(|| FileSystem {
root: EasyFileSystem::root_inode(&EasyFileSystem::open(BLOCK_DEVICE.clone())),
});
static FILE_INDEX: Lazy<Mutex<FileIndex>> = Lazy::new(|| Mutex::new(FileIndex::new()));
pub struct FileSystem {
root: Inode,
}
#[derive(Clone)]
pub struct OpenedFile {
pub handle: FileHandle,
pub meta: Option<Arc<Mutex<FileMeta>>>,
}
impl OpenedFile {
pub fn new(handle: FileHandle, meta: Arc<Mutex<FileMeta>>) -> Self {
Self {
handle,
meta: Some(meta),
}
}
pub fn empty(read: bool, write: bool) -> Self {
Self {
handle: FileHandle::empty(read, write),
meta: None,
}
}
#[inline]
pub fn readable(&self) -> bool {
self.handle.readable()
}
#[inline]
pub fn writable(&self) -> bool {
self.handle.writable()
}
#[inline]
pub fn read(&self, buf: UserBuffer) -> isize {
self.handle.read(buf)
}
#[inline]
pub fn write(&self, buf: UserBuffer) -> isize {
self.handle.write(buf)
}
}
pub struct FileMeta {
pub ino: u64,
pub mode: StatMode,
pub nlink: u32,
backing_path: String,
}
struct FileIndex {
next_ino: u64,
aliases: BTreeMap<String, Arc<Mutex<FileMeta>>>,
hidden: BTreeSet<String>,
}
impl FileIndex {
const fn new() -> Self {
Self {
next_ino: 1,
aliases: BTreeMap::new(),
hidden: BTreeSet::new(),
}
}
fn alloc_meta(&mut self, path: &str) -> Arc<Mutex<FileMeta>> {
let meta = Arc::new(Mutex::new(FileMeta {
ino: self.next_ino,
mode: StatMode::FILE,
nlink: 1,
backing_path: path.to_string(),
}));
self.next_ino += 1;
self.aliases.insert(path.to_string(), meta.clone());
self.hidden.remove(path);
meta
}
fn visible_meta(&self, path: &str) -> Option<Arc<Mutex<FileMeta>>> {
self.aliases.get(path).cloned()
}
fn path_hidden(&self, path: &str) -> bool {
self.hidden.contains(path)
}
}
impl FileSystem {
fn ensure_meta_for_path(&self, path: &str) -> Option<Arc<Mutex<FileMeta>>> {
let mut index = FILE_INDEX.lock();
if let Some(meta) = index.visible_meta(path) {
return Some(meta);
}
if index.path_hidden(path) || self.root.find(path).is_none() {
return None;
}
Some(index.alloc_meta(path))
}
pub fn open_file(&self, path: &str, flags: OpenFlags) -> Option<Arc<OpenedFile>> {
let (readable, writable) = flags.read_write();
if flags.contains(OpenFlags::CREATE) {
let meta = {
let mut index = FILE_INDEX.lock();
if let Some(meta) = index.visible_meta(path) {
meta
} else {
if index.path_hidden(path) {
index.hidden.remove(path);
}
index.alloc_meta(path)
}
};
let inode = if let Some(inode) = self.root.find(path) {
inode
} else {
self.root.create(path)?
};
inode.clear();
return Some(Arc::new(OpenedFile::new(
FileHandle::new(readable, writable, inode),
meta,
)));
}
let meta = self.ensure_meta_for_path(path)?;
let backing_path = {
let meta = meta.lock();
meta.backing_path.clone()
};
let inode = self.root.find(backing_path.as_str())?;
if flags.contains(OpenFlags::TRUNC) {
inode.clear();
}
Some(Arc::new(OpenedFile::new(
FileHandle::new(readable, writable, inode),
meta,
)))
}
}
impl FSManager for FileSystem {
fn open(&self, path: &str, flags: OpenFlags) -> Option<Arc<FileHandle>> {
self.open_file(path, flags)
.map(|opened| Arc::new(opened.handle.clone()))
}
fn find(&self, path: &str) -> Option<Arc<Inode>> {
if let Some(meta) = FILE_INDEX.lock().visible_meta(path) {
let backing_path = meta.lock().backing_path.clone();
self.root.find(backing_path.as_str())
} else if FILE_INDEX.lock().path_hidden(path) {
None
} else {
self.root.find(path)
}
}
fn readdir(&self, _path: &str) -> Option<alloc::vec::Vec<String>> {
Some(self.root.readdir())
}
fn link(&self, src: &str, dst: &str) -> isize {
if src == dst {
return -1;
}
let mut index = FILE_INDEX.lock();
if index.visible_meta(dst).is_some() || index.path_hidden(dst) {
return -1;
}
let Some(meta) = (if let Some(meta) = index.visible_meta(src) {
Some(meta)
} else if self.root.find(src).is_some() {
Some(index.alloc_meta(src))
} else {
None
}) else {
return -1;
};
meta.lock().nlink += 1;
index.aliases.insert(dst.to_string(), meta);
index.hidden.remove(dst);
0
}
fn unlink(&self, path: &str) -> isize {
let mut index = FILE_INDEX.lock();
let Some(meta) = (if let Some(meta) = index.aliases.remove(path) {
Some(meta)
} else if self.root.find(path).is_some() && !index.path_hidden(path) {
let meta = index.alloc_meta(path);
index.aliases.remove(path);
Some(meta)
} else {
None
}) else {
return -1;
};
index.hidden.insert(path.to_string());
let mut meta = meta.lock();
if meta.nlink > 0 {
meta.nlink -= 1;
}
0
}
}
pub fn read_all(fd: Arc<OpenedFile>) -> Vec<u8> {
let mut offset = 0usize;
let mut buffer = [0u8; 512];
let mut v: Vec<u8> = Vec::new();
if let Some(inode) = &fd.handle.inode {
loop {
let len = inode.read_at(offset, &mut buffer);
if len == 0 {
break;
}
offset += len;
v.extend_from_slice(&buffer[..len]);
}
}
v
}