Skip to content

Commit ac0f637

Browse files
committed
rust: fs: introduce FileSystem::read_dir
Allow Rust file systems to report the contents of their directory inodes. The reported entries cannot be opened yet. Signed-off-by: Wedson Almeida Filho <[email protected]>
1 parent b26f77a commit ac0f637

File tree

2 files changed

+236
-6
lines changed

2 files changed

+236
-6
lines changed

rust/kernel/fs.rs

Lines changed: 189 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,70 @@ pub trait FileSystem {
2828
/// This is called during initialisation of a superblock after [`FileSystem::super_params`] has
2929
/// completed successfully.
3030
fn init_root(sb: &SuperBlock<Self>) -> Result<ARef<INode<Self>>>;
31+
32+
/// Reads directory entries from directory inodes.
33+
///
34+
/// [`DirEmitter::pos`] holds the current position of the directory reader.
35+
fn read_dir(inode: &INode<Self>, emitter: &mut DirEmitter) -> Result;
36+
}
37+
38+
/// The types of directory entries reported by [`FileSystem::read_dir`].
39+
#[repr(u32)]
40+
#[derive(Copy, Clone)]
41+
pub enum DirEntryType {
42+
/// Unknown type.
43+
Unknown = bindings::DT_UNKNOWN,
44+
45+
/// Named pipe (first-in, first-out) type.
46+
Fifo = bindings::DT_FIFO,
47+
48+
/// Character device type.
49+
Chr = bindings::DT_CHR,
50+
51+
/// Directory type.
52+
Dir = bindings::DT_DIR,
53+
54+
/// Block device type.
55+
Blk = bindings::DT_BLK,
56+
57+
/// Regular file type.
58+
Reg = bindings::DT_REG,
59+
60+
/// Symbolic link type.
61+
Lnk = bindings::DT_LNK,
62+
63+
/// Named unix-domain socket type.
64+
Sock = bindings::DT_SOCK,
65+
66+
/// White-out type.
67+
Wht = bindings::DT_WHT,
68+
}
69+
70+
impl From<INodeType> for DirEntryType {
71+
fn from(value: INodeType) -> Self {
72+
match value {
73+
INodeType::Dir => DirEntryType::Dir,
74+
}
75+
}
76+
}
77+
78+
impl core::convert::TryFrom<u32> for DirEntryType {
79+
type Error = crate::error::Error;
80+
81+
fn try_from(v: u32) -> Result<Self> {
82+
match v {
83+
v if v == Self::Unknown as u32 => Ok(Self::Unknown),
84+
v if v == Self::Fifo as u32 => Ok(Self::Fifo),
85+
v if v == Self::Chr as u32 => Ok(Self::Chr),
86+
v if v == Self::Dir as u32 => Ok(Self::Dir),
87+
v if v == Self::Blk as u32 => Ok(Self::Blk),
88+
v if v == Self::Reg as u32 => Ok(Self::Reg),
89+
v if v == Self::Lnk as u32 => Ok(Self::Lnk),
90+
v if v == Self::Sock as u32 => Ok(Self::Sock),
91+
v if v == Self::Wht as u32 => Ok(Self::Wht),
92+
_ => Err(EDOM),
93+
}
94+
}
3195
}
3296

3397
/// A registration of a file system.
@@ -161,9 +225,7 @@ impl<T: FileSystem + ?Sized> NewINode<T> {
161225

162226
let mode = match params.typ {
163227
INodeType::Dir => {
164-
// SAFETY: `simple_dir_operations` never changes, it's safe to reference it.
165-
inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations };
166-
228+
inode.__bindgen_anon_3.i_fop = &Tables::<T>::DIR_FILE_OPERATIONS;
167229
// SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it.
168230
inode.i_op = unsafe { &bindings::simple_dir_inode_operations };
169231
bindings::S_IFDIR
@@ -403,6 +465,126 @@ impl<T: FileSystem + ?Sized> Tables<T> {
403465
free_cached_objects: None,
404466
shutdown: None,
405467
};
468+
469+
const DIR_FILE_OPERATIONS: bindings::file_operations = bindings::file_operations {
470+
owner: ptr::null_mut(),
471+
llseek: Some(bindings::generic_file_llseek),
472+
read: Some(bindings::generic_read_dir),
473+
write: None,
474+
read_iter: None,
475+
write_iter: None,
476+
iopoll: None,
477+
iterate_shared: Some(Self::read_dir_callback),
478+
poll: None,
479+
unlocked_ioctl: None,
480+
compat_ioctl: None,
481+
mmap: None,
482+
mmap_supported_flags: 0,
483+
open: None,
484+
flush: None,
485+
release: None,
486+
fsync: None,
487+
fasync: None,
488+
lock: None,
489+
get_unmapped_area: None,
490+
check_flags: None,
491+
flock: None,
492+
splice_write: None,
493+
splice_read: None,
494+
splice_eof: None,
495+
setlease: None,
496+
fallocate: None,
497+
show_fdinfo: None,
498+
copy_file_range: None,
499+
remap_file_range: None,
500+
fadvise: None,
501+
uring_cmd: None,
502+
uring_cmd_iopoll: None,
503+
};
504+
505+
unsafe extern "C" fn read_dir_callback(
506+
file: *mut bindings::file,
507+
ctx_ptr: *mut bindings::dir_context,
508+
) -> core::ffi::c_int {
509+
from_result(|| {
510+
// SAFETY: The C API guarantees that `file` is valid for read. And since `f_inode` is
511+
// immutable, we can read it directly.
512+
let inode = unsafe { &*(*file).f_inode.cast::<INode<T>>() };
513+
514+
// SAFETY: The C API guarantees that this is the only reference to the `dir_context`
515+
// instance.
516+
let emitter = unsafe { &mut *ctx_ptr.cast::<DirEmitter>() };
517+
let orig_pos = emitter.pos();
518+
519+
// Call the module implementation. We ignore errors if directory entries have been
520+
// succesfully emitted: this is because we want users to see them before the error.
521+
match T::read_dir(inode, emitter) {
522+
Ok(_) => Ok(0),
523+
Err(e) => {
524+
if emitter.pos() == orig_pos {
525+
Err(e)
526+
} else {
527+
Ok(0)
528+
}
529+
}
530+
}
531+
})
532+
}
533+
}
534+
535+
/// Directory entry emitter.
536+
///
537+
/// This is used in [`FileSystem::read_dir`] implementations to report the directory entry.
538+
#[repr(transparent)]
539+
pub struct DirEmitter(bindings::dir_context);
540+
541+
impl DirEmitter {
542+
/// Returns the current position of the emitter.
543+
pub fn pos(&self) -> i64 {
544+
self.0.pos
545+
}
546+
547+
/// Emits a directory entry.
548+
///
549+
/// `pos_inc` is the number with which to increment the current position on success.
550+
///
551+
/// `name` is the name of the entry.
552+
///
553+
/// `ino` is the inode number of the entry.
554+
///
555+
/// `etype` is the type of the entry.
556+
///
557+
/// Returns `false` when the entry could not be emitted, possibly because the user-provided
558+
/// buffer is full.
559+
pub fn emit(&mut self, pos_inc: i64, name: &[u8], ino: Ino, etype: DirEntryType) -> bool {
560+
let Ok(name_len) = i32::try_from(name.len()) else {
561+
return false;
562+
};
563+
564+
let Some(actor) = self.0.actor else {
565+
return false;
566+
};
567+
568+
let Some(new_pos) = self.0.pos.checked_add(pos_inc) else {
569+
return false;
570+
};
571+
572+
// SAFETY: `name` is valid at least for the duration of the `actor` call.
573+
let ret = unsafe {
574+
actor(
575+
&mut self.0,
576+
name.as_ptr().cast(),
577+
name_len,
578+
self.0.pos,
579+
ino,
580+
etype as _,
581+
)
582+
};
583+
if ret {
584+
self.0.pos = new_pos;
585+
}
586+
ret
587+
}
406588
}
407589

408590
/// Kernel module that exposes a single file system implemented by `T`.
@@ -431,7 +613,7 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
431613
///
432614
/// ```
433615
/// # mod module_fs_sample {
434-
/// use kernel::fs::{INode, NewSuperBlock, SuperBlock, SuperParams};
616+
/// use kernel::fs::{DirEmitter, INode, NewSuperBlock, SuperBlock, SuperParams};
435617
/// use kernel::prelude::*;
436618
/// use kernel::{c_str, fs, types::ARef};
437619
///
@@ -452,6 +634,9 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
452634
/// fn init_root(_sb: &SuperBlock<Self>) -> Result<ARef<INode<Self>>> {
453635
/// todo!()
454636
/// }
637+
/// fn read_dir(_: &INode<Self>, _: &mut DirEmitter) -> Result {
638+
/// todo!()
639+
/// }
455640
/// }
456641
/// # }
457642
/// ```

samples/rust/rust_rofs.rs

Lines changed: 47 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::{INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams};
5+
use kernel::fs::{
6+
DirEmitter, INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams,
7+
};
68
use kernel::prelude::*;
79
use kernel::{c_str, fs, time::UNIX_EPOCH, types::ARef, 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");
@@ -33,7 +59,7 @@ impl fs::FileSystem for RoFs {
3359
Either::Right(new) => new.init(INodeParams {
3460
typ: INodeType::Dir,
3561
mode: 0o555,
36-
size: 1,
62+
size: ENTRIES.len().try_into()?,
3763
blocks: 1,
3864
nlink: 2,
3965
uid: 0,
@@ -44,4 +70,23 @@ impl fs::FileSystem for RoFs {
4470
}),
4571
}
4672
}
73+
74+
fn read_dir(inode: &INode<Self>, emitter: &mut DirEmitter) -> Result {
75+
if inode.ino() != 1 {
76+
return Ok(());
77+
}
78+
79+
let pos = emitter.pos();
80+
if pos >= ENTRIES.len().try_into()? {
81+
return Ok(());
82+
}
83+
84+
for e in ENTRIES.iter().skip(pos.try_into()?) {
85+
if !emitter.emit(1, e.name, e.ino, e.etype.into()) {
86+
break;
87+
}
88+
}
89+
90+
Ok(())
91+
}
4792
}

0 commit comments

Comments
 (0)