Skip to content

Commit ca2e818

Browse files
jounathaenmkroening
andcommitted
made getdents64 stateful
Co-authored-by: Martin Kröning <martin.kroening@eonerc.rwth-aachen.de>
1 parent b1d1aa5 commit ca2e818

File tree

6 files changed

+180
-77
lines changed

6 files changed

+180
-77
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: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ use alloc::string::String;
55
use alloc::sync::Arc;
66
use alloc::vec::Vec;
77
use core::marker::PhantomData;
8-
use core::mem::MaybeUninit;
8+
use core::mem::{MaybeUninit, align_of, offset_of, size_of};
99
use core::sync::atomic::{AtomicU64, Ordering};
1010
use core::task::Poll;
1111
use core::{future, mem};
1212

13+
use align_address::Align;
1314
use async_lock::Mutex;
1415
use async_trait::async_trait;
1516
use fuse_abi::linux::*;
@@ -27,6 +28,7 @@ use crate::fs::{
2728
SeekWhence, VfsNode,
2829
};
2930
use crate::mm::device_alloc::DeviceAlloc;
31+
use crate::syscalls::Dirent64;
3032
use crate::time::{time_t, timespec};
3133
use crate::{arch, io};
3234

@@ -810,20 +812,24 @@ impl Clone for FuseFileHandle {
810812
}
811813
}
812814

813-
#[derive(Debug, Clone)]
815+
#[derive(Debug)]
814816
pub struct FuseDirectoryHandle {
815817
name: Option<String>,
818+
read_position: Mutex<usize>,
816819
}
817820

818821
impl FuseDirectoryHandle {
819822
pub fn new(name: Option<String>) -> Self {
820-
Self { name }
823+
Self {
824+
name,
825+
read_position: Mutex::new(0),
826+
}
821827
}
822828
}
823829

824830
#[async_trait]
825831
impl ObjectInterface for FuseDirectoryHandle {
826-
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
832+
async fn getdents(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
827833
let path: CString = if let Some(name) = &self.name {
828834
CString::new("/".to_string() + name).unwrap()
829835
} else {
@@ -848,7 +854,8 @@ impl ObjectInterface for FuseDirectoryHandle {
848854

849855
// Linux seems to allocate a single page to store the dirfile
850856
let len = MAX_READ_LEN as u32;
851-
let mut offset: usize = 0;
857+
let rsp_offset: &mut usize = &mut *self.read_position.lock().await;
858+
let mut buf_offset: usize = 0;
852859

853860
// read content of the directory
854861
let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
@@ -868,31 +875,53 @@ impl ObjectInterface for FuseDirectoryHandle {
868875
return Err(io::Error::ENOENT);
869876
}
870877

871-
let mut entries: Vec<DirectoryEntry> = Vec::new();
872-
while (rsp.headers.out_header.len as usize) - offset > core::mem::size_of::<fuse_dirent>() {
878+
let mut ret = 0;
879+
880+
while (rsp.headers.out_header.len as usize) - *rsp_offset > size_of::<fuse_dirent>() {
873881
let dirent = unsafe {
874882
&*rsp
875883
.payload
876884
.as_ref()
877885
.unwrap()
878886
.as_ptr()
879-
.byte_add(offset)
887+
.byte_add(*rsp_offset)
880888
.cast::<fuse_dirent>()
881889
};
882890

883-
offset += core::mem::size_of::<fuse_dirent>() + dirent.namelen as usize;
884-
// Align to dirent struct
885-
offset = ((offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
891+
let dirent_len = offset_of!(Dirent64, d_name) + dirent.namelen as usize + 1;
892+
let next_dirent = (buf_offset + dirent_len).align_up(align_of::<Dirent64>());
886893

887-
let name: &'static [u8] = unsafe {
888-
core::slice::from_raw_parts(
889-
dirent.name.as_ptr().cast(),
890-
dirent.namelen.try_into().unwrap(),
891-
)
892-
};
893-
entries.push(DirectoryEntry::new(unsafe {
894-
core::str::from_utf8_unchecked(name).to_string()
895-
}));
894+
if next_dirent > buf.len() {
895+
// target buffer full -> we return the nr. of bytes written (like linux does)
896+
break;
897+
}
898+
899+
// could be replaced with slice_as_ptr once maybe_uninit_slice is stabilized.
900+
let target_dirent = buf[buf_offset].as_mut_ptr().cast::<Dirent64>();
901+
unsafe {
902+
target_dirent.write(Dirent64 {
903+
d_ino: dirent.ino,
904+
d_off: 0,
905+
d_reclen: (dirent_len.align_up(align_of::<Dirent64>()))
906+
.try_into()
907+
.unwrap(),
908+
d_type: (dirent.type_ as u8).try_into().unwrap(),
909+
d_name: PhantomData {},
910+
});
911+
let nameptr = core::ptr::from_mut(&mut (*(target_dirent)).d_name).cast::<u8>();
912+
core::ptr::copy_nonoverlapping(
913+
dirent.name.as_ptr().cast::<u8>(),
914+
nameptr,
915+
dirent.namelen as usize,
916+
);
917+
nameptr.add(dirent.namelen as usize).write(0); // zero termination
918+
}
919+
920+
*rsp_offset += core::mem::size_of::<fuse_dirent>() + dirent.namelen as usize;
921+
// Align to dirent struct
922+
*rsp_offset = ((*rsp_offset) + U64_SIZE - 1) & (!(U64_SIZE - 1));
923+
buf_offset = next_dirent;
924+
ret = buf_offset;
896925
}
897926

898927
let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
@@ -901,7 +930,20 @@ impl ObjectInterface for FuseDirectoryHandle {
901930
.lock()
902931
.send_command(cmd, rsp_payload_len)?;
903932

904-
Ok(entries)
933+
Ok(ret)
934+
}
935+
936+
/// lseek for a directory entry is the equivalent for seekdir on linux. But on Hermit this is
937+
/// logically the same operation, so we can just use the same fn in the backend.
938+
/// Any other offset than 0 is not supported. (Mostly because it doesn't make any sense, as
939+
/// userspace applications have no way of knowing valid offsets)
940+
async fn lseek(&self, offset: isize, whence: SeekWhence) -> io::Result<isize> {
941+
if whence != SeekWhence::Set && offset != 0 {
942+
error!("Invalid offset for directory lseek ({offset})");
943+
return Err(io::Error::EINVAL);
944+
}
945+
*self.read_position.lock().await = offset as usize;
946+
Ok(offset)
905947
}
906948
}
907949

src/fs/mem.rs

Lines changed: 65 additions & 9 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};
24-
use crate::fs::{DirectoryEntry, FileAttr, NodeKind, SeekWhence, VfsNode};
26+
use crate::fs::{DirectoryEntry, FileAttr, FileType, NodeKind, SeekWhence, VfsNode};
27+
use crate::syscalls::Dirent64;
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::Unknown, // 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(0); // 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 whence != SeekWhence::Set && 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
@@ -130,8 +130,9 @@ impl DirectoryReader {
130130

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

0 commit comments

Comments
 (0)