diff --git a/docs/src/kernel/linux-compatibility.md b/docs/src/kernel/linux-compatibility.md index a0bd177f6..f65df7e2a 100644 --- a/docs/src/kernel/linux-compatibility.md +++ b/docs/src/kernel/linux-compatibility.md @@ -98,7 +98,7 @@ provided by Linux on x86-64 architecture. | 75 | fdatasync | ❌ | | 76 | truncate | ✅ | | 77 | ftruncate | ✅ | -| 78 | getdents | ❌ | +| 78 | getdents | ✅ | | 79 | getcwd | ✅ | | 80 | chdir | ✅ | | 81 | fchdir | ✅ | diff --git a/kernel/aster-nix/src/syscall/arch/x86.rs b/kernel/aster-nix/src/syscall/arch/x86.rs index e6fab2b1c..0a034a3f6 100644 --- a/kernel/aster-nix/src/syscall/arch/x86.rs +++ b/kernel/aster-nix/src/syscall/arch/x86.rs @@ -28,7 +28,7 @@ use crate::syscall::{ fsync::sys_fsync, futex::sys_futex, getcwd::sys_getcwd, - getdents64::sys_getdents64, + getdents64::{sys_getdents, sys_getdents64}, getegid::sys_getegid, geteuid::sys_geteuid, getgid::sys_getgid, @@ -184,6 +184,7 @@ impl_syscall_nums_and_dispatch_fn! { SYS_FSYNC = 74 => sys_fsync(args[..1]); SYS_TRUNCATE = 76 => sys_truncate(args[..2]); SYS_FTRUNCATE = 77 => sys_ftruncate(args[..2]); + SYS_GETDENTS = 78 => sys_getdents(args[..3]); SYS_GETCWD = 79 => sys_getcwd(args[..2]); SYS_CHDIR = 80 => sys_chdir(args[..1]); SYS_FCHDIR = 81 => sys_fchdir(args[..1]); diff --git a/kernel/aster-nix/src/syscall/getdents64.rs b/kernel/aster-nix/src/syscall/getdents64.rs index 796a4cc26..b0879c461 100644 --- a/kernel/aster-nix/src/syscall/getdents64.rs +++ b/kernel/aster-nix/src/syscall/getdents64.rs @@ -15,6 +15,31 @@ use crate::{ util::write_bytes_to_user, }; +pub fn sys_getdents(fd: FileDesc, buf_addr: Vaddr, buf_len: usize) -> Result { + debug!( + "fd = {}, buf_addr = 0x{:x}, buf_len = 0x{:x}", + fd, buf_addr, buf_len + ); + + let file = { + let current = current!(); + let file_table = current.file_table().lock(); + file_table.get_file(fd)?.clone() + }; + let inode_handle = file + .downcast_ref::() + .ok_or(Error::with_message(Errno::EBADF, "not inode"))?; + if inode_handle.dentry().type_() != InodeType::Dir { + return_errno!(Errno::ENOTDIR); + } + let mut buffer = vec![0u8; buf_len]; + let mut reader = DirentBufferReader::::new(&mut buffer); // Use the non-64-bit reader + let _ = inode_handle.readdir(&mut reader)?; + let read_len = reader.read_len(); + write_bytes_to_user(buf_addr, &buffer[..read_len])?; + Ok(SyscallReturn::Return(read_len as _)) +} + pub fn sys_getdents64(fd: FileDesc, buf_addr: Vaddr, buf_len: usize) -> Result { debug!( "fd = {}, buf_addr = 0x{:x}, buf_len = 0x{:x}", @@ -40,6 +65,16 @@ pub fn sys_getdents64(fd: FileDesc, buf_addr: Vaddr, buf_len: usize) -> Result Self; + /// Get the length of a directory entry. + fn len(&self) -> usize; + /// Try to serialize a directory entry into buffer. + fn serialize(&self, buf: &mut [u8]) -> Result<()>; +} + /// The Buffered DirentReader to visit the dir entry. /// The DirentSerializer T decides how to serialize the data. struct DirentBufferReader<'a, T: DirentSerializer> { @@ -74,14 +109,63 @@ impl<'a, T: DirentSerializer> DirentVisitor for DirentBufferReader<'a, T> { } } -/// The DirentSerializer can decide how to serialize the data. -trait DirentSerializer { - /// Create a DirentSerializer. - fn new(ino: u64, offset: u64, type_: InodeType, name: CString) -> Self; - /// Get the length of a directory entry. - fn len(&self) -> usize; - /// Try to serialize a directory entry into buffer. - fn serialize(&self, buf: &mut [u8]) -> Result<()>; +#[derive(Debug)] +struct Dirent { + inner: DirentInner, + name: CString, +} + +#[repr(packed)] +#[derive(Debug, Clone, Copy)] +struct DirentInner { + d_ino: u64, + d_off: u64, + d_reclen: u16, +} + +impl DirentSerializer for Dirent { + fn new(ino: u64, offset: u64, _type_: InodeType, name: CString) -> Self { + let d_reclen = { + let len = + core::mem::size_of::() + name.as_c_str().to_bytes_with_nul().len(); + align_up(len, 8) as u16 + }; + Self { + inner: DirentInner { + d_ino: ino, + d_off: offset, + d_reclen, + }, + name, + } + } + + fn len(&self) -> usize { + self.inner.d_reclen as usize + } + + fn serialize(&self, buf: &mut [u8]) -> Result<()> { + // Ensure buffer is large enough for the directory entry + if self.len() > buf.len() { + return_errno_with_message!(Errno::EINVAL, "buffer is too small"); + } + + let d_ino = self.inner.d_ino; + let d_off = self.inner.d_off; + let d_reclen = self.inner.d_reclen; + let items: [&[u8]; 4] = [ + d_ino.as_bytes(), + d_off.as_bytes(), + d_reclen.as_bytes(), + self.name.as_c_str().to_bytes_with_nul(), + ]; + let mut offset = 0; + for item in items { + buf[offset..offset + item.len()].copy_from_slice(item); + offset += item.len(); + } + Ok(()) + } } #[derive(Debug)] diff --git a/regression/syscall_test/blocklists/getdents_test b/regression/syscall_test/blocklists/getdents_test index cd33c269f..af6297516 100644 --- a/regression/syscall_test/blocklists/getdents_test +++ b/regression/syscall_test/blocklists/getdents_test @@ -1,11 +1,2 @@ -GetdentsTest/0.VerifyEntries -GetdentsTest/0.VerifyPadding -GetdentsTest/0.SmallDir -GetdentsTest/0.LargeDir GetdentsTest/0.PartialBuffer -GetdentsTest/0.ProcSelfFd -GetdentsTest/0.NotDir -GetdentsTest/0.SeekResetsCursor -GetdentsTest/0.Issue128ProcSeekEnd GetdentsTest/1.PartialBuffer -GetdentsTest/1.ProcSelfFd \ No newline at end of file