2024-06-21 10:57:08 +08:00

543 lines
18 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
#![allow(unused_variables)]
use alloc::sync::Arc;
use hashbrown::HashMap;
use rand::{Rng, RngCore};
use super::{Inode, InodeMode, InodeType};
use crate::prelude::*;
pub struct FileInMemory {
pub name: String,
pub inode: Arc<dyn Inode>,
pub valid_len: usize,
pub contents: Vec<u8>,
}
pub struct DirInMemory {
pub depth: u32,
pub name: String,
pub inode: Arc<dyn Inode>,
pub sub_names: Vec<String>,
pub sub_dirs: HashMap<String, DentryInMemory>,
}
pub enum DentryInMemory {
File(FileInMemory),
Dir(DirInMemory),
}
pub enum Operation {
Read(usize, usize),
Write(usize, usize),
Resize(usize),
Create(String, InodeType),
Lookup(String),
Readdir(),
Unlink(String),
Rmdir(String),
Rename(String, String),
}
impl Operation {
const CREATE_FILE_ID: usize = 0;
const CREATE_DIR_ID: usize = 1;
const UNLINK_ID: usize = 2;
const RMDIR_ID: usize = 3;
const LOOKUP_ID: usize = 4;
const READDIR_ID: usize = 5;
const RENAME_ID: usize = 6;
const DIR_OP_NUM: usize = 7;
const READ_ID: usize = 0;
const WRITE_ID: usize = 1;
const RESIZE_ID: usize = 2;
const FILE_OP_NUM: usize = 3;
const MAX_PAGE_PER_FILE: usize = 10;
pub fn generate_random_dir_operation(
dir: &mut DirInMemory,
idx: u32,
rng: &mut dyn RngCore,
) -> Self {
let op_id = rng.gen_range(0..Self::DIR_OP_NUM);
if op_id == Self::CREATE_FILE_ID {
Operation::Create(idx.to_string(), InodeType::File)
} else if op_id == Self::CREATE_DIR_ID {
Operation::Create(idx.to_string(), InodeType::Dir)
} else if op_id == Self::UNLINK_ID && !dir.sub_names.is_empty() {
let rand_idx = rng.gen_range(0..dir.sub_names.len());
let name = dir.sub_names[rand_idx].clone();
Operation::Unlink(name)
} else if op_id == Self::RMDIR_ID && !dir.sub_names.is_empty() {
let rand_idx = rng.gen_range(0..dir.sub_names.len());
let name = dir.sub_names[rand_idx].clone();
Operation::Rmdir(name)
} else if op_id == Self::LOOKUP_ID && !dir.sub_names.is_empty() {
let rand_idx = rng.gen_range(0..dir.sub_names.len());
let name = dir.sub_names[rand_idx].clone();
Operation::Lookup(name)
} else if op_id == Self::READDIR_ID {
Operation::Readdir()
} else if op_id == Self::RENAME_ID && !dir.sub_names.is_empty() {
let rand_old_idx = rng.gen_range(0..dir.sub_names.len());
let old_name = dir.sub_names[rand_old_idx].clone();
let rename_to_an_exist = rng.gen_bool(0.5);
if rename_to_an_exist {
let rand_new_idx = rng.gen_range(0..dir.sub_names.len());
let new_name = dir.sub_names[rand_new_idx].clone();
Operation::Rename(old_name, new_name)
} else {
Operation::Rename(old_name, idx.to_string())
}
} else {
Operation::Create(idx.to_string(), InodeType::File)
}
}
pub fn generate_random_file_operation(
file: &mut FileInMemory,
idx: u32,
rng: &mut dyn RngCore,
) -> Self {
let op_id = rng.gen_range(0..Self::FILE_OP_NUM);
if op_id == Self::READ_ID {
let (offset, len) =
generate_random_offset_len(Self::MAX_PAGE_PER_FILE * PAGE_SIZE, rng);
Operation::Read(offset, len)
} else if op_id == Self::WRITE_ID {
let (offset, len) =
generate_random_offset_len(Self::MAX_PAGE_PER_FILE * PAGE_SIZE, rng);
Operation::Write(offset, len)
} else if op_id == Self::RESIZE_ID {
let pg_num = rng.gen_range(0..Self::MAX_PAGE_PER_FILE);
let new_size = (pg_num * PAGE_SIZE).max(file.contents.len());
Operation::Resize(new_size)
} else {
let valid_len = file.valid_len;
Operation::Read(0, valid_len)
}
}
}
impl DirInMemory {
pub fn remove_sub_names(&mut self, name: &String) {
for idx in 0..self.sub_names.len() {
if self.sub_names[idx].eq(name) {
self.sub_names.remove(idx);
break;
}
}
}
fn test_create(&mut self, name: &String, type_: InodeType) {
info!(
"Create: parent = {:?}, name = {:?}, type = {:?}",
self.name, name, type_
);
let create_result = self.inode.create(name, type_, InodeMode::all());
if self.sub_dirs.contains_key(name) {
assert!(create_result.is_err());
info!(
" create {:?}/{:?} failed: {:?}",
self.name,
name,
create_result.unwrap_err()
);
return;
}
assert!(
create_result.is_ok(),
"Fail to create {:?}: {:?}",
name,
create_result.unwrap_err()
);
info!(
" create {:?}/{:?}({:?}) succeeeded",
self.name, name, type_
);
let new_dentry_in_mem = if type_ == InodeType::File {
let file = FileInMemory {
name: name.clone(),
inode: create_result.unwrap(),
valid_len: 0,
contents: Vec::<u8>::new(),
};
DentryInMemory::File(file)
} else {
DentryInMemory::Dir(DirInMemory {
depth: self.depth + 1,
name: name.clone(),
inode: create_result.unwrap(),
sub_names: Vec::new(),
sub_dirs: HashMap::new(),
})
};
let _ = self.sub_dirs.insert(name.to_string(), new_dentry_in_mem);
self.sub_names.push(name.to_string());
}
fn test_lookup(&self, name: &String) {
info!("Lookup: parent = {:?}, name = {:?}", self.name, name);
let lookup_result = self.inode.lookup(name);
if self.sub_dirs.get(name).is_some() {
assert!(
lookup_result.is_ok(),
"Fail to lookup {:?}: {:?}",
name,
lookup_result.unwrap_err()
);
info!(" lookup {:?}/{:?} succeeded", self.name, name);
} else {
assert!(lookup_result.is_err());
info!(
" lookup {:?}/{:?} failed: {:?}",
self.name,
name,
lookup_result.unwrap_err()
);
}
}
fn test_readdir(&mut self) {
info!("Readdir: parent = {:?}", self.name);
let mut sub: Vec<String> = Vec::new();
let readdir_result = self.inode.readdir_at(0, &mut sub);
assert!(readdir_result.is_ok(), "Fail to read directory",);
assert!(readdir_result.unwrap() == self.sub_dirs.len() + 2);
assert!(sub.len() == self.sub_dirs.len() + 2);
// To remove "." and ".."
sub.remove(0);
sub.remove(0);
sub.sort();
self.sub_names.sort();
for (i, name) in sub.iter().enumerate() {
assert!(
name.eq(&self.sub_names[i]),
"Directory entry mismatch: read {:?} should be {:?}",
name,
self.sub_names[i]
);
}
}
fn test_unlink(&mut self, name: &String) {
info!("Unlink: parent = {:?}, name = {:?}", self.name, name);
let unlink_result = self.inode.unlink(name);
if let Option::Some(sub) = self.sub_dirs.get(name)
&& let DentryInMemory::File(_) = sub
{
assert!(
unlink_result.is_ok(),
"Fail to remove file {:?}/{:?}: {:?}",
self.name,
name,
unlink_result.unwrap_err()
);
info!(" unlink {:?}/{:?} succeeded", self.name, name);
let _ = self.sub_dirs.remove(name);
self.remove_sub_names(name);
} else {
assert!(unlink_result.is_err());
info!(
" unlink {:?}/{:?} failed: {:?}",
self.name,
name,
unlink_result.unwrap_err()
);
}
}
fn test_rmdir(&mut self, name: &String) {
info!("Rmdir: parent = {:?}, name = {:?}", self.name, name);
let rmdir_result = self.inode.rmdir(name);
if let Option::Some(sub) = self.sub_dirs.get(name)
&& let DentryInMemory::Dir(sub_dir) = sub
&& sub_dir.sub_dirs.is_empty()
{
assert!(
rmdir_result.is_ok(),
"Fail to remove directory {:?}/{:?}: {:?}",
self.name,
name,
rmdir_result.unwrap_err()
);
info!(" rmdir {:?}/{:?} succeeded", self.name, name);
let _ = self.sub_dirs.remove(name);
self.remove_sub_names(name);
} else {
assert!(rmdir_result.is_err());
info!(
" rmdir {:?}/{:?} failed: {:?}",
self.name,
name,
rmdir_result.unwrap_err()
);
}
}
fn test_rename(&mut self, old_name: &String, new_name: &String) {
info!(
"Rename: parent = {:?}, old_name = {:?}, target = {:?}, new_name = {:?}",
self.name, old_name, self.name, new_name
);
let rename_result = self.inode.rename(old_name, &self.inode, new_name);
if old_name.eq(new_name) {
assert!(rename_result.is_ok());
info!(
" rename {:?}/{:?} to {:?}/{:?} succeeded",
self.name, old_name, self.name, new_name
);
return;
}
let mut valid_rename: bool = false;
let mut exist: bool = false;
if let Option::Some(old_sub) = self.sub_dirs.get(old_name) {
let exist_new_sub = self.sub_dirs.get(new_name);
match old_sub {
DentryInMemory::File(old_file) => {
if let Option::Some(exist_new_sub_) = exist_new_sub
&& let DentryInMemory::File(exist_new_file) = exist_new_sub_
{
valid_rename = true;
exist = true;
} else if exist_new_sub.is_none() {
valid_rename = true;
}
}
DentryInMemory::Dir(old_dir) => {
if let Option::Some(exist_new_sub_) = exist_new_sub
&& let DentryInMemory::Dir(exist_new_dir) = exist_new_sub_
&& exist_new_dir.sub_dirs.is_empty()
{
valid_rename = true;
exist = true;
} else if exist_new_sub.is_none() {
valid_rename = true;
}
}
}
}
if valid_rename {
assert!(
rename_result.is_ok(),
"Fail to rename {:?}/{:?} to {:?}/{:?}: {:?}",
self.name,
old_name,
self.name,
new_name,
rename_result.unwrap_err()
);
info!(
" rename {:?}/{:?} to {:?}/{:?} succeeded",
self.name, old_name, self.name, new_name
);
let lookup_new_inode_result = self.inode.lookup(new_name);
assert!(
lookup_new_inode_result.is_ok(),
"Fail to lookup new name {:?}: {:?}",
new_name,
lookup_new_inode_result.unwrap_err()
);
let mut old = self.sub_dirs.remove(old_name).unwrap();
self.remove_sub_names(old_name);
match old {
DentryInMemory::Dir(ref mut dir) => {
dir.inode = lookup_new_inode_result.unwrap();
dir.name.clone_from(new_name);
dir.depth = self.depth + 1;
}
DentryInMemory::File(ref mut file) => {
file.inode = lookup_new_inode_result.unwrap();
file.name.clone_from(new_name);
}
}
if exist {
let _ = self.sub_dirs.remove(new_name);
self.remove_sub_names(new_name);
}
self.sub_dirs.insert(new_name.to_string(), old);
self.sub_names.push(new_name.to_string());
} else {
assert!(rename_result.is_err());
info!(
" rename {:?}/{:?} to {:?}/{:?} failed: {:?}",
self.name,
old_name,
self.name,
new_name,
rename_result.unwrap_err()
);
}
}
pub fn execute_and_test(&mut self, op: Operation) {
match op {
Operation::Create(name, type_) => self.test_create(&name, type_),
Operation::Lookup(name) => self.test_lookup(&name),
Operation::Readdir() => self.test_readdir(),
Operation::Unlink(name) => self.test_unlink(&name),
Operation::Rmdir(name) => self.test_rmdir(&name),
Operation::Rename(old_name, new_name) => self.test_rename(&old_name, &new_name),
_ => {}
}
}
}
impl FileInMemory {
fn test_read(&self, offset: usize, len: usize) {
info!(
"Read: name = {:?}, offset = {:?}, len = {:?}",
self.name, offset, len
);
let mut buf = vec![0; len];
let read_result = self.inode.read_at(offset, &mut buf);
assert!(
read_result.is_ok(),
"Fail to read file in range [{:?}, {:?}): {:?}",
offset,
offset + len,
read_result.unwrap_err()
);
info!(" read succeeded");
let (start, end) = (
offset.min(self.valid_len),
(offset + len).min(self.valid_len),
);
assert!(
buf[..(end - start)].eq(&self.contents[start..end]),
"Read file contents mismatch"
);
}
fn test_write(&mut self, offset: usize, len: usize, rng: &mut dyn RngCore) {
// Avoid holes in a file.
let (write_start_offset, write_len) = if offset > self.valid_len {
(self.valid_len, len + offset - self.valid_len)
} else {
(offset, len)
};
info!(
"Write: name = {:?}, offset = {:?}, len = {:?}",
self.name, write_start_offset, write_len
);
let mut buf = vec![0; write_len];
rng.fill_bytes(&mut buf);
let write_result = self.inode.write_at(write_start_offset, &buf);
assert!(
write_result.is_ok(),
"Fail to write file in range [{:?}, {:?}): {:?}",
write_start_offset,
write_start_offset + write_len,
write_result.unwrap_err()
);
info!(" write succeeded");
if write_start_offset + write_len > self.contents.len() {
self.contents.resize(write_start_offset + write_len, 0);
}
self.valid_len = self.valid_len.max(write_start_offset + write_len);
self.contents[write_start_offset..write_start_offset + write_len]
.copy_from_slice(&buf[..write_len]);
}
fn test_resize(&mut self, new_size: usize) {
info!("Resize: name = {:?}, new_size = {:?}", self.name, new_size);
// Todo: may need more consideration
let resize_result = self.inode.resize(new_size);
assert!(
resize_result.is_ok(),
"Fail to resize file to {:?}: {:?}",
new_size,
resize_result.unwrap_err()
);
self.contents.resize(new_size, 0);
self.valid_len = self.valid_len.min(new_size);
}
pub fn execute_and_test(&mut self, op: Operation, rng: &mut dyn RngCore) {
match op {
Operation::Read(offset, len) => self.test_read(offset, len),
Operation::Write(offset, len) => self.test_write(offset, len, rng),
Operation::Resize(new_size) => self.test_resize(new_size),
_ => {}
}
}
}
impl DentryInMemory {
pub fn execute_and_test(&mut self, op: Operation, rng: &mut dyn RngCore) {
match self {
DentryInMemory::Dir(dir) => {
dir.execute_and_test(op);
}
DentryInMemory::File(file) => {
file.execute_and_test(op, rng);
}
}
}
pub fn sub_cnt(&self) -> usize {
match self {
DentryInMemory::Dir(dir) => dir.sub_names.len(),
DentryInMemory::File(file) => 0,
}
}
}
fn random_select_from_dir_tree<'a>(
root: &'a mut DentryInMemory,
rng: &mut dyn RngCore,
) -> &'a mut DentryInMemory {
let sub_cnt = root.sub_cnt();
if sub_cnt == 0 {
root
} else {
let stop_get_deeper = rng.gen_bool(0.5);
if stop_get_deeper {
root
} else if let DentryInMemory::Dir(dir) = root {
let sub_idx = rng.gen_range(0..sub_cnt);
let sub = dir.sub_dirs.get_mut(&dir.sub_names[sub_idx]);
let sub_dir = sub.unwrap();
random_select_from_dir_tree(sub_dir, rng)
} else {
unreachable!();
}
}
}
fn generate_random_offset_len(max_size: usize, rng: &mut dyn RngCore) -> (usize, usize) {
let offset = rng.gen_range(0..max_size);
let len = rng.gen_range(0..max_size - offset);
(offset, len)
}
pub fn new_fs_in_memory(root: Arc<dyn Inode>) -> DentryInMemory {
DentryInMemory::Dir(DirInMemory {
depth: 0,
name: (&"root").to_string(),
inode: root,
sub_names: Vec::new(),
sub_dirs: HashMap::new(),
})
}
pub fn generate_random_operation<'a>(
root: &'a mut DentryInMemory,
idx: u32,
rng: &mut dyn RngCore,
) -> (&'a mut DentryInMemory, Operation) {
let dentry = random_select_from_dir_tree(root, rng);
let op = match dentry {
DentryInMemory::Dir(dir) => Operation::generate_random_dir_operation(dir, idx, rng),
DentryInMemory::File(file) => Operation::generate_random_file_operation(file, idx, rng),
};
(dentry, op)
}