Browse Source

fix(fs, mm): 修复fs、mm上有关系统调用的bug (#1208)

* fix(fs, mm): 修复fs、mm上有关系统调用的bug

**filesystem:**

- 修改read系统调用:修复`O_PATH`文件模式该有的功能,使其能通过gvisor/syscall/read的测试。参考:[[file_table.c - fs/file_table.c - Linux source code v2.6.39 - Bootlin Elixir Cross Referencer](https://elixir.bootlin.com/linux/v2.6.39/source/fs/file_table.c#L331)](https://elixir.bootlin.com/linux/v2.6.39/source/fs/file_table.c#L331),在读取之前先进行检查文件模式是否为`O_PATH`
- 修改getcwd系统调用:修正成跟linux语义一样,返回目录长度而不是地址。因为gvisor用这个系统调用如果返回的是地址是会报错的,改成跟linux一样就不会报错了。参考:[[dcache.c - fs/dcache.c - Linux source code v2.6.39 - Bootlin Elixir Cross Referencer](https://elixir.bootlin.com/linux/v2.6.39/source/fs/dcache.c#L2774)](https://elixir.bootlin.com/linux/v2.6.39/source/fs/dcache.c#L2774)
- 修改unlink系统调用:在unlink删除inode之后,要将inode对应的pagecache的dirty标识去掉,否则在`flush_dirty_pages()`的时候,会将标记为dirty的pagecache进行`page_writeback()`,但是对应的pagecache的inode已经被释放了,这时候直接unwrap()就会导致panic。参考:[[namei.c - fs/namei.c - Linux source code v2.6.6 - Bootlin Elixir Cross Referencer](https://elixir.bootlin.com/linux/v2.6.6/source/fs/namei.c#L1714)](https://elixir.bootlin.com/linux/v2.6.6/source/fs/namei.c#L1714)

**mm:**

- 添加`truncate_inode_pages()`,用来截断文件从指定偏移量的页缓存,但目前该函数功能仅是将pagecache的dirty标识去掉。参考:[[truncate.c - mm/truncate.c - Linux source code v2.6.6 - Bootlin Elixir Cross Referencer](https://elixir.bootlin.com/linux/v2.6.6/source/mm/truncate.c#L112)](https://elixir.bootlin.com/linux/v2.6.6/source/mm/truncate.c#L112)

**syscall:**

- 修改了`convert_with_offset()`的判断逻辑,使其能够从用户空间读取0字节的数据,也是为了能够通过gvisor/syscall/read的测试

目前是能够跑通gvisor syscall测试`read_test`并通过所有测例
![image-20250614224227721](https://github.com/user-attachments/assets/b1c04720-da5c-464a-a7f3-c01f3e82783a)
黄铭涛 18 hours ago
parent
commit
a69d1a93b6

+ 4 - 0
kernel/src/filesystem/page_cache.rs

@@ -312,6 +312,10 @@ impl InnerPageCache {
 
         Ok(())
     }
+
+    pub fn pages_count(&self) -> usize {
+        return self.pages.len();
+    }
 }
 
 impl Drop for InnerPageCache {

+ 2 - 3
kernel/src/filesystem/vfs/syscall/mod.rs

@@ -13,7 +13,6 @@ use crate::{
     driver::base::{block::SeekFrom, device::device_number::DeviceNumber},
     filesystem::vfs::{file::FileDescriptorVec, vcore as Vcore},
     libs::rwlock::RwLockWriteGuard,
-    mm::VirtAddr,
     process::ProcessManager,
     syscall::{
         user_access::{self, check_and_clone_cstr, UserBufferWriter},
@@ -633,7 +632,7 @@ impl Syscall {
     ///
     /// @return 成功,返回的指针指向包含工作目录路径的字符串
     /// @return 错误,没有足够的空间
-    pub fn getcwd(buf: &mut [u8]) -> Result<VirtAddr, SystemError> {
+    pub fn getcwd(buf: &mut [u8]) -> Result<usize, SystemError> {
         let proc = ProcessManager::current_pcb();
         let cwd = proc.basic().cwd();
 
@@ -645,7 +644,7 @@ impl Syscall {
         buf[..cwd_len].copy_from_slice(cwd_bytes);
         buf[cwd_len] = 0;
 
-        return Ok(VirtAddr::new(buf.as_ptr() as usize));
+        return Ok(cwd_len + 1);
     }
 
     /// # 获取目录中的数据

+ 9 - 5
kernel/src/filesystem/vfs/syscall/sys_read.rs

@@ -2,6 +2,7 @@ use system_error::SystemError;
 
 use crate::arch::interrupt::TrapFrame;
 use crate::arch::syscall::nr::SYS_READ;
+use crate::filesystem::vfs::file::FileMode;
 use crate::process::ProcessManager;
 use crate::syscall::table::FormattedSyscallParam;
 use crate::syscall::table::Syscall;
@@ -93,13 +94,16 @@ pub(super) fn do_read(fd: i32, buf: &mut [u8]) -> Result<usize, SystemError> {
     let binding = ProcessManager::current_pcb().fd_table();
     let fd_table_guard = binding.read();
 
-    let file = fd_table_guard.get_file_by_fd(fd);
-    if file.is_none() {
-        return Err(SystemError::EBADF);
-    }
+    let file = fd_table_guard
+        .get_file_by_fd(fd)
+        .ok_or(SystemError::EBADF)?;
+
     // drop guard 以避免无法调度的问题
     drop(fd_table_guard);
-    let file = file.unwrap();
+
+    if file.mode().contains(FileMode::O_PATH) {
+        return Err(SystemError::EBADF);
+    }
 
     return file.read(buf.len(), buf);
 }

+ 6 - 1
kernel/src/filesystem/vfs/vcore.rs

@@ -18,6 +18,7 @@ use crate::{
         },
     },
     libs::spinlock::SpinLock,
+    mm::truncate::truncate_inode_pages,
     process::ProcessManager,
     syscall::user_access::check_and_clone_cstr,
 };
@@ -262,7 +263,7 @@ pub fn do_unlink_at(dirfd: i32, path: &str) -> Result<u64, SystemError> {
         }
     }
     // 禁止在目录上unlink
-    if inode.unwrap().metadata()?.file_type == FileType::Dir {
+    if inode.as_ref().unwrap().metadata()?.file_type == FileType::Dir {
         return Err(SystemError::EPERM);
     }
 
@@ -278,6 +279,10 @@ pub fn do_unlink_at(dirfd: i32, path: &str) -> Result<u64, SystemError> {
     // 删除文件
     parent_inode.unlink(filename)?;
 
+    if let Some(page_cache) = inode.unwrap().page_cache().clone() {
+        truncate_inode_pages(page_cache, 0);
+    }
+
     return Ok(0);
 }
 

+ 1 - 0
kernel/src/mm/mod.rs

@@ -32,6 +32,7 @@ pub mod no_init;
 pub mod page;
 pub mod percpu;
 pub mod syscall;
+pub mod truncate;
 pub mod ucontext;
 
 /// 内核INIT进程的用户地址空间结构体(仅在process_init中初始化)

+ 32 - 0
kernel/src/mm/truncate.rs

@@ -0,0 +1,32 @@
+use super::page::{Page, PageFlags};
+use crate::filesystem::page_cache::PageCache;
+use alloc::sync::Arc;
+
+/// # 功能
+///
+/// 从指定偏移量开始,截断与当前文件的所有页缓存,目前仅是将文件相关的页缓存页的dirty位去除
+///
+/// # 参数
+///
+/// - page_cache: 与文件inode关联的页缓存
+/// - start: 偏移量
+pub fn truncate_inode_pages(page_cache: Arc<PageCache>, start: usize) {
+    let guard = page_cache.lock_irqsave();
+    let pages_count = guard.pages_count();
+
+    for i in start..pages_count {
+        let page = guard.get_page(i);
+        let page = if let Some(page) = page {
+            page
+        } else {
+            log::warn!("try to truncate page from different page cache");
+            return;
+        };
+        truncate_complete_page(page_cache.clone(), page.clone());
+    }
+}
+
+fn truncate_complete_page(_page_cache: Arc<PageCache>, page: Arc<Page>) {
+    let mut guard = page.write_irqsave();
+    guard.remove_flags(PageFlags::PG_DIRTY);
+}

+ 1 - 1
kernel/src/syscall/mod.rs

@@ -505,7 +505,7 @@ impl Syscall {
                     Err(e)
                 } else {
                     let buf = unsafe { core::slice::from_raw_parts_mut(buf, size) };
-                    Self::getcwd(buf).map(|ptr| ptr.data())
+                    Self::getcwd(buf)
                 }
             }
 

+ 2 - 2
kernel/src/syscall/user_access.rs

@@ -334,11 +334,11 @@ impl<'a> UserBufferWriter<'a> {
     }
 
     fn convert_with_offset<T>(src: &mut [u8], offset: usize) -> Result<&mut [T], SystemError> {
-        if offset >= src.len() {
+        if offset > src.len() {
             return Err(SystemError::EINVAL);
         }
         let byte_buffer: &mut [u8] = &mut src[offset..];
-        if byte_buffer.len() % core::mem::size_of::<T>() != 0 || byte_buffer.is_empty() {
+        if byte_buffer.len() % core::mem::size_of::<T>() != 0 {
             return Err(SystemError::EINVAL);
         }