diff --git a/.travis.yml b/.travis.yml index 0f33674..52b1d96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ language: rust rust: - - nightly \ No newline at end of file + - nightly +script: + - cargo test --verbose -- --test-threads=1 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8f342a4..7a981fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,15 @@ name = "simple-filesystem" version = "0.0.1" authors = ["WangRunji "] +[[bin]] +name = "mksfs" +path = "src/bin/mksfs.rs" +required-features = ["std"] + [dependencies] bit-vec = { default-features = false, git = "https://github.com/AltSysrq/bit-vec.git" } # default-features contains 'std' static_assertions = "0.2.5" [features] -debug_print = [] \ No newline at end of file +debug_print = [] +std = [] \ No newline at end of file diff --git a/src/bin/mksfs.rs b/src/bin/mksfs.rs new file mode 100644 index 0000000..98d2cc6 --- /dev/null +++ b/src/bin/mksfs.rs @@ -0,0 +1,95 @@ +extern crate simple_filesystem; + +use std::env; +use std::fs; +use std::io::{Read, Write, Result}; +use std::path::Path; +use std::mem::uninitialized; +use simple_filesystem::*; + +fn main() -> Result<()> { + let args: Vec<_> = env::args().collect(); + let cmd = &args[1]; + let dir_path = Path::new(&args[2]); + let img_path = Path::new(&args[3]); + match cmd.as_str() { + "zip" => zip(dir_path, img_path), + "unzip" => unzip(dir_path, img_path), + _ => { + println!("USAGE: "); + panic!("Invalid command: {}", cmd); + }, + } +} + +fn zip(path: &Path, img_path: &Path) -> Result<()> { + let img = fs::OpenOptions::new().read(true).write(true).create(true).open(img_path)?; + let sfs = SimpleFileSystem::create(Box::new(img), 0x1000000); + let inode = sfs.root_inode(); + zip_dir(path, inode)?; + sfs.sync().expect("Failed to sync"); + Ok(()) +} + +fn zip_dir(path: &Path, inode: INodePtr) -> Result<()> { + let dir = fs::read_dir(path).expect("Failed to open dir"); + for entry in dir { + let entry = entry?; + let name_ = entry.file_name(); + let name = name_.to_str().unwrap(); + let type_ = entry.file_type()?; + if type_.is_file() { + let inode = inode.borrow_mut().create(name, FileType::File).expect("Failed to create INode"); + let mut file = fs::File::open(entry.path())?; + inode.borrow_mut().resize(file.metadata().unwrap().len() as usize).expect("Failed to resize INode"); + let mut buf: [u8; 4096] = unsafe { uninitialized() }; + let mut offset = 0usize; + let mut len = 4096; + while len == 4096 { + len = file.read(&mut buf)?; + inode.borrow().write_at(offset, &buf).expect("Failed to write image"); + offset += len; + } + } else if type_.is_dir() { + let inode = inode.borrow_mut().create(name, FileType::Dir).expect("Failed to create INode"); + zip_dir(entry.path().as_path(), inode)?; + } + } + Ok(()) +} + +fn unzip(path: &Path, img_path: &Path) -> Result<()> { + let img = fs::File::open(img_path)?; + let sfs = SimpleFileSystem::open(Box::new(img)).expect("Failed to open sfs"); + let inode = sfs.root_inode(); + fs::create_dir(&path)?; + unzip_dir(path, inode) +} + +fn unzip_dir(path: &Path, inode: INodePtr) -> Result<()> { + let files = inode.borrow().list().expect("Failed to list files from INode"); + for name in files.iter().skip(2) { + let inode = inode.borrow().lookup(name.as_str()).expect("Failed to lookup"); + let mut path = path.to_path_buf(); + path.push(name); + let info = inode.borrow().info().expect("Failed to get file info"); + match info.type_ { + FileType::File => { + let mut file = fs::File::create(&path)?; + let mut buf: [u8; 4096] = unsafe { uninitialized() }; + let mut offset = 0usize; + let mut len = 4096; + while len == 4096 { + len = inode.borrow().read_at(offset, buf.as_mut()).expect("Failed to read from INode"); + file.write(&buf[..len])?; + offset += len; + } + } + FileType::Dir => { + fs::create_dir(&path)?; + unzip_dir(path.as_path(), inode)?; + } + } + } + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index eeaab74..1999d19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,3 +31,28 @@ mod tests; pub use sfs::*; pub use vfs::*; pub use blocked_device::BlockedDevice; + +#[cfg(any(test, feature = "std"))] +pub mod std_impl { + use std::fs::{File, OpenOptions}; + use std::io::{Read, Write, Seek, SeekFrom}; + use super::Device; + + impl Device for File { + fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Option { + let offset = offset as u64; + match self.seek(SeekFrom::Start(offset)) { + Ok(real_offset) if real_offset == offset => self.read(buf).ok(), + _ => None, + } + } + + fn write_at(&mut self, offset: usize, buf: &[u8]) -> Option { + let offset = offset as u64; + match self.seek(SeekFrom::Start(offset)) { + Ok(real_offset) if real_offset == offset => self.write(buf).ok(), + _ => None, + } + } + } +} \ No newline at end of file diff --git a/src/sfs.rs b/src/sfs.rs index 5982280..5e6db36 100644 --- a/src/sfs.rs +++ b/src/sfs.rs @@ -4,6 +4,7 @@ use core::cell::{RefCell, RefMut}; use core::mem::{uninitialized, size_of}; use core::slice; use core::fmt::{Debug, Formatter, Error}; +use core::any::Any; use dirty::Dirty; use structs::*; use vfs::{self, Device}; @@ -96,33 +97,51 @@ impl INode { } } /// Only for Dir - fn get_file_inode_id(&self, name: &str) -> Option { + fn get_file_inode_and_entry_id(&self, name: &str) -> Option<(INodeId,usize)> { (0..self.disk_inode.blocks) .map(|i| { use vfs::INode; let mut entry: DiskEntry = unsafe { uninitialized() }; self._read_at(i as usize * BLKSIZE, entry.as_buf_mut()).unwrap(); - entry + (entry,i) }) - .find(|entry| entry.name.as_ref() == name) - .map(|entry| entry.id as INodeId) + .find(|(entry,id)| entry.name.as_ref() == name) + .map(|(entry,id)| (entry.id as INodeId,id as usize)) + } + fn get_file_inode_id(&self, name: &str) -> Option { + self.get_file_inode_and_entry_id(name).map(|(inode_id,entry_id)|{inode_id}) } /// Init dir content. Insert 2 init entries. - fn init_dir(&mut self, parent: INodeId) -> vfs::Result<()> { + /// This do not init nlinks, please modify the nlinks in the invoker. + fn init_dir_entry(&mut self, parent: INodeId) -> vfs::Result<()> { use vfs::INode; + let fs = self.fs.upgrade().unwrap(); // Insert entries: '.' '..' self._resize(BLKSIZE * 2).unwrap(); self._write_at(BLKSIZE * 1, DiskEntry { id: parent as u32, name: Str256::from(".."), }.as_buf()).unwrap(); - let id = self.id as u32; self._write_at(BLKSIZE * 0, DiskEntry { - id, + id: self.id as u32, name: Str256::from("."), }.as_buf()).unwrap(); Ok(()) } + /// remove a page in middle of file and insert the last page here, useful for dirent remove + /// should be only used in unlink + fn remove_dirent_page(&mut self, id: usize) -> vfs::Result<()> { + assert!(id < self.disk_inode.blocks as usize); + let fs = self.fs.upgrade().unwrap(); + let to_remove=self.get_disk_block_id(id).unwrap(); + let current_last=self.get_disk_block_id(self.disk_inode.blocks as usize -1).unwrap(); + self.set_disk_block_id(id,current_last).unwrap(); + self.disk_inode.blocks -= 1; + let new_size=self.disk_inode.blocks as usize * BLKSIZE; + self._set_size(new_size); + fs.free_block(to_remove); + Ok(()) + } /// Resize content size, no matter what type it is. fn _resize(&mut self, len: usize) -> vfs::Result<()> { assert!(len <= MAX_FILE_SIZE, "file size exceed limit"); @@ -145,7 +164,7 @@ impl INode { } // clean up let old_size = self._size(); - self.disk_inode.size = len as u32; + self._set_size(len); self._clean_at(old_size, len).unwrap(); } Ordering::Less => { @@ -163,7 +182,7 @@ impl INode { self.disk_inode.blocks = blocks; } } - self.disk_inode.size = len as u32; + self._set_size(len); Ok(()) } /// Get the actual size of this inode, @@ -175,6 +194,15 @@ impl INode { _ => unimplemented!(), } } + /// Set the ucore compat size of this inode, + /// Size in inode for dir is size of entries + fn _set_size(&mut self,len: usize) { + self.disk_inode.size=match self.disk_inode.type_ { + FileType::Dir => self.disk_inode.blocks as usize * DIRENT_SIZE, + FileType::File => len, + _ => unimplemented!(), + } as u32 + } /// Read/Write content, no matter what type it is fn _io_at(&self, begin: usize, end: usize, mut f: F) -> vfs::Result where F: FnMut(RefMut>, &BlockRange, usize) @@ -217,6 +245,13 @@ impl INode { }).unwrap(); Ok(()) } + fn nlinks_inc(&mut self) { + self.disk_inode.nlinks+=1; + } + fn nlinks_dec(&mut self) { + assert!(self.disk_inode.nlinks>0); + self.disk_inode.nlinks-=1; + } } impl vfs::INode for INode { @@ -235,12 +270,18 @@ impl vfs::INode for INode { assert_eq!(self.disk_inode.type_, FileType::File, "write_at is only available on file"); self._write_at(offset, buf) } + /// the size returned here is logical size(entry num for directory), not the disk space used. fn info(&self) -> vfs::Result { Ok(vfs::FileInfo { - size: self._size(), + size: match self.disk_inode.type_ { + FileType::File => self.disk_inode.size as usize, + FileType::Dir => self.disk_inode.blocks as usize, + _ => unimplemented!(), + }, mode: 0, type_: vfs::FileType::from(self.disk_inode.type_.clone()), blocks: self.disk_inode.blocks as usize, + nlinks: self.disk_inode.nlinks as usize, }) } fn sync(&mut self) -> vfs::Result<()> { @@ -259,6 +300,7 @@ impl vfs::INode for INode { let fs = self.fs.upgrade().unwrap(); let info = self.info().unwrap(); assert_eq!(info.type_, vfs::FileType::Dir); + assert!(info.nlinks>0); // Ensure the name is not exist assert!(self.get_file_inode_id(name).is_none(), "file name exist"); @@ -274,47 +316,150 @@ impl vfs::INode for INode { id: inode.borrow().id as u32, name: Str256::from(name), }; - self._resize(info.size + BLKSIZE).unwrap(); - self._write_at(info.size, entry.as_buf()).unwrap(); + let old_size=self._size(); + self._resize(old_size + BLKSIZE).unwrap(); + self._write_at(old_size, entry.as_buf()).unwrap(); + inode.borrow_mut().nlinks_inc(); + if(type_==vfs::FileType::Dir){ + inode.borrow_mut().nlinks_inc();//for . + self.nlinks_inc();//for .. + } Ok(inode) } - fn lookup(&self, path: &str) -> vfs::Result> { + fn unlink(&mut self, name: &str) -> vfs::Result<()> { + assert!(name!="."); + assert!(name!=".."); + let fs = self.fs.upgrade().unwrap(); + let info = self.info().unwrap(); + assert_eq!(info.type_, vfs::FileType::Dir); + + let inode_and_entry_id = self.get_file_inode_and_entry_id(name); + if inode_and_entry_id.is_none() { + return Err(()); + } + let (inode_id,entry_id)=inode_and_entry_id.unwrap(); + let inode = fs.get_inode(inode_id); + + let type_ = inode.borrow().disk_inode.type_; + if(type_==FileType::Dir){ + // only . and .. + assert!(inode.borrow().disk_inode.blocks==2); + } + inode.borrow_mut().nlinks_dec(); + if(type_==FileType::Dir){ + inode.borrow_mut().nlinks_dec();//for . + self.nlinks_dec();//for .. + } + self.remove_dirent_page(entry_id); + + Ok(()) + } + fn link(&mut self, name: &str, other:&mut vfs::INode) -> vfs::Result<()> { + let fs = self.fs.upgrade().unwrap(); + let info = self.info().unwrap(); + assert_eq!(info.type_, vfs::FileType::Dir); + assert!(info.nlinks>0); + assert!(self.get_file_inode_id(name).is_none(), "file name exist"); + let child = other.downcast_mut::().unwrap(); + assert!(Rc::ptr_eq(&fs,&child.fs.upgrade().unwrap())); + assert!(child.info().unwrap().type_!=vfs::FileType::Dir); + let entry = DiskEntry { + id: child.id as u32, + name: Str256::from(name), + }; + let old_size=self._size(); + self._resize(old_size + BLKSIZE).unwrap(); + self._write_at(old_size, entry.as_buf()).unwrap(); + child.nlinks_inc(); + Ok(()) + } + fn rename(&mut self, old_name: &str, new_name: &str) -> vfs::Result<()>{ + let info = self.info().unwrap(); + assert_eq!(info.type_, vfs::FileType::Dir); + assert!(info.nlinks>0); + + assert!(self.get_file_inode_id(new_name).is_none(), "file name exist"); + + let inode_and_entry_id = self.get_file_inode_and_entry_id(old_name); + if inode_and_entry_id.is_none() { + return Err(()); + } + let (_,entry_id)=inode_and_entry_id.unwrap(); + + // in place modify name + let mut entry: DiskEntry = unsafe { uninitialized() }; + let entry_pos=entry_id as usize * BLKSIZE; + self._read_at(entry_pos, entry.as_buf_mut()).unwrap(); + entry.name=Str256::from(new_name); + self._write_at(entry_pos, entry.as_buf()).unwrap(); + + Ok(()) + } + fn move_(&mut self, old_name: &str,target:&mut vfs::INode, new_name: &str) -> vfs::Result<()>{ let fs = self.fs.upgrade().unwrap(); let info = self.info().unwrap(); assert_eq!(info.type_, vfs::FileType::Dir); + assert!(info.nlinks>0); + let dest = target.downcast_mut::().unwrap(); + assert!(Rc::ptr_eq(&fs,&dest.fs.upgrade().unwrap())); + assert!(dest.info().unwrap().type_==vfs::FileType::Dir); + assert!(dest.info().unwrap().nlinks>0); + + assert!(dest.get_file_inode_id(new_name).is_none(), "file name exist"); - let (name, rest_path) = match path.find('/') { - None => (path, ""), - Some(pos) => (&path[0..pos], &path[pos + 1..]), + let inode_and_entry_id = self.get_file_inode_and_entry_id(old_name); + if inode_and_entry_id.is_none() { + return Err(()); + } + let (inode_id,entry_id)=inode_and_entry_id.unwrap(); + let inode = fs.get_inode(inode_id); + + let entry = DiskEntry { + id: inode_id as u32, + name: Str256::from(new_name), }; + let old_size=dest._size(); + dest._resize(old_size + BLKSIZE).unwrap(); + dest._write_at(old_size, entry.as_buf()).unwrap(); + + self.remove_dirent_page(entry_id); + + if(inode.borrow().info().unwrap().type_==vfs::FileType::Dir){ + self.nlinks_dec(); + dest.nlinks_inc(); + } + + Ok(()) + } + fn find(&self, name: &str) -> vfs::Result> { + let fs = self.fs.upgrade().unwrap(); + let info = self.info().unwrap(); + assert_eq!(info.type_, vfs::FileType::Dir); let inode_id = self.get_file_inode_id(name); if inode_id.is_none() { return Err(()); } let inode = fs.get_inode(inode_id.unwrap()); - - let type_ = inode.borrow().disk_inode.type_; - match type_ { - FileType::File => if rest_path == "" { Ok(inode) } else { Err(()) }, - FileType::Dir => if rest_path == "" { Ok(inode) } else { inode.borrow().lookup(rest_path) }, - _ => unimplemented!(), - } + Ok(inode) } - fn list(&self) -> vfs::Result> { + fn get_entry(&self,id: usize) -> vfs::Result { assert_eq!(self.disk_inode.type_, FileType::Dir); - - Ok((0..self.disk_inode.blocks) - .map(|i| { - use vfs::INode; - let mut entry: DiskEntry = unsafe { uninitialized() }; - self._read_at(i as usize * BLKSIZE, entry.as_buf_mut()).unwrap(); - String::from(entry.name.as_ref()) - }).collect()) + assert!(id Weak { self.fs.clone() } + fn as_any_ref(&self) -> &Any { + self + } + fn as_any_mut(&mut self) -> &mut Any { + self + } } impl Drop for INode { @@ -322,6 +467,12 @@ impl Drop for INode { fn drop(&mut self) { use vfs::INode; self.sync().expect("failed to sync"); + if(self.disk_inode.nlinks<=0){ + let fs = self.fs.upgrade().unwrap(); + self._resize(0); + self.disk_inode.sync(); + fs.free_block(self.id); + } } } @@ -393,7 +544,9 @@ impl SimpleFileSystem { { use vfs::INode; let root = sfs._new_inode(BLKN_ROOT, Dirty::new_dirty(DiskINode::new_dir())); - root.borrow_mut().init_dir(BLKN_ROOT).unwrap(); + root.borrow_mut().init_dir_entry(BLKN_ROOT).unwrap(); + root.borrow_mut().nlinks_inc();//for . + root.borrow_mut().nlinks_inc();//for ..(root's parent is itself) root.borrow_mut().sync().unwrap(); } @@ -416,8 +569,15 @@ impl SimpleFileSystem { let id = self.free_map.borrow_mut().alloc(); if id.is_some() { self.super_block.borrow_mut().unused_blocks -= 1; // will panic if underflow + id + }else{ + self.flush_unreachable_inodes(); + let id = self.free_map.borrow_mut().alloc(); + if id.is_some() { + self.super_block.borrow_mut().unused_blocks -= 1; + } + id } - id } /// Free a block fn free_block(&self, block_id: usize) { @@ -462,9 +622,29 @@ impl SimpleFileSystem { let id = self.alloc_block().unwrap(); let disk_inode = Dirty::new_dirty(DiskINode::new_dir()); let inode = self._new_inode(id, disk_inode); - inode.borrow_mut().init_dir(parent).unwrap(); + inode.borrow_mut().init_dir_entry(parent).unwrap(); Ok(inode) } + fn flush_unreachable_inodes(&self){ + let mut inodes = self.inodes.borrow_mut(); + let ids: Vec<_> = inodes.keys().cloned().collect(); + for id in ids.iter() { + let mut should_remove=false; + // Since non-lexical-lifetime is not stable... + { + let inodeRc=inodes.get(&id).unwrap(); + if Rc::strong_count(inodeRc)<=1 { + use vfs::INode; + if inodeRc.borrow().info().unwrap().nlinks==0 { + should_remove=true; + } + } + } + if(should_remove){ + inodes.remove(&id); + } + } + } } impl vfs::FileSystem for SimpleFileSystem { @@ -488,6 +668,7 @@ impl vfs::FileSystem for SimpleFileSystem { use vfs::INode; inode.borrow_mut().sync().unwrap(); } + self.flush_unreachable_inodes(); Ok(()) } diff --git a/src/structs.rs b/src/structs.rs index 2b07667..9001498 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -29,6 +29,7 @@ pub struct DiskINode { /// one of SYS_TYPE_* above pub type_: FileType, /// number of hard links to this file + /// Note: "." and ".." is counted in this nlinks pub nlinks: u16, /// number of blocks pub blocks: u32, @@ -176,6 +177,8 @@ pub const BLKBITS: usize = BLKSIZE * 8; pub const ENTRY_SIZE: usize = 4; /// number of entries in a block pub const BLK_NENTRY: usize = BLKSIZE / ENTRY_SIZE; +/// size of a dirent used in the size field +pub const DIRENT_SIZE: usize = MAX_FNAME_LEN + 1; /// file types #[repr(u16)] diff --git a/src/tests.rs b/src/tests.rs index f09e89f..8a6b355 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -9,24 +9,6 @@ use std::rc::Rc; use std::mem::uninitialized; use super::structs::{DiskEntry, AsBuf}; -impl Device for File { - fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Option { - let offset = offset as u64; - match self.seek(SeekFrom::Start(offset)) { - Ok(real_offset) if real_offset == offset => self.read(buf).ok(), - _ => None, - } - } - - fn write_at(&mut self, offset: usize, buf: &[u8]) -> Option { - let offset = offset as u64; - match self.seek(SeekFrom::Start(offset)) { - Ok(real_offset) if real_offset == offset => self.write(buf).ok(), - _ => None, - } - } -} - fn _open_sample_file() -> Rc { fs::copy("sfs.img","test.img").expect("failed to open sfs.img"); let file = OpenOptions::new() @@ -51,9 +33,10 @@ fn open_sample_file() { #[test] fn create_new_sfs() { let sfs = _create_new_sfs(); + let root = sfs.root_inode(); } -//#[test] +// #[test] fn print_root() { let sfs = _open_sample_file(); let root = sfs.root_inode(); @@ -61,6 +44,9 @@ fn print_root() { let files = root.borrow().list().unwrap(); println!("{:?}", files); + assert_eq!(files[3],root.borrow().get_entry(3).unwrap()); + + sfs.sync().unwrap(); } #[test] @@ -74,6 +60,7 @@ fn create_file() { type_: FileType::File, mode: 0, blocks: 0, + nlinks: 1, }); sfs.sync().unwrap(); @@ -123,23 +110,24 @@ fn resize() { #[test] fn create_then_lookup() { let sfs = _create_new_sfs(); - let root = sfs.root_inode(); - - assert!(Rc::ptr_eq(&root.borrow().lookup(".").unwrap(), &root), "failed to find ."); - assert!(Rc::ptr_eq(&root.borrow().lookup("..").unwrap(), &root), "failed to find .."); + { + let root = sfs.root_inode(); - let file1 = root.borrow_mut().create("file1", FileType::File) - .expect("failed to create file1"); - assert!(Rc::ptr_eq(&root.borrow().lookup("file1").unwrap(), &file1), "failed to find file1"); - assert!(root.borrow().lookup("file2").is_err(), "found non-existent file"); + assert!(Rc::ptr_eq(&root.borrow().lookup(".").unwrap(), &root), "failed to find ."); + assert!(Rc::ptr_eq(&root.borrow().lookup("..").unwrap(), &root), "failed to find .."); - let dir1 = root.borrow_mut().create("dir1", FileType::Dir) - .expect("failed to create dir1"); - let file2 = dir1.borrow_mut().create("file2", FileType::File) - .expect("failed to create /dir1/file2"); - assert!(Rc::ptr_eq(&root.borrow().lookup("dir1/file2").unwrap(), &file2), "failed to find dir1/file1"); - assert!(Rc::ptr_eq(&dir1.borrow().lookup("..").unwrap(), &root), "failed to find .. from dir1"); + let file1 = root.borrow_mut().create("file1", FileType::File) + .expect("failed to create file1"); + assert!(Rc::ptr_eq(&root.borrow().lookup("file1").unwrap(), &file1), "failed to find file1"); + assert!(root.borrow().lookup("file2").is_err(), "found non-existent file"); + let dir1 = root.borrow_mut().create("dir1", FileType::Dir) + .expect("failed to create dir1"); + let file2 = dir1.borrow_mut().create("file2", FileType::File) + .expect("failed to create /dir1/file2"); + assert!(Rc::ptr_eq(&root.borrow().lookup("dir1/file2").unwrap(), &file2), "failed to find dir1/file1"); + assert!(Rc::ptr_eq(&dir1.borrow().lookup("..").unwrap(), &root), "failed to find .. from dir1"); + } sfs.sync().unwrap(); } @@ -158,10 +146,190 @@ fn rc_layout() { fn kernel_image_file_create(){ let sfs = _open_sample_file(); let root = sfs.root_inode(); - let files_count_before = root.borrow().list().unwrap(); + let files_count_before = root.borrow().list().unwrap().len(); root.borrow_mut().create("hello2",FileType::File).unwrap(); - let files_count_after = root.borrow().list().unwrap(); - assert_eq!(files_count_before+1, files_count_after) + let files_count_after = root.borrow().list().unwrap().len(); + assert_eq!(files_count_before+1, files_count_after); + assert!(root.borrow().lookup("hello2").is_ok()); + + sfs.sync().unwrap(); +} + +// #[test] +fn kernel_image_file_unlink(){ + let sfs = _open_sample_file(); + let root = sfs.root_inode(); + let files_count_before = root.borrow().list().unwrap().len(); + root.borrow_mut().unlink("hello").unwrap(); + let files_count_after = root.borrow().list().unwrap().len(); + assert_eq!(files_count_before, files_count_after+1); + assert!(root.borrow().lookup("hello").is_err()); + + sfs.sync().unwrap(); +} + + +// #[test] +fn kernel_image_file_rename(){ + let sfs = _open_sample_file(); + let root = sfs.root_inode(); + let files_count_before = root.borrow().list().unwrap().len(); + root.borrow_mut().rename("hello","hello2").unwrap(); + let files_count_after = root.borrow().list().unwrap().len(); + assert_eq!(files_count_before, files_count_after); + assert!(root.borrow().lookup("hello").is_err()); + assert!(root.borrow().lookup("hello2").is_ok()); + + sfs.sync().unwrap(); +} + +// #[test] +fn kernel_image_file_move(){ + use core::ops::DerefMut; + let sfs = _open_sample_file(); + let root = sfs.root_inode(); + let files_count_before = root.borrow().list().unwrap().len(); + root.borrow_mut().unlink("divzero").unwrap(); + let rust_dir = root.borrow_mut().create("rust", FileType::Dir).unwrap(); + root.borrow_mut().move_("hello",rust_dir.borrow_mut().deref_mut(),"hello_world").unwrap(); + let files_count_after = root.borrow().list().unwrap().len(); + assert_eq!(files_count_before, files_count_after+1); + assert!(root.borrow().lookup("hello").is_err()); + assert!(root.borrow().lookup("divzero").is_err()); + assert!(root.borrow().lookup("rust").is_ok()); + assert!(rust_dir.borrow().lookup("hello_world").is_ok()); + + sfs.sync().unwrap(); +} + +#[test] +fn hard_link(){ + let sfs = _create_new_sfs(); + let root = sfs.root_inode(); + let file1 = root.borrow_mut().create("file1", FileType::File).unwrap(); + use core::ops::DerefMut; + root.borrow_mut().link("file2",file1.borrow_mut().deref_mut()).unwrap(); + let file2 = root.borrow().lookup("file2").unwrap(); + file1.borrow_mut().resize(100); + assert_eq!(file2.borrow().info().unwrap().size,100); + + sfs.sync().unwrap(); +} + +#[test] +fn nlinks(){ + use core::ops::DerefMut; + let sfs = _create_new_sfs(); + let root = sfs.root_inode(); + // -root + assert_eq!(root.borrow().info().unwrap().nlinks,2); + + let file1 = root.borrow_mut().create("file1", FileType::File).unwrap(); + // -root + // `-file1 + assert_eq!(file1.borrow().info().unwrap().nlinks,1); + assert_eq!(root.borrow().info().unwrap().nlinks,2); + + let dir1 = root.borrow_mut().create("dir1", FileType::Dir).unwrap(); + // -root + // +-dir1 + // `-file1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + + root.borrow_mut().rename("dir1", "dir_1").unwrap(); + // -root + // +-dir_1 + // `-file1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + + dir1.borrow_mut().link("file1_",file1.borrow_mut().deref_mut()).unwrap(); + // -root + // +-dir_1 + // | `-file1_ + // `-file1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + assert_eq!(file1.borrow().info().unwrap().nlinks,2); + + let dir2 = root.borrow_mut().create("dir2", FileType::Dir).unwrap(); + // -root + // +-dir_1 + // | `-file1_ + // +-dir2 + // `-file1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,2); + assert_eq!(root.borrow().info().unwrap().nlinks,4); + assert_eq!(file1.borrow().info().unwrap().nlinks,2); + + root.borrow_mut().rename("file1","file_1").unwrap(); + // -root + // +-dir_1 + // | `-file1_ + // +-dir2 + // `-file_1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,2); + assert_eq!(root.borrow().info().unwrap().nlinks,4); + assert_eq!(file1.borrow().info().unwrap().nlinks,2); + + root.borrow_mut().move_("file_1",dir2.borrow_mut().deref_mut(),"file__1").unwrap(); + // -root + // +-dir_1 + // | `-file1_ + // `-dir2 + // `-file__1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,2); + assert_eq!(root.borrow().info().unwrap().nlinks,4); + assert_eq!(file1.borrow().info().unwrap().nlinks,2); + + root.borrow_mut().move_("dir_1",dir2.borrow_mut().deref_mut(),"dir__1").unwrap(); + // -root + // `-dir2 + // +-dir__1 + // | `-file1_ + // `-file__1 + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,3); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + assert_eq!(file1.borrow().info().unwrap().nlinks,2); + + dir2.borrow_mut().unlink("file__1").unwrap(); + // -root + // `-dir2 + // `-dir__1 + // `-file1_ + assert_eq!(file1.borrow().info().unwrap().nlinks,1); + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,3); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + + dir1.borrow_mut().unlink("file1_").unwrap(); + // -root + // `-dir2 + // `-dir__1 + assert_eq!(file1.borrow().info().unwrap().nlinks,0); + assert_eq!(dir1.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,3); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + + dir2.borrow_mut().unlink("dir__1").unwrap(); + // -root + // `-dir2 + assert_eq!(file1.borrow().info().unwrap().nlinks,0); + assert_eq!(dir1.borrow().info().unwrap().nlinks,0); + assert_eq!(root.borrow().info().unwrap().nlinks,3); + assert_eq!(dir2.borrow().info().unwrap().nlinks,2); + + root.borrow_mut().unlink("dir2").unwrap(); + // -root + assert_eq!(file1.borrow().info().unwrap().nlinks,0); + assert_eq!(dir1.borrow().info().unwrap().nlinks,0); + assert_eq!(root.borrow().info().unwrap().nlinks,2); + assert_eq!(dir2.borrow().info().unwrap().nlinks,0); sfs.sync().unwrap(); } \ No newline at end of file diff --git a/src/vfs.rs b/src/vfs.rs index ad2b560..f04fb54 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -3,6 +3,7 @@ use core::cell::RefCell; use core::mem::size_of; use core; use core::fmt::Debug; +use core::any::Any; /// Interface for FS to read & write /// TODO: use std::io::{Read, Write} @@ -12,29 +13,86 @@ pub trait Device { } /// Abstract operations on a inode. -pub trait INode: Debug { +pub trait INode: Debug + Any { fn open(&mut self, flags: u32) -> Result<()>; fn close(&mut self) -> Result<()>; fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result; fn write_at(&self, offset: usize, buf: &[u8]) -> Result; fn info(&self) -> Result; fn sync(&mut self) -> Result<()>; -// fn name_file(&mut self) -> Result<()>; -// fn reclaim(&mut self) -> Result<()>; fn resize(&mut self, len: usize) -> Result<()>; fn create(&mut self, name: &str, type_: FileType) -> Result; - fn lookup(&self, path: &str) -> Result; - fn list(&self) -> Result>; + fn unlink(&mut self, name: &str) -> Result<()>; + /// user of the vfs api should call borrow_mut by itself + fn link(&mut self, name: &str, other:&mut INode) -> Result<()>; + fn rename(&mut self, old_name: &str, new_name: &str) -> Result<()>; + // when self==target use rename instead since it's not possible to have two mut_ref at the same time. + fn move_(&mut self, old_name: &str,target:&mut INode, new_name: &str) -> Result<()>; + /// lookup with only one layer + fn find(&self, name: &str) -> Result; + /// like list()[id] + /// only get one item in list, often faster than list + fn get_entry(&self,id: usize) -> Result; // fn io_ctrl(&mut self, op: u32, data: &[u8]) -> Result<()>; fn fs(&self) -> Weak; + /// this is used to implement dynamics cast + /// simply return self in the implement of the function + fn as_any_ref(&self) -> &Any; + /// this is used to implement dynamics cast + /// simply return self in the implement of the function + fn as_any_mut(&mut self) -> &mut Any; +} + +impl INode{ + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any_ref().downcast_ref::() + } + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.as_any_mut().downcast_mut::() + } + pub fn list(&self) -> Result> { + let info=self.info().unwrap(); + assert_eq!(info.type_, FileType::Dir); + Ok((0..info.size).map(|i|{ + self.get_entry(i).unwrap() + }).collect()) + } + pub fn lookup(&self, path: &str) -> Result { + if(self.info().unwrap().type_ != FileType::Dir){ + return Err(()) + } + let mut result=self.find(".").unwrap(); + let mut rest_path=path; + while rest_path != "" { + if(result.borrow().info().unwrap().type_ != FileType::Dir){ + return Err(()) + } + let mut name; + match rest_path.find('/') { + None => {name=rest_path; rest_path=""}, + Some(pos) => {name=&rest_path[0..pos]; rest_path=&rest_path[pos + 1..]}, + }; + let found=result.borrow().find(name); + match found { + Err(_) => return Err(()), + Ok(inode) => result=inode, + }; + } + Ok(result) + } } #[derive(Debug, Eq, PartialEq)] pub struct FileInfo { + // Note: for normal file size is the actuate file size + // for directory this is count of dirent. pub size: usize, pub mode: u32, pub type_: FileType, pub blocks: usize, + // Note: different from linux, "." and ".." count in nlinks + // this is same as original ucore. + pub nlinks: usize, } #[derive(Debug, Eq, PartialEq)]