use ic_stable_structures::Memory;
use crate::{
error::Error,
filename_cache::FilenameCache,
runtime::{
dir::Dir,
fd::{FdEntry, FdTable, STDERR_FD},
file::File,
structure_helpers::{create_hard_link, find_node, rm_dir_entry},
types::{RIGHTS_FD_READ, RIGHTS_FD_WRITE},
},
storage::{
Storage,
types::{DirEntry, DirEntryIndex, FileType, Metadata, MountedFileSizePolicy, Node},
},
};
pub use crate::runtime::types::{
Advice, ChunkSize, ChunkType, DstBuf, DstIoVec, Fd, FdFlags, FdStat, OpenFlags, SrcBuf,
SrcIoVec, Whence,
};
pub use crate::storage::types::FileSize;
pub struct FileSystem {
pub(crate) root_fd: Fd,
pub(crate) fd_table: FdTable,
pub(crate) names_cache: FilenameCache,
pub storage: Box<dyn Storage>,
}
impl FileSystem {
pub fn new(storage: Box<dyn Storage>) -> Result<Self, Error> {
let mut fd_table = FdTable::new();
if storage.get_version() == 0 {
return Ok(Self {
root_fd: 0,
fd_table,
names_cache: FilenameCache::new(),
storage,
});
}
let root_node = storage.root_node();
let root_entry = Dir::new(root_node, FdStat::default(), &*storage)?;
let root_fd = fd_table.open_root(FdEntry::Dir(root_entry));
let names_cache = FilenameCache::new();
Ok(Self {
root_fd,
fd_table,
names_cache,
storage,
})
}
pub fn get_storage_version(&self) -> u32 {
self.storage.get_version()
}
pub fn root_fd(&self) -> Fd {
self.root_fd
}
pub fn root_path(&self) -> &str {
"/"
}
pub fn advice(
&mut self,
fd: Fd,
_offset: FileSize,
_len: FileSize,
_advice: Advice,
) -> Result<(), Error> {
let meta = self.metadata(fd)?;
if meta.file_type != FileType::RegularFile {
return Err(Error::BadFileDescriptor);
}
Ok(())
}
pub fn allocate(
&mut self,
fd: Fd,
_offset: FileSize,
_additional_size: FileSize,
) -> Result<(), Error> {
let meta = self.metadata(fd)?;
if meta.file_type != FileType::RegularFile {
return Err(Error::BadFileDescriptor);
}
Ok(())
}
pub fn close(&mut self, fd: Fd) -> Result<(), Error> {
if fd == crate::runtime::fd::ROOT_FD {
return Ok(());
}
match self.fd_table.get(fd) {
Some(FdEntry::File(file)) => {
self.storage.flush(file.node);
}
Some(FdEntry::Dir(_dir)) => {
}
None => Err(Error::BadFileDescriptor)?,
};
self.fd_table.close(fd).ok_or(Error::BadFileDescriptor)?;
Ok(())
}
pub fn flush(&mut self, fd: Fd) -> Result<(), Error> {
let file = self.get_file(fd)?;
self.storage.flush(file.node);
Ok(())
}
pub fn renumber(&mut self, from: Fd, to: Fd) -> Result<(), Error> {
self.fd_table.renumber(from, to)
}
pub(crate) fn get_node(&self, fd: Fd) -> Result<Node, Error> {
match self.fd_table.get(fd) {
Some(FdEntry::File(file)) => Ok(file.node),
Some(FdEntry::Dir(dir)) => Ok(dir.node),
None => Err(Error::BadFileDescriptor),
}
}
pub(crate) fn get_file(&self, fd: Fd) -> Result<File, Error> {
match self.fd_table.get(fd) {
Some(FdEntry::Dir(_)) => Err(Error::BadFileDescriptor),
Some(FdEntry::File(file)) => Ok(file.clone()),
None => Err(Error::BadFileDescriptor),
}
}
pub(crate) fn put_file(&mut self, fd: Fd, file: File) {
self.fd_table.update(fd, FdEntry::File(file))
}
pub(crate) fn get_dir(&self, fd: Fd) -> Result<Dir, Error> {
match self.fd_table.get(fd) {
Some(FdEntry::Dir(dir)) => Ok(dir.clone()),
Some(FdEntry::File(_)) => Err(Error::NotADirectoryOrSymbolicLink),
None => Err(Error::BadFileDescriptor),
}
}
pub fn mount_memory_file(
&mut self,
filename: &str,
memory: Box<dyn Memory>,
policy: MountedFileSizePolicy,
) -> Result<(), Error> {
let filename = filename.strip_prefix('/').unwrap_or(filename);
let fd = self.open(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::CREATE,
0,
)?;
let result = (|| {
let file = self.get_file(fd)?;
self.storage.mount_node(file.node, memory, policy)
})();
let _ = self.close(fd);
result
}
pub fn init_memory_file(&mut self, filename: &str) -> Result<(), Error> {
let filename = filename.strip_prefix('/').unwrap_or(filename);
let fd = self.open(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::empty(),
0,
)?;
let result = (|| {
let file = self.get_file(fd)?;
self.storage.init_mounted_memory(file.node)
})();
let _ = self.close(fd);
result
}
pub fn store_memory_file(&mut self, filename: &str) -> Result<(), Error> {
let filename = filename.strip_prefix('/').unwrap_or(filename);
let fd = self.open(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::empty(),
0,
)?;
let result = (|| {
let file = self.get_file(fd)?;
self.storage.store_mounted_memory(file.node)
})();
let _ = self.close(fd);
result
}
pub fn unmount_memory_file(&mut self, filename: &str) -> Result<Box<dyn Memory>, Error> {
let filename = filename.strip_prefix('/').unwrap_or(filename);
let fd = self.open(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::empty(),
0,
)?;
let result = (|| {
let file = self.get_file(fd)?;
let memory = self.storage.unmount_node(file.node)?;
Ok(memory)
})();
let _ = self.close(fd);
result
}
pub fn get_direntry(&self, fd: Fd, index: DirEntryIndex) -> Result<DirEntry, Error> {
self.get_dir(fd)?.get_entry(index, self.storage.as_ref())
}
pub fn with_direntries(
&self,
fd: Fd,
initial_index: Option<DirEntryIndex>,
f: &mut dyn FnMut(&DirEntryIndex, &DirEntry) -> bool,
) -> Result<(), Error> {
let dir = self.get_dir(fd)?;
self.storage.with_direntries(dir.node, initial_index, f);
Ok(())
}
fn put_dir(&mut self, fd: Fd, dir: Dir) {
self.fd_table.update(fd, FdEntry::Dir(dir))
}
pub fn read(&mut self, fd: Fd, dst: &mut [u8]) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
if file.stat.rights_base & RIGHTS_FD_READ == 0 {
return Err(Error::OperationNotPermitted);
}
let read_size = file.read_with_cursor(dst, self.storage.as_mut())?;
self.put_file(fd, file);
Ok(read_size)
}
pub fn read_vec(&mut self, fd: Fd, dst: DstIoVec) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
if file.stat.rights_base & RIGHTS_FD_READ == 0 {
return Err(Error::OperationNotPermitted);
}
let mut read_size = 0;
for buf in dst {
let buf = unsafe { std::slice::from_raw_parts_mut(buf.buf, buf.len) };
let size = file.read_with_cursor(buf, self.storage.as_mut())?;
read_size += size;
}
self.put_file(fd, file);
Ok(read_size)
}
pub fn read_vec_with_offset(
&mut self,
fd: Fd,
dst: DstIoVec,
offset: FileSize,
) -> Result<FileSize, Error> {
let file = self.get_file(fd)?;
if file.stat.rights_base & RIGHTS_FD_READ == 0 {
return Err(Error::OperationNotPermitted);
}
let mut read_size = 0;
for buf in dst {
let rbuf = unsafe { std::slice::from_raw_parts_mut(buf.buf, buf.len) };
let size = file.read_with_offset(read_size + offset, rbuf, self.storage.as_mut())?;
read_size += size;
}
self.put_file(fd, file);
Ok(read_size)
}
pub fn write(&mut self, fd: Fd, src: &[u8]) -> Result<FileSize, Error> {
let buf = SrcBuf {
buf: src.as_ptr(),
len: src.len(),
};
self.write_vec(fd, &[buf])
}
pub fn write_vec(&mut self, fd: Fd, src: SrcIoVec) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
if file.stat.rights_base & RIGHTS_FD_WRITE == 0 {
return Err(Error::OperationNotPermitted);
}
let is_append = file.stat.flags.contains(FdFlags::APPEND);
let offset = if is_append {
let meta = self.metadata_from_node(file.node)?;
meta.size
} else {
file.cursor
};
let mut written_size = 0;
for buf in src {
let buf = unsafe { std::slice::from_raw_parts(buf.buf, buf.len) };
let size = file.write_with_offset(written_size + offset, buf, self.storage.as_mut())?;
written_size += size;
}
file.cursor = written_size + offset;
self.put_file(fd, file);
Ok(written_size)
}
pub fn write_vec_with_offset(
&mut self,
fd: Fd,
src: SrcIoVec,
offset: FileSize,
) -> Result<FileSize, Error> {
let file = self.get_file(fd)?;
if file.stat.rights_base & RIGHTS_FD_WRITE == 0 {
return Err(Error::OperationNotPermitted);
}
let is_append = file.stat.flags.contains(FdFlags::APPEND);
let offset = if is_append {
let meta = self.metadata_from_node(file.node)?;
meta.size
} else {
offset
};
let mut written_size = 0;
for buf in src {
let buf = unsafe { std::slice::from_raw_parts(buf.buf, buf.len) };
let size = file.write_with_offset(written_size + offset, buf, self.storage.as_mut())?;
written_size += size;
}
self.put_file(fd, file);
Ok(written_size)
}
pub fn seek(&mut self, fd: Fd, delta: i64, whence: Whence) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
let pos = file.seek(delta, whence, self.storage.as_mut())?;
self.put_file(fd, file);
Ok(pos)
}
pub fn tell(&mut self, fd: Fd) -> Result<FileSize, Error> {
let file = self.get_file(fd)?;
let pos = file.tell();
Ok(pos)
}
pub fn metadata_from_node(&self, node: Node) -> Result<Metadata, Error> {
self.storage.get_metadata(node)
}
pub fn metadata(&self, fd: Fd) -> Result<Metadata, Error> {
if fd <= STDERR_FD {
return Ok(Metadata {
node: u64::MAX, file_type: FileType::RegularFile,
link_count: 0,
size: 0,
times: crate::storage::types::Times::default(),
chunk_type: None,
maximum_size_allowed: None,
first_dir_entry: None,
last_dir_entry: None,
});
}
let node = self.get_node(fd)?;
self.storage.get_metadata(node)
}
pub fn set_metadata(&mut self, fd: Fd, metadata: Metadata) -> Result<(), Error> {
let node = self.get_node(fd)?;
self.storage.put_metadata(node, &metadata)?;
Ok(())
}
pub fn set_file_size(&mut self, fd: Fd, new_size: FileSize) -> Result<(), Error> {
let file = self.get_file(fd)?;
if file.stat.rights_base & RIGHTS_FD_WRITE == 0 {
return Err(Error::InvalidArgument);
}
let mut metadata = self.storage.get_metadata(file.node)?;
metadata.size = new_size;
self.storage.put_metadata(file.node, &metadata)?;
Ok(())
}
pub fn set_file_size_limit(&mut self, fd: Fd, max_size: FileSize) -> Result<(), Error> {
let file = self.get_file(fd)?;
let mut metadata = self.storage.get_metadata(file.node)?;
metadata.maximum_size_allowed = Some(max_size);
self.storage.put_metadata(file.node, &metadata)?;
Ok(())
}
pub fn set_accessed_time(&mut self, fd: Fd, time: u64) -> Result<(), Error> {
let node = self.get_node(fd)?;
let mut metadata = self.storage.get_metadata(node)?;
metadata.times.accessed = time;
self.storage.put_metadata(node, &metadata)?;
Ok(())
}
pub fn set_modified_time(&mut self, fd: Fd, time: u64) -> Result<(), Error> {
let node = self.get_node(fd)?;
let mut metadata = self.storage.get_metadata(node)?;
metadata.times.modified = time;
self.storage.put_metadata(node, &metadata)?;
Ok(())
}
pub fn get_stat(&self, fd: Fd) -> Result<(FileType, FdStat), Error> {
match self.fd_table.get(fd) {
None => Err(Error::BadFileDescriptor),
Some(FdEntry::File(file)) => Ok((FileType::RegularFile, file.stat)),
Some(FdEntry::Dir(dir)) => Ok((FileType::Directory, dir.stat)),
}
}
pub fn set_stat(&mut self, fd: Fd, stat: FdStat) -> Result<(), Error> {
match self.fd_table.get(fd) {
Some(FdEntry::File(file)) => {
let mut file = file.clone();
file.stat = stat;
self.put_file(fd, file);
Ok(())
}
Some(FdEntry::Dir(dir)) => {
if stat.flags.contains(FdFlags::APPEND) || stat.flags.contains(FdFlags::NONBLOCK) {
return Err(Error::BadFileDescriptor);
}
let mut dir = dir.clone();
dir.stat = stat;
self.put_dir(fd, dir);
Ok(())
}
None => Err(Error::BadFileDescriptor),
}
}
pub fn open_metadata(&mut self, parent: Fd, path: &str) -> Result<Metadata, Error> {
let dir = self.get_dir(parent)?;
let node = find_node(dir.node, path, &mut self.names_cache, self.storage.as_ref())?;
self.storage.get_metadata(node)
}
pub fn open(
&mut self,
parent_fd: Fd,
path: &str,
stat: FdStat,
flags: OpenFlags,
ctime: u64,
) -> Result<Fd, Error> {
let dir = self.get_dir(parent_fd)?;
let res = find_node(dir.node, path, &mut self.names_cache, self.storage.as_ref());
match res {
Ok(node) => {
if flags.contains(OpenFlags::CREATE) && flags.contains(OpenFlags::EXCLUSIVE) {
return Err(Error::FileExists);
}
self.open_internal(node, stat, flags)
}
Err(Error::NoSuchFileOrDirectory) => {
if !flags.contains(OpenFlags::CREATE) {
return Err(Error::NoSuchFileOrDirectory);
}
if flags.contains(OpenFlags::DIRECTORY) {
self.create_open_directory(parent_fd, path, stat, ctime)
} else {
self.create_open_file(parent_fd, path, stat, ctime)
}
}
Err(err) => Err(err),
}
}
fn open_internal(&mut self, node: Node, stat: FdStat, flags: OpenFlags) -> Result<Fd, Error> {
let metadata = self.storage.get_metadata(node)?;
match metadata.file_type {
FileType::Directory => {
let dir = Dir::new(node, stat, self.storage.as_mut())?;
let fd = self.fd_table.open(FdEntry::Dir(dir));
Ok(fd)
}
FileType::RegularFile => {
if flags.contains(OpenFlags::DIRECTORY) {
return Err(Error::OperationNotPermitted);
}
let file = File::new(node, stat, self.storage.as_mut())?;
if flags.contains(OpenFlags::TRUNCATE) {
file.truncate(self.storage.as_mut())?;
}
let fd = self.fd_table.open(FdEntry::File(file));
Ok(fd)
}
FileType::SymbolicLink => unimplemented!("Symbolic links are not supported"),
}
}
pub(crate) fn create_open_file(
&mut self,
parent: Fd,
path: &str,
stat: FdStat,
ctime: u64,
) -> Result<Fd, Error> {
let dir = self.get_dir(parent)?;
let child = dir.create_file(
path,
stat,
&mut self.names_cache,
self.storage.as_mut(),
ctime,
)?;
let child_fd = self.fd_table.open(FdEntry::File(child));
self.put_dir(parent, dir);
Ok(child_fd)
}
pub fn remove_file(&mut self, parent: Fd, path: &str) -> Result<(), Error> {
let dir = self.get_dir(parent)?;
dir.remove_file(
path,
self.fd_table.node_refcount(),
&mut self.names_cache,
self.storage.as_mut(),
)?;
self.put_dir(parent, dir);
Ok(())
}
pub fn mkdir(&mut self, parent: Fd, path: &str, stat: FdStat, ctime: u64) -> Result<(), Error> {
let dir = self.get_dir(parent)?;
dir.create_dir(
path,
stat,
&mut self.names_cache,
self.storage.as_mut(),
ctime,
)?;
self.put_dir(parent, dir);
Ok(())
}
pub fn create_open_directory(
&mut self,
parent: Fd,
path: &str,
stat: FdStat,
ctime: u64,
) -> Result<Fd, Error> {
let dir = self.get_dir(parent)?;
let child = dir.create_dir(
path,
stat,
&mut self.names_cache,
self.storage.as_mut(),
ctime,
)?;
let child_fd = self.fd_table.open(FdEntry::Dir(child));
self.put_dir(parent, dir);
Ok(child_fd)
}
pub fn remove_dir(&mut self, parent: Fd, path: &str) -> Result<(), Error> {
let dir = self.get_dir(parent)?;
dir.remove_dir(
path,
self.fd_table.node_refcount(),
&mut self.names_cache,
self.storage.as_mut(),
)?;
self.put_dir(parent, dir);
Ok(())
}
pub(crate) fn list_dir_internal(
&mut self,
dir_fd: Fd,
file_type: Option<FileType>,
) -> Result<Vec<(Node, String)>, Error> {
let mut res = vec![];
self.with_direntries(dir_fd, Some(0), &mut |_index, entry| -> bool {
let filename = unsafe {
std::str::from_utf8_unchecked(&entry.name.bytes[..(entry.name.length as usize)])
}
.to_string();
if let Some(file_type) = file_type {
let meta = self
.metadata_from_node(entry.node)
.expect("Metadata not found!");
if meta.file_type == file_type {
res.push((entry.node, filename));
}
} else {
res.push((entry.node, filename));
}
true
})?;
Ok(res)
}
pub fn remove_recursive(&mut self, parent: Fd, path: &str) -> Result<(), Error> {
let meta = self.open_metadata(parent, path)?;
if meta.file_type == FileType::RegularFile {
return self.remove_file(parent, path);
}
let dir_fd = self.open(parent, path, FdStat::default(), OpenFlags::DIRECTORY, 0)?;
let result = (|| {
let children = self.list_dir_internal(dir_fd, None)?;
for (child_node, child_name) in children {
let child_meta = self.storage.get_metadata(child_node)?;
match child_meta.file_type {
FileType::Directory => {
self.remove_recursive(dir_fd, &child_name)?;
}
FileType::RegularFile => {
self.remove_file(dir_fd, &child_name)?;
}
FileType::SymbolicLink => {
unimplemented!("Symbolic links are not supported yet");
}
}
}
Ok(())
})();
self.close(dir_fd)?;
self.remove_dir(parent, path)?;
result
}
pub fn create_hard_link(
&mut self,
old_fd: Fd,
old_path: &str,
new_fd: Fd,
new_path: &str,
) -> Result<Fd, Error> {
let src_dir = self.get_dir(old_fd)?;
let dst_dir = self.get_dir(new_fd)?;
create_hard_link(
dst_dir.node,
new_path,
src_dir.node,
old_path,
false,
self.fd_table.node_refcount(),
&mut self.names_cache,
self.storage.as_mut(),
)?;
let node = find_node(
dst_dir.node,
new_path,
&mut self.names_cache,
self.storage.as_ref(),
)?;
self.open_internal(node, FdStat::default(), OpenFlags::empty())
}
pub fn rename(
&mut self,
old_fd: Fd,
old_path: &str,
new_fd: Fd,
new_path: &str,
) -> Result<Fd, Error> {
let src_dir = self.get_dir(old_fd)?;
let dst_dir = self.get_dir(new_fd)?;
create_hard_link(
dst_dir.node,
new_path,
src_dir.node,
old_path,
true,
self.fd_table.node_refcount(),
&mut self.names_cache,
self.storage.as_mut(),
)?;
let (node, _metadata) = rm_dir_entry(
src_dir.node,
old_path,
None,
true,
self.fd_table.node_refcount(),
&mut self.names_cache,
self.storage.as_mut(),
)?;
self.open_internal(node, FdStat::default(), OpenFlags::empty())
}
#[cfg(test)]
pub(crate) fn get_test_storage(&mut self) -> &mut dyn Storage {
self.storage.as_mut()
}
#[cfg(test)]
pub(crate) fn get_test_file(&self, fd: Fd) -> File {
self.get_file(fd).unwrap()
}
}