Skip to content

Commit d09a60e

Browse files
committed
rust: fs: allow per-inode data
This allows file systems to attach extra [typed] data to each inode. If no data is needed, we use the regular inode kmem_cache, otherwise we create a new one. Signed-off-by: Wedson Almeida Filho <[email protected]>
1 parent 1722f55 commit d09a60e

File tree

4 files changed

+129
-16
lines changed

4 files changed

+129
-16
lines changed

rust/helpers.c

+7
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@ void rust_helper_kunmap_local(const void *vaddr)
205205
}
206206
EXPORT_SYMBOL_GPL(rust_helper_kunmap_local);
207207

208+
void *rust_helper_alloc_inode_sb(struct super_block *sb,
209+
struct kmem_cache *cache, gfp_t gfp)
210+
{
211+
return alloc_inode_sb(sb, cache, gfp);
212+
}
213+
EXPORT_SYMBOL_GPL(rust_helper_alloc_inode_sb);
214+
208215
void rust_helper_i_uid_write(struct inode *inode, uid_t uid)
209216
{
210217
i_uid_write(inode, uid);

rust/kernel/fs.rs

+118-9
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
use crate::error::{code::*, from_result, to_result, Error, Result};
1010
use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque};
1111
use crate::{
12-
bindings, folio::LockedFolio, init::PinInit, str::CStr, time::Timespec, try_pin_init,
13-
ThisModule,
12+
bindings, container_of, folio::LockedFolio, init::PinInit, mem_cache::MemCache, str::CStr,
13+
time::Timespec, try_pin_init, ThisModule,
1414
};
15-
use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr};
15+
use core::mem::{size_of, ManuallyDrop, MaybeUninit};
16+
use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
1617
use macros::{pin_data, pinned_drop};
1718

1819
#[cfg(CONFIG_BUFFER_HEAD)]
@@ -37,6 +38,9 @@ pub trait FileSystem {
3738
/// Data associated with each file system instance (super-block).
3839
type Data: ForeignOwnable + Send + Sync;
3940

41+
/// Type of data associated with each inode.
42+
type INodeData: Send + Sync;
43+
4044
/// The name of the file system type.
4145
const NAME: &'static CStr;
4246

@@ -161,6 +165,7 @@ impl core::convert::TryFrom<u32> for DirEntryType {
161165
pub struct Registration {
162166
#[pin]
163167
fs: Opaque<bindings::file_system_type>,
168+
inode_cache: Option<MemCache>,
164169
#[pin]
165170
_pin: PhantomPinned,
166171
}
@@ -178,6 +183,14 @@ impl Registration {
178183
pub fn new<T: FileSystem + ?Sized>(module: &'static ThisModule) -> impl PinInit<Self, Error> {
179184
try_pin_init!(Self {
180185
_pin: PhantomPinned,
186+
inode_cache: if size_of::<T::INodeData>() == 0 {
187+
None
188+
} else {
189+
Some(MemCache::try_new::<INodeWithData<T::INodeData>>(
190+
T::NAME,
191+
Some(Self::inode_init_once_callback::<T>),
192+
)?)
193+
},
181194
fs <- Opaque::try_ffi_init(|fs_ptr| {
182195
// SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write.
183196
let fs = unsafe { &mut *fs_ptr };
@@ -232,6 +245,16 @@ impl Registration {
232245
unsafe { T::Data::from_foreign(ptr) };
233246
}
234247
}
248+
249+
unsafe extern "C" fn inode_init_once_callback<T: FileSystem + ?Sized>(
250+
outer_inode: *mut core::ffi::c_void,
251+
) {
252+
let ptr = outer_inode.cast::<INodeWithData<T::INodeData>>();
253+
254+
// SAFETY: This is only used in `new`, so we know that we have a valid `INodeWithData`
255+
// instance whose inode part can be initialised.
256+
unsafe { bindings::inode_init_once(ptr::addr_of_mut!((*ptr).inode)) };
257+
}
235258
}
236259

237260
#[pinned_drop]
@@ -270,6 +293,15 @@ impl<T: FileSystem + ?Sized> INode<T> {
270293
unsafe { &*(*self.0.get()).i_sb.cast() }
271294
}
272295

296+
/// Returns the data associated with the inode.
297+
pub fn data(&self) -> &T::INodeData {
298+
let outerp = container_of!(self.0.get(), INodeWithData<T::INodeData>, inode);
299+
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference
300+
// (`&self`) to it. Additionally, we know `T::INodeData` is always initialised in an
301+
// `INode`.
302+
unsafe { &*(*outerp).data.as_ptr() }
303+
}
304+
273305
/// Returns the size of the inode contents.
274306
pub fn size(&self) -> i64 {
275307
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference.
@@ -290,15 +322,29 @@ unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for INode<T> {
290322
}
291323
}
292324

325+
struct INodeWithData<T> {
326+
data: MaybeUninit<T>,
327+
inode: bindings::inode,
328+
}
329+
293330
/// An inode that is locked and hasn't been initialised yet.
294331
#[repr(transparent)]
295332
pub struct NewINode<T: FileSystem + ?Sized>(ARef<INode<T>>);
296333

297334
impl<T: FileSystem + ?Sized> NewINode<T> {
298335
/// Initialises the new inode with the given parameters.
299-
pub fn init(self, params: INodeParams) -> Result<ARef<INode<T>>> {
300-
// SAFETY: This is a new inode, so it's safe to manipulate it mutably.
301-
let inode = unsafe { &mut *self.0 .0.get() };
336+
pub fn init(self, params: INodeParams<T::INodeData>) -> Result<ARef<INode<T>>> {
337+
let outerp = container_of!(self.0 .0.get(), INodeWithData<T::INodeData>, inode);
338+
339+
// SAFETY: This is a newly-created inode. No other references to it exist, so it is
340+
// safe to mutably dereference it.
341+
let outer = unsafe { &mut *outerp.cast_mut() };
342+
343+
// N.B. We must always write this to a newly allocated inode because the free callback
344+
// expects the data to be initialised and drops it.
345+
outer.data.write(params.value);
346+
347+
let inode = &mut outer.inode;
302348

303349
let mode = match params.typ {
304350
INodeType::Dir => {
@@ -414,7 +460,7 @@ pub enum INodeType {
414460
/// Required inode parameters.
415461
///
416462
/// This is used when creating new inodes.
417-
pub struct INodeParams {
463+
pub struct INodeParams<T> {
418464
/// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to
419465
/// everyone, the owner group, and the owner.
420466
pub mode: u16,
@@ -449,6 +495,9 @@ pub struct INodeParams {
449495

450496
/// Last access time.
451497
pub atime: Timespec,
498+
499+
/// Value to attach to this node.
500+
pub value: T,
452501
}
453502

454503
/// A file system super block.
@@ -745,8 +794,12 @@ impl<T: FileSystem + ?Sized> Tables<T> {
745794
}
746795

747796
const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
748-
alloc_inode: None,
749-
destroy_inode: None,
797+
alloc_inode: if size_of::<T::INodeData>() != 0 {
798+
Some(Self::alloc_inode_callback)
799+
} else {
800+
None
801+
},
802+
destroy_inode: Some(Self::destroy_inode_callback),
750803
free_inode: None,
751804
dirty_inode: None,
752805
write_inode: None,
@@ -776,6 +829,61 @@ impl<T: FileSystem + ?Sized> Tables<T> {
776829
shutdown: None,
777830
};
778831

832+
unsafe extern "C" fn alloc_inode_callback(
833+
sb: *mut bindings::super_block,
834+
) -> *mut bindings::inode {
835+
// SAFETY: The callback contract guarantees that `sb` is valid for read.
836+
let super_type = unsafe { (*sb).s_type };
837+
838+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
839+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
840+
// superblock associated to it.
841+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
842+
843+
// SAFETY: `sb` and `cache` are guaranteed to be valid by the callback contract and by
844+
// the existence of a superblock respectively.
845+
let ptr = unsafe {
846+
bindings::alloc_inode_sb(sb, MemCache::ptr(&reg.inode_cache), bindings::GFP_KERNEL)
847+
}
848+
.cast::<INodeWithData<T::INodeData>>();
849+
if ptr.is_null() {
850+
return ptr::null_mut();
851+
}
852+
ptr::addr_of_mut!((*ptr).inode)
853+
}
854+
855+
unsafe extern "C" fn destroy_inode_callback(inode: *mut bindings::inode) {
856+
// SAFETY: By the C contract, `inode` is a valid pointer.
857+
let is_bad = unsafe { bindings::is_bad_inode(inode) };
858+
859+
// SAFETY: The inode is guaranteed to be valid by the callback contract. Additionally, the
860+
// superblock is also guaranteed to still be valid by the inode existence.
861+
let super_type = unsafe { (*(*inode).i_sb).s_type };
862+
863+
// SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily
864+
// embedded in a `Registration`, which is guaranteed to be valid because it has a
865+
// superblock associated to it.
866+
let reg = unsafe { &*container_of!(super_type, Registration, fs) };
867+
let ptr = container_of!(inode, INodeWithData<T::INodeData>, inode).cast_mut();
868+
869+
if !is_bad {
870+
// SAFETY: The code either initialises the data or marks the inode as bad. Since the
871+
// inode is not bad, the data is initialised, and thus safe to drop.
872+
unsafe { ptr::drop_in_place((*ptr).data.as_mut_ptr()) };
873+
}
874+
875+
if size_of::<T::INodeData>() == 0 {
876+
// SAFETY: When the size of `INodeData` is zero, we don't use a separate mem_cache, so
877+
// it is allocated from the regular mem_cache, which is what `free_inode_nonrcu` uses
878+
// to free the inode.
879+
unsafe { bindings::free_inode_nonrcu(inode) };
880+
} else {
881+
// The callback contract guarantees that the inode was previously allocated via the
882+
// `alloc_inode_callback` callback, so it is safe to free it back to the cache.
883+
unsafe { bindings::kmem_cache_free(MemCache::ptr(&reg.inode_cache), ptr.cast()) };
884+
}
885+
}
886+
779887
unsafe extern "C" fn statfs_callback(
780888
dentry: *mut bindings::dentry,
781889
buf: *mut bindings::kstatfs,
@@ -1119,6 +1227,7 @@ impl<T: FileSystem + ?Sized + Sync + Send> crate::InPlaceModule for Module<T> {
11191227
/// struct MyFs;
11201228
/// impl fs::FileSystem for MyFs {
11211229
/// type Data = ();
1230+
/// type INodeData =();
11221231
/// const NAME: &'static CStr = c_str!("myfs");
11231232
/// fn fill_super(_: NewSuperBlock<'_, Self>) -> Result<&SuperBlock<Self>> {
11241233
/// todo!()

rust/kernel/mem_cache.rs

-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ pub(crate) struct MemCache {
1717
}
1818

1919
impl MemCache {
20-
#[allow(dead_code)]
2120
pub(crate) fn try_new<T>(
2221
name: &'static CStr,
2322
init: Option<unsafe extern "C" fn(*mut core::ffi::c_void)>,
@@ -37,7 +36,6 @@ impl MemCache {
3736
Ok(Self { ptr })
3837
}
3938

40-
#[allow(dead_code)]
4139
pub(crate) fn ptr(c: &Option<Self>) -> *mut bindings::kmem_cache {
4240
match c {
4341
Some(m) => m.ptr.as_ptr(),

samples/rust/rust_rofs.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const ENTRIES: [Entry; 4] = [
5353
struct RoFs;
5454
impl fs::FileSystem for RoFs {
5555
type Data = ();
56+
type INodeData = &'static Entry;
5657
const NAME: &'static CStr = c_str!("rust-fs");
5758

5859
fn fill_super(sb: NewSuperBlock<'_, Self>) -> Result<&SuperBlock<Self>> {
@@ -77,6 +78,7 @@ impl fs::FileSystem for RoFs {
7778
atime: UNIX_EPOCH,
7879
ctime: UNIX_EPOCH,
7980
mtime: UNIX_EPOCH,
81+
value: &ENTRIES[0],
8082
})?,
8183
};
8284
sb.init_root(root)
@@ -121,6 +123,7 @@ impl fs::FileSystem for RoFs {
121123
atime: UNIX_EPOCH,
122124
ctime: UNIX_EPOCH,
123125
mtime: UNIX_EPOCH,
126+
value: e,
124127
}),
125128
};
126129
}
@@ -130,11 +133,7 @@ impl fs::FileSystem for RoFs {
130133
}
131134

132135
fn read_folio(inode: &INode<Self>, mut folio: LockedFolio<'_>) -> Result {
133-
let data = match inode.ino() {
134-
2 => ENTRIES[2].contents,
135-
3 => ENTRIES[3].contents,
136-
_ => return Err(EINVAL),
137-
};
136+
let data = inode.data().contents;
138137

139138
let pos = usize::try_from(folio.pos()).unwrap_or(usize::MAX);
140139
let copied = if pos >= data.len() {

0 commit comments

Comments
 (0)