use ic_stable_structures::Memory;
use crate::{
error::Error,
runtime::{
dir::Dir,
fd::{FdEntry, FdTable},
file::File,
structure_helpers::{create_hard_link, find_node, rm_dir_entry},
},
storage::{
types::{DirEntry, DirEntryIndex, FileSize, FileType, Metadata, Node},
Storage,
},
};
pub use crate::runtime::fd::Fd;
pub use crate::runtime::types::{
ChunkSize, DstBuf, DstIoVec, FdFlags, FdStat, OpenFlags, SrcBuf, SrcIoVec, Whence,
};
pub struct FileSystem {
root_fd: Fd,
fd_table: FdTable,
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,
storage,
});
}
let root_node = storage.root_node();
let root_entry = Dir::new(root_node, FdStat::default(), &*storage)?;
let root_fd = fd_table.open(FdEntry::Dir(root_entry));
Ok(Self {
root_fd,
fd_table,
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 renumber(&mut self, from: Fd, to: Fd) -> Result<(), Error> {
self.fd_table.renumber(from, to)
}
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::NotFound),
}
}
fn get_file(&self, fd: Fd) -> Result<File, Error> {
match self.fd_table.get(fd) {
Some(FdEntry::File(file)) => Ok(file.clone()),
Some(FdEntry::Dir(_)) => Err(Error::InvalidFileType),
None => Err(Error::NotFound),
}
}
fn put_file(&mut self, fd: Fd, file: File) {
self.fd_table.update(fd, FdEntry::File(file))
}
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::InvalidFileType),
None => Err(Error::NotFound),
}
}
pub fn mount_memory_file(
&mut self,
filename: &str,
memory: Box<dyn Memory>,
) -> Result<(), Error> {
let fd = self.open_or_create(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::CREATE,
0,
)?;
let node = self.get_node(fd)?;
self.close(fd)?;
self.storage.mount_node(node, memory)?;
Ok(())
}
pub fn init_memory_file(&mut self, filename: &str) -> Result<(), Error> {
let fd = self.open_or_create(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::empty(),
0,
)?;
let node = self.get_node(fd)?;
self.close(fd)?;
self.storage.init_mounted_memory(node)
}
pub fn store_memory_file(&mut self, filename: &str) -> Result<(), Error> {
let fd = self.open_or_create(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::empty(),
0,
)?;
let node = self.get_node(fd)?;
self.close(fd)?;
self.storage.store_mounted_memory(node)
}
pub fn unmount_memory_file(&mut self, filename: &str) -> Result<Box<dyn Memory>, Error> {
let fd = self.open_or_create(
self.root_fd,
filename,
FdStat::default(),
OpenFlags::empty(),
0,
)?;
let node = self.get_node(fd)?;
self.close(fd)?;
let memory = self.storage.unmount_node(node)?;
Ok(memory)
}
pub fn get_direntry(&self, fd: Fd, index: DirEntryIndex) -> Result<DirEntry, Error> {
self.get_dir(fd)?.get_entry(index, self.storage.as_ref())
}
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)?;
let read_size = file.read_with_cursor(dst, self.storage.as_mut())?;
self.put_file(fd, file);
Ok(read_size)
}
pub fn write(&mut self, fd: Fd, src: &[u8]) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
let written_size = file.write_with_cursor(src, self.storage.as_mut())?;
self.put_file(fd, file);
Ok(written_size)
}
pub fn read_vec(&mut self, fd: Fd, dst: DstIoVec) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
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)?;
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_vec(&mut self, fd: Fd, src: SrcIoVec) -> Result<FileSize, Error> {
let mut file = self.get_file(fd)?;
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_cursor(buf, self.storage.as_mut())?;
written_size += size;
}
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)?;
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 close(&mut self, fd: Fd) -> Result<(), Error> {
self.fd_table.close(fd).ok_or(Error::NotFound)?;
Ok(())
}
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> {
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_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::NotFound),
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)) => {
let mut dir = dir.clone();
dir.stat = stat;
self.put_dir(fd, dir);
Ok(())
}
None => Err(Error::NotFound),
}
}
pub fn open_metadata(&self, parent: Fd, path: &str) -> Result<Metadata, Error> {
let dir = self.get_dir(parent)?;
let node = find_node(dir.node, path, self.storage.as_ref())?;
self.storage.get_metadata(node)
}
pub fn open_or_create(
&mut self,
parent: Fd,
path: &str,
stat: FdStat,
flags: OpenFlags,
ctime: u64,
) -> Result<Fd, Error> {
let dir = self.get_dir(parent)?;
match find_node(dir.node, path, self.storage.as_ref()) {
Ok(node) => self.open(node, stat, flags),
Err(Error::NotFound) => {
if !flags.contains(OpenFlags::CREATE) {
return Err(Error::NotFound);
}
if flags.contains(OpenFlags::DIRECTORY) {
return Err(Error::InvalidFileType);
}
self.create_file(parent, path, stat, ctime)
}
Err(err) => Err(err),
}
}
fn open(&mut self, node: Node, stat: FdStat, flags: OpenFlags) -> Result<Fd, Error> {
if flags.contains(OpenFlags::EXCLUSIVE) {
return Err(Error::FileAlreadyExists);
}
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::InvalidFileType);
}
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 yet"),
}
}
pub fn create_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, 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(), self.storage.as_mut())
}
pub fn create_dir(
&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, 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(), self.storage.as_mut())
}
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.storage.as_mut(),
)?;
let node = find_node(dst_dir.node, new_path, self.storage.as_ref())?;
self.open(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.storage.as_mut(),
)?;
let (node, _metadata) = rm_dir_entry(
src_dir.node,
old_path,
None,
self.fd_table.node_refcount(),
self.storage.as_mut(),
)?;
self.open(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()
}
}
#[cfg(test)]
mod tests {
use ic_stable_structures::{Memory, VectorMemory};
use crate::{
error::Error,
fs::{DstBuf, FdFlags, SrcBuf},
runtime::{
structure_helpers::find_node,
types::{FdStat, OpenFlags},
},
storage::types::{FileSize, FileType},
test_utils::{
new_vector_memory, read_text_file, test_fs, test_fs_setups, test_fs_transient,
write_text_fd, write_text_file,
},
};
use super::{Fd, FileSystem};
pub fn list_files(fs: &mut FileSystem, path: &str) -> Vec<String> {
use std::str::FromStr;
let mut res = vec![];
let dir = fs.root_fd();
let fd = fs
.open_or_create(dir, path, FdStat::default(), OpenFlags::DIRECTORY, 0)
.unwrap();
let meta = fs.metadata(fd).unwrap();
let mut entry_index = meta.first_dir_entry;
while let Some(index) = entry_index {
let entry = fs.get_direntry(fd, index).unwrap();
let filename_str: &str =
std::str::from_utf8(&entry.name.bytes[0..(entry.name.length as usize)]).unwrap();
let st = String::from_str(filename_str).unwrap();
res.push(st);
entry_index = entry.next_entry;
}
res
}
#[test]
fn get_root_info() {
let fs = test_fs();
let fd = fs.root_fd();
let path = fs.root_path();
assert!(fd == 3);
assert!(path == "/");
}
#[test]
fn create_file() {
let mut fs = test_fs();
let file = fs
.create_file(fs.root_fd(), "test.txt", Default::default(), 0)
.unwrap();
assert!(file > fs.root_fd());
}
#[test]
fn create_dir() {
let mut fs = test_fs();
let dir = fs
.create_dir(fs.root_fd(), "test", FdStat::default(), 0)
.unwrap();
let fd = fs
.create_file(dir, "file.txt", FdStat::default(), 0)
.unwrap();
fs.write(fd, "Hello, world!".as_bytes()).unwrap();
let dir = fs
.open_or_create(
fs.root_fd(),
"test",
FdStat::default(),
OpenFlags::empty(),
0,
)
.unwrap();
let fd = fs
.open_or_create(dir, "file.txt", FdStat::default(), OpenFlags::empty(), 0)
.unwrap();
let mut buf = [0; 13];
fs.read(fd, &mut buf).unwrap();
assert_eq!(&buf, "Hello, world!".as_bytes());
}
#[test]
fn create_file_creates_a_few_files() {
let mut fs = test_fs();
let dir = fs.root_fd();
fs.create_file(dir, "test1.txt", FdStat::default(), 0)
.unwrap();
fs.create_file(dir, "test2.txt", FdStat::default(), 0)
.unwrap();
fs.create_file(dir, "test3.txt", FdStat::default(), 0)
.unwrap();
let meta = fs.metadata(fs.root_fd()).unwrap();
let entry_index = meta.first_dir_entry.unwrap();
let entry1 = fs.get_direntry(fs.root_fd(), entry_index).unwrap();
let entry2 = fs.get_direntry(fs.root_fd(), entry_index + 1).unwrap();
let entry3 = fs.get_direntry(fs.root_fd(), entry_index + 2).unwrap();
assert_eq!(entry1.prev_entry, None);
assert_eq!(entry1.next_entry, Some(entry_index + 1));
assert_eq!(entry2.prev_entry, Some(entry_index));
assert_eq!(entry2.next_entry, Some(entry_index + 2));
assert_eq!(entry3.prev_entry, Some(entry_index + 1));
assert_eq!(entry3.next_entry, None);
}
#[test]
fn close_file_fd_reused() {
let mut fs = test_fs();
let dir = fs.root_fd();
fs.create_file(dir, "test1.txt", FdStat::default(), 0)
.unwrap();
let fd2 = fs
.create_file(dir, "test2.txt", FdStat::default(), 0)
.unwrap();
fs.create_file(dir, "test3.txt", FdStat::default(), 0)
.unwrap();
fs.close(fd2).unwrap();
let fd4 = fs
.create_file(dir, "test4.txt", FdStat::default(), 0)
.unwrap();
assert_eq!(fd2, fd4);
}
#[test]
fn fd_renumber() {
let mut fs = test_fs();
let dir = fs.root_fd();
let fd1 = fs
.create_file(dir, "test1.txt", FdStat::default(), 0)
.unwrap();
let fd2 = fs
.create_file(dir, "test2.txt", FdStat::default(), 0)
.unwrap();
let entry1 = fs.get_node(fd1);
fs.renumber(fd1, fd2).unwrap();
assert!(fs.get_node(fd1).is_err());
assert_eq!(fs.get_node(fd2), entry1);
}
#[test]
fn seek_and_write() {
let mut fs = test_fs();
let dir = fs.root_fd();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.seek(fd, 24, super::Whence::SET).unwrap();
fs.write(fd, &[1, 2, 3, 4, 5]).unwrap();
let meta = fs.metadata(fd).unwrap();
assert_eq!(meta.size, 29);
fs.seek(fd, 0, crate::fs::Whence::SET).unwrap();
let mut buf = [42u8; 29];
let rr = fs.read(fd, &mut buf).unwrap();
assert_eq!(rr, 29);
assert_eq!(
buf,
[
0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
4, 5
]
);
fs.close(fd).unwrap();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
let mut buf = [42u8; 29];
let rr = fs.read(fd, &mut buf).unwrap();
assert_eq!(rr, 29);
assert_eq!(
buf,
[
0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
4, 5
]
);
fs.close(fd).unwrap();
}
#[test]
fn read_and_write_vec() {
let mut fs = test_fs();
let dir = fs.root_fd();
let write_content1 = "This is a sample file content.";
let write_content2 = "1234567890";
let write_content = [
SrcBuf {
buf: write_content1.as_ptr(),
len: write_content1.len(),
},
SrcBuf {
buf: write_content2.as_ptr(),
len: write_content2.len(),
},
];
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.write_vec(fd, write_content.as_ref()).unwrap();
let meta = fs.metadata(fd).unwrap();
assert_eq!(meta.size, 40);
fs.seek(fd, 0, crate::fs::Whence::SET).unwrap();
let mut read_content1 = String::from("......................");
let mut read_content2 = String::from("......................");
let read_content = [
DstBuf {
buf: read_content1.as_mut_ptr(),
len: read_content1.len(),
},
DstBuf {
buf: read_content2.as_mut_ptr(),
len: read_content2.len(),
},
];
fs.read_vec(fd, &read_content).unwrap();
assert_eq!("This is a sample file ", read_content1);
assert_eq!("content.1234567890....", read_content2);
fs.close(fd).unwrap();
}
#[test]
fn read_and_write_vec_with_offset() {
let mut fs = test_fs();
let dir = fs.root_fd();
let write_content1 = "This is a sample file content.";
let write_content2 = "1234567890";
let write_content = [
SrcBuf {
buf: write_content1.as_ptr(),
len: write_content1.len(),
},
SrcBuf {
buf: write_content2.as_ptr(),
len: write_content2.len(),
},
];
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.write_vec_with_offset(fd, write_content.as_ref(), 2)
.unwrap();
let meta = fs.metadata(fd).unwrap();
assert_eq!(meta.size, 42);
fs.seek(fd, 0, crate::fs::Whence::SET).unwrap();
let mut read_content1 = String::from("......................");
let mut read_content2 = String::from("......................");
let read_content = [
DstBuf {
buf: read_content1.as_mut_ptr(),
len: read_content1.len(),
},
DstBuf {
buf: read_content2.as_mut_ptr(),
len: read_content2.len(),
},
];
fs.read_vec_with_offset(fd, &read_content, 1).unwrap();
assert_eq!("\0This is a sample file", read_content1);
assert_eq!(" content.1234567890...", read_content2);
fs.close(fd).unwrap();
}
#[test]
fn seek_and_write_transient() {
let mut fs = test_fs_transient();
let dir = fs.root_fd();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.seek(fd, 24, super::Whence::SET).unwrap();
fs.write(fd, &[1, 2, 3, 4, 5]).unwrap();
let meta = fs.metadata(fd).unwrap();
assert_eq!(meta.size, 29);
fs.seek(fd, 0, crate::fs::Whence::SET).unwrap();
let mut buf = [42u8; 29];
let rr = fs.read(fd, &mut buf).unwrap();
assert_eq!(rr, 29);
assert_eq!(
buf,
[
0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
4, 5
]
);
fs.close(fd).unwrap();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
let mut buf = [42u8; 29];
let rr = fs.read(fd, &mut buf).unwrap();
assert_eq!(rr, 29);
assert_eq!(
buf,
[
0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
4, 5
]
);
fs.close(fd).unwrap();
}
#[test]
fn create_and_remove_file() {
for mut fs in test_fs_setups("virtual_memory.txt") {
let dir = fs.root_fd();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.write(fd, &[1, 2, 3, 4, 5]).unwrap();
fs.close(fd).unwrap();
fs.remove_file(dir, "test.txt").unwrap();
let err = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::empty(), 0)
.unwrap_err();
assert_eq!(err, Error::NotFound);
}
}
#[test]
fn cannot_remove_opened_file() {
for mut fs in test_fs_setups("virtual_memory.txt") {
let dir = fs.root_fd();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.write(fd, &[1, 2, 3, 4, 5]).unwrap();
let err = fs.remove_file(dir, "test.txt").unwrap_err();
assert_eq!(err, Error::CannotRemoveOpenedNode);
fs.close(fd).unwrap();
fs.remove_file(dir, "test.txt").unwrap();
}
}
#[test]
fn cannot_remove_directory_as_file() {
for mut fs in test_fs_setups("virtual_memory.txt") {
let dir = fs.root_fd();
let fd = fs.create_dir(dir, "test", FdStat::default(), 0).unwrap();
fs.close(fd).unwrap();
let err = fs.remove_file(dir, "test").unwrap_err();
assert_eq!(err, Error::ExpectedToRemoveFile);
fs.remove_dir(dir, "test").unwrap();
}
}
#[test]
fn cannot_remove_file_as_directory() {
for mut fs in test_fs_setups("virtual_memory.txt") {
let dir = fs.root_fd();
let fd = fs
.open_or_create(dir, "test.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.close(fd).unwrap();
let err = fs.remove_dir(dir, "test.txt").unwrap_err();
assert_eq!(err, Error::ExpectedToRemoveDirectory);
fs.remove_file(dir, "test.txt").unwrap();
}
}
#[test]
fn renumber_when_the_alternative_file_exists() {
for mut fs in test_fs_setups("virtual_memory.txt") {
let dir = fs.root_fd();
let fd1 = fs
.open_or_create(dir, "file1.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.write(fd1, &[1, 2, 3, 4, 5]).unwrap();
let pos1 = fs.tell(fd1).unwrap();
let fd2 = fs
.open_or_create(dir, "file2.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
let pos2 = fs.tell(fd2).unwrap();
assert!(pos1 == 5);
assert!(pos2 == 0);
fs.renumber(fd1, fd2).unwrap();
let pos2_renumbered = fs.tell(fd2).unwrap();
assert!(pos1 == pos2_renumbered);
let res = fs.tell(fd1);
assert!(res.is_err());
}
}
#[test]
fn renumber_when_the_alternative_file_doesnt_exist() {
for mut fs in test_fs_setups("file1.txt") {
let dir = fs.root_fd();
let fd1 = fs
.open_or_create(dir, "file1.txt", FdStat::default(), OpenFlags::CREATE, 0)
.unwrap();
fs.write(fd1, &[1, 2, 3, 4, 5]).unwrap();
let pos1 = fs.tell(fd1).unwrap();
assert!(pos1 == 5);
let fd2 = 100;
fs.renumber(fd1, fd2).unwrap();
let pos2_renumbered = fs.tell(fd2).unwrap();
assert!(pos1 == pos2_renumbered);
let res = fs.tell(fd1);
assert!(res.is_err());
}
}
#[test]
fn set_modified_set_accessed_time() {
for mut fs in test_fs_setups("") {
let dir = fs.root_fd();
let fd1 = fs
.open_or_create(dir, "file1.txt", FdStat::default(), OpenFlags::CREATE, 111)
.unwrap();
fs.write(fd1, &[1, 2, 3, 4, 5]).unwrap();
fs.set_accessed_time(fd1, 333).unwrap();
fs.set_modified_time(fd1, 222).unwrap();
let metadata = fs.metadata(fd1).unwrap();
let times = metadata.times;
assert_eq!(times.accessed, 333);
assert_eq!(times.modified, 222);
assert_eq!(times.created, 111);
assert_eq!(metadata.size, 5);
assert_eq!(metadata.file_type, FileType::RegularFile);
}
}
#[test]
fn set_stat_get_stat() {
let mut fs = test_fs();
let dir = fs.root_fd();
let fd1 = fs
.open_or_create(dir, "file1.txt", FdStat::default(), OpenFlags::CREATE, 111)
.unwrap();
fs.write(fd1, &[1, 2, 3, 4, 5]).unwrap();
let (file_type, mut stat) = fs.get_stat(fd1).unwrap();
assert_eq!(file_type, FileType::RegularFile);
assert_eq!(stat.flags, FdFlags::empty());
stat.flags = FdFlags::APPEND;
fs.set_stat(fd1, stat).unwrap();
let (_, stat2) = fs.get_stat(fd1).unwrap();
assert_eq!(stat2.flags, FdFlags::APPEND);
}
fn create_test_file_with_content(
fs: &mut FileSystem,
parent: Fd,
file_name: &str,
content: Vec<String>,
) -> Fd {
let file_fd = fs
.create_file(parent, file_name, FdStat::default(), 0)
.unwrap();
let mut src = String::from("");
for str in content.iter() {
src.push_str(str.as_str());
}
let bytes_written = fs.write(file_fd, src.as_bytes()).unwrap();
assert!(bytes_written > 0);
file_fd as Fd
}
fn create_test_file(fs: &mut FileSystem, parent_fd: Fd, file_name: &str) -> Fd {
create_test_file_with_content(
fs,
parent_fd,
file_name,
vec![
String::from("This is a sample text."),
String::from("1234567890"),
],
)
}
#[test]
fn link_seek_tell() {
let mut fs = test_fs();
let dir = fs.root_fd();
let root_fd = 3i32;
let file_name1 = String::from("file.txt");
let file_name2 = String::from("file_link.txt");
let file_fd = create_test_file(&mut fs, root_fd as Fd, &file_name1);
let root_node = fs.storage.as_ref().root_node();
let node1 = find_node(root_node, &file_name1, fs.storage.as_ref()).unwrap();
let position = fs.tell(file_fd).unwrap();
assert_eq!(position, 32);
fs.seek(file_fd, 10, crate::fs::Whence::SET).unwrap();
let position_after_seek = fs.tell(file_fd).unwrap();
assert_eq!(position_after_seek, 10);
let mut buf_to_read1 = String::from("...............");
let bytes_read = fs
.read(file_fd, unsafe { buf_to_read1.as_bytes_mut() })
.unwrap();
assert_eq!(bytes_read, 15);
assert_eq!(buf_to_read1, "sample text.123");
fs.create_hard_link(dir, &file_name1, dir, &file_name2)
.unwrap();
let node2 = find_node(root_node, &file_name2, fs.storage.as_ref()).unwrap();
assert_eq!(node1, node2);
let link_file_fd = fs
.open_or_create(
dir,
"file_link.txt",
FdStat::default(),
OpenFlags::empty(),
0,
)
.unwrap();
fs.seek(link_file_fd, 10, crate::fs::Whence::SET).unwrap();
let position_link = fs.tell(link_file_fd).unwrap();
assert_eq!(position_link, 10);
let mut buf_to_read1 = String::from("................");
let bytes_read = fs
.read(link_file_fd, unsafe { buf_to_read1.as_bytes_mut() })
.unwrap();
assert_eq!(bytes_read, 16);
assert_eq!(buf_to_read1, "sample text.1234");
}
#[test]
fn renaming_folder_with_contents() {
let mut fs = test_fs();
let root_fd = fs.root_fd();
let file_name = String::from("dir1/dir2/file.txt");
create_test_file(&mut fs, root_fd as Fd, &file_name);
fs.rename(root_fd, "dir1/dir2", root_fd, "dir2").unwrap();
let file_fd = fs
.open_or_create(
root_fd,
"dir2/file.txt",
FdStat::default(),
OpenFlags::empty(),
0,
)
.unwrap();
fs.seek(file_fd, 10, crate::fs::Whence::SET).unwrap();
let mut buf_to_read1 = String::from("................");
let bytes_read = fs
.read(file_fd, unsafe { buf_to_read1.as_bytes_mut() })
.unwrap();
assert_eq!(bytes_read, 16);
assert_eq!(buf_to_read1, "sample text.1234");
}
#[test]
fn write_and_read_25_files() {
for mut fs in test_fs_setups("auto/my_file_6.txt") {
let root_fd = fs.root_fd();
const SIZE_OF_FILE: usize = 1_000_000;
let dir_name = "auto";
let file_count: u8 = 25;
for i in 0..file_count {
let filename = format!("{}/my_file_{}.txt", dir_name, i);
let content = format!("{i}");
let times = SIZE_OF_FILE / content.len();
println!("Writing to {filename}");
write_text_file(&mut fs, root_fd, filename.as_str(), content.as_str(), times)
.unwrap();
}
for i in 0..file_count {
let filename = format!("{}/my_file_{}.txt", dir_name, i);
let expected_content = format!("{i}{i}{i}");
println!("Reading {}", filename);
let text_read = read_text_file(
&mut fs,
root_fd,
filename.as_str(),
0,
expected_content.len(),
);
assert_eq!(expected_content, text_read);
}
}
}
#[test]
fn empty_path_support() {
let mut fs = test_fs();
let root_fd = fs.root_fd();
write_text_file(&mut fs, root_fd, "f1/f2/text.txt", "content123", 100).unwrap();
let content = read_text_file(&mut fs, root_fd, "f1/f2/text.txt", 7, 10);
assert_eq!(content, "123content");
let content = read_text_file(&mut fs, root_fd, "f1//f2/text.txt", 6, 10);
assert_eq!(content, "t123conten");
let content = read_text_file(&mut fs, root_fd, "/f1//f2/text.txt", 5, 10);
assert_eq!(content, "nt123conte");
write_text_file(&mut fs, root_fd, "text.txt", "abc", 100).unwrap();
let content = read_text_file(&mut fs, root_fd, "text.txt", 0, 6);
assert_eq!(content, "abcabc");
let content = read_text_file(&mut fs, root_fd, "/text.txt", 0, 6);
assert_eq!(content, "abcabc");
let content = read_text_file(&mut fs, root_fd, "///////text.txt", 0, 6);
assert_eq!(content, "abcabc");
}
#[test]
fn writing_into_mounted_memory() {
let memory: VectorMemory = new_vector_memory();
let mut fs = test_fs();
let root_fd = fs.root_fd();
fs.mount_memory_file("test.txt", Box::new(memory.clone()))
.unwrap();
let content = "ABCDEFG123";
write_text_file(&mut fs, root_fd, "test.txt", content, 1).unwrap();
let mut buf = [0u8; 100];
memory.read(0, &mut buf);
println!("{:?}", buf);
}
#[test]
fn deleting_mounted_file_fails() {
let memory: VectorMemory = new_vector_memory();
let mut fs = test_fs();
let root_fd = fs.root_fd();
fs.mount_memory_file("test.txt", Box::new(memory.clone()))
.unwrap();
let res = fs.remove_file(root_fd, "test.txt");
assert!(
res.is_err(),
"Deleting a mounted file should not be allowed!"
);
let files = list_files(&mut fs, "");
assert_eq!(files[0], "test.txt".to_string());
}
#[test]
fn mounted_memory_store_and_init_roundtrip() {
for mut fs in test_fs_setups("") {
let memory1: VectorMemory = new_vector_memory();
let memory2: VectorMemory = new_vector_memory();
let file_name = "test.txt";
fs.mount_memory_file(file_name, Box::new(memory1.clone()))
.unwrap();
let content = "ABCDEFG123";
let len = content.len();
let count = 1000;
memory1.grow(5);
for i in 0..count {
memory1.write(i as u64 * len as u64, content.as_bytes());
}
let fd = fs
.open_or_create(
fs.root_fd,
file_name,
FdStat::default(),
OpenFlags::empty(),
0,
)
.unwrap();
let mut metadata = fs.metadata(fd).unwrap();
metadata.size = len as FileSize * count as FileSize;
fs.set_metadata(fd, metadata).unwrap();
fs.close(fd).unwrap();
fs.store_memory_file(file_name).unwrap();
fs.unmount_memory_file(file_name).unwrap();
fs.mount_memory_file(file_name, Box::new(memory2.clone()))
.unwrap();
fs.init_memory_file(file_name).unwrap();
let mut buf1 = [0u8; 10];
let mut buf2 = [0u8; 10];
for i in 0..count {
memory1.read(i as u64 * len as u64, &mut buf1);
memory2.read(i as u64 * len as u64, &mut buf2);
assert_eq!(buf1, buf2);
}
}
}
#[test]
fn writing_from_different_file_descriptors() {
for mut fs in test_fs_setups("f1/f2/text.txt") {
let root_fd = fs.root_fd();
let fd1 = fs
.open_or_create(
root_fd,
"f1/f2/text.txt",
FdStat::default(),
OpenFlags::CREATE,
40,
)
.unwrap();
let fd2 = fs
.open_or_create(
root_fd,
"f1//f2/text.txt",
FdStat::default(),
OpenFlags::CREATE,
44,
)
.unwrap();
write_text_fd(&mut fs, fd1, "abc", 1).unwrap();
write_text_fd(&mut fs, fd2, "123", 1).unwrap();
write_text_fd(&mut fs, fd1, "xyz", 1).unwrap();
let content = read_text_file(&mut fs, root_fd, "/f1/f2/text.txt", 0, 9);
assert_eq!("123xyz", content);
}
}
#[test]
fn write_into_empty_filename_fails() {
for mut fs in test_fs_setups("") {
let root_fd = fs.root_fd();
let res = write_text_file(&mut fs, root_fd, "", "content123", 100);
assert!(res.is_err());
}
}
}