实践作业

硬链接

硬链接要求两个不同的目录项指向同一个文件,在我们的文件系统中也就是两个不同名称目录项指向同一个磁盘块。

本节要求实现三个系统调用 sys_linkat、sys_unlinkat、sys_stat

linkat

  • syscall ID: 37

  • 功能:创建一个文件的一个硬链接, linkat标准接口

  • C接口: int linkat(int olddirfd, char* oldpath, int newdirfd, char* newpath, unsigned int flags)

  • Rust 接口: fn linkat(olddirfd: i32, oldpath: *const u8, newdirfd: i32, newpath: *const u8, flags: u32) -> i32

    • 参数:

      olddirfd,newdirfd: 仅为了兼容性考虑,本次实验中始终为 AT_FDCWD (-100),可以忽略。flags: 仅为了兼容性考虑,本次实验中始终为 0,可以忽略。oldpath:原有文件路径newpath: 新的链接文件路径。

    • 说明:

      为了方便,不考虑新文件路径已经存在的情况(属于未定义行为),除非链接同名文件。返回值:如果出现了错误则返回 -1,否则返回 0。

    • 可能的错误

      链接同名文件。

unlinkat:

  • syscall ID: 35

  • 功能:取消一个文件路径到文件的链接, unlinkat标准接口

  • C接口: int unlinkat(int dirfd, char* path, unsigned int flags)

  • Rust 接口: fn unlinkat(dirfd: i32, path: *const u8, flags: u32) -> i32

    • 参数:

      dirfd: 仅为了兼容性考虑,本次实验中始终为 AT_FDCWD (-100),可以忽略。flags: 仅为了兼容性考虑,本次实验中始终为 0,可以忽略。path:文件路径。

    • 说明:

      为了方便,不考虑使用 unlink 彻底删除文件的情况。

  • 返回值:如果出现了错误则返回 -1,否则返回 0。

    • 可能的错误

      文件不存在。

fstat:

  • syscall ID: 80

  • 功能:获取文件状态。

  • C接口: int fstat(int fd, struct Stat* st)

  • Rust 接口: fn fstat(fd: i32, st: *mut Stat) -> i32

    • 参数:

      fd: 文件描述符st: 文件状态结构体

      #[repr(C)] 
      #[derive(Debug)] 
      pub struct Stat {    
          /// 文件所在磁盘驱动器号,该实验中写死为 0 即可    
          pub dev: u64,    /// inode 文件所在 inode 编号    
          pub ino: u64,    /// 文件类型    
          pub mode: StatMode,    /// 硬链接数量,初始为1    
          pub nlink: u32,    /// 无需考虑,为了兼容性设计    
          pad: [u64; 7], } 
      /// StatMode 定义: 
      bitflags! {    
          pub struct StatMode: u32 
          {        
              const NULL  = 0;        
              /// directory        
              const DIR   = 0o040000;        
              /// ordinary regular file        
              const FILE  = 0o100000;    
          } 
      }
      

实现

link的实现会调用inode模块中的add_a_link方法,它是ROOT_NODE的一个方法

pub fn add_a_link(&self, oldpath: &str, newpath:&str) -> isize {
		let mut fs = self.fs.lock();
		self.modify_disk_inode(|root_inode| {
			let file_count = (root_inode.size as usize) / DIRENT_SZ;
			let new_size = (file_count + 1) * DIRENT_SZ;
			for i in 0..file_count {
				let mut dirent = DirEntry::empty();
				root_inode.read_at(i * DIRENT_SZ, dirent.as_bytes_mut(), &self.block_device);
				if dirent.name() == oldpath {
					self.increase_size(new_size as u32, root_inode, &mut fs);
					root_inode.write_at(new_size * DIRENT_SZ, DirEntry::new(newpath, dirent.inode_number()).as_bytes(), &self.block_device);
				}
			}
		});			
		0
	}

​ 首先,代码中会尝试获取锁,之后通过回调函数,对自己这个inode进行操作。由于是根inode,我们可以遍历其中的目录,遍历其中的文件名,如果发现文件名和old_path相同的,可以添加一个记录,由于是硬链接公用一个inode即可。

unlink的实现会稍微复杂一点,

pub fn rm_a_link(&self, path: &str) -> isize {
		let mut fs = self.fs.lock();
		self.modify_disk_inode(|root_inode| {
			let file_count = (root_inode.size as usize) / DIRENT_SZ;
			for i in 0..file_count {
				let mut dirent = DirEntry::empty();
				root_inode.read_at(i * DIRENT_SZ, dirent.as_bytes_mut(), &self.block_device);
				if dirent.name() == path {
					for j in i+1..file_count{
						root_inode.read_at(i*DIRENT_SZ, dirent.as_bytes_mut(), &self.block_device);
						root_inode.write_at((i-1) * DIRENT_SZ, dirent.as_bytes(),  &self.block_device);

					}
					root_inode.size -= 1;
					break;
				}
			}	
		});
		0
	}

​ 我们仍然遍历根目录,如果找到一个和我们要找的文件名相同的项,我们就根据比较排序类似的方法, 将读到的后一项覆盖到前一项,这样目录中就不会出现空项。

fstat

​ 我们需要在File中实现一个新的方法,用于获取这个实现File Trait的结构体的信息。

fn stat(&self, st:&mut Stat) -> usize {
		let inner = self.inner.exclusive_access();
		inner.inode.stat(unsafe {
				core::slice::from_raw_parts_mut(st as *mut Stat as usize as *mut u8,
					core::mem::size_of::<Stat>(),
				)	
		});
		1
	}

​ 我们实现了fstat方法,Stat结构体如下

pub struct Stat {
	pub dev: u64,
	pub ino: u64,
	pub mode: StatMode,
	pub nlink: u32,
	pad: [u64; 7],
}

​ 由于要在这里没有在easy-fs实现Stat结构体,我选择将Stat转化为字节数组引用进行传递

  • 第一步,&mut Stat可以转化为* mut Stat指针
  • 指针可以转化为usize地址
  • 再将usize转化为一个*mut u8的指针

​ 最后构造一个引用

pub fn stat(&self, st: &mut [u8]) -> usize{
    	let mut fs = self.fs.lock();
		let st_trans:&mut Stat = unsafe { ( st.as_mut_ptr() as usize as *mut Stat).as_mut().unwrap()};
		let inode_id = ((self.block_id - self.fs.lock().inode_area_start_block as usize) * BLOCK_SZ + self.block_offset) / core::mem::size_of::<Inode>();
		st_trans.ino = inode_id as u64;
		let mut link_num = 0;
		let mut _type = StatMode::FILE;
		EasyFileSystem::root_inode(&self.fs.read_disk_inode(|disk_inode| {
			let file_count = (disk_inode.size as usize) / DIRENT_SZ;
			for i in 0..file_count {
				let mut dirent = DirEntry::empty();
				if dirent.inode_number() == inode_id as u32 {
					link_num += 1;
				}
			}

			if(disk_inode.is_dir()){
				_type = StatMode::DIR;
			}
		});
		st_trans.nlink = link_num;
		st_trans.mode = _type;
		1
	}

最后在easy-fs侧实现了这样一个函数

  • 第一步,首先根据自己的inode的结构体信息计算出自己在块设备中的位置
  • 遍历根目录中的文件,如果发现inode号相同,则将链接数加1
  • 最后设置文件类型