Skip to content

Commit f9c71b3

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 <walmeida@microsoft.com>
1 parent 94240e5 commit f9c71b3

4 files changed

Lines changed: 129 additions & 16 deletions

File tree

rust/helpers.c

Lines changed: 7 additions & 0 deletions
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

Lines changed: 118 additions & 9 deletions
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: *mut bindings::file_system_type| {
182195
// SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write.
183196
unsafe { fs_ptr.write(bindings::file_system_type::default()) };
@@ -235,6 +248,16 @@ impl Registration {
235248
unsafe { T::Data::from_foreign(ptr) };
236249
}
237250
}
251+
252+
unsafe extern "C" fn inode_init_once_callback<T: FileSystem + ?Sized>(
253+
outer_inode: *mut core::ffi::c_void,
254+
) {
255+
let ptr = outer_inode.cast::<INodeWithData<T::INodeData>>();
256+
257+
// SAFETY: This is only used in `new`, so we know that we have a valid `INodeWithData`
258+
// instance whose inode part can be initialised.
259+
unsafe { bindings::inode_init_once(ptr::addr_of_mut!((*ptr).inode)) };
260+
}
238261
}
239262

240263
#[pinned_drop]
@@ -276,6 +299,15 @@ impl<T: FileSystem + ?Sized> INode<T> {
276299
unsafe { &*(*self.0.get()).i_sb.cast() }
277300
}
278301

302+
/// Returns the data associated with the inode.
303+
pub fn data(&self) -> &T::INodeData {
304+
let outerp = container_of!(self.0.get(), INodeWithData<T::INodeData>, inode);
305+
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference
306+
// (`&self`) to it. Additionally, we know `T::INodeData` is always initialised in an
307+
// `INode`.
308+
unsafe { &*(*outerp).data.as_ptr() }
309+
}
310+
279311
/// Returns the size of the inode contents.
280312
pub fn size(&self) -> i64 {
281313
// SAFETY: `self` is guaranteed to be valid by the existence of a shared reference.
@@ -296,15 +328,29 @@ unsafe impl<T: FileSystem + ?Sized> AlwaysRefCounted for INode<T> {
296328
}
297329
}
298330

331+
struct INodeWithData<T> {
332+
data: MaybeUninit<T>,
333+
inode: bindings::inode,
334+
}
335+
299336
/// An inode that is locked and hasn't been initialised yet.
300337
#[repr(transparent)]
301338
pub struct NewINode<T: FileSystem + ?Sized>(ARef<INode<T>>);
302339

303340
impl<T: FileSystem + ?Sized> NewINode<T> {
304341
/// Initialises the new inode with the given parameters.
305-
pub fn init(self, params: INodeParams) -> Result<ARef<INode<T>>> {
306-
// SAFETY: This is a new inode, so it's safe to manipulate it mutably.
307-
let inode = unsafe { &mut *self.0 .0.get() };
342+
pub fn init(self, params: INodeParams<T::INodeData>) -> Result<ARef<INode<T>>> {
343+
let outerp = container_of!(self.0 .0.get(), INodeWithData<T::INodeData>, inode);
344+
345+
// SAFETY: This is a newly-created inode. No other references to it exist, so it is
346+
// safe to mutably dereference it.
347+
let outer = unsafe { &mut *outerp.cast_mut() };
348+
349+
// N.B. We must always write this to a newly allocated inode because the free callback
350+
// expects the data to be initialised and drops it.
351+
outer.data.write(params.value);
352+
353+
let inode = &mut outer.inode;
308354

309355
let mode = match params.typ {
310356
INodeType::Dir => {
@@ -420,7 +466,7 @@ pub enum INodeType {
420466
/// Required inode parameters.
421467
///
422468
/// This is used when creating new inodes.
423-
pub struct INodeParams {
469+
pub struct INodeParams<T> {
424470
/// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to
425471
/// everyone, the owner group, and the owner.
426472
pub mode: u16,
@@ -455,6 +501,9 @@ pub struct INodeParams {
455501

456502
/// Last access time.
457503
pub atime: Timespec,
504+
505+
/// Value to attach to this node.
506+
pub value: T,
458507
}
459508

460509
/// A file system super block.
@@ -751,8 +800,12 @@ impl<T: FileSystem + ?Sized> Tables<T> {
751800
}
752801

753802
const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
754-
alloc_inode: None,
755-
destroy_inode: None,
803+
alloc_inode: if size_of::<T::INodeData>() != 0 {
804+
Some(Self::alloc_inode_callback)
805+
} else {
806+
None
807+
},
808+
destroy_inode: Some(Self::destroy_inode_callback),
756809
free_inode: None,
757810
dirty_inode: None,
758811
write_inode: None,
@@ -782,6 +835,61 @@ impl<T: FileSystem + ?Sized> Tables<T> {
782835
shutdown: None,
783836
};
784837

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

rust/kernel/mem_cache.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ impl MemCache {
2020
/// Allocates a new `kmem_cache` for type `T`.
2121
///
2222
/// `init` is called by the C code when entries are allocated.
23-
#[allow(dead_code)]
2423
pub(crate) fn try_new<T>(
2524
name: &'static CStr,
2625
init: Option<unsafe extern "C" fn(*mut core::ffi::c_void)>,
@@ -43,7 +42,6 @@ impl MemCache {
4342
/// Returns the pointer to the `kmem_cache` instance, or null if it's `None`.
4443
///
4544
/// This is a helper for functions like `alloc_inode_sb` where the cache is optional.
46-
#[allow(dead_code)]
4745
pub(crate) fn ptr(c: &Option<Self>) -> *mut bindings::kmem_cache {
4846
match c {
4947
Some(m) => m.ptr.as_ptr(),

samples/rust/rust_rofs.rs

Lines changed: 4 additions & 5 deletions
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)