Skip to content

Commit 4d17269

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

File tree

6 files changed

+207
-77
lines changed

6 files changed

+207
-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};
@@ -13,7 +12,7 @@ use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
1312
use crate::arch::kernel::core_local::core_scheduler;
1413
use crate::errno::Errno;
1514
use crate::executor::block_on;
16-
use crate::fs::{DirectoryEntry, FileAttr, SeekWhence};
15+
use crate::fs::{FileAttr, SeekWhence};
1716
use crate::io;
1817

1918
mod eventfd;
@@ -179,10 +178,10 @@ pub(crate) trait ObjectInterface: Sync + Send + core::fmt::Debug {
179178
Err(Errno::Inval)
180179
}
181180

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

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::*;
@@ -28,6 +29,7 @@ use crate::fs::{
2829
SeekWhence, VfsNode,
2930
};
3031
use crate::mm::device_alloc::DeviceAlloc;
32+
use crate::syscalls::Dirent64;
3133
use crate::time::{time_t, timespec};
3234
use crate::{arch, io};
3335

@@ -811,20 +813,24 @@ impl Clone for FuseFileHandle {
811813
}
812814
}
813815

814-
#[derive(Debug, Clone)]
816+
#[derive(Debug)]
815817
pub struct FuseDirectoryHandle {
816818
name: Option<String>,
819+
read_position: Mutex<usize>,
817820
}
818821

819822
impl FuseDirectoryHandle {
820823
pub fn new(name: Option<String>) -> Self {
821-
Self { name }
824+
Self {
825+
name,
826+
read_position: Mutex::new(0),
827+
}
822828
}
823829
}
824830

825831
#[async_trait]
826832
impl ObjectInterface for FuseDirectoryHandle {
827-
async fn readdir(&self) -> io::Result<Vec<DirectoryEntry>> {
833+
async fn getdents(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
828834
let path: CString = if let Some(name) = &self.name {
829835
CString::new("/".to_string() + name).unwrap()
830836
} else {
@@ -849,7 +855,8 @@ impl ObjectInterface for FuseDirectoryHandle {
849855

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

854861
// read content of the directory
855862
let (mut cmd, rsp_payload_len) = ops::Read::create(fuse_nid, fuse_fh, len, 0);
@@ -869,31 +876,53 @@ impl ObjectInterface for FuseDirectoryHandle {
869876
return Err(Errno::Noent);
870877
}
871878

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

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

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

899928
let (cmd, rsp_payload_len) = ops::Release::create(fuse_nid, fuse_fh);
@@ -902,7 +931,20 @@ impl ObjectInterface for FuseDirectoryHandle {
902931
.lock()
903932
.send_command(cmd, rsp_payload_len)?;
904933

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

src/fs/mem.rs

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ 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::errno::Errno;
2325
use crate::executor::block_on;
2426
use crate::fd::{AccessPermission, ObjectInterface, OpenOption, PollEvent};
25-
use crate::fs::{DirectoryEntry, FileAttr, NodeKind, SeekWhence, VfsNode};
27+
use crate::fs::{DirectoryEntry, FileAttr, FileType, NodeKind, SeekWhence, VfsNode};
28+
use crate::syscalls::Dirent64;
2629
use crate::time::timespec;
2730
use crate::{arch, io};
2831

@@ -375,11 +378,12 @@ impl RamFile {
375378
}
376379
}
377380

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

385389
impl MemDirectoryInterface {
@@ -388,19 +392,71 @@ impl MemDirectoryInterface {
388392
RwLock<BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>>,
389393
>,
390394
) -> Self {
391-
Self { inner }
395+
Self {
396+
inner,
397+
read_idx: Mutex::new(0),
398+
}
392399
}
393400
}
394401

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

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

src/fs/mod.rs

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

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

0 commit comments

Comments
 (0)