// 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, pub valid_len: usize, pub contents: Vec, } pub struct DirInMemory { pub depth: u32, pub name: String, pub inode: Arc, pub sub_names: Vec, pub sub_dirs: HashMap, } 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::::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 = 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) -> 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) }