Skip to content

Commit 10ea01a

Browse files
committed
rust: add support for file system parameters
This allows file system contexts to be further initialised with parameters from userspace before a fs is mounted or reconfigured. Signed-off-by: Wedson Almeida Filho <[email protected]>
1 parent 856eb0e commit 10ea01a

File tree

6 files changed

+718
-3
lines changed

6 files changed

+718
-3
lines changed

rust/helpers.c

+10
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <linux/skbuff.h>
3939
#include <linux/uaccess.h>
4040
#include <linux/uio.h>
41+
#include <linux/fs_parser.h>
4142

4243
__noreturn void rust_helper_BUG(void)
4344
{
@@ -645,6 +646,15 @@ void rust_helper_lockdep_unregister_key(struct lock_class_key *key)
645646
}
646647
EXPORT_SYMBOL_GPL(rust_helper_lockdep_unregister_key);
647648

649+
int rust_helper_fs_parse(struct fs_context *fc,
650+
const struct fs_parameter_spec *desc,
651+
struct fs_parameter *param,
652+
struct fs_parse_result *result)
653+
{
654+
return fs_parse(fc, desc, param, result);
655+
}
656+
EXPORT_SYMBOL_GPL(rust_helper_fs_parse);
657+
648658
/*
649659
* We use `bindgen`'s `--size_t-is-usize` option to bind the C `size_t` type
650660
* as the Rust `usize` type, so we can use it in contexts where Rust

rust/kernel/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ pub mod code {
304304
declare_err!(ERESTARTSYS, "Restart the system call.");
305305

306306
declare_err!(ENOTSUPP, "Operation is not supported.");
307+
308+
declare_err!(ENOPARAM, "Parameter not supported.");
307309
}
308310

309311
/// Generic integer kernel error.

rust/kernel/fs.rs

+139-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
77
use crate::{
88
bindings, error::code::*, error::from_kernel_result, str::CStr, to_result,
9-
types::PointerWrapper, AlwaysRefCounted, Result, ScopeGuard, ThisModule,
9+
types::PointerWrapper, AlwaysRefCounted, Error, Result, ScopeGuard, ThisModule,
1010
};
1111
use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin, ptr};
1212
use macros::vtable;
1313

14+
pub mod param;
15+
1416
/// A file system context.
1517
///
1618
/// It is used to gather configuration to then mount or reconfigure a file system.
@@ -19,18 +21,48 @@ pub trait Context<T: Type + ?Sized> {
1921
/// Type of the data associated with the context.
2022
type Data: PointerWrapper + Send + Sync + 'static;
2123

24+
/// The typed file system parameters.
25+
///
26+
/// Users are encouraged to define it using the [`crate::define_fs_params`] macro.
27+
const PARAMS: param::SpecTable<'static, Self::Data> = param::SpecTable::empty();
28+
2229
/// Creates a new context.
2330
fn try_new() -> Result<Self::Data>;
31+
32+
/// Parses a parameter that wasn't specified in [`Self::PARAMS`].
33+
fn parse_unknown_param(
34+
_data: &mut Self::Data,
35+
_name: &CStr,
36+
_value: param::Value<'_>,
37+
) -> Result {
38+
Err(ENOPARAM)
39+
}
40+
41+
/// Parses the whole parameter block, potentially skipping regular handling for parts of it.
42+
///
43+
/// The return value is the portion of the input buffer for which the regular handling
44+
/// (involving [`Self::PARAMS`] and [`Self::parse_unknown_param`]) will still be carried out.
45+
/// If it's `None`, the regular handling is not performed at all.
46+
fn parse_monolithic<'a>(
47+
_data: &mut Self::Data,
48+
_buf: Option<&'a mut [u8]>,
49+
) -> Result<Option<&'a mut [u8]>> {
50+
Ok(None)
51+
}
2452
}
2553

2654
struct Tables<T: Type + ?Sized>(T);
2755
impl<T: Type + ?Sized> Tables<T> {
2856
const CONTEXT: bindings::fs_context_operations = bindings::fs_context_operations {
2957
free: Some(Self::free_callback),
30-
parse_param: None,
58+
parse_param: Some(Self::parse_param_callback),
3159
get_tree: Some(Self::get_tree_callback),
3260
reconfigure: Some(Self::reconfigure_callback),
33-
parse_monolithic: None,
61+
parse_monolithic: if <T::Context as Context<T>>::HAS_PARSE_MONOLITHIC {
62+
Some(Self::parse_monolithic_callback)
63+
} else {
64+
None
65+
},
3466
dup: None,
3567
};
3668

@@ -44,6 +76,55 @@ impl<T: Type + ?Sized> Tables<T> {
4476
}
4577
}
4678

79+
unsafe extern "C" fn parse_param_callback(
80+
fc: *mut bindings::fs_context,
81+
param: *mut bindings::fs_parameter,
82+
) -> core::ffi::c_int {
83+
from_kernel_result! {
84+
// SAFETY: The callback contract guarantees that `fc` is valid.
85+
let ptr = unsafe { (*fc).fs_private };
86+
87+
// SAFETY: The value of `ptr` (coming from `fs_private` was initialised in
88+
// `init_fs_context_callback` to the result of an `into_pointer` call. Since the
89+
// context is valid, `from_pointer` wasn't called yet, so `ptr` is valid. Additionally,
90+
// the callback contract guarantees that callbacks are serialised, so it is ok to
91+
// mutably reference it.
92+
let mut data =
93+
unsafe { <<T::Context as Context<T>>::Data as PointerWrapper>::borrow_mut(ptr) };
94+
let mut result = bindings::fs_parse_result::default();
95+
// SAFETY: All parameters are valid at least for the duration of the call.
96+
let opt =
97+
unsafe { bindings::fs_parse(fc, T::Context::PARAMS.first, param, &mut result) };
98+
99+
// SAFETY: The callback contract guarantees that `param` is valid for the duration of
100+
// the callback.
101+
let param = unsafe { &*param };
102+
if opt >= 0 {
103+
let opt = opt as usize;
104+
if opt >= T::Context::PARAMS.handlers.len() {
105+
return Err(EINVAL);
106+
}
107+
T::Context::PARAMS.handlers[opt].handle_param(&mut data, param, &result)?;
108+
return Ok(0);
109+
}
110+
111+
if opt != ENOPARAM.to_kernel_errno() {
112+
return Err(Error::from_kernel_errno(opt));
113+
}
114+
115+
if !T::Context::HAS_PARSE_UNKNOWN_PARAM {
116+
return Err(ENOPARAM);
117+
}
118+
119+
let val = param::Value::from_fs_parameter(param);
120+
// SAFETY: The callback contract guarantees the parameter key to be valid and last at
121+
// least the duration of the callback.
122+
T::Context::parse_unknown_param(
123+
&mut data, unsafe { CStr::from_char_ptr(param.key) }, val)?;
124+
Ok(0)
125+
}
126+
}
127+
47128
unsafe extern "C" fn fill_super_callback(
48129
sb_ptr: *mut bindings::super_block,
49130
_fc: *mut bindings::fs_context,
@@ -64,6 +145,8 @@ impl<T: Type + ?Sized> Tables<T> {
64145
sb.s_time_gran = 1;
65146

66147
// Create and initialise the root inode.
148+
149+
// SAFETY: `sb` was just created initialised, so it is safe pass it to `new_inode`.
67150
let inode = unsafe { bindings::new_inode(sb) };
68151
if inode.is_null() {
69152
return Err(ENOMEM);
@@ -117,6 +200,40 @@ impl<T: Type + ?Sized> Tables<T> {
117200
EINVAL.to_kernel_errno()
118201
}
119202

203+
unsafe extern "C" fn parse_monolithic_callback(
204+
fc: *mut bindings::fs_context,
205+
buf: *mut core::ffi::c_void,
206+
) -> core::ffi::c_int {
207+
from_kernel_result! {
208+
// SAFETY: The callback contract guarantees that `fc` is valid.
209+
let ptr = unsafe { (*fc).fs_private };
210+
211+
// SAFETY: The value of `ptr` (coming from `fs_private` was initialised in
212+
// `init_fs_context_callback` to the result of an `into_pointer` call. Since the
213+
// context is valid, `from_pointer` wasn't called yet, so `ptr` is valid. Additionally,
214+
// the callback contract guarantees that callbacks are serialised, so it is ok to
215+
// mutably reference it.
216+
let mut data =
217+
unsafe { <<T::Context as Context<T>>::Data as PointerWrapper>::borrow_mut(ptr) };
218+
let page = if buf.is_null() {
219+
None
220+
} else {
221+
// SAFETY: This callback is called to handle the `mount` syscall, which takes a
222+
// page-sized buffer as data.
223+
Some(unsafe { &mut *ptr::slice_from_raw_parts_mut(buf.cast(), crate::PAGE_SIZE) })
224+
};
225+
let regular = T::Context::parse_monolithic(&mut data, page)?;
226+
if let Some(buf) = regular {
227+
// SAFETY: Both `fc` and `buf` are guaranteed to be valid; the former because the
228+
// callback is still ongoing and the latter because its lifefime is tied to that of
229+
// `page`, which is also valid for the duration of the callback.
230+
to_result(
231+
unsafe { bindings::generic_parse_monolithic(fc, buf.as_mut_ptr().cast()) })?;
232+
}
233+
Ok(0)
234+
}
235+
}
236+
120237
const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
121238
alloc_inode: None,
122239
destroy_inode: None,
@@ -240,6 +357,7 @@ impl Registration {
240357
fs.owner = module.0;
241358
fs.name = T::NAME.as_char_ptr();
242359
fs.fs_flags = T::FLAGS;
360+
fs.parameters = T::Context::PARAMS.first;
243361
fs.init_fs_context = Some(Self::init_fs_context_callback::<T>);
244362
fs.kill_sb = Some(Self::kill_sb_callback::<T>);
245363

@@ -372,3 +490,21 @@ unsafe impl AlwaysRefCounted for DEntry {
372490
unsafe { bindings::dput(obj.cast().as_ptr()) }
373491
}
374492
}
493+
494+
/// Wraps the kernel's `struct filename`.
495+
#[repr(transparent)]
496+
pub struct Filename(pub(crate) UnsafeCell<bindings::filename>);
497+
498+
impl Filename {
499+
/// Creates a reference to a [`Filename`] from a valid pointer.
500+
///
501+
/// # Safety
502+
///
503+
/// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the
504+
/// returned [`Filename`] instance.
505+
pub(crate) unsafe fn from_ptr<'a>(ptr: *const bindings::filename) -> &'a Filename {
506+
// SAFETY: The safety requirements guarantee the validity of the dereference, while the
507+
// `Filename` type being transparent makes the cast ok.
508+
unsafe { &*ptr.cast() }
509+
}
510+
}

0 commit comments

Comments
 (0)