Skip to content

Commit 95b9950

Browse files
committed
made getdents64 stateful
1 parent 53a6b77 commit 95b9950

File tree

6 files changed

+195
-76
lines changed

6 files changed

+195
-76
lines changed

src/fd/mod.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use alloc::boxed::Box;
22
use alloc::sync::Arc;
3-
use alloc::vec::Vec;
43
use core::future::{self, Future};
54
use core::mem::MaybeUninit;
65
use core::task::Poll::{Pending, Ready};
@@ -12,7 +11,7 @@ use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
1211

1312
use crate::arch::kernel::core_local::core_scheduler;
1413
use crate::executor::block_on;
15-
use crate::fs::{DirectoryEntry, FileAttr, SeekWhence};
14+
use crate::fs::{FileAttr, SeekWhence};
1615
use crate::io;
1716

1817
mod eventfd;
@@ -178,10 +177,10 @@ pub(crate) trait ObjectInterface: Sync + Send + core::fmt::Debug {
178177
Err(io::Error::EINVAL)
179178
}
180179

181-
/// 'readdir' returns a pointer to a dirent structure
182-
/// representing the next directory entry in the directory stream
183-
/// pointed to by the file descriptor
184-
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
180+
/// `getdents` fills the given buffer `_buf` with [`Dirent64`](crate::syscalls::Dirent64)
181+
/// formatted entries of a directory, imitating the Linux `getdents64` syscall.
182+
/// On success, the number of bytes read is returned. On end of directory, 0 is returned. On error, -1 is returned
183+
async fn getdents(&self, _buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
185184
Err(io::Error::EINVAL)
186185
}
187186

src/fs/fuse.rs

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ use alloc::ffi::CString;
44
use alloc::string::String;
55
use alloc::sync::Arc;
66
use alloc::vec::Vec;
7-
use core::mem::MaybeUninit;
7+
use core::marker::PhantomData;
8+
use core::mem::{MaybeUninit, align_of, offset_of, size_of};
89
use core::sync::atomic::{AtomicU64, Ordering};
910
use core::task::Poll;
1011
use core::{future, mem};
1112

13+
use align_address::Align;
1214
use async_lock::Mutex;
1315
use async_trait::async_trait;
1416
use fuse_abi::linux::*;
@@ -28,6 +30,7 @@ use crate::fs::{
2830
SeekWhence, VfsNode,
2931
};
3032
use crate::mm::device_alloc::DeviceAlloc;
33+
use crate::syscalls::Dirent64;
3134
use crate::time::{time_t, timespec};
3235
use crate::{arch, io};
3336

@@ -792,20 +795,24 @@ impl Clone for FuseFileHandle {
792795
}
793796
}
794797

795-
#[derive(Debug, Clone)]
798+
#[derive(Debug)]
796799
pub struct FuseDirectoryHandle {
797800
name: Option<String>,
801+
read_position: Mutex<usize>,
798802
}
799803

800804
impl FuseDirectoryHandle {
801805
pub fn new(name: Option<String>) -> Self {
802-
Self { name }
806+
Self {
807+
name,
808+
read_position: Mutex::new(0),
809+
}
803810
}
804811
}
805812

806813
#[async_trait]
807814
impl ObjectInterface for FuseDirectoryHandle {
808-
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
815+
async fn getdents(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
809816
let path: CString = if let Some(name) = &self.name {
810817
CString::new("/".to_string() + name).unwrap()
811818
} else {
@@ -830,7 +837,8 @@ impl ObjectInterface for FuseDirectoryHandle {
830837

831838
// Linux seems to allocate a single page to store the dirfile
832839
let len = MAX_READ_LEN as u32;
833-
let mut offset: usize = 0;
840+
let rsp_offset: &mut usize = &mut *self.read_position.lock().await;
841+
let mut buf_offset: usize = 0;
834842

835843
// read content of the directory
836844
let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
@@ -850,31 +858,53 @@ impl ObjectInterface for FuseDirectoryHandle {
850858
return Err(io::Error::ENOENT);
851859
}
852860

853-
let mut entries: Vec<DirectoryEntry> = Vec::new();
854-
while (rsp.headers.out_header.len as usize) - offset > core::mem::size_of::<fuse_dirent>() {
861+
let mut ret = 0;
862+
863+
while (rsp.headers.out_header.len as usize) - *rsp_offset > size_of::<fuse_dirent>() {
855864
let dirent = unsafe {
856865
&*rsp
857866
.payload
858867
.as_ref()
859868
.unwrap()
860869
.as_ptr()
861-
.byte_add(offset)
870+
.byte_add(*rsp_offset)
862871
.cast::<fuse_dirent>()
863872
};
864873

865-
offset += core::mem::size_of::<fuse_dirent>() + dirent.namelen as usize;
866-
// Align to dirent struct
867-
offset = ((offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
874+
let dirent_len = offset_of!(Dirent64, d_name) + dirent.namelen as usize + 1;
875+
let next_dirent = (buf_offset + dirent_len).align_up(align_of::<Dirent64>());
868876

869-
let name: &'static [u8] = unsafe {
870-
core::slice::from_raw_parts(
871-
dirent.name.as_ptr().cast(),
872-
dirent.namelen.try_into().unwrap(),
873-
)
874-
};
875-
entries.push(DirectoryEntry::new(unsafe {
876-
core::str::from_utf8_unchecked(name).to_string()
877-
}));
877+
if next_dirent > buf.len() {
878+
// target buffer full -> we return the nr. of bytes written (like linux does)
879+
break;
880+
}
881+
882+
// could be replaced with slice_as_ptr once maybe_uninit_slice is stabilized.
883+
let target_dirent = buf[buf_offset].as_mut_ptr().cast::<Dirent64>();
884+
unsafe {
885+
target_dirent.write(Dirent64 {
886+
d_ino: dirent.ino,
887+
d_off: 0,
888+
d_reclen: (dirent_len.align_up(align_of::<Dirent64>()))
889+
.try_into()
890+
.unwrap(),
891+
d_type: (dirent.type_ as u8).try_into().unwrap(),
892+
d_name: PhantomData {},
893+
});
894+
let nameptr = core::ptr::from_mut(&mut (*(target_dirent)).d_name).cast::<u8>();
895+
core::ptr::copy_nonoverlapping(
896+
dirent.name.as_ptr().cast::<u8>(),
897+
nameptr,
898+
dirent.namelen as usize,
899+
);
900+
nameptr.add(dirent.namelen as usize).write_bytes(0, 1); // zero termination
901+
}
902+
903+
*rsp_offset += core::mem::size_of::<fuse_dirent>() + dirent.namelen as usize;
904+
// Align to dirent struct
905+
*rsp_offset = ((*rsp_offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
906+
buf_offset = next_dirent;
907+
ret = buf_offset;
878908
}
879909

880910
let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
@@ -883,7 +913,20 @@ impl ObjectInterface for FuseDirectoryHandle {
883913
.lock()
884914
.send_command(cmd, rsp_payload_len)?;
885915

886-
Ok(entries)
916+
Ok(ret)
917+
}
918+
919+
/// lseek for a directory entry is the equivalent for seekdir on linux. But on Hermit this is
920+
/// logically the same operation, so we can just use the same fn in the backend.
921+
/// Any other offset than 0 is not supported. (Mostly because it doesn't make any sense, as
922+
/// userspace applications have no way of knowing valid offsets)
923+
async fn lseek(&self, offset: isize, _whence: SeekWhence) -> io::Result<isize> {
924+
if offset != 0 {
925+
error!("Invalid offset for directory lseek ({offset})");
926+
return Err(io::Error::EINVAL);
927+
}
928+
*self.read_position.lock().await = offset as usize;
929+
Ok(offset)
887930
}
888931
}
889932

src/fs/mem.rs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ use alloc::collections::BTreeMap;
1414
use alloc::string::String;
1515
use alloc::sync::Arc;
1616
use alloc::vec::Vec;
17-
use core::mem::MaybeUninit;
17+
use core::marker::PhantomData;
18+
use core::mem::{MaybeUninit, offset_of};
1819

20+
use align_address::Align;
1921
use async_lock::{Mutex, RwLock};
2022
use async_trait::async_trait;
2123

2224
use crate::executor::block_on;
2325
use crate::fd::{AccessPermission, ObjectInterface, OpenOption, PollEvent};
2426
use crate::fs::{DirectoryEntry, FileAttr, NodeKind, SeekWhence, VfsNode};
27+
use crate::syscalls::{Dirent64, FileType};
2528
use crate::time::timespec;
2629
use crate::{arch, io};
2730

@@ -374,11 +377,12 @@ impl RamFile {
374377
}
375378
}
376379

377-
#[derive(Debug, Clone)]
380+
#[derive(Debug)]
378381
pub struct MemDirectoryInterface {
379382
/// Directory entries
380383
inner:
381384
Arc<RwLock<BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>>>,
385+
read_idx: Mutex<usize>,
382386
}
383387

384388
impl MemDirectoryInterface {
@@ -387,19 +391,71 @@ impl MemDirectoryInterface {
387391
RwLock<BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>>,
388392
>,
389393
) -> Self {
390-
Self { inner }
394+
Self {
395+
inner,
396+
read_idx: Mutex::new(0),
397+
}
391398
}
392399
}
393400

394401
#[async_trait]
395402
impl ObjectInterface for MemDirectoryInterface {
396-
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
397-
let mut entries: Vec<DirectoryEntry> = Vec::new();
398-
for name in self.inner.read().await.keys() {
399-
entries.push(DirectoryEntry::new(name.clone()));
403+
async fn getdents(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
404+
let mut buf_offset: usize = 0;
405+
let mut ret = 0;
406+
let mut read_idx = self.read_idx.lock().await;
407+
for name in self.inner.read().await.keys().skip(*read_idx) {
408+
let namelen = name.len();
409+
410+
let dirent_len = offset_of!(Dirent64, d_name) + namelen + 1;
411+
let next_dirent = (buf_offset + dirent_len).align_up(align_of::<Dirent64>());
412+
413+
if next_dirent > buf.len() {
414+
// target buffer full -> we return the nr. of bytes written (like linux does)
415+
break;
416+
}
417+
418+
*read_idx += 1;
419+
420+
// could be replaced with slice_as_ptr once maybe_uninit_slice is stabilized.
421+
let target_dirent = buf[buf_offset].as_mut_ptr().cast::<Dirent64>();
422+
423+
unsafe {
424+
target_dirent.write(Dirent64 {
425+
d_ino: 1, // TODO: we don't have inodes in the mem filesystem. Maybe this could lead to problems
426+
d_off: 0,
427+
d_reclen: (dirent_len.align_up(align_of::<Dirent64>()))
428+
.try_into()
429+
.unwrap(),
430+
d_type: FileType::DtUnknown, // TODO: Proper filetype
431+
d_name: PhantomData {},
432+
});
433+
let nameptr = core::ptr::from_mut(&mut (*(target_dirent)).d_name).cast::<u8>();
434+
core::ptr::copy_nonoverlapping(
435+
name.as_bytes().as_ptr().cast::<u8>(),
436+
nameptr,
437+
namelen,
438+
);
439+
nameptr.add(namelen).write_bytes(0, 1); // zero termination
440+
}
441+
442+
buf_offset = next_dirent;
443+
ret = buf_offset;
400444
}
445+
Ok(ret)
446+
}
401447

402-
Ok(entries)
448+
/// lseek for a directory entry is the equivalent for seekdir on linux. But on Hermit this is
449+
/// logically the same operation, so we can just use the same fn in the backend.
450+
/// Any other offset than 0 is not supported. (Mostly because it doesn't make any sense, as
451+
/// userspace applications have no way of knowing valid offsets)
452+
async fn lseek(&self, offset: isize, _whence: SeekWhence) -> io::Result<isize> {
453+
if offset != 0 {
454+
error!("Invalid offset for directory lseek ({offset})");
455+
return Err(io::Error::EINVAL);
456+
}
457+
*self.read_idx.lock().await = offset as usize;
458+
Ok(offset)
403459
}
404460
}
405461

src/fs/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,9 @@ impl DirectoryReader {
129129

130130
#[async_trait]
131131
impl ObjectInterface for DirectoryReader {
132-
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
133-
Ok(self.0.clone())
132+
async fn getdents(&self, _buf: &mut [core::mem::MaybeUninit<u8>]) -> io::Result<usize> {
133+
let _ = self.0.clone(); // Dummy instruction to avoid warning for the moment
134+
unimplemented!("")
134135
}
135136
}
136137

0 commit comments

Comments
 (0)