Skip to content

Commit ec59c28

Browse files
committed
rust: fs: introduce FileSystem::read_dir
This allows file systems to report the contents of their directory inodes. User processes can use `getdents` to enumerate the entries reported by `read_dir`. Signed-off-by: Wedson Almeida Filho <walmeida@microsoft.com>
1 parent 287c7da commit ec59c28

2 files changed

Lines changed: 199 additions & 6 deletions

File tree

rust/kernel/fs.rs

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,81 @@ pub trait FileSystem {
2222

2323
/// Initialises a super block for this file system type.
2424
fn fill_super(sb: NewSuperBlock<'_, Self>) -> Result<&SuperBlock<Self>>;
25+
26+
/// Reads directory entries from directory inodes.
27+
///
28+
/// `pos` is the current position of the directory reader.
29+
///
30+
/// `report` is a closure that takes the following arguments, in order: the entry name, its
31+
/// position, its inode number, and its type. It returns `true` if the entry was copied to
32+
/// caller or `false` otherwise (in which case, the `read_dir` implementation is advised to
33+
/// stop calling `report`).
34+
///
35+
/// On success, returns the new position of the reader.
36+
fn read_dir(
37+
inode: &INode<Self>,
38+
pos: i64,
39+
report: impl FnMut(&[u8], i64, u64, DirEntryType) -> bool,
40+
) -> Result<i64>;
41+
}
42+
43+
/// The types of directory entries reported by [`FileSystem::read_dir`].
44+
#[repr(u32)]
45+
#[derive(Copy, Clone)]
46+
pub enum DirEntryType {
47+
/// Unknown type.
48+
Unknown = bindings::DT_UNKNOWN,
49+
50+
/// Named pipe (first-in, first-out) type.
51+
Fifo = bindings::DT_FIFO,
52+
53+
/// Character device type.
54+
Chr = bindings::DT_CHR,
55+
56+
/// Directory type.
57+
Dir = bindings::DT_DIR,
58+
59+
/// Block device type.
60+
Blk = bindings::DT_BLK,
61+
62+
/// Regular file type.
63+
Reg = bindings::DT_REG,
64+
65+
/// Symbolic link type.
66+
Lnk = bindings::DT_LNK,
67+
68+
/// Named unix-domain socket type.
69+
Sock = bindings::DT_SOCK,
70+
71+
/// White-out type.
72+
Wht = bindings::DT_WHT,
73+
}
74+
75+
impl From<INodeType> for DirEntryType {
76+
fn from(value: INodeType) -> Self {
77+
match value {
78+
INodeType::Dir => DirEntryType::Dir,
79+
}
80+
}
81+
}
82+
83+
impl core::convert::TryFrom<u32> for DirEntryType {
84+
type Error = crate::error::Error;
85+
86+
fn try_from(v: u32) -> Result<Self> {
87+
match v {
88+
v if v == Self::Unknown as u32 => Ok(Self::Unknown),
89+
v if v == Self::Fifo as u32 => Ok(Self::Fifo),
90+
v if v == Self::Chr as u32 => Ok(Self::Chr),
91+
v if v == Self::Dir as u32 => Ok(Self::Dir),
92+
v if v == Self::Blk as u32 => Ok(Self::Blk),
93+
v if v == Self::Reg as u32 => Ok(Self::Reg),
94+
v if v == Self::Lnk as u32 => Ok(Self::Lnk),
95+
v if v == Self::Sock as u32 => Ok(Self::Sock),
96+
v if v == Self::Wht as u32 => Ok(Self::Wht),
97+
_ => Err(EDOM),
98+
}
99+
}
25100
}
26101

27102
/// A registration of a file system.
@@ -149,9 +224,7 @@ impl<T: FileSystem + ?Sized> NewINode<T> {
149224

150225
let mode = match params.typ {
151226
INodeType::Dir => {
152-
// SAFETY: `simple_dir_operations` never changes, it's safe to reference it.
153-
inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations };
154-
227+
inode.__bindgen_anon_3.i_fop = &Tables::<T>::DIR_FILE_OPERATIONS;
155228
// SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it.
156229
inode.i_op = unsafe { &bindings::simple_dir_inode_operations };
157230
bindings::S_IFDIR
@@ -419,6 +492,70 @@ impl<T: FileSystem + ?Sized> Tables<T> {
419492
free_cached_objects: None,
420493
shutdown: None,
421494
};
495+
496+
const DIR_FILE_OPERATIONS: bindings::file_operations = bindings::file_operations {
497+
owner: ptr::null_mut(),
498+
llseek: Some(bindings::generic_file_llseek),
499+
read: Some(bindings::generic_read_dir),
500+
write: None,
501+
read_iter: None,
502+
write_iter: None,
503+
iopoll: None,
504+
iterate_shared: Some(Self::read_dir_callback),
505+
poll: None,
506+
unlocked_ioctl: None,
507+
compat_ioctl: None,
508+
mmap: None,
509+
mmap_supported_flags: 0,
510+
open: None,
511+
flush: None,
512+
release: None,
513+
fsync: None,
514+
fasync: None,
515+
lock: None,
516+
get_unmapped_area: None,
517+
check_flags: None,
518+
flock: None,
519+
splice_write: None,
520+
splice_read: None,
521+
splice_eof: None,
522+
setlease: None,
523+
fallocate: None,
524+
show_fdinfo: None,
525+
copy_file_range: None,
526+
remap_file_range: None,
527+
fadvise: None,
528+
uring_cmd: None,
529+
uring_cmd_iopoll: None,
530+
};
531+
532+
unsafe extern "C" fn read_dir_callback(
533+
file: *mut bindings::file,
534+
ctx_ptr: *mut bindings::dir_context,
535+
) -> core::ffi::c_int {
536+
from_result(|| {
537+
// SAFETY: The C API guarantees that `file` is valid for read. And since `f_inode` is
538+
// immutable, we can read it directly.
539+
let inode = unsafe { &*(*file).f_inode.cast::<INode<T>>() };
540+
541+
// SAFETY: The C API guarantees that this is the only reference to `dir_context`.
542+
let ctx = unsafe { &mut *ctx_ptr };
543+
let new_pos = T::read_dir(inode, ctx.pos, |name, foffset, ino, typ| {
544+
let Ok(name_len) = i32::try_from(name.len()) else {
545+
return false;
546+
};
547+
let Some(actor) = ctx.actor else {
548+
return false;
549+
};
550+
551+
// SAFETY: `ctx` is valid by the C API convention, and `name` is valid at least for
552+
// the duration of the `actor` call.
553+
unsafe { actor(ctx, name.as_ptr().cast(), name_len, foffset, ino, typ as _) }
554+
})?;
555+
ctx.pos = new_pos;
556+
Ok(0)
557+
})
558+
}
422559
}
423560

424561
/// Kernel module that exposes a single file system implemented by `T`.
@@ -447,7 +584,7 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
447584
///
448585
/// ```
449586
/// # mod module_ro_fs_sample {
450-
/// use kernel::fs::{NewSuperBlock, SuperBlock};
587+
/// use kernel::fs::{DirEntryType, INode, NewSuperBlock, SuperBlock};
451588
/// use kernel::prelude::*;
452589
/// use kernel::{c_str, fs};
453590
///
@@ -465,6 +602,13 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
465602
/// fn fill_super(_: NewSuperBlock<'_, Self>) -> Result<&SuperBlock<Self>> {
466603
/// todo!()
467604
/// }
605+
/// fn read_dir(
606+
/// _: &INode<Self>,
607+
/// _: i64,
608+
/// _: impl FnMut(&[u8], i64, u64, DirEntryType) -> bool,
609+
/// ) -> Result<i64> {
610+
/// todo!()
611+
/// }
468612
/// }
469613
/// # }
470614
/// ```

samples/rust/rust_rofs.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
//! Rust read-only file system sample.
44
5-
use kernel::fs::{INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams};
5+
use kernel::fs::{
6+
DirEntryType, INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams,
7+
};
68
use kernel::prelude::*;
79
use kernel::{c_str, fs, time::UNIX_EPOCH, types::Either};
810

@@ -14,6 +16,30 @@ kernel::module_fs! {
1416
license: "GPL",
1517
}
1618

19+
struct Entry {
20+
name: &'static [u8],
21+
ino: u64,
22+
etype: INodeType,
23+
}
24+
25+
const ENTRIES: [Entry; 3] = [
26+
Entry {
27+
name: b".",
28+
ino: 1,
29+
etype: INodeType::Dir,
30+
},
31+
Entry {
32+
name: b"..",
33+
ino: 1,
34+
etype: INodeType::Dir,
35+
},
36+
Entry {
37+
name: b"subdir",
38+
ino: 2,
39+
etype: INodeType::Dir,
40+
},
41+
];
42+
1743
struct RoFs;
1844
impl fs::FileSystem for RoFs {
1945
const NAME: &'static CStr = c_str!("rust-fs");
@@ -24,7 +50,7 @@ impl fs::FileSystem for RoFs {
2450
Either::Right(new) => new.init(INodeParams {
2551
typ: INodeType::Dir,
2652
mode: 0o555,
27-
size: 1,
53+
size: ENTRIES.len().try_into()?,
2854
blocks: 1,
2955
nlink: 2,
3056
uid: 0,
@@ -44,4 +70,27 @@ impl fs::FileSystem for RoFs {
4470
root,
4571
)
4672
}
73+
74+
fn read_dir(
75+
inode: &INode<Self>,
76+
mut pos: i64,
77+
mut report: impl FnMut(&[u8], i64, u64, DirEntryType) -> bool,
78+
) -> Result<i64> {
79+
if inode.ino() != 1 {
80+
return Ok(pos);
81+
}
82+
83+
if pos >= ENTRIES.len().try_into()? {
84+
return Ok(pos);
85+
}
86+
87+
for e in ENTRIES.iter().skip(pos.try_into()?) {
88+
if !report(e.name, pos, e.ino, e.etype.into()) {
89+
break;
90+
}
91+
pos += 1;
92+
}
93+
94+
Ok(pos)
95+
}
4796
}

0 commit comments

Comments
 (0)