From a3fe8d85ed5125dab2283ee2a2c7b4ea5819e8b0 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:08 -0300 Subject: [PATCH 01/29] xattr: make the xattr array itself const As it is currently declared, the xattr_handler structs are const but the array containing their pointers is not. This patch makes it so that fs modules can place them in .rodata, which makes it harder for accidental/malicious modifications at runtime. Signed-off-by: Wedson Almeida Filho --- fs/xattr.c | 6 +++--- include/linux/fs.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xattr.c b/fs/xattr.c index efd4736bc94b09..417440087e7bf7 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -56,7 +56,7 @@ strcmp_prefix(const char *a, const char *a_prefix) static const struct xattr_handler * xattr_resolve_name(struct inode *inode, const char **name) { - const struct xattr_handler **handlers = inode->i_sb->s_xattr; + const struct xattr_handler *const *handlers = inode->i_sb->s_xattr; const struct xattr_handler *handler; if (!(inode->i_opflags & IOP_XATTR)) { @@ -162,7 +162,7 @@ xattr_permission(struct mnt_idmap *idmap, struct inode *inode, int xattr_supports_user_prefix(struct inode *inode) { - const struct xattr_handler **handlers = inode->i_sb->s_xattr; + const struct xattr_handler *const *handlers = inode->i_sb->s_xattr; const struct xattr_handler *handler; if (!(inode->i_opflags & IOP_XATTR)) { @@ -999,7 +999,7 @@ int xattr_list_one(char **buffer, ssize_t *remaining_size, const char *name) ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size) { - const struct xattr_handler *handler, **handlers = dentry->d_sb->s_xattr; + const struct xattr_handler *handler, *const *handlers = dentry->d_sb->s_xattr; ssize_t remaining_size = buffer_size; int err = 0; diff --git a/include/linux/fs.h b/include/linux/fs.h index b528f063e8ffaa..62f04af477d2ab 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1206,7 +1206,7 @@ struct super_block { #ifdef CONFIG_SECURITY void *s_security; #endif - const struct xattr_handler **s_xattr; + const struct xattr_handler *const *s_xattr; #ifdef CONFIG_FS_ENCRYPTION const struct fscrypt_operations *s_cop; struct fscrypt_keyring *s_master_keys; /* master crypto keys in use */ From 484ec70025ff9887d9ca228ec631264039cee355 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:08 -0300 Subject: [PATCH 02/29] rust: introduce `InPlaceModule` This allows modules to be initialised in-place in pinned memory, which enables the usage of pinned types (e.g., mutexes, spinlocks, driver registrations, etc.) in modules without any extra allocations. Drivers that don't need this may continue to implement `Module` without any changes. Signed-off-by: Wedson Almeida Filho --- rust/kernel/lib.rs | 26 +++++++++++++++++++++++++- rust/macros/module.rs | 18 ++++++------------ scripts/Makefile.build | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index e8811700239aaf..584e79885cf4d3 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -17,6 +17,7 @@ #![feature(dispatch_from_dyn)] #![feature(new_uninit)] #![feature(receiver_trait)] +#![feature(return_position_impl_trait_in_trait)] #![feature(unsize)] // Ensure conditional compilation based on the kernel configuration works; @@ -60,7 +61,7 @@ const __LOG_PREFIX: &[u8] = b"rust_kernel\0"; /// The top level entrypoint to implementing a kernel module. /// /// For any teardown or cleanup operations, your type may implement [`Drop`]. -pub trait Module: Sized + Sync { +pub trait Module: Sized + Sync + Send { /// Called at module initialization time. /// /// Use this method to perform whatever setup or registration your module @@ -70,6 +71,29 @@ pub trait Module: Sized + Sync { fn init(module: &'static ThisModule) -> error::Result; } +/// A module that is pinned and initialised in-place. +pub trait InPlaceModule: Sync + Send { + /// Creates an initialiser for the module. + /// + /// It is called when the module is loaded. + fn init(module: &'static ThisModule) -> impl init::PinInit; +} + +impl InPlaceModule for T { + fn init(module: &'static ThisModule) -> impl init::PinInit { + let initer = move |slot: *mut Self| { + let m = ::init(module)?; + + // SAFETY: `slot` is valid for write per the contract with `pin_init_from_closure`. + unsafe { slot.write(m) }; + Ok(()) + }; + + // SAFETY: On success, `initer` always fully initialises an instance of `Self`. + unsafe { init::pin_init_from_closure(initer) } + } +} + /// Equivalent to `THIS_MODULE` in the C API. /// /// C header: `include/linux/export.h` diff --git a/rust/macros/module.rs b/rust/macros/module.rs index d62d8710d77ab0..9152bd691c5a6d 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -208,7 +208,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { #[used] static __IS_RUST_MODULE: () = (); - static mut __MOD: Option<{type_}> = None; + static mut __MOD: core::mem::MaybeUninit<{type_}> = core::mem::MaybeUninit::uninit(); // SAFETY: `__this_module` is constructed by the kernel at load time and will not be // freed until the module is unloaded. @@ -270,23 +270,17 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream { }} fn __init() -> core::ffi::c_int {{ - match <{type_} as kernel::Module>::init(&THIS_MODULE) {{ - Ok(m) => {{ - unsafe {{ - __MOD = Some(m); - }} - return 0; - }} - Err(e) => {{ - return e.to_errno(); - }} + let initer = <{type_} as kernel::InPlaceModule>::init(&THIS_MODULE); + match unsafe {{ initer.__pinned_init(__MOD.as_mut_ptr()) }} {{ + Ok(m) => 0, + Err(e) => e.to_errno(), }} }} fn __exit() {{ unsafe {{ // Invokes `drop()` on `__MOD`, which should be used for cleanup. - __MOD = None; + __MOD.assume_init_drop(); }} }} diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 82e3fb19fdafc9..9731092b2e4fb5 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -262,7 +262,7 @@ $(obj)/%.lst: $(src)/%.c FORCE # Compile Rust sources (.rs) # --------------------------------------------------------------------------- -rust_allowed_features := new_uninit +rust_allowed_features := new_uninit,impl_trait_in_assoc_type # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree From da1a2b6f3f806225783ec0420ecec1d87e8c3fa8 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:08 -0300 Subject: [PATCH 03/29] samples: rust: add in-place initialisation sample This is a modified version of rust_minimal that is initialised in-place. Signed-off-by: Wedson Almeida Filho --- samples/rust/Kconfig | 11 ++++++++++ samples/rust/Makefile | 1 + samples/rust/rust_inplace.rs | 42 ++++++++++++++++++++++++++++++++++++ scripts/Makefile.build | 2 +- 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 samples/rust/rust_inplace.rs diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index b0f74a81c8f9ad..59f44a8b6958ef 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -20,6 +20,17 @@ config SAMPLE_RUST_MINIMAL If unsure, say N. +config SAMPLE_RUST_INPLACE + tristate "Minimal in-place" + help + This option builds the Rust minimal module with in-place + initialisation. + + To compile this as a module, choose M here: + the module will be called rust_inplace. + + If unsure, say N. + config SAMPLE_RUST_PRINT tristate "Printing macros" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 03086dabbea44f..791fc18180e98b 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o +obj-$(CONFIG_SAMPLE_RUST_INPLACE) += rust_inplace.o obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs diff --git a/samples/rust/rust_inplace.rs b/samples/rust/rust_inplace.rs new file mode 100644 index 00000000000000..db92a24643a788 --- /dev/null +++ b/samples/rust/rust_inplace.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust minimal in-place sample. + +use kernel::prelude::*; + +module! { + type: RustInPlace, + name: "rust_inplace", + author: "Rust for Linux Contributors", + description: "Rust minimal in-place sample", + license: "GPL", +} + +#[pin_data(PinnedDrop)] +struct RustInPlace { + numbers: Vec, +} + +impl kernel::InPlaceModule for RustInPlace { + fn init(_module: &'static ThisModule) -> impl PinInit { + pr_info!("Rust minimal sample (init)\n"); + pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); + try_pin_init!(Self { + numbers: { + let mut numbers = Vec::new(); + numbers.try_push(72)?; + numbers.try_push(108)?; + numbers.try_push(200)?; + numbers + }, + }) + } +} + +#[pinned_drop] +impl PinnedDrop for RustInPlace { + fn drop(self: Pin<&mut Self>) { + pr_info!("My numbers are {:?}\n", self.numbers); + pr_info!("Rust minimal inplace sample (exit)\n"); + } +} diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 9731092b2e4fb5..10f5b7d7040237 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -262,7 +262,7 @@ $(obj)/%.lst: $(src)/%.c FORCE # Compile Rust sources (.rs) # --------------------------------------------------------------------------- -rust_allowed_features := new_uninit,impl_trait_in_assoc_type +rust_allowed_features := new_uninit,return_position_impl_trait_in_trait # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree From 883e433c37f7e4aec46c85c0e31a8cdac79eb674 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:09 -0300 Subject: [PATCH 04/29] rust: add the `container_of` macro This is the Rust version of the macro with the same name in C. It produces a raw pointer to an outer type from a pointer to an inner field. Signed-off-by: Wedson Almeida Filho --- rust/kernel/lib.rs | 33 +++++++++++++++++++++++++++++++++ scripts/Makefile.build | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 584e79885cf4d3..03042bacdf1550 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -16,6 +16,7 @@ #![feature(coerce_unsized)] #![feature(dispatch_from_dyn)] #![feature(new_uninit)] +#![feature(offset_of)] #![feature(receiver_trait)] #![feature(return_position_impl_trait_in_trait)] #![feature(unsize)] @@ -113,6 +114,38 @@ impl ThisModule { } } +/// Produces a pointer to an object from a pointer to one of its fields. +/// +/// # Safety +/// +/// Callers must ensure that the pointer to the field is in fact a pointer to the specified field, +/// as opposed to a pointer to another object of the same type. If this condition is not met, +/// any dereference of the resulting pointer is UB. +/// +/// # Examples +/// +/// ``` +/// # use kernel::container_of; +/// struct Test { +/// a: u64, +/// b: u32, +/// } +/// +/// let test = Test { a: 10, b: 20 }; +/// let b_ptr = &test.b; +/// let test_alias = container_of!(b_ptr, Test, b); +/// assert!(core::ptr::eq(&test, test_alias)); +/// ``` +#[macro_export] +macro_rules! container_of { + ($ptr:expr, $type:ty, $($f:tt)*) => {{ + let ptr = $ptr as *const _ as *const u8; + let offset = ::core::mem::offset_of!($type, $($f)*); + $crate::build_assert!(offset <= isize::MAX as usize); + ptr.wrapping_sub(offset) as *const $type + }} +} + #[cfg(not(any(testlib, test)))] #[panic_handler] fn panic(info: &core::panic::PanicInfo<'_>) -> ! { diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 10f5b7d7040237..f57d40deebdb8f 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -262,7 +262,7 @@ $(obj)/%.lst: $(src)/%.c FORCE # Compile Rust sources (.rs) # --------------------------------------------------------------------------- -rust_allowed_features := new_uninit,return_position_impl_trait_in_trait +rust_allowed_features := new_uninit,return_position_impl_trait_in_trait,offset_of # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree From 14513c0b73057b2d4013f9e3628071aa59736317 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:09 -0300 Subject: [PATCH 05/29] rust: init: introduce `Opaque::try_ffi_init` We'll need it, for example, when calling `register_filesystem` to initialise a file system registration. Signed-off-by: Wedson Almeida Filho --- rust/kernel/types.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index fdb778e65d79d3..81bf1534cc70a1 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -237,14 +237,22 @@ impl Opaque { /// uninitialized. Additionally, access to the inner `T` requires `unsafe`, so the caller needs /// to verify at that point that the inner value is valid. pub fn ffi_init(init_func: impl FnOnce(*mut T)) -> impl PinInit { + Self::try_ffi_init(move |slot| { + init_func(slot); + Ok(()) + }) + } + + /// Similar to [`Self::ffi_init`], except that the closure can fail. + /// + /// To avoid leaks on failure, the closure must drop any fields it has initialised before the + /// failure. + pub fn try_ffi_init( + init_func: impl FnOnce(*mut T) -> Result<(), E>, + ) -> impl PinInit { // SAFETY: We contain a `MaybeUninit`, so it is OK for the `init_func` to not fully // initialize the `T`. - unsafe { - init::pin_init_from_closure::<_, ::core::convert::Infallible>(move |slot| { - init_func(Self::raw_get(slot)); - Ok(()) - }) - } + unsafe { init::pin_init_from_closure(|slot| init_func(Self::raw_get(slot))) } } /// Returns a raw pointer to the opaque data. From c7d0fb29a11d07cb9e826bbcec4696b984582e73 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:09 -0300 Subject: [PATCH 06/29] rust: time: introduce `time` module It only contains the bare minimum to implement inode timestamps. Signed-off-by: Wedson Almeida Filho --- rust/kernel/lib.rs | 1 + rust/kernel/time.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 rust/kernel/time.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 03042bacdf1550..a74ea4691fac3a 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -46,6 +46,7 @@ pub mod std_vendor; pub mod str; pub mod sync; pub mod task; +pub mod time; pub mod types; #[doc(hidden)] diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs new file mode 100644 index 00000000000000..a380f920b6f0ea --- /dev/null +++ b/rust/kernel/time.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Time representation in the kernel. +//! +//! C headers: [`include/linux/time64.h`](../../include/linux/time64.h) + +use crate::{bindings, error::code::*, error::Result}; + +/// A [`Timespec`] instance at the Unix epoch. +pub const UNIX_EPOCH: Timespec = Timespec { + t: bindings::timespec64 { + tv_sec: 0, + tv_nsec: 0, + }, +}; + +/// A timestamp. +#[derive(Copy, Clone)] +#[repr(transparent)] +pub struct Timespec { + t: bindings::timespec64, +} + +impl Timespec { + /// Creates a new timestamp. + /// + /// `sec` is the number of seconds since the Unix epoch. `nsec` is the number of nanoseconds + /// within that second. + pub fn new(sec: u64, nsec: u32) -> Result { + if nsec >= 1000000000 { + return Err(EDOM); + } + + Ok(Self { + t: bindings::timespec64 { + tv_sec: sec.try_into()?, + tv_nsec: nsec.try_into()?, + }, + }) + } +} + +impl From for bindings::timespec64 { + fn from(v: Timespec) -> Self { + v.t + } +} From ca4a93caff8b96a54a68fb052959801468bce01a Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:09 -0300 Subject: [PATCH 07/29] rust: types: add little-endian type It allows us to read/write data in little-endian format. Compared to just using the correctly-sized integer type, it has the advantage of forcing users to convert to and from little endian format (which will be no-ops in little-endian architectures). Signed-off-by: Wedson Almeida Filho --- rust/kernel/types.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index 81bf1534cc70a1..36cdb3db771801 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -395,3 +395,71 @@ pub enum Either { /// Constructs an instance of [`Either`] containing a value of type `R`. Right(R), } + +/// A type that can be represented in little-endian bytes. +pub trait LittleEndian { + /// Converts from native to little-endian encoding. + fn to_le(self) -> Self; + + /// Converts from little-endian to the CPU's encoding. + fn to_cpu(self) -> Self; +} + +macro_rules! define_le { + ($($t:ty),+) => { + $( + impl LittleEndian for $t { + fn to_le(self) -> Self { + Self::to_le(self) + } + + fn to_cpu(self) -> Self { + Self::from_le(self) + } + } + )* + }; +} + +define_le!(u8, u16, u32, u64, i8, i16, i32, i64); + +/// A little-endian representation of `T`. +/// +/// # Examples +/// +/// ``` +/// use kernel::types::LE; +/// +/// struct Example { +/// a: LE, +/// b: LE, +/// } +/// +/// fn new(x: u32, y: u32) -> Example { +/// Example { +/// a: x.into(), // Converts to LE. +/// b: y.into(), // Converts to LE. +/// } +/// } +/// +/// fn sum(e: &Example) -> u32 { +/// // `value` extracts the value in cpu representation. +/// e.a.value() + e.b.value() +/// } +/// ``` +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct LE(T); + +impl LE { + /// Returns the native-endian value. + pub fn value(&self) -> T { + self.0.to_cpu() + } +} + +impl core::convert::From for LE { + fn from(value: T) -> LE { + LE(value.to_le()) + } +} From a44bdccf050c7b07d0463ea5021aa08782f1bbd7 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:09 -0300 Subject: [PATCH 08/29] rust: types: introduce `FromBytes` trait If a type `T` implements this trait, it's possible to safely get a shared reference to a `T` from a byte slice. This commit also provides a macro that allows the automatic derivation of `FromBytes` for simple structs whose fields all implement `FromBytes` as well. Signed-off-by: Wedson Almeida Filho --- rust/kernel/types.rs | 101 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index 36cdb3db771801..02c041a5dccfa3 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -7,7 +7,7 @@ use alloc::boxed::Box; use core::{ cell::UnsafeCell, marker::{PhantomData, PhantomPinned}, - mem::MaybeUninit, + mem::{align_of, size_of, MaybeUninit}, ops::{Deref, DerefMut}, ptr::NonNull, }; @@ -463,3 +463,102 @@ impl core::convert::From for LE { LE(value.to_le()) } } + +/// Specifies that a type is safely readable from byte slices. +/// +/// Not all types can be safely read from byte slices; examples from +/// include [`bool`] that +/// must be either `0` or `1`, and [`char`] that cannot be a surrogate or above [`char::MAX`]. +/// +/// # Safety +/// +/// Implementers must ensure that any bit pattern is valid for this type. +pub unsafe trait FromBytes: Sized { + /// Converts the given byte slice into a shared reference to [`Self`]. + /// + /// It fails if the size or alignment requirements are not satisfied. + fn from_bytes(data: &[u8], offset: usize) -> Option<&Self> { + if offset > data.len() { + return None; + } + let data = &data[offset..]; + let ptr = data.as_ptr(); + if ptr as usize % align_of::() != 0 || data.len() < size_of::() { + return None; + } + // SAFETY: The memory is valid for read because we have a reference to it. We have just + // checked the minimum size and alignment as well. + Some(unsafe { &*ptr.cast() }) + } + + /// Converts the given byte slice into a shared slice of [`Self`]. + /// + /// It fails if the size or alignment requirements are not satisfied. + fn from_bytes_to_slice(data: &[u8]) -> Option<&[Self]> { + let ptr = data.as_ptr(); + if ptr as usize % align_of::() != 0 { + return None; + } + // SAFETY: The memory is valid for read because we have a reference to it. We have just + // checked the minimum alignment as well, and the length of the slice is calculated from + // the length of `Self`. + Some(unsafe { core::slice::from_raw_parts(ptr.cast(), data.len() / size_of::()) }) + } +} + +// SAFETY: All bit patterns are acceptable values of the types below. +unsafe impl FromBytes for u8 {} +unsafe impl FromBytes for u16 {} +unsafe impl FromBytes for u32 {} +unsafe impl FromBytes for u64 {} +unsafe impl FromBytes for usize {} +unsafe impl FromBytes for i8 {} +unsafe impl FromBytes for i16 {} +unsafe impl FromBytes for i32 {} +unsafe impl FromBytes for i64 {} +unsafe impl FromBytes for isize {} +unsafe impl FromBytes for [T; N] {} +unsafe impl FromBytes for LE {} + +/// Derive [`FromBytes`] for the structs defined in the block. +/// +/// # Examples +/// +/// ``` +/// kernel::derive_readable_from_bytes! { +/// #[repr(C)] +/// struct SuperBlock { +/// a: u16, +/// _padding: [u8; 6], +/// b: u64, +/// } +/// +/// #[repr(C)] +/// struct Inode { +/// a: u16, +/// b: u16, +/// c: u32, +/// } +/// } +/// ``` +#[macro_export] +macro_rules! derive_readable_from_bytes { + ($($(#[$outer:meta])* $outerv:vis struct $name:ident { + $($(#[$m:meta])* $v:vis $id:ident : $t:ty),* $(,)? + })*)=> { + $( + $(#[$outer])* + $outerv struct $name { + $( + $(#[$m])* + $v $id: $t, + )* + } + unsafe impl $crate::types::FromBytes for $name {} + const _: () = { + const fn is_readable_from_bytes() {} + $(is_readable_from_bytes::<$t>();)* + }; + )* + }; +} From caf9b29bf38fa226e10ca676caa8c0ce21caa08c Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:09 -0300 Subject: [PATCH 09/29] rust: mem_cache: introduce `MemCache` This is just the basics of `kmem_cache` to be used in file systems for inodes. All dead-code annotations will be removed in the next commit. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 4 +++ rust/bindings/lib.rs | 4 +++ rust/kernel/lib.rs | 1 + rust/kernel/mem_cache.rs | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 rust/kernel/mem_cache.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index c91a3c24f6070a..3b620ae070212f 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -17,3 +17,7 @@ const size_t BINDINGS_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN; const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO; + +const slab_flags_t BINDINGS_SLAB_RECLAIM_ACCOUNT = SLAB_RECLAIM_ACCOUNT; +const slab_flags_t BINDINGS_SLAB_MEM_SPREAD = SLAB_MEM_SPREAD; +const slab_flags_t BINDINGS_SLAB_ACCOUNT = SLAB_ACCOUNT; diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 9bcbea04dac305..6a8c6cd17e4570 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -51,3 +51,7 @@ pub use bindings_raw::*; pub const GFP_KERNEL: gfp_t = BINDINGS_GFP_KERNEL; pub const __GFP_ZERO: gfp_t = BINDINGS___GFP_ZERO; + +pub const SLAB_RECLAIM_ACCOUNT: slab_flags_t = BINDINGS_SLAB_RECLAIM_ACCOUNT; +pub const SLAB_MEM_SPREAD: slab_flags_t = BINDINGS_SLAB_MEM_SPREAD; +pub const SLAB_ACCOUNT: slab_flags_t = BINDINGS_SLAB_ACCOUNT; diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index a74ea4691fac3a..187d58f906a5c7 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -38,6 +38,7 @@ pub mod init; pub mod ioctl; #[cfg(CONFIG_KUNIT)] pub mod kunit; +pub mod mem_cache; pub mod prelude; pub mod print; mod static_assert; diff --git a/rust/kernel/mem_cache.rs b/rust/kernel/mem_cache.rs new file mode 100644 index 00000000000000..05e5f2bc9781e8 --- /dev/null +++ b/rust/kernel/mem_cache.rs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel memory caches (kmem_cache). +//! +//! C headers: [`include/linux/slab.h`](../../include/linux/slab.h) + +use crate::error::{code::*, Result}; +use crate::{bindings, str::CStr}; +use core::ptr; + +/// A kernel memory cache. +/// +/// This isn't ready to be made public yet because it only provides functionality useful for the +/// allocation of inodes in file systems. +pub(crate) struct MemCache { + ptr: ptr::NonNull, +} + +impl MemCache { + /// Allocates a new `kmem_cache` for type `T`. + /// + /// `init` is called by the C code when entries are allocated. + #[allow(dead_code)] + pub(crate) fn try_new( + name: &'static CStr, + init: Option, + ) -> Result { + // SAFETY: `name` is static, so always valid. + let ptr = ptr::NonNull::new(unsafe { + bindings::kmem_cache_create( + name.as_char_ptr(), + core::mem::size_of::().try_into()?, + core::mem::align_of::().try_into()?, + bindings::SLAB_RECLAIM_ACCOUNT | bindings::SLAB_MEM_SPREAD | bindings::SLAB_ACCOUNT, + init, + ) + }) + .ok_or(ENOMEM)?; + + Ok(Self { ptr }) + } + + /// Returns the pointer to the `kmem_cache` instance, or null if it's `None`. + /// + /// This is a helper for functions like `alloc_inode_sb` where the cache is optional. + #[allow(dead_code)] + pub(crate) fn ptr(c: &Option) -> *mut bindings::kmem_cache { + match c { + Some(m) => m.ptr.as_ptr(), + None => ptr::null_mut(), + } + } +} + +impl Drop for MemCache { + fn drop(&mut self) { + // SAFETY: Just an FFI call with no additional safety requirements. + unsafe { bindings::rcu_barrier() }; + + // SAFETY: `ptr` was previously returned by successful call to `kmem_cache_create`, so it's + // ok to destroy it here. + unsafe { bindings::kmem_cache_destroy(self.ptr.as_ptr()) }; + } +} From b0bc357ef7a98904600826dea3de79c0c67eb0a7 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:10 -0300 Subject: [PATCH 10/29] kbuild: rust: allow modules to allocate memory The `allocator_api` feature is needed by drivers that allocate memory. Signed-off-by: Wedson Almeida Filho --- scripts/Makefile.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.build b/scripts/Makefile.build index f57d40deebdb8f..78379175d61db4 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -262,7 +262,7 @@ $(obj)/%.lst: $(src)/%.c FORCE # Compile Rust sources (.rs) # --------------------------------------------------------------------------- -rust_allowed_features := new_uninit,return_position_impl_trait_in_trait,offset_of +rust_allowed_features := new_uninit,return_position_impl_trait_in_trait,offset_of,allocator_api # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree From 528babded936b25d489e644413faa8d66fa8d274 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:10 -0300 Subject: [PATCH 11/29] rust: fs: add registration/unregistration of file systems Allow basic registration and unregistration of Rust file system types. Unregistration happens automatically when a registration variable is dropped (e.g., when it goes out of scope). File systems registered this way are visible in `/proc/filesystems` but cannot be mounted yet because `init_fs_context` fails. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 1 + rust/kernel/error.rs | 2 - rust/kernel/fs.rs | 80 +++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 rust/kernel/fs.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 3b620ae070212f..9c23037b33d044 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 05fcab6abfe63e..e6d7ce46be550a 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -320,8 +320,6 @@ pub(crate) fn from_err_ptr(ptr: *mut T) -> Result<*mut T> { /// }) /// } /// ``` -// TODO: Remove `dead_code` marker once an in-kernel client is available. -#[allow(dead_code)] pub(crate) fn from_result(f: F) -> T where T: From, diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs new file mode 100644 index 00000000000000..f3fb09db41ba45 --- /dev/null +++ b/rust/kernel/fs.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel file systems. +//! +//! This module allows Rust code to register new kernel file systems. +//! +//! C headers: [`include/linux/fs.h`](../../include/linux/fs.h) + +use crate::error::{code::*, from_result, to_result, Error}; +use crate::types::Opaque; +use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule}; +use core::{marker::PhantomPinned, pin::Pin}; +use macros::{pin_data, pinned_drop}; + +/// A file system type. +pub trait FileSystem { + /// The name of the file system type. + const NAME: &'static CStr; +} + +/// A registration of a file system. +#[pin_data(PinnedDrop)] +pub struct Registration { + #[pin] + fs: Opaque, + #[pin] + _pin: PhantomPinned, +} + +// SAFETY: `Registration` doesn't provide any `&self` methods, so it is safe to pass references +// to it around. +unsafe impl Sync for Registration {} + +// SAFETY: Both registration and unregistration are implemented in C and safe to be performed +// from any thread, so `Registration` is `Send`. +unsafe impl Send for Registration {} + +impl Registration { + /// Creates the initialiser of a new file system registration. + pub fn new(module: &'static ThisModule) -> impl PinInit { + try_pin_init!(Self { + _pin: PhantomPinned, + fs <- Opaque::try_ffi_init(|fs_ptr: *mut bindings::file_system_type| { + // SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write. + unsafe { fs_ptr.write(bindings::file_system_type::default()) }; + + // SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write, and it has + // just been initialised above, so it's also valid for read. + let fs = unsafe { &mut *fs_ptr }; + fs.owner = module.0; + fs.name = T::NAME.as_char_ptr(); + fs.init_fs_context = Some(Self::init_fs_context_callback); + fs.kill_sb = Some(Self::kill_sb_callback); + fs.fs_flags = 0; + + // SAFETY: Pointers stored in `fs` are static so will live for as long as the + // registration is active (it is undone in `drop`). + to_result(unsafe { bindings::register_filesystem(fs_ptr) }) + }), + }) + } + + unsafe extern "C" fn init_fs_context_callback( + _fc_ptr: *mut bindings::fs_context, + ) -> core::ffi::c_int { + from_result(|| Err(ENOTSUPP)) + } + + unsafe extern "C" fn kill_sb_callback(_sb_ptr: *mut bindings::super_block) {} +} + +#[pinned_drop] +impl PinnedDrop for Registration { + fn drop(self: Pin<&mut Self>) { + // SAFETY: If an instance of `Self` has been successfully created, a call to + // `register_filesystem` has necessarily succeeded. So it's ok to call + // `unregister_filesystem` on the previously registered fs. + unsafe { bindings::unregister_filesystem(self.fs.get()) }; + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 187d58f906a5c7..00059b80c24092 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -34,6 +34,7 @@ extern crate self as kernel; mod allocator; mod build_assert; pub mod error; +pub mod fs; pub mod init; pub mod ioctl; #[cfg(CONFIG_KUNIT)] From e909f439481cf6a3df00c7064b0d64cee8630fe9 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:10 -0300 Subject: [PATCH 12/29] rust: fs: introduce the `module_fs` macro Simplify the declaration of modules that only expose a file system type. They can now do it using the `module_fs` macro. Signed-off-by: Wedson Almeida Filho --- rust/kernel/fs.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index f3fb09db41ba45..1df54c234101b9 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -9,7 +9,7 @@ use crate::error::{code::*, from_result, to_result, Error}; use crate::types::Opaque; use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule}; -use core::{marker::PhantomPinned, pin::Pin}; +use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin}; use macros::{pin_data, pinned_drop}; /// A file system type. @@ -78,3 +78,57 @@ impl PinnedDrop for Registration { unsafe { bindings::unregister_filesystem(self.fs.get()) }; } } + +/// Kernel module that exposes a single file system implemented by `T`. +#[pin_data] +pub struct Module { + #[pin] + fs_reg: Registration, + _p: PhantomData, +} + +impl crate::InPlaceModule for Module { + fn init(module: &'static ThisModule) -> impl PinInit { + try_pin_init!(Self { + fs_reg <- Registration::new::(module), + _p: PhantomData, + }) + } +} + +/// Declares a kernel module that exposes a single file system. +/// +/// The `type` argument must be a type which implements the [`FileSystem`] trait. Also accepts +/// various forms of kernel metadata. +/// +/// # Examples +/// +/// ``` +/// # mod module_fs_sample { +/// use kernel::prelude::*; +/// use kernel::{c_str, fs}; +/// +/// kernel::module_fs! { +/// type: MyFs, +/// name: "myfs", +/// author: "Rust for Linux Contributors", +/// description: "My Rust fs", +/// license: "GPL", +/// } +/// +/// struct MyFs; +/// impl fs::FileSystem for MyFs { +/// const NAME: &'static CStr = c_str!("myfs"); +/// } +/// # } +/// ``` +#[macro_export] +macro_rules! module_fs { + (type: $type:ty, $($f:tt)*) => { + type ModuleType = $crate::fs::Module<$type>; + $crate::macros::module! { + type: ModuleType, + $($f)* + } + } +} From ad07f4be62670a92bd672b90eee074653e8cc99e Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:10 -0300 Subject: [PATCH 13/29] samples: rust: add initial ro file system sample Introduce a basic sample that for now only registers the file system and doesn't really provide any functionality beyond having it listed in `/proc/filesystems`. New functionality will be added to the sample in subsequent patches as their abstractions are introduced. Signed-off-by: Wedson Almeida Filho --- samples/rust/Kconfig | 10 ++++++++++ samples/rust/Makefile | 1 + samples/rust/rust_rofs.rs | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 samples/rust/rust_rofs.rs diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 59f44a8b6958ef..2f26c5c5281382 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -41,6 +41,16 @@ config SAMPLE_RUST_PRINT If unsure, say N. +config SAMPLE_RUST_ROFS + tristate "Read-only file system" + help + This option builds the Rust read-only file system sample. + + To compile this as a module, choose M here: + the module will be called rust_rofs. + + If unsure, say N. + config SAMPLE_RUST_HOSTPROGS bool "Host programs" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 791fc18180e98b..df1e4341ae9587 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o obj-$(CONFIG_SAMPLE_RUST_INPLACE) += rust_inplace.o obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o +obj-$(CONFIG_SAMPLE_RUST_ROFS) += rust_rofs.o subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs new file mode 100644 index 00000000000000..1c00b1da8b94ab --- /dev/null +++ b/samples/rust/rust_rofs.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust read-only file system sample. + +use kernel::prelude::*; +use kernel::{c_str, fs}; + +kernel::module_fs! { + type: RoFs, + name: "rust_rofs", + author: "Rust for Linux Contributors", + description: "Rust read-only file system sample", + license: "GPL", +} + +struct RoFs; +impl fs::FileSystem for RoFs { + const NAME: &'static CStr = c_str!("rust-fs"); +} From 626056a68acdd94209deb99e183cced74e86ed60 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:10 -0300 Subject: [PATCH 14/29] rust: fs: introduce `FileSystem::super_params` Allow Rust file systems to initialise superblocks, which allows them to be mounted (though they are still empty). Some scaffolding code is added to create an empty directory as the root. It is replaced by proper inode creation in a subsequent patch in this series. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 5 + rust/bindings/lib.rs | 4 + rust/kernel/fs.rs | 176 ++++++++++++++++++++++++++++++-- samples/rust/rust_rofs.rs | 10 ++ 4 files changed, 189 insertions(+), 6 deletions(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 9c23037b33d044..ca1898ce9527c5 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -22,3 +23,7 @@ const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO; const slab_flags_t BINDINGS_SLAB_RECLAIM_ACCOUNT = SLAB_RECLAIM_ACCOUNT; const slab_flags_t BINDINGS_SLAB_MEM_SPREAD = SLAB_MEM_SPREAD; const slab_flags_t BINDINGS_SLAB_ACCOUNT = SLAB_ACCOUNT; + +const unsigned long BINDINGS_SB_RDONLY = SB_RDONLY; + +const loff_t BINDINGS_MAX_LFS_FILESIZE = MAX_LFS_FILESIZE; diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 6a8c6cd17e4570..426915d3fb57c0 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -55,3 +55,7 @@ pub const __GFP_ZERO: gfp_t = BINDINGS___GFP_ZERO; pub const SLAB_RECLAIM_ACCOUNT: slab_flags_t = BINDINGS_SLAB_RECLAIM_ACCOUNT; pub const SLAB_MEM_SPREAD: slab_flags_t = BINDINGS_SLAB_MEM_SPREAD; pub const SLAB_ACCOUNT: slab_flags_t = BINDINGS_SLAB_ACCOUNT; + +pub const SB_RDONLY: core::ffi::c_ulong = BINDINGS_SB_RDONLY; + +pub const MAX_LFS_FILESIZE: loff_t = BINDINGS_MAX_LFS_FILESIZE; diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 1df54c234101b9..31cf643aaded57 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -6,16 +6,22 @@ //! //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h) -use crate::error::{code::*, from_result, to_result, Error}; +use crate::error::{code::*, from_result, to_result, Error, Result}; use crate::types::Opaque; use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule}; use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin}; use macros::{pin_data, pinned_drop}; +/// Maximum size of an inode. +pub const MAX_LFS_FILESIZE: i64 = bindings::MAX_LFS_FILESIZE; + /// A file system type. pub trait FileSystem { /// The name of the file system type. const NAME: &'static CStr; + + /// Returns the parameters to initialise a super block. + fn super_params(sb: &NewSuperBlock) -> Result; } /// A registration of a file system. @@ -49,7 +55,7 @@ impl Registration { let fs = unsafe { &mut *fs_ptr }; fs.owner = module.0; fs.name = T::NAME.as_char_ptr(); - fs.init_fs_context = Some(Self::init_fs_context_callback); + fs.init_fs_context = Some(Self::init_fs_context_callback::); fs.kill_sb = Some(Self::kill_sb_callback); fs.fs_flags = 0; @@ -60,13 +66,22 @@ impl Registration { }) } - unsafe extern "C" fn init_fs_context_callback( - _fc_ptr: *mut bindings::fs_context, + unsafe extern "C" fn init_fs_context_callback( + fc_ptr: *mut bindings::fs_context, ) -> core::ffi::c_int { - from_result(|| Err(ENOTSUPP)) + from_result(|| { + // SAFETY: The C callback API guarantees that `fc_ptr` is valid. + let fc = unsafe { &mut *fc_ptr }; + fc.ops = &Tables::::CONTEXT; + Ok(0) + }) } - unsafe extern "C" fn kill_sb_callback(_sb_ptr: *mut bindings::super_block) {} + unsafe extern "C" fn kill_sb_callback(sb_ptr: *mut bindings::super_block) { + // SAFETY: In `get_tree_callback` we always call `get_tree_nodev`, so `kill_anon_super` is + // the appropriate function to call for cleanup. + unsafe { bindings::kill_anon_super(sb_ptr) }; + } } #[pinned_drop] @@ -79,6 +94,151 @@ impl PinnedDrop for Registration { } } +/// A file system super block. +/// +/// Wraps the kernel's `struct super_block`. +#[repr(transparent)] +pub struct SuperBlock(Opaque, PhantomData); + +/// Required superblock parameters. +/// +/// This is returned by implementations of [`FileSystem::super_params`]. +pub struct SuperParams { + /// The magic number of the superblock. + pub magic: u32, + + /// The size of a block in powers of 2 (i.e., for a value of `n`, the size is `2^n`). + pub blocksize_bits: u8, + + /// Maximum size of a file. + /// + /// The maximum allowed value is [`MAX_LFS_FILESIZE`]. + pub maxbytes: i64, + + /// Granularity of c/m/atime in ns (cannot be worse than a second). + pub time_gran: u32, +} + +/// A superblock that is still being initialised. +/// +/// # Invariants +/// +/// The superblock is a newly-created one and this is the only active pointer to it. +#[repr(transparent)] +pub struct NewSuperBlock(bindings::super_block, PhantomData); + +struct Tables(T); +impl Tables { + const CONTEXT: bindings::fs_context_operations = bindings::fs_context_operations { + free: None, + parse_param: None, + get_tree: Some(Self::get_tree_callback), + reconfigure: None, + parse_monolithic: None, + dup: None, + }; + + unsafe extern "C" fn get_tree_callback(fc: *mut bindings::fs_context) -> core::ffi::c_int { + // SAFETY: `fc` is valid per the callback contract. `fill_super_callback` also has + // the right type and is a valid callback. + unsafe { bindings::get_tree_nodev(fc, Some(Self::fill_super_callback)) } + } + + unsafe extern "C" fn fill_super_callback( + sb_ptr: *mut bindings::super_block, + _fc: *mut bindings::fs_context, + ) -> core::ffi::c_int { + from_result(|| { + // SAFETY: The callback contract guarantees that `sb_ptr` is a unique pointer to a + // newly-created superblock. + let sb = unsafe { &mut *sb_ptr.cast() }; + let params = T::super_params(sb)?; + + sb.0.s_magic = params.magic as _; + sb.0.s_op = &Tables::::SUPER_BLOCK; + sb.0.s_maxbytes = params.maxbytes; + sb.0.s_time_gran = params.time_gran; + sb.0.s_blocksize_bits = params.blocksize_bits; + sb.0.s_blocksize = 1; + if sb.0.s_blocksize.leading_zeros() < params.blocksize_bits.into() { + return Err(EINVAL); + } + sb.0.s_blocksize = 1 << sb.0.s_blocksize_bits; + sb.0.s_flags |= bindings::SB_RDONLY; + + // The following is scaffolding code that will be removed in a subsequent patch. It is + // needed to build a root dentry, otherwise core code will BUG(). + // SAFETY: `sb` is the superblock being initialised, it is valid for read and write. + let inode = unsafe { bindings::new_inode(&mut sb.0) }; + if inode.is_null() { + return Err(ENOMEM); + } + + // SAFETY: `inode` is valid for write. + unsafe { bindings::set_nlink(inode, 2) }; + + { + // SAFETY: This is a newly-created inode. No other references to it exist, so it is + // safe to mutably dereference it. + let inode = unsafe { &mut *inode }; + inode.i_ino = 1; + inode.i_mode = (bindings::S_IFDIR | 0o755) as _; + + // SAFETY: `simple_dir_operations` never changes, it's safe to reference it. + inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations }; + + // SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it. + inode.i_op = unsafe { &bindings::simple_dir_inode_operations }; + } + + // SAFETY: `d_make_root` requires that `inode` be valid and referenced, which is the + // case for this call. + // + // It takes over the inode, even on failure, so we don't need to clean it up. + let dentry = unsafe { bindings::d_make_root(inode) }; + if dentry.is_null() { + return Err(ENOMEM); + } + + sb.0.s_root = dentry; + + Ok(0) + }) + } + + const SUPER_BLOCK: bindings::super_operations = bindings::super_operations { + alloc_inode: None, + destroy_inode: None, + free_inode: None, + dirty_inode: None, + write_inode: None, + drop_inode: None, + evict_inode: None, + put_super: None, + sync_fs: None, + freeze_super: None, + freeze_fs: None, + thaw_super: None, + unfreeze_fs: None, + statfs: None, + remount_fs: None, + umount_begin: None, + show_options: None, + show_devname: None, + show_path: None, + show_stats: None, + #[cfg(CONFIG_QUOTA)] + quota_read: None, + #[cfg(CONFIG_QUOTA)] + quota_write: None, + #[cfg(CONFIG_QUOTA)] + get_dquots: None, + nr_cached_objects: None, + free_cached_objects: None, + shutdown: None, + }; +} + /// Kernel module that exposes a single file system implemented by `T`. #[pin_data] pub struct Module { @@ -105,6 +265,7 @@ impl crate::InPlaceModule for Module { /// /// ``` /// # mod module_fs_sample { +/// use kernel::fs::{NewSuperBlock, SuperParams}; /// use kernel::prelude::*; /// use kernel::{c_str, fs}; /// @@ -119,6 +280,9 @@ impl crate::InPlaceModule for Module { /// struct MyFs; /// impl fs::FileSystem for MyFs { /// const NAME: &'static CStr = c_str!("myfs"); +/// fn super_params(_: &NewSuperBlock) -> Result { +/// todo!() +/// } /// } /// # } /// ``` diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 1c00b1da8b94ab..9878bf88b99197 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -2,6 +2,7 @@ //! Rust read-only file system sample. +use kernel::fs::{NewSuperBlock, SuperParams}; use kernel::prelude::*; use kernel::{c_str, fs}; @@ -16,4 +17,13 @@ kernel::module_fs! { struct RoFs; impl fs::FileSystem for RoFs { const NAME: &'static CStr = c_str!("rust-fs"); + + fn super_params(_sb: &NewSuperBlock) -> Result { + Ok(SuperParams { + magic: 0x52555354, + blocksize_bits: 12, + maxbytes: fs::MAX_LFS_FILESIZE, + time_gran: 1, + }) + } } From a448dc5e3e9eafcd1f49b8be95e1ac83a556ab40 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:10 -0300 Subject: [PATCH 15/29] rust: fs: introduce `INode` Allow Rust file systems to handle typed and ref-counted inodes. This is in preparation for creating new inodes (for example, to create the root inode of a new superblock), which comes in the next patch in the series. Signed-off-by: Wedson Almeida Filho --- rust/helpers.c | 7 +++++++ rust/kernel/fs.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/rust/helpers.c b/rust/helpers.c index 4c86fe4a7e05e7..fe45f8ddb31fc5 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -144,6 +145,12 @@ struct kunit *rust_helper_kunit_get_current_test(void) } EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test); +off_t rust_helper_i_size_read(const struct inode *inode) +{ + return i_size_read(inode); +} +EXPORT_SYMBOL_GPL(rust_helper_i_size_read); + /* * `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can * use it in contexts where Rust expects a `usize` like slice (array) indices. diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 31cf643aaded57..30fa1f312f3328 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -7,9 +7,9 @@ //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h) use crate::error::{code::*, from_result, to_result, Error, Result}; -use crate::types::Opaque; +use crate::types::{AlwaysRefCounted, Opaque}; use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule}; -use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin}; +use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr}; use macros::{pin_data, pinned_drop}; /// Maximum size of an inode. @@ -94,6 +94,55 @@ impl PinnedDrop for Registration { } } +/// The number of an inode. +pub type Ino = u64; + +/// A node in the file system index (inode). +/// +/// Wraps the kernel's `struct inode`. +/// +/// # Invariants +/// +/// Instances of this type are always ref-counted, that is, a call to `ihold` ensures that the +/// allocation remains valid at least until the matching call to `iput`. +#[repr(transparent)] +pub struct INode(Opaque, PhantomData); + +impl INode { + /// Returns the number of the inode. + pub fn ino(&self) -> Ino { + // SAFETY: `i_ino` is immutable, and `self` is guaranteed to be valid by the existence of a + // shared reference (&self) to it. + unsafe { (*self.0.get()).i_ino } + } + + /// Returns the super-block that owns the inode. + pub fn super_block(&self) -> &SuperBlock { + // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a + // shared reference (&self) to it. + unsafe { &*(*self.0.get()).i_sb.cast() } + } + + /// Returns the size of the inode contents. + pub fn size(&self) -> i64 { + // SAFETY: `self` is guaranteed to be valid by the existence of a shared reference. + unsafe { bindings::i_size_read(self.0.get()) } + } +} + +// SAFETY: The type invariants guarantee that `INode` is always ref-counted. +unsafe impl AlwaysRefCounted for INode { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference means that the refcount is nonzero. + unsafe { bindings::ihold(self.0.get()) }; + } + + unsafe fn dec_ref(obj: ptr::NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is nonzero. + unsafe { bindings::iput(obj.cast().as_ptr()) } + } +} + /// A file system super block. /// /// Wraps the kernel's `struct super_block`. From b26f77a8d228d94f01ddf732e1376bcd819aa970 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:11 -0300 Subject: [PATCH 16/29] rust: fs: introduce `FileSystem::init_root` Allow Rust file systems to specify their root directory. Also allow them to create (and do cache lookups of) directory inodes. (More types of inodes are added in subsequent patches in the series.) The `NewINode` type ensures that a new inode is properly initialised before it is marked so. It also facilitates error paths by automatically marking inodes as failed if they're not properly initialised. Signed-off-by: Wedson Almeida Filho --- rust/helpers.c | 12 +++ rust/kernel/fs.rs | 178 +++++++++++++++++++++++++++++++------- samples/rust/rust_rofs.rs | 22 ++++- 3 files changed, 181 insertions(+), 31 deletions(-) diff --git a/rust/helpers.c b/rust/helpers.c index fe45f8ddb31fc5..c5a2bec6467d14 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -145,6 +145,18 @@ struct kunit *rust_helper_kunit_get_current_test(void) } EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test); +void rust_helper_i_uid_write(struct inode *inode, uid_t uid) +{ + i_uid_write(inode, uid); +} +EXPORT_SYMBOL_GPL(rust_helper_i_uid_write); + +void rust_helper_i_gid_write(struct inode *inode, gid_t gid) +{ + i_gid_write(inode, gid); +} +EXPORT_SYMBOL_GPL(rust_helper_i_gid_write); + off_t rust_helper_i_size_read(const struct inode *inode) { return i_size_read(inode); diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 30fa1f312f3328..f3a41cf57502ae 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -7,9 +7,9 @@ //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h) use crate::error::{code::*, from_result, to_result, Error, Result}; -use crate::types::{AlwaysRefCounted, Opaque}; -use crate::{bindings, init::PinInit, str::CStr, try_pin_init, ThisModule}; -use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr}; +use crate::types::{ARef, AlwaysRefCounted, Either, Opaque}; +use crate::{bindings, init::PinInit, str::CStr, time::Timespec, try_pin_init, ThisModule}; +use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr}; use macros::{pin_data, pinned_drop}; /// Maximum size of an inode. @@ -22,6 +22,12 @@ pub trait FileSystem { /// Returns the parameters to initialise a super block. fn super_params(sb: &NewSuperBlock) -> Result; + + /// Initialises and returns the root inode of the given superblock. + /// + /// This is called during initialisation of a superblock after [`FileSystem::super_params`] has + /// completed successfully. + fn init_root(sb: &SuperBlock) -> Result>>; } /// A registration of a file system. @@ -143,12 +149,136 @@ unsafe impl AlwaysRefCounted for INode { } } +/// An inode that is locked and hasn't been initialised yet. +#[repr(transparent)] +pub struct NewINode(ARef>); + +impl NewINode { + /// Initialises the new inode with the given parameters. + pub fn init(self, params: INodeParams) -> Result>> { + // SAFETY: This is a new inode, so it's safe to manipulate it mutably. + let inode = unsafe { &mut *self.0 .0.get() }; + + let mode = match params.typ { + INodeType::Dir => { + // SAFETY: `simple_dir_operations` never changes, it's safe to reference it. + inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations }; + + // SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it. + inode.i_op = unsafe { &bindings::simple_dir_inode_operations }; + bindings::S_IFDIR + } + }; + + inode.i_mode = (params.mode & 0o777) | u16::try_from(mode)?; + inode.i_size = params.size; + inode.i_blocks = params.blocks; + + inode.__i_ctime = params.ctime.into(); + inode.i_mtime = params.mtime.into(); + inode.i_atime = params.atime.into(); + + // SAFETY: inode is a new inode, so it is valid for write. + unsafe { + bindings::set_nlink(inode, params.nlink); + bindings::i_uid_write(inode, params.uid); + bindings::i_gid_write(inode, params.gid); + bindings::unlock_new_inode(inode); + } + + // SAFETY: We are manually destructuring `self` and preventing `drop` from being called. + Ok(unsafe { (&ManuallyDrop::new(self).0 as *const ARef>).read() }) + } +} + +impl Drop for NewINode { + fn drop(&mut self) { + // SAFETY: The new inode failed to be turned into an initialised inode, so it's safe (and + // in fact required) to call `iget_failed` on it. + unsafe { bindings::iget_failed(self.0 .0.get()) }; + } +} + +/// The type of the inode. +#[derive(Copy, Clone)] +pub enum INodeType { + /// Directory type. + Dir, +} + +/// Required inode parameters. +/// +/// This is used when creating new inodes. +pub struct INodeParams { + /// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to + /// everyone, the owner group, and the owner. + pub mode: u16, + + /// Type of inode. + /// + /// Also carries additional per-type data. + pub typ: INodeType, + + /// Size of the contents of the inode. + /// + /// Its maximum value is [`MAX_LFS_FILESIZE`]. + pub size: i64, + + /// Number of blocks. + pub blocks: u64, + + /// Number of links to the inode. + pub nlink: u32, + + /// User id. + pub uid: u32, + + /// Group id. + pub gid: u32, + + /// Creation time. + pub ctime: Timespec, + + /// Last modification time. + pub mtime: Timespec, + + /// Last access time. + pub atime: Timespec, +} + /// A file system super block. /// /// Wraps the kernel's `struct super_block`. #[repr(transparent)] pub struct SuperBlock(Opaque, PhantomData); +impl SuperBlock { + /// Tries to get an existing inode or create a new one if it doesn't exist yet. + pub fn get_or_create_inode(&self, ino: Ino) -> Result>, NewINode>> { + // SAFETY: The only initialisation missing from the superblock is the root, and this + // function is needed to create the root, so it's safe to call it. + let inode = + ptr::NonNull::new(unsafe { bindings::iget_locked(self.0.get(), ino) }).ok_or(ENOMEM)?; + + // SAFETY: `inode` is valid for read, but there could be concurrent writers (e.g., if it's + // an already-initialised inode), so we use `read_volatile` to read its current state. + let state = unsafe { ptr::read_volatile(ptr::addr_of!((*inode.as_ptr()).i_state)) }; + if state & u64::from(bindings::I_NEW) == 0 { + // The inode is cached. Just return it. + // + // SAFETY: `inode` had its refcount incremented by `iget_locked`; this increment is now + // owned by `ARef`. + Ok(Either::Left(unsafe { ARef::from_raw(inode.cast()) })) + } else { + // SAFETY: The new inode is valid but not fully initialised yet, so it's ok to create a + // `NewINode`. + Ok(Either::Right(NewINode(unsafe { + ARef::from_raw(inode.cast()) + }))) + } + } +} + /// Required superblock parameters. /// /// This is returned by implementations of [`FileSystem::super_params`]. @@ -215,41 +345,28 @@ impl Tables { sb.0.s_blocksize = 1 << sb.0.s_blocksize_bits; sb.0.s_flags |= bindings::SB_RDONLY; - // The following is scaffolding code that will be removed in a subsequent patch. It is - // needed to build a root dentry, otherwise core code will BUG(). - // SAFETY: `sb` is the superblock being initialised, it is valid for read and write. - let inode = unsafe { bindings::new_inode(&mut sb.0) }; - if inode.is_null() { - return Err(ENOMEM); - } - - // SAFETY: `inode` is valid for write. - unsafe { bindings::set_nlink(inode, 2) }; - - { - // SAFETY: This is a newly-created inode. No other references to it exist, so it is - // safe to mutably dereference it. - let inode = unsafe { &mut *inode }; - inode.i_ino = 1; - inode.i_mode = (bindings::S_IFDIR | 0o755) as _; - - // SAFETY: `simple_dir_operations` never changes, it's safe to reference it. - inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations }; + // SAFETY: The callback contract guarantees that `sb_ptr` is a unique pointer to a + // newly-created (and initialised above) superblock. + let sb = unsafe { &mut *sb_ptr.cast() }; + let root = T::init_root(sb)?; - // SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it. - inode.i_op = unsafe { &bindings::simple_dir_inode_operations }; + // Reject root inode if it belongs to a different superblock. + if !ptr::eq(root.super_block(), sb) { + return Err(EINVAL); } // SAFETY: `d_make_root` requires that `inode` be valid and referenced, which is the // case for this call. // // It takes over the inode, even on failure, so we don't need to clean it up. - let dentry = unsafe { bindings::d_make_root(inode) }; + let dentry = unsafe { bindings::d_make_root(ManuallyDrop::new(root).0.get()) }; if dentry.is_null() { return Err(ENOMEM); } - sb.0.s_root = dentry; + // SAFETY: The callback contract guarantees that `sb_ptr` is a unique pointer to a + // newly-created (and initialised above) superblock. + unsafe { (*sb_ptr).s_root = dentry }; Ok(0) }) @@ -314,9 +431,9 @@ impl crate::InPlaceModule for Module { /// /// ``` /// # mod module_fs_sample { -/// use kernel::fs::{NewSuperBlock, SuperParams}; +/// use kernel::fs::{INode, NewSuperBlock, SuperBlock, SuperParams}; /// use kernel::prelude::*; -/// use kernel::{c_str, fs}; +/// use kernel::{c_str, fs, types::ARef}; /// /// kernel::module_fs! { /// type: MyFs, @@ -332,6 +449,9 @@ impl crate::InPlaceModule for Module { /// fn super_params(_: &NewSuperBlock) -> Result { /// todo!() /// } +/// fn init_root(_sb: &SuperBlock) -> Result>> { +/// todo!() +/// } /// } /// # } /// ``` diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 9878bf88b99197..9e5f4c7d1c06bd 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -2,9 +2,9 @@ //! Rust read-only file system sample. -use kernel::fs::{NewSuperBlock, SuperParams}; +use kernel::fs::{INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams}; use kernel::prelude::*; -use kernel::{c_str, fs}; +use kernel::{c_str, fs, time::UNIX_EPOCH, types::ARef, types::Either}; kernel::module_fs! { type: RoFs, @@ -26,4 +26,22 @@ impl fs::FileSystem for RoFs { time_gran: 1, }) } + + fn init_root(sb: &SuperBlock) -> Result>> { + match sb.get_or_create_inode(1)? { + Either::Left(existing) => Ok(existing), + Either::Right(new) => new.init(INodeParams { + typ: INodeType::Dir, + mode: 0o555, + size: 1, + blocks: 1, + nlink: 2, + uid: 0, + gid: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, + }), + } + } } From ac0f637053323b961f969312e1c2277e6b1eed7e Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:11 -0300 Subject: [PATCH 17/29] 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 --- rust/kernel/fs.rs | 193 +++++++++++++++++++++++++++++++++++++- samples/rust/rust_rofs.rs | 49 +++++++++- 2 files changed, 236 insertions(+), 6 deletions(-) diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index f3a41cf57502ae..89611c44e4c54b 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -28,6 +28,70 @@ pub trait FileSystem { /// This is called during initialisation of a superblock after [`FileSystem::super_params`] has /// completed successfully. fn init_root(sb: &SuperBlock) -> Result>>; + + /// Reads directory entries from directory inodes. + /// + /// [`DirEmitter::pos`] holds the current position of the directory reader. + fn read_dir(inode: &INode, emitter: &mut DirEmitter) -> Result; +} + +/// The types of directory entries reported by [`FileSystem::read_dir`]. +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum DirEntryType { + /// Unknown type. + Unknown = bindings::DT_UNKNOWN, + + /// Named pipe (first-in, first-out) type. + Fifo = bindings::DT_FIFO, + + /// Character device type. + Chr = bindings::DT_CHR, + + /// Directory type. + Dir = bindings::DT_DIR, + + /// Block device type. + Blk = bindings::DT_BLK, + + /// Regular file type. + Reg = bindings::DT_REG, + + /// Symbolic link type. + Lnk = bindings::DT_LNK, + + /// Named unix-domain socket type. + Sock = bindings::DT_SOCK, + + /// White-out type. + Wht = bindings::DT_WHT, +} + +impl From for DirEntryType { + fn from(value: INodeType) -> Self { + match value { + INodeType::Dir => DirEntryType::Dir, + } + } +} + +impl core::convert::TryFrom for DirEntryType { + type Error = crate::error::Error; + + fn try_from(v: u32) -> Result { + match v { + v if v == Self::Unknown as u32 => Ok(Self::Unknown), + v if v == Self::Fifo as u32 => Ok(Self::Fifo), + v if v == Self::Chr as u32 => Ok(Self::Chr), + v if v == Self::Dir as u32 => Ok(Self::Dir), + v if v == Self::Blk as u32 => Ok(Self::Blk), + v if v == Self::Reg as u32 => Ok(Self::Reg), + v if v == Self::Lnk as u32 => Ok(Self::Lnk), + v if v == Self::Sock as u32 => Ok(Self::Sock), + v if v == Self::Wht as u32 => Ok(Self::Wht), + _ => Err(EDOM), + } + } } /// A registration of a file system. @@ -161,9 +225,7 @@ impl NewINode { let mode = match params.typ { INodeType::Dir => { - // SAFETY: `simple_dir_operations` never changes, it's safe to reference it. - inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations }; - + inode.__bindgen_anon_3.i_fop = &Tables::::DIR_FILE_OPERATIONS; // SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it. inode.i_op = unsafe { &bindings::simple_dir_inode_operations }; bindings::S_IFDIR @@ -403,6 +465,126 @@ impl Tables { free_cached_objects: None, shutdown: None, }; + + const DIR_FILE_OPERATIONS: bindings::file_operations = bindings::file_operations { + owner: ptr::null_mut(), + llseek: Some(bindings::generic_file_llseek), + read: Some(bindings::generic_read_dir), + write: None, + read_iter: None, + write_iter: None, + iopoll: None, + iterate_shared: Some(Self::read_dir_callback), + poll: None, + unlocked_ioctl: None, + compat_ioctl: None, + mmap: None, + mmap_supported_flags: 0, + open: None, + flush: None, + release: None, + fsync: None, + fasync: None, + lock: None, + get_unmapped_area: None, + check_flags: None, + flock: None, + splice_write: None, + splice_read: None, + splice_eof: None, + setlease: None, + fallocate: None, + show_fdinfo: None, + copy_file_range: None, + remap_file_range: None, + fadvise: None, + uring_cmd: None, + uring_cmd_iopoll: None, + }; + + unsafe extern "C" fn read_dir_callback( + file: *mut bindings::file, + ctx_ptr: *mut bindings::dir_context, + ) -> core::ffi::c_int { + from_result(|| { + // SAFETY: The C API guarantees that `file` is valid for read. And since `f_inode` is + // immutable, we can read it directly. + let inode = unsafe { &*(*file).f_inode.cast::>() }; + + // SAFETY: The C API guarantees that this is the only reference to the `dir_context` + // instance. + let emitter = unsafe { &mut *ctx_ptr.cast::() }; + let orig_pos = emitter.pos(); + + // Call the module implementation. We ignore errors if directory entries have been + // succesfully emitted: this is because we want users to see them before the error. + match T::read_dir(inode, emitter) { + Ok(_) => Ok(0), + Err(e) => { + if emitter.pos() == orig_pos { + Err(e) + } else { + Ok(0) + } + } + } + }) + } +} + +/// Directory entry emitter. +/// +/// This is used in [`FileSystem::read_dir`] implementations to report the directory entry. +#[repr(transparent)] +pub struct DirEmitter(bindings::dir_context); + +impl DirEmitter { + /// Returns the current position of the emitter. + pub fn pos(&self) -> i64 { + self.0.pos + } + + /// Emits a directory entry. + /// + /// `pos_inc` is the number with which to increment the current position on success. + /// + /// `name` is the name of the entry. + /// + /// `ino` is the inode number of the entry. + /// + /// `etype` is the type of the entry. + /// + /// Returns `false` when the entry could not be emitted, possibly because the user-provided + /// buffer is full. + pub fn emit(&mut self, pos_inc: i64, name: &[u8], ino: Ino, etype: DirEntryType) -> bool { + let Ok(name_len) = i32::try_from(name.len()) else { + return false; + }; + + let Some(actor) = self.0.actor else { + return false; + }; + + let Some(new_pos) = self.0.pos.checked_add(pos_inc) else { + return false; + }; + + // SAFETY: `name` is valid at least for the duration of the `actor` call. + let ret = unsafe { + actor( + &mut self.0, + name.as_ptr().cast(), + name_len, + self.0.pos, + ino, + etype as _, + ) + }; + if ret { + self.0.pos = new_pos; + } + ret + } } /// Kernel module that exposes a single file system implemented by `T`. @@ -431,7 +613,7 @@ impl crate::InPlaceModule for Module { /// /// ``` /// # mod module_fs_sample { -/// use kernel::fs::{INode, NewSuperBlock, SuperBlock, SuperParams}; +/// use kernel::fs::{DirEmitter, INode, NewSuperBlock, SuperBlock, SuperParams}; /// use kernel::prelude::*; /// use kernel::{c_str, fs, types::ARef}; /// @@ -452,6 +634,9 @@ impl crate::InPlaceModule for Module { /// fn init_root(_sb: &SuperBlock) -> Result>> { /// todo!() /// } +/// fn read_dir(_: &INode, _: &mut DirEmitter) -> Result { +/// todo!() +/// } /// } /// # } /// ``` diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 9e5f4c7d1c06bd..4e61a94afa7043 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -2,7 +2,9 @@ //! Rust read-only file system sample. -use kernel::fs::{INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams}; +use kernel::fs::{ + DirEmitter, INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams, +}; use kernel::prelude::*; use kernel::{c_str, fs, time::UNIX_EPOCH, types::ARef, types::Either}; @@ -14,6 +16,30 @@ kernel::module_fs! { license: "GPL", } +struct Entry { + name: &'static [u8], + ino: u64, + etype: INodeType, +} + +const ENTRIES: [Entry; 3] = [ + Entry { + name: b".", + ino: 1, + etype: INodeType::Dir, + }, + Entry { + name: b"..", + ino: 1, + etype: INodeType::Dir, + }, + Entry { + name: b"subdir", + ino: 2, + etype: INodeType::Dir, + }, +]; + struct RoFs; impl fs::FileSystem for RoFs { const NAME: &'static CStr = c_str!("rust-fs"); @@ -33,7 +59,7 @@ impl fs::FileSystem for RoFs { Either::Right(new) => new.init(INodeParams { typ: INodeType::Dir, mode: 0o555, - size: 1, + size: ENTRIES.len().try_into()?, blocks: 1, nlink: 2, uid: 0, @@ -44,4 +70,23 @@ impl fs::FileSystem for RoFs { }), } } + + fn read_dir(inode: &INode, emitter: &mut DirEmitter) -> Result { + if inode.ino() != 1 { + return Ok(()); + } + + let pos = emitter.pos(); + if pos >= ENTRIES.len().try_into()? { + return Ok(()); + } + + for e in ENTRIES.iter().skip(pos.try_into()?) { + if !emitter.emit(1, e.name, e.ino, e.etype.into()) { + break; + } + } + + Ok(()) + } } From 14b32d0d0a804538bacb570cf1abfb720c56959c Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:11 -0300 Subject: [PATCH 18/29] rust: fs: introduce `FileSystem::lookup` Allow Rust file systems to create inodes that are children of a directory inode when they're looked up by name. Signed-off-by: Wedson Almeida Filho --- rust/kernel/error.rs | 1 - rust/kernel/fs.rs | 65 +++++++++++++++++++++++++++++++++++++-- samples/rust/rust_rofs.rs | 25 +++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index e6d7ce46be550a..484fa7c11de18e 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -131,7 +131,6 @@ impl Error { } /// Returns the error encoded as a pointer. - #[allow(dead_code)] pub(crate) fn to_ptr(self) -> *mut T { // SAFETY: self.0 is a valid error due to its invariant. unsafe { bindings::ERR_PTR(self.0.into()) as *mut _ } diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 89611c44e4c54b..681fef8e3af11a 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -33,6 +33,9 @@ pub trait FileSystem { /// /// [`DirEmitter::pos`] holds the current position of the directory reader. fn read_dir(inode: &INode, emitter: &mut DirEmitter) -> Result; + + /// Returns the inode corresponding to the directory entry with the given name. + fn lookup(parent: &INode, name: &[u8]) -> Result>>; } /// The types of directory entries reported by [`FileSystem::read_dir`]. @@ -226,8 +229,7 @@ impl NewINode { let mode = match params.typ { INodeType::Dir => { inode.__bindgen_anon_3.i_fop = &Tables::::DIR_FILE_OPERATIONS; - // SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it. - inode.i_op = unsafe { &bindings::simple_dir_inode_operations }; + inode.i_op = &Tables::::DIR_INODE_OPERATIONS; bindings::S_IFDIR } }; @@ -530,6 +532,62 @@ impl Tables { } }) } + + const DIR_INODE_OPERATIONS: bindings::inode_operations = bindings::inode_operations { + lookup: Some(Self::lookup_callback), + get_link: None, + permission: None, + get_inode_acl: None, + readlink: None, + create: None, + link: None, + unlink: None, + symlink: None, + mkdir: None, + rmdir: None, + mknod: None, + rename: None, + setattr: None, + getattr: None, + listxattr: None, + fiemap: None, + update_time: None, + atomic_open: None, + tmpfile: None, + get_acl: None, + set_acl: None, + fileattr_set: None, + fileattr_get: None, + get_offset_ctx: None, + }; + + extern "C" fn lookup_callback( + parent_ptr: *mut bindings::inode, + dentry: *mut bindings::dentry, + _flags: u32, + ) -> *mut bindings::dentry { + // SAFETY: The C API guarantees that `parent_ptr` is a valid inode. + let parent = unsafe { &*parent_ptr.cast::>() }; + + // SAFETY: The C API guarantees that `dentry` is valid for read. Since the name is + // immutable, it's ok to read its length directly. + let len = unsafe { (*dentry).d_name.__bindgen_anon_1.__bindgen_anon_1.len }; + let Ok(name_len) = usize::try_from(len) else { + return ENOENT.to_ptr(); + }; + + // SAFETY: The C API guarantees that `dentry` is valid for read. Since the name is + // immutable, it's ok to read it directly. + let name = unsafe { core::slice::from_raw_parts((*dentry).d_name.name, name_len) }; + match T::lookup(parent, name) { + Err(e) => e.to_ptr(), + // SAFETY: The returned inode is valid and referenced (by the type invariants), so + // it is ok to transfer this increment to `d_splice_alias`. + Ok(inode) => unsafe { + bindings::d_splice_alias(ManuallyDrop::new(inode).0.get(), dentry) + }, + } + } } /// Directory entry emitter. @@ -637,6 +695,9 @@ impl crate::InPlaceModule for Module { /// fn read_dir(_: &INode, _: &mut DirEmitter) -> Result { /// todo!() /// } +/// fn lookup(_: &INode, _: &[u8]) -> Result>> { +/// todo!() +/// } /// } /// # } /// ``` diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 4e61a94afa7043..4cc8525884a9d1 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -89,4 +89,29 @@ impl fs::FileSystem for RoFs { Ok(()) } + + fn lookup(parent: &INode, name: &[u8]) -> Result>> { + if parent.ino() != 1 { + return Err(ENOENT); + } + + match name { + b"subdir" => match parent.super_block().get_or_create_inode(2)? { + Either::Left(existing) => Ok(existing), + Either::Right(new) => new.init(INodeParams { + typ: INodeType::Dir, + mode: 0o555, + size: 0, + blocks: 1, + nlink: 2, + uid: 0, + gid: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, + }), + }, + _ => Err(ENOENT), + } + } } From 5e601b9e469fc41d910efe6609f5806cc05cb63b Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:11 -0300 Subject: [PATCH 19/29] rust: folio: introduce basic support for folios Allow Rust file systems to handle ref-counted folios. Provide the minimum needed to implement `read_folio` (part of `struct address_space_operations`) in read-only file systems and to read uncached blocks. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 3 + rust/bindings/lib.rs | 2 + rust/helpers.c | 81 ++++++++++++ rust/kernel/folio.rs | 215 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 302 insertions(+) create mode 100644 rust/kernel/folio.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index ca1898ce9527c5..53a99ea512d175 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -27,3 +28,5 @@ const slab_flags_t BINDINGS_SLAB_ACCOUNT = SLAB_ACCOUNT; const unsigned long BINDINGS_SB_RDONLY = SB_RDONLY; const loff_t BINDINGS_MAX_LFS_FILESIZE = MAX_LFS_FILESIZE; + +const size_t BINDINGS_PAGE_SIZE = PAGE_SIZE; diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 426915d3fb57c0..a96b7f08e57dae 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -59,3 +59,5 @@ pub const SLAB_ACCOUNT: slab_flags_t = BINDINGS_SLAB_ACCOUNT; pub const SB_RDONLY: core::ffi::c_ulong = BINDINGS_SB_RDONLY; pub const MAX_LFS_FILESIZE: loff_t = BINDINGS_MAX_LFS_FILESIZE; + +pub const PAGE_SIZE: usize = BINDINGS_PAGE_SIZE; diff --git a/rust/helpers.c b/rust/helpers.c index c5a2bec6467d14..f2ce3e7b688ce7 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -23,10 +23,14 @@ #include #include #include +#include #include #include #include +#include +#include #include +#include #include #include #include @@ -145,6 +149,77 @@ struct kunit *rust_helper_kunit_get_current_test(void) } EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test); +void *rust_helper_kmap(struct page *page) +{ + return kmap(page); +} +EXPORT_SYMBOL_GPL(rust_helper_kmap); + +void rust_helper_kunmap(struct page *page) +{ + kunmap(page); +} +EXPORT_SYMBOL_GPL(rust_helper_kunmap); + +void rust_helper_folio_get(struct folio *folio) +{ + folio_get(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_folio_get); + +void rust_helper_folio_put(struct folio *folio) +{ + folio_put(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_folio_put); + +struct page *rust_helper_folio_page(struct folio *folio, size_t n) +{ + return folio_page(folio, n); +} + +loff_t rust_helper_folio_pos(struct folio *folio) +{ + return folio_pos(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_folio_pos); + +size_t rust_helper_folio_size(struct folio *folio) +{ + return folio_size(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_folio_size); + +void rust_helper_folio_mark_uptodate(struct folio *folio) +{ + folio_mark_uptodate(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_folio_mark_uptodate); + +void rust_helper_folio_set_error(struct folio *folio) +{ + folio_set_error(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_folio_set_error); + +void rust_helper_flush_dcache_folio(struct folio *folio) +{ + flush_dcache_folio(folio); +} +EXPORT_SYMBOL_GPL(rust_helper_flush_dcache_folio); + +void *rust_helper_kmap_local_folio(struct folio *folio, size_t offset) +{ + return kmap_local_folio(folio, offset); +} +EXPORT_SYMBOL_GPL(rust_helper_kmap_local_folio); + +void rust_helper_kunmap_local(const void *vaddr) +{ + kunmap_local(vaddr); +} +EXPORT_SYMBOL_GPL(rust_helper_kunmap_local); + void rust_helper_i_uid_write(struct inode *inode, uid_t uid) { i_uid_write(inode, uid); @@ -163,6 +238,12 @@ off_t rust_helper_i_size_read(const struct inode *inode) } EXPORT_SYMBOL_GPL(rust_helper_i_size_read); +void rust_helper_mapping_set_large_folios(struct address_space *mapping) +{ + mapping_set_large_folios(mapping); +} +EXPORT_SYMBOL_GPL(rust_helper_mapping_set_large_folios); + /* * `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can * use it in contexts where Rust expects a `usize` like slice (array) indices. diff --git a/rust/kernel/folio.rs b/rust/kernel/folio.rs new file mode 100644 index 00000000000000..ef8a08b9796225 --- /dev/null +++ b/rust/kernel/folio.rs @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Groups of contiguous pages, folios. +//! +//! C headers: [`include/linux/mm.h`](../../include/linux/mm.h) + +use crate::error::{code::*, Result}; +use crate::types::{ARef, AlwaysRefCounted, Opaque, ScopeGuard}; +use core::{cmp::min, ptr}; + +/// Wraps the kernel's `struct folio`. +/// +/// # Invariants +/// +/// Instances of this type are always ref-counted, that is, a call to `folio_get` ensures that the +/// allocation remains valid at least until the matching call to `folio_put`. +#[repr(transparent)] +pub struct Folio(pub(crate) Opaque); + +// SAFETY: The type invariants guarantee that `Folio` is always ref-counted. +unsafe impl AlwaysRefCounted for Folio { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference means that the refcount is nonzero. + unsafe { bindings::folio_get(self.0.get()) }; + } + + unsafe fn dec_ref(obj: ptr::NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is nonzero. + unsafe { bindings::folio_put(obj.cast().as_ptr()) } + } +} + +impl Folio { + /// Tries to allocate a new folio. + /// + /// On success, returns a folio made up of 2^order pages. + pub fn try_new(order: u32) -> Result { + if order > bindings::MAX_ORDER { + return Err(EDOM); + } + + // SAFETY: We checked that `order` is within the max allowed value. + let f = ptr::NonNull::new(unsafe { bindings::folio_alloc(bindings::GFP_KERNEL, order) }) + .ok_or(ENOMEM)?; + + // SAFETY: The folio returned by `folio_alloc` is referenced. The ownership of the + // reference is transferred to the `ARef` instance. + Ok(UniqueFolio(unsafe { ARef::from_raw(f.cast()) })) + } + + /// Returns the byte position of this folio in its file. + pub fn pos(&self) -> i64 { + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount. + unsafe { bindings::folio_pos(self.0.get()) } + } + + /// Returns the byte size of this folio. + pub fn size(&self) -> usize { + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount. + unsafe { bindings::folio_size(self.0.get()) } + } + + /// Flushes the data cache for the pages that make up the folio. + pub fn flush_dcache(&self) { + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount. + unsafe { bindings::flush_dcache_folio(self.0.get()) } + } +} + +/// A [`Folio`] that has a single reference to it. +pub struct UniqueFolio(pub(crate) ARef); + +impl UniqueFolio { + /// Maps the contents of a folio page into a slice. + pub fn map_page(&self, page_index: usize) -> Result> { + if page_index >= self.0.size() / bindings::PAGE_SIZE { + return Err(EDOM); + } + + // SAFETY: We just checked that the index is within bounds of the folio. + let page = unsafe { bindings::folio_page(self.0 .0.get(), page_index) }; + + // SAFETY: `page` is valid because it was returned by `folio_page` above. + let ptr = unsafe { bindings::kmap(page) }; + + // SAFETY: We just mapped `ptr`, so it's valid for read. + let data = unsafe { core::slice::from_raw_parts(ptr.cast::(), bindings::PAGE_SIZE) }; + + Ok(MapGuard { data, page }) + } +} + +/// A mapped [`UniqueFolio`]. +pub struct MapGuard<'a> { + data: &'a [u8], + page: *mut bindings::page, +} + +impl core::ops::Deref for MapGuard<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.data + } +} + +impl Drop for MapGuard<'_> { + fn drop(&mut self) { + // SAFETY: A `MapGuard` instance is only created when `kmap` succeeds, so it's ok to unmap + // it when the guard is dropped. + unsafe { bindings::kunmap(self.page) }; + } +} + +/// A locked [`Folio`]. +pub struct LockedFolio<'a>(&'a Folio); + +impl LockedFolio<'_> { + /// Creates a new locked folio from a raw pointer. + /// + /// # Safety + /// + /// Callers must ensure that the folio is valid and locked. Additionally, that the + /// responsibility of unlocking is transferred to the new instance of [`LockedFolio`]. Lastly, + /// that the returned [`LockedFolio`] doesn't outlive the refcount that keeps it alive. + #[allow(dead_code)] + pub(crate) unsafe fn from_raw(folio: *const bindings::folio) -> Self { + let ptr = folio.cast(); + // SAFETY: The safety requirements ensure that `folio` (from which `ptr` is derived) is + // valid and will remain valid while the `LockedFolio` instance lives. + Self(unsafe { &*ptr }) + } + + /// Marks the folio as being up to date. + pub fn mark_uptodate(&mut self) { + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount. + unsafe { bindings::folio_mark_uptodate(self.0 .0.get()) } + } + + /// Sets the error flag on the folio. + pub fn set_error(&mut self) { + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount. + unsafe { bindings::folio_set_error(self.0 .0.get()) } + } + + fn for_each_page( + &mut self, + offset: usize, + len: usize, + mut cb: impl FnMut(&mut [u8]) -> Result, + ) -> Result { + let mut remaining = len; + let mut next_offset = offset; + + // Check that we don't overflow the folio. + let end = offset.checked_add(len).ok_or(EDOM)?; + if end > self.size() { + return Err(EINVAL); + } + + while remaining > 0 { + let page_offset = next_offset & (bindings::PAGE_SIZE - 1); + let usable = min(remaining, bindings::PAGE_SIZE - page_offset); + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount; + // `next_offset` is also guaranteed be lesss than the folio size. + let ptr = unsafe { bindings::kmap_local_folio(self.0 .0.get(), next_offset) }; + + // SAFETY: `ptr` was just returned by the `kmap_local_folio` above. + let _guard = ScopeGuard::new(|| unsafe { bindings::kunmap_local(ptr) }); + + // SAFETY: `kmap_local_folio` maps whole page so we know it's mapped for at least + // `usable` bytes. + let s = unsafe { core::slice::from_raw_parts_mut(ptr.cast::(), usable) }; + cb(s)?; + + next_offset += usable; + remaining -= usable; + } + + Ok(()) + } + + /// Writes the given slice into the folio. + pub fn write(&mut self, offset: usize, data: &[u8]) -> Result { + let mut remaining = data; + + self.for_each_page(offset, data.len(), |s| { + s.copy_from_slice(&remaining[..s.len()]); + remaining = &remaining[s.len()..]; + Ok(()) + }) + } + + /// Writes zeroes into the folio. + pub fn zero_out(&mut self, offset: usize, len: usize) -> Result { + self.for_each_page(offset, len, |s| { + s.fill(0); + Ok(()) + }) + } +} + +impl core::ops::Deref for LockedFolio<'_> { + type Target = Folio; + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl Drop for LockedFolio<'_> { + fn drop(&mut self) { + // SAFETY: The folio is valid because the shared reference implies a non-zero refcount. + unsafe { bindings::folio_unlock(self.0 .0.get()) } + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 00059b80c24092..0e85b380da6449 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -34,6 +34,7 @@ extern crate self as kernel; mod allocator; mod build_assert; pub mod error; +pub mod folio; pub mod fs; pub mod init; pub mod ioctl; From c02d2b9c336a987784deb39a75302c77842b1c4b Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:11 -0300 Subject: [PATCH 20/29] rust: fs: introduce `FileSystem::read_folio` Allow Rust file systems to create regular file inodes backed by the page cache. The contents of such files are read into folios via `read_folio`. Signed-off-by: Wedson Almeida Filho --- rust/kernel/folio.rs | 1 - rust/kernel/fs.rs | 75 +++++++++++++++++++++++++++++++++++++-- samples/rust/rust_rofs.rs | 69 ++++++++++++++++++++++++----------- 3 files changed, 122 insertions(+), 23 deletions(-) diff --git a/rust/kernel/folio.rs b/rust/kernel/folio.rs index ef8a08b9796225..b7f80291b0e110 100644 --- a/rust/kernel/folio.rs +++ b/rust/kernel/folio.rs @@ -123,7 +123,6 @@ impl LockedFolio<'_> { /// Callers must ensure that the folio is valid and locked. Additionally, that the /// responsibility of unlocking is transferred to the new instance of [`LockedFolio`]. Lastly, /// that the returned [`LockedFolio`] doesn't outlive the refcount that keeps it alive. - #[allow(dead_code)] pub(crate) unsafe fn from_raw(folio: *const bindings::folio) -> Self { let ptr = folio.cast(); // SAFETY: The safety requirements ensure that `folio` (from which `ptr` is derived) is diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 681fef8e3af11a..ee3dce87032b75 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -8,7 +8,10 @@ use crate::error::{code::*, from_result, to_result, Error, Result}; use crate::types::{ARef, AlwaysRefCounted, Either, Opaque}; -use crate::{bindings, init::PinInit, str::CStr, time::Timespec, try_pin_init, ThisModule}; +use crate::{ + bindings, folio::LockedFolio, init::PinInit, str::CStr, time::Timespec, try_pin_init, + ThisModule, +}; use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr}; use macros::{pin_data, pinned_drop}; @@ -36,6 +39,9 @@ pub trait FileSystem { /// Returns the inode corresponding to the directory entry with the given name. fn lookup(parent: &INode, name: &[u8]) -> Result>>; + + /// Reads the contents of the inode into the given folio. + fn read_folio(inode: &INode, folio: LockedFolio<'_>) -> Result; } /// The types of directory entries reported by [`FileSystem::read_dir`]. @@ -74,6 +80,7 @@ impl From for DirEntryType { fn from(value: INodeType) -> Self { match value { INodeType::Dir => DirEntryType::Dir, + INodeType::Reg => DirEntryType::Reg, } } } @@ -232,6 +239,15 @@ impl NewINode { inode.i_op = &Tables::::DIR_INODE_OPERATIONS; bindings::S_IFDIR } + INodeType::Reg => { + // SAFETY: `generic_ro_fops` never changes, it's safe to reference it. + inode.__bindgen_anon_3.i_fop = unsafe { &bindings::generic_ro_fops }; + inode.i_data.a_ops = &Tables::::FILE_ADDRESS_SPACE_OPERATIONS; + + // SAFETY: The `i_mapping` pointer doesn't change and is valid. + unsafe { bindings::mapping_set_large_folios(inode.i_mapping) }; + bindings::S_IFREG + } }; inode.i_mode = (params.mode & 0o777) | u16::try_from(mode)?; @@ -268,6 +284,9 @@ impl Drop for NewINode { pub enum INodeType { /// Directory type. Dir, + + /// Regular file type. + Reg, } /// Required inode parameters. @@ -588,6 +607,55 @@ impl Tables { }, } } + + const FILE_ADDRESS_SPACE_OPERATIONS: bindings::address_space_operations = + bindings::address_space_operations { + writepage: None, + read_folio: Some(Self::read_folio_callback), + writepages: None, + dirty_folio: None, + readahead: None, + write_begin: None, + write_end: None, + bmap: None, + invalidate_folio: None, + release_folio: None, + free_folio: None, + direct_IO: None, + migrate_folio: None, + launder_folio: None, + is_partially_uptodate: None, + is_dirty_writeback: None, + error_remove_page: None, + swap_activate: None, + swap_deactivate: None, + swap_rw: None, + }; + + extern "C" fn read_folio_callback( + _file: *mut bindings::file, + folio: *mut bindings::folio, + ) -> i32 { + from_result(|| { + // SAFETY: All pointers are valid and stable. + let inode = unsafe { + &*(*(*folio) + .__bindgen_anon_1 + .page + .__bindgen_anon_1 + .__bindgen_anon_1 + .mapping) + .host + .cast::>() + }; + + // SAFETY: The C contract guarantees that the folio is valid and locked, with ownership + // of the lock transferred to the callee (this function). The folio is also guaranteed + // not to outlive this function. + T::read_folio(inode, unsafe { LockedFolio::from_raw(folio) })?; + Ok(0) + }) + } } /// Directory entry emitter. @@ -673,7 +741,7 @@ impl crate::InPlaceModule for Module { /// # mod module_fs_sample { /// use kernel::fs::{DirEmitter, INode, NewSuperBlock, SuperBlock, SuperParams}; /// use kernel::prelude::*; -/// use kernel::{c_str, fs, types::ARef}; +/// use kernel::{c_str, folio::LockedFolio, fs, types::ARef}; /// /// kernel::module_fs! { /// type: MyFs, @@ -698,6 +766,9 @@ impl crate::InPlaceModule for Module { /// fn lookup(_: &INode, _: &[u8]) -> Result>> { /// todo!() /// } +/// fn read_folio(_: &INode, _: LockedFolio<'_>) -> Result { +/// todo!() +/// } /// } /// # } /// ``` diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 4cc8525884a9d1..ef651ad381853a 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -6,7 +6,7 @@ use kernel::fs::{ DirEmitter, INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams, }; use kernel::prelude::*; -use kernel::{c_str, fs, time::UNIX_EPOCH, types::ARef, types::Either}; +use kernel::{c_str, folio::LockedFolio, fs, time::UNIX_EPOCH, types::ARef, types::Either}; kernel::module_fs! { type: RoFs, @@ -20,6 +20,7 @@ struct Entry { name: &'static [u8], ino: u64, etype: INodeType, + contents: &'static [u8], } const ENTRIES: [Entry; 3] = [ @@ -27,16 +28,19 @@ const ENTRIES: [Entry; 3] = [ name: b".", ino: 1, etype: INodeType::Dir, + contents: b"", }, Entry { name: b"..", ino: 1, etype: INodeType::Dir, + contents: b"", }, Entry { - name: b"subdir", + name: b"test.txt", ino: 2, - etype: INodeType::Dir, + etype: INodeType::Reg, + contents: b"hello\n", }, ]; @@ -95,23 +99,48 @@ impl fs::FileSystem for RoFs { return Err(ENOENT); } - match name { - b"subdir" => match parent.super_block().get_or_create_inode(2)? { - Either::Left(existing) => Ok(existing), - Either::Right(new) => new.init(INodeParams { - typ: INodeType::Dir, - mode: 0o555, - size: 0, - blocks: 1, - nlink: 2, - uid: 0, - gid: 0, - atime: UNIX_EPOCH, - ctime: UNIX_EPOCH, - mtime: UNIX_EPOCH, - }), - }, - _ => Err(ENOENT), + for e in &ENTRIES { + if name == e.name { + return match parent.super_block().get_or_create_inode(e.ino)? { + Either::Left(existing) => Ok(existing), + Either::Right(new) => new.init(INodeParams { + typ: e.etype, + mode: 0o444, + size: e.contents.len().try_into()?, + blocks: 1, + nlink: 1, + uid: 0, + gid: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, + }), + }; + } } + + Err(ENOENT) + } + + fn read_folio(inode: &INode, mut folio: LockedFolio<'_>) -> Result { + let data = match inode.ino() { + 2 => ENTRIES[2].contents, + _ => return Err(EINVAL), + }; + + let pos = usize::try_from(folio.pos()).unwrap_or(usize::MAX); + let copied = if pos >= data.len() { + 0 + } else { + let to_copy = core::cmp::min(data.len() - pos, folio.size()); + folio.write(0, &data[pos..][..to_copy])?; + to_copy + }; + + folio.zero_out(copied, folio.size() - copied)?; + folio.mark_uptodate(); + folio.flush_dcache(); + + Ok(()) } } From ce0acb6dc64f34fe6c6f53f68d53d9cafb8ee67c Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:11 -0300 Subject: [PATCH 21/29] rust: fs: introduce `FileSystem::read_xattr` Allow Rust file systems to expose xattrs associated with inodes. `overlayfs` uses an xattr to indicate that a directory is opaque (i.e., that lower layers should not be looked up). The planned file systems need to support opaque directories, so they must be able to implement this. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 1 + rust/kernel/error.rs | 2 ++ rust/kernel/fs.rs | 43 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 53a99ea512d175..fa754c5e85a2b8 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -15,6 +15,7 @@ #include #include #include +#include /* `bindgen` gets confused at certain things. */ const size_t BINDINGS_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN; diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 484fa7c11de18e..6c167583b275ca 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -81,6 +81,8 @@ pub mod code { declare_err!(EIOCBQUEUED, "iocb queued, will get completion event."); declare_err!(ERECALLCONFLICT, "Conflict with recalled state."); declare_err!(ENOGRACE, "NFS file lock reclaim refused."); + declare_err!(ENODATA, "No data available."); + declare_err!(EOPNOTSUPP, "Operation not supported on transport endpoint."); } /// Generic integer kernel error. diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index ee3dce87032b75..adf9cbee16d29d 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -42,6 +42,14 @@ pub trait FileSystem { /// Reads the contents of the inode into the given folio. fn read_folio(inode: &INode, folio: LockedFolio<'_>) -> Result; + + /// Reads an xattr. + /// + /// Returns the number of bytes written to `outbuf`. If it is too small, returns the number of + /// bytes needs to hold the attribute. + fn read_xattr(_inode: &INode, _name: &CStr, _outbuf: &mut [u8]) -> Result { + Err(EOPNOTSUPP) + } } /// The types of directory entries reported by [`FileSystem::read_dir`]. @@ -418,6 +426,7 @@ impl Tables { sb.0.s_magic = params.magic as _; sb.0.s_op = &Tables::::SUPER_BLOCK; + sb.0.s_xattr = &Tables::::XATTR_HANDLERS[0]; sb.0.s_maxbytes = params.maxbytes; sb.0.s_time_gran = params.time_gran; sb.0.s_blocksize_bits = params.blocksize_bits; @@ -487,6 +496,40 @@ impl Tables { shutdown: None, }; + const XATTR_HANDLERS: [*const bindings::xattr_handler; 2] = [&Self::XATTR_HANDLER, ptr::null()]; + + const XATTR_HANDLER: bindings::xattr_handler = bindings::xattr_handler { + name: ptr::null(), + prefix: crate::c_str!("").as_char_ptr(), + flags: 0, + list: None, + get: Some(Self::xattr_get_callback), + set: None, + }; + + unsafe extern "C" fn xattr_get_callback( + _handler: *const bindings::xattr_handler, + _dentry: *mut bindings::dentry, + inode_ptr: *mut bindings::inode, + name: *const core::ffi::c_char, + buffer: *mut core::ffi::c_void, + size: usize, + ) -> core::ffi::c_int { + from_result(|| { + // SAFETY: The C API guarantees that `inode_ptr` is a valid inode. + let inode = unsafe { &*inode_ptr.cast::>() }; + + // SAFETY: The c API guarantees that `name` is a valid null-terminated string. It + // also guarantees that it's valid for the duration of the callback. + let name = unsafe { CStr::from_char_ptr(name) }; + + // SAFETY: The C API guarantees that `buffer` is at least `size` bytes in length. + let buf = unsafe { core::slice::from_raw_parts_mut(buffer.cast(), size) }; + let len = T::read_xattr(inode, name, buf)?; + Ok(len.try_into()?) + }) + } + const DIR_FILE_OPERATIONS: bindings::file_operations = bindings::file_operations { owner: ptr::null_mut(), llseek: Some(bindings::generic_file_llseek), From 3f9496662b8dba04ccfa50220c97c543aa4e73a3 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:12 -0300 Subject: [PATCH 22/29] rust: fs: introduce `FileSystem::statfs` Allow Rust file systems to expose their stats. `overlayfs` requires that this be implemented by all file systems that are part of an overlay. The planned file systems need to be overlayed with overlayfs, so they must be able to implement this. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 1 + rust/kernel/error.rs | 1 + rust/kernel/fs.rs | 52 ++++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index fa754c5e85a2b8..e2b2ccc835e3f7 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 6c167583b275ca..829756cf6c48d9 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -83,6 +83,7 @@ pub mod code { declare_err!(ENOGRACE, "NFS file lock reclaim refused."); declare_err!(ENODATA, "No data available."); declare_err!(EOPNOTSUPP, "Operation not supported on transport endpoint."); + declare_err!(ENOSYS, "Invalid system call number."); } /// Generic integer kernel error. diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index adf9cbee16d29d..8f34da50e69480 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -50,6 +50,31 @@ pub trait FileSystem { fn read_xattr(_inode: &INode, _name: &CStr, _outbuf: &mut [u8]) -> Result { Err(EOPNOTSUPP) } + + /// Get filesystem statistics. + fn statfs(_sb: &SuperBlock) -> Result { + Err(ENOSYS) + } +} + +/// File system stats. +/// +/// A subset of C's `kstatfs`. +pub struct Stat { + /// Magic number of the file system. + pub magic: u32, + + /// The maximum length of a file name. + pub namelen: i64, + + /// Block size. + pub bsize: i64, + + /// Number of files in the file system. + pub files: u64, + + /// Number of blocks in the file system. + pub blocks: u64, } /// The types of directory entries reported by [`FileSystem::read_dir`]. @@ -478,7 +503,7 @@ impl Tables { freeze_fs: None, thaw_super: None, unfreeze_fs: None, - statfs: None, + statfs: Some(Self::statfs_callback), remount_fs: None, umount_begin: None, show_options: None, @@ -496,6 +521,31 @@ impl Tables { shutdown: None, }; + unsafe extern "C" fn statfs_callback( + dentry: *mut bindings::dentry, + buf: *mut bindings::kstatfs, + ) -> core::ffi::c_int { + from_result(|| { + // SAFETY: The C API guarantees that `dentry` is valid for read. `d_sb` is + // immutable, so it's safe to read it. The superblock is guaranteed to be valid dor + // the duration of the call. + let sb = unsafe { &*(*dentry).d_sb.cast::>() }; + let s = T::statfs(sb)?; + + // SAFETY: The C API guarantees that `buf` is valid for read and write. + let buf = unsafe { &mut *buf }; + buf.f_type = s.magic.into(); + buf.f_namelen = s.namelen; + buf.f_bsize = s.bsize; + buf.f_files = s.files; + buf.f_blocks = s.blocks; + buf.f_bfree = 0; + buf.f_bavail = 0; + buf.f_ffree = 0; + Ok(0) + }) + } + const XATTR_HANDLERS: [*const bindings::xattr_handler; 2] = [&Self::XATTR_HANDLER, ptr::null()]; const XATTR_HANDLER: bindings::xattr_handler = bindings::xattr_handler { From 6032d937b8734800303beda561e33785c1b9a25e Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:12 -0300 Subject: [PATCH 23/29] rust: fs: introduce more inode types Allow Rust file system modules to create inodes that are symlinks, pipes, sockets, char devices and block devices (in addition to the already-supported directories and regular files). Signed-off-by: Wedson Almeida Filho --- rust/helpers.c | 6 +++ rust/kernel/fs.rs | 88 +++++++++++++++++++++++++++++++++++++++ samples/rust/rust_rofs.rs | 9 +++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/rust/helpers.c b/rust/helpers.c index f2ce3e7b688ce7..af335d1912e7e8 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -244,6 +244,12 @@ void rust_helper_mapping_set_large_folios(struct address_space *mapping) } EXPORT_SYMBOL_GPL(rust_helper_mapping_set_large_folios); +unsigned int rust_helper_MKDEV(unsigned int major, unsigned int minor) +{ + return MKDEV(major, minor); +} +EXPORT_SYMBOL_GPL(rust_helper_MKDEV); + /* * `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can * use it in contexts where Rust expects a `usize` like slice (array) indices. diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 8f34da50e69480..5b7eaa16d25479 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -112,8 +112,13 @@ pub enum DirEntryType { impl From for DirEntryType { fn from(value: INodeType) -> Self { match value { + INodeType::Fifo => DirEntryType::Fifo, + INodeType::Chr(_, _) => DirEntryType::Chr, INodeType::Dir => DirEntryType::Dir, + INodeType::Blk(_, _) => DirEntryType::Blk, INodeType::Reg => DirEntryType::Reg, + INodeType::Lnk => DirEntryType::Lnk, + INodeType::Sock => DirEntryType::Sock, } } } @@ -281,6 +286,46 @@ impl NewINode { unsafe { bindings::mapping_set_large_folios(inode.i_mapping) }; bindings::S_IFREG } + INodeType::Lnk => { + inode.i_op = &Tables::::LNK_INODE_OPERATIONS; + inode.i_data.a_ops = &Tables::::FILE_ADDRESS_SPACE_OPERATIONS; + + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { bindings::inode_nohighmem(inode) }; + bindings::S_IFLNK + } + INodeType::Fifo => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { bindings::init_special_inode(inode, bindings::S_IFIFO as _, 0) }; + bindings::S_IFIFO + } + INodeType::Sock => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { bindings::init_special_inode(inode, bindings::S_IFSOCK as _, 0) }; + bindings::S_IFSOCK + } + INodeType::Chr(major, minor) => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { + bindings::init_special_inode( + inode, + bindings::S_IFCHR as _, + bindings::MKDEV(major, minor & bindings::MINORMASK), + ) + }; + bindings::S_IFCHR + } + INodeType::Blk(major, minor) => { + // SAFETY: `inode` is valid for write as it's a new inode. + unsafe { + bindings::init_special_inode( + inode, + bindings::S_IFBLK as _, + bindings::MKDEV(major, minor & bindings::MINORMASK), + ) + }; + bindings::S_IFBLK + } }; inode.i_mode = (params.mode & 0o777) | u16::try_from(mode)?; @@ -315,11 +360,26 @@ impl Drop for NewINode { /// The type of the inode. #[derive(Copy, Clone)] pub enum INodeType { + /// Named pipe (first-in, first-out) type. + Fifo, + + /// Character device type. + Chr(u32, u32), + /// Directory type. Dir, + /// Block device type. + Blk(u32, u32), + /// Regular file type. Reg, + + /// Symbolic link type. + Lnk, + + /// Named unix-domain socket type. + Sock, } /// Required inode parameters. @@ -701,6 +761,34 @@ impl Tables { } } + const LNK_INODE_OPERATIONS: bindings::inode_operations = bindings::inode_operations { + lookup: None, + get_link: Some(bindings::page_get_link), + permission: None, + get_inode_acl: None, + readlink: None, + create: None, + link: None, + unlink: None, + symlink: None, + mkdir: None, + rmdir: None, + mknod: None, + rename: None, + setattr: None, + getattr: None, + listxattr: None, + fiemap: None, + update_time: None, + atomic_open: None, + tmpfile: None, + get_acl: None, + set_acl: None, + fileattr_set: None, + fileattr_get: None, + get_offset_ctx: None, + }; + const FILE_ADDRESS_SPACE_OPERATIONS: bindings::address_space_operations = bindings::address_space_operations { writepage: None, diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index ef651ad381853a..95ce28efa1c339 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -23,7 +23,7 @@ struct Entry { contents: &'static [u8], } -const ENTRIES: [Entry; 3] = [ +const ENTRIES: [Entry; 4] = [ Entry { name: b".", ino: 1, @@ -42,6 +42,12 @@ const ENTRIES: [Entry; 3] = [ etype: INodeType::Reg, contents: b"hello\n", }, + Entry { + name: b"link.txt", + ino: 3, + etype: INodeType::Lnk, + contents: b"./test.txt", + }, ]; struct RoFs; @@ -125,6 +131,7 @@ impl fs::FileSystem for RoFs { fn read_folio(inode: &INode, mut folio: LockedFolio<'_>) -> Result { let data = match inode.ino() { 2 => ENTRIES[2].contents, + 3 => ENTRIES[3].contents, _ => return Err(EINVAL), }; From 1cf6e5ef569b3be1f926cd74b7dbdf9e620b7bff Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:12 -0300 Subject: [PATCH 24/29] rust: fs: add per-superblock data Allow Rust file systems to associate [typed] data to super blocks when they're created. Since we only have a pointer-sized field in which to store the state, it must implement the `ForeignOwnable` trait. Signed-off-by: Wedson Almeida Filho --- rust/kernel/fs.rs | 42 +++++++++++++++++++++++++++++++++------ samples/rust/rust_rofs.rs | 4 +++- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 5b7eaa16d25479..e9a9362d289737 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -7,7 +7,7 @@ //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h) use crate::error::{code::*, from_result, to_result, Error, Result}; -use crate::types::{ARef, AlwaysRefCounted, Either, Opaque}; +use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque}; use crate::{ bindings, folio::LockedFolio, init::PinInit, str::CStr, time::Timespec, try_pin_init, ThisModule, @@ -20,11 +20,14 @@ pub const MAX_LFS_FILESIZE: i64 = bindings::MAX_LFS_FILESIZE; /// A file system type. pub trait FileSystem { + /// Data associated with each file system instance (super-block). + type Data: ForeignOwnable + Send + Sync; + /// The name of the file system type. const NAME: &'static CStr; /// Returns the parameters to initialise a super block. - fn super_params(sb: &NewSuperBlock) -> Result; + fn super_params(sb: &NewSuperBlock) -> Result>; /// Initialises and returns the root inode of the given superblock. /// @@ -174,7 +177,7 @@ impl Registration { fs.owner = module.0; fs.name = T::NAME.as_char_ptr(); fs.init_fs_context = Some(Self::init_fs_context_callback::); - fs.kill_sb = Some(Self::kill_sb_callback); + fs.kill_sb = Some(Self::kill_sb_callback::); fs.fs_flags = 0; // SAFETY: Pointers stored in `fs` are static so will live for as long as the @@ -195,10 +198,22 @@ impl Registration { }) } - unsafe extern "C" fn kill_sb_callback(sb_ptr: *mut bindings::super_block) { + unsafe extern "C" fn kill_sb_callback( + sb_ptr: *mut bindings::super_block, + ) { // SAFETY: In `get_tree_callback` we always call `get_tree_nodev`, so `kill_anon_super` is // the appropriate function to call for cleanup. unsafe { bindings::kill_anon_super(sb_ptr) }; + + // SAFETY: The C API contract guarantees that `sb_ptr` is valid for read. + let ptr = unsafe { (*sb_ptr).s_fs_info }; + if !ptr.is_null() { + // SAFETY: The only place where `s_fs_info` is assigned is `NewSuperBlock::init`, where + // it's initialised with the result of an `into_foreign` call. We checked above that + // `ptr` is non-null because it would be null if we never reached the point where we + // init the field. + unsafe { T::Data::from_foreign(ptr) }; + } } } @@ -429,6 +444,14 @@ pub struct INodeParams { pub struct SuperBlock(Opaque, PhantomData); impl SuperBlock { + /// Returns the data associated with the superblock. + pub fn data(&self) -> ::Borrowed<'_> { + // SAFETY: This method is only available after the `NeedsData` typestate, so `s_fs_info` + // has been initialised initialised with the result of a call to `T::into_foreign`. + let ptr = unsafe { (*self.0.get()).s_fs_info }; + unsafe { T::Data::borrow(ptr) } + } + /// Tries to get an existing inode or create a new one if it doesn't exist yet. pub fn get_or_create_inode(&self, ino: Ino) -> Result>, NewINode>> { // SAFETY: The only initialisation missing from the superblock is the root, and this @@ -458,7 +481,7 @@ impl SuperBlock { /// Required superblock parameters. /// /// This is returned by implementations of [`FileSystem::super_params`]. -pub struct SuperParams { +pub struct SuperParams { /// The magic number of the superblock. pub magic: u32, @@ -472,6 +495,9 @@ pub struct SuperParams { /// Granularity of c/m/atime in ns (cannot be worse than a second). pub time_gran: u32, + + /// Data to be associated with the superblock. + pub data: T, } /// A superblock that is still being initialised. @@ -522,6 +548,9 @@ impl Tables { sb.0.s_blocksize = 1 << sb.0.s_blocksize_bits; sb.0.s_flags |= bindings::SB_RDONLY; + // N.B.: Even on failure, `kill_sb` is called and frees the data. + sb.0.s_fs_info = params.data.into_foreign().cast_mut(); + // SAFETY: The callback contract guarantees that `sb_ptr` is a unique pointer to a // newly-created (and initialised above) superblock. let sb = unsafe { &mut *sb_ptr.cast() }; @@ -934,8 +963,9 @@ impl crate::InPlaceModule for Module { /// /// struct MyFs; /// impl fs::FileSystem for MyFs { +/// type Data = (); /// const NAME: &'static CStr = c_str!("myfs"); -/// fn super_params(_: &NewSuperBlock) -> Result { +/// fn super_params(_: &NewSuperBlock) -> Result> { /// todo!() /// } /// fn init_root(_sb: &SuperBlock) -> Result>> { diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 95ce28efa1c339..093425650f26d9 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -52,14 +52,16 @@ const ENTRIES: [Entry; 4] = [ struct RoFs; impl fs::FileSystem for RoFs { + type Data = (); const NAME: &'static CStr = c_str!("rust-fs"); - fn super_params(_sb: &NewSuperBlock) -> Result { + fn super_params(_sb: &NewSuperBlock) -> Result> { Ok(SuperParams { magic: 0x52555354, blocksize_bits: 12, maxbytes: fs::MAX_LFS_FILESIZE, time_gran: 1, + data: (), }) } From 516d0e402d41677ce1115aab88ee9d4cd42fff08 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:12 -0300 Subject: [PATCH 25/29] rust: fs: add basic support for fs buffer heads Introduce the abstractions that will be used by modules to handle buffer heads, which will be used to access cached blocks from block devices. All dead-code annotations are removed in the next commit in the series. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 1 + rust/helpers.c | 15 ++++++++ rust/kernel/fs.rs | 3 ++ rust/kernel/fs/buffer.rs | 61 +++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 rust/kernel/fs/buffer.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index e2b2ccc835e3f7..d328375f7cb7be 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -7,6 +7,7 @@ */ #include +#include #include #include #include diff --git a/rust/helpers.c b/rust/helpers.c index af335d1912e7e8..a5393c6b93f2f0 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -250,6 +251,20 @@ unsigned int rust_helper_MKDEV(unsigned int major, unsigned int minor) } EXPORT_SYMBOL_GPL(rust_helper_MKDEV); +#ifdef CONFIG_BUFFER_HEAD +void rust_helper_get_bh(struct buffer_head *bh) +{ + get_bh(bh); +} +EXPORT_SYMBOL_GPL(rust_helper_get_bh); + +void rust_helper_put_bh(struct buffer_head *bh) +{ + put_bh(bh); +} +EXPORT_SYMBOL_GPL(rust_helper_put_bh); +#endif + /* * `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can * use it in contexts where Rust expects a `usize` like slice (array) indices. diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index e9a9362d289737..4f04cb1d3c6f24 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -15,6 +15,9 @@ use crate::{ use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr}; use macros::{pin_data, pinned_drop}; +#[cfg(CONFIG_BUFFER_HEAD)] +pub mod buffer; + /// Maximum size of an inode. pub const MAX_LFS_FILESIZE: i64 = bindings::MAX_LFS_FILESIZE; diff --git a/rust/kernel/fs/buffer.rs b/rust/kernel/fs/buffer.rs new file mode 100644 index 00000000000000..6052af8822b3f8 --- /dev/null +++ b/rust/kernel/fs/buffer.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! File system buffers. +//! +//! C headers: [`include/linux/buffer_head.h`](../../../include/linux/buffer_head.h) + +use crate::types::{ARef, AlwaysRefCounted, Opaque}; +use core::ptr; + +/// Wraps the kernel's `struct buffer_head`. +/// +/// # Invariants +/// +/// Instances of this type are always ref-counted, that is, a call to `get_bh` ensures that the +/// allocation remains valid at least until the matching call to `put_bh`. +#[repr(transparent)] +pub struct Head(Opaque); + +// SAFETY: The type invariants guarantee that `INode` is always ref-counted. +unsafe impl AlwaysRefCounted for Head { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference means that the refcount is nonzero. + unsafe { bindings::get_bh(self.0.get()) }; + } + + unsafe fn dec_ref(obj: ptr::NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is nonzero. + unsafe { bindings::put_bh(obj.cast().as_ptr()) } + } +} + +impl Head { + /// Returns the block data associated with the given buffer head. + pub fn data(&self) -> &[u8] { + let h = self.0.get(); + // SAFETY: The existence of a shared reference guarantees that the buffer head is + // available and so we can access its contents. + unsafe { core::slice::from_raw_parts((*h).b_data.cast(), (*h).b_size) } + } +} + +/// A view of a buffer. +/// +/// It may contain just a contiguous subset of the buffer. +pub struct View { + head: ARef, + offset: usize, + size: usize, +} + +impl View { + #[allow(dead_code)] + pub(crate) fn new(head: ARef, offset: usize, size: usize) -> Self { + Self { head, size, offset } + } + + /// Returns the view of the buffer head. + pub fn data(&self) -> &[u8] { + &self.head.data()[self.offset..][..self.size] + } +} From 0605dba93970521eb01aba81e3a85a7737b09516 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:12 -0300 Subject: [PATCH 26/29] rust: fs: allow file systems backed by a block device Allow Rust file systems that are backed by block devices (in addition to in-memory ones). Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 1 + rust/helpers.c | 14 +++ rust/kernel/fs.rs | 177 +++++++++++++++++++++++++++++--- rust/kernel/fs/buffer.rs | 1 - 4 files changed, 180 insertions(+), 13 deletions(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index d328375f7cb7be..8403f13d4d4851 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -7,6 +7,7 @@ */ #include +#include #include #include #include diff --git a/rust/helpers.c b/rust/helpers.c index a5393c6b93f2f0..bc19f3b7b93e6e 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -252,6 +253,13 @@ unsigned int rust_helper_MKDEV(unsigned int major, unsigned int minor) EXPORT_SYMBOL_GPL(rust_helper_MKDEV); #ifdef CONFIG_BUFFER_HEAD +struct buffer_head *rust_helper_sb_bread(struct super_block *sb, + sector_t block) +{ + return sb_bread(sb, block); +} +EXPORT_SYMBOL_GPL(rust_helper_sb_bread); + void rust_helper_get_bh(struct buffer_head *bh) { get_bh(bh); @@ -265,6 +273,12 @@ void rust_helper_put_bh(struct buffer_head *bh) EXPORT_SYMBOL_GPL(rust_helper_put_bh); #endif +sector_t rust_helper_bdev_nr_sectors(struct block_device *bdev) +{ + return bdev_nr_sectors(bdev); +} +EXPORT_SYMBOL_GPL(rust_helper_bdev_nr_sectors); + /* * `bindgen` binds the C `size_t` type as the Rust `usize` type, so we can * use it in contexts where Rust expects a `usize` like slice (array) indices. diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 4f04cb1d3c6f24..b1ad5c110dbbf3 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -7,11 +7,9 @@ //! C headers: [`include/linux/fs.h`](../../include/linux/fs.h) use crate::error::{code::*, from_result, to_result, Error, Result}; -use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque}; -use crate::{ - bindings, folio::LockedFolio, init::PinInit, str::CStr, time::Timespec, try_pin_init, - ThisModule, -}; +use crate::folio::{LockedFolio, UniqueFolio}; +use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque, ScopeGuard}; +use crate::{bindings, init::PinInit, str::CStr, time::Timespec, try_pin_init, ThisModule}; use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr}; use macros::{pin_data, pinned_drop}; @@ -21,6 +19,17 @@ pub mod buffer; /// Maximum size of an inode. pub const MAX_LFS_FILESIZE: i64 = bindings::MAX_LFS_FILESIZE; +/// Type of superblock keying. +/// +/// It determines how C's `fs_context_operations::get_tree` is implemented. +pub enum Super { + /// Multiple independent superblocks may exist. + Independent, + + /// Uses a block device. + BlockDev, +} + /// A file system type. pub trait FileSystem { /// Data associated with each file system instance (super-block). @@ -29,6 +38,9 @@ pub trait FileSystem { /// The name of the file system type. const NAME: &'static CStr; + /// Determines how superblocks for this file system type are keyed. + const SUPER_TYPE: Super = Super::Independent; + /// Returns the parameters to initialise a super block. fn super_params(sb: &NewSuperBlock) -> Result>; @@ -181,7 +193,9 @@ impl Registration { fs.name = T::NAME.as_char_ptr(); fs.init_fs_context = Some(Self::init_fs_context_callback::); fs.kill_sb = Some(Self::kill_sb_callback::); - fs.fs_flags = 0; + fs.fs_flags = if let Super::BlockDev = T::SUPER_TYPE { + bindings::FS_REQUIRES_DEV as i32 + } else { 0 }; // SAFETY: Pointers stored in `fs` are static so will live for as long as the // registration is active (it is undone in `drop`). @@ -204,9 +218,16 @@ impl Registration { unsafe extern "C" fn kill_sb_callback( sb_ptr: *mut bindings::super_block, ) { - // SAFETY: In `get_tree_callback` we always call `get_tree_nodev`, so `kill_anon_super` is - // the appropriate function to call for cleanup. - unsafe { bindings::kill_anon_super(sb_ptr) }; + match T::SUPER_TYPE { + // SAFETY: In `get_tree_callback` we always call `get_tree_bdev` for + // `Super::BlockDev`, so `kill_block_super` is the appropriate function to call + // for cleanup. + Super::BlockDev => unsafe { bindings::kill_block_super(sb_ptr) }, + // SAFETY: In `get_tree_callback` we always call `get_tree_nodev` for + // `Super::Independent`, so `kill_anon_super` is the appropriate function to call + // for cleanup. + Super::Independent => unsafe { bindings::kill_anon_super(sb_ptr) }, + } // SAFETY: The C API contract guarantees that `sb_ptr` is valid for read. let ptr = unsafe { (*sb_ptr).s_fs_info }; @@ -479,6 +500,65 @@ impl SuperBlock { }))) } } + + /// Reads a block from the block device. + #[cfg(CONFIG_BUFFER_HEAD)] + pub fn bread(&self, block: u64) -> Result> { + // Fail requests for non-blockdev file systems. This is a compile-time check. + match T::SUPER_TYPE { + Super::BlockDev => {} + _ => return Err(EIO), + } + + // SAFETY: This function is only valid after the `NeedsInit` typestate, so the block size + // is known and the superblock can be used to read blocks. + let ptr = + ptr::NonNull::new(unsafe { bindings::sb_bread(self.0.get(), block) }).ok_or(EIO)?; + // SAFETY: `sb_bread` returns a referenced buffer head. Ownership of the increment is + // passed to the `ARef` instance. + Ok(unsafe { ARef::from_raw(ptr.cast()) }) + } + + /// Reads `size` bytes starting from `offset` bytes. + /// + /// Returns an iterator that returns slices based on blocks. + #[cfg(CONFIG_BUFFER_HEAD)] + pub fn read( + &self, + offset: u64, + size: u64, + ) -> Result> + '_> { + struct BlockIter<'a, T: FileSystem + ?Sized> { + sb: &'a SuperBlock, + next_offset: u64, + end: u64, + } + impl<'a, T: FileSystem + ?Sized> Iterator for BlockIter<'a, T> { + type Item = Result; + + fn next(&mut self) -> Option { + if self.next_offset >= self.end { + return None; + } + + // SAFETY: The superblock is valid and has had its block size initialised. + let block_size = unsafe { (*self.sb.0.get()).s_blocksize }; + let bh = match self.sb.bread(self.next_offset / block_size) { + Ok(bh) => bh, + Err(e) => return Some(Err(e)), + }; + let boffset = self.next_offset & (block_size - 1); + let bsize = core::cmp::min(self.end - self.next_offset, block_size - boffset); + self.next_offset += bsize; + Some(Ok(buffer::View::new(bh, boffset as usize, bsize as usize))) + } + } + Ok(BlockIter { + sb: self, + next_offset: offset, + end: offset.checked_add(size).ok_or(ERANGE)?, + }) + } } /// Required superblock parameters. @@ -511,6 +591,70 @@ pub struct SuperParams { #[repr(transparent)] pub struct NewSuperBlock(bindings::super_block, PhantomData); +impl NewSuperBlock { + /// Reads sectors. + /// + /// `count` must be such that the total size doesn't exceed a page. + pub fn sread(&self, sector: u64, count: usize, folio: &mut UniqueFolio) -> Result { + // Fail requests for non-blockdev file systems. This is a compile-time check. + match T::SUPER_TYPE { + // The superblock is valid and given that it's a blockdev superblock it must have a + // valid `s_bdev`. + Super::BlockDev => {} + _ => return Err(EIO), + } + + crate::build_assert!(count * (bindings::SECTOR_SIZE as usize) <= bindings::PAGE_SIZE); + + // Read the sectors. + let mut bio = bindings::bio::default(); + let bvec = Opaque::::uninit(); + + // SAFETY: `bio` and `bvec` are allocated on the stack, they're both valid. + unsafe { + bindings::bio_init( + &mut bio, + self.0.s_bdev, + bvec.get(), + 1, + bindings::req_op_REQ_OP_READ, + ) + }; + + // SAFETY: `bio` was just initialised with `bio_init` above, so it's safe to call + // `bio_uninit` on the way out. + let mut bio = + ScopeGuard::new_with_data(bio, |mut b| unsafe { bindings::bio_uninit(&mut b) }); + + // SAFETY: We have one free `bvec` (initialsied above). We also know that size won't exceed + // a page size (build_assert above). + unsafe { + bindings::bio_add_folio_nofail( + &mut *bio, + folio.0 .0.get(), + count * (bindings::SECTOR_SIZE as usize), + 0, + ) + }; + bio.bi_iter.bi_sector = sector; + + // SAFETY: The bio was fully initialised above. + to_result(unsafe { bindings::submit_bio_wait(&mut *bio) })?; + Ok(()) + } + + /// Returns the number of sectors in the underlying block device. + pub fn sector_count(&self) -> Result { + // Fail requests for non-blockdev file systems. This is a compile-time check. + match T::SUPER_TYPE { + // The superblock is valid and given that it's a blockdev superblock it must have a + // valid `s_bdev`. + Super::BlockDev => Ok(unsafe { bindings::bdev_nr_sectors(self.0.s_bdev) }), + _ => Err(EIO), + } + } +} + struct Tables(T); impl Tables { const CONTEXT: bindings::fs_context_operations = bindings::fs_context_operations { @@ -523,9 +667,18 @@ impl Tables { }; unsafe extern "C" fn get_tree_callback(fc: *mut bindings::fs_context) -> core::ffi::c_int { - // SAFETY: `fc` is valid per the callback contract. `fill_super_callback` also has - // the right type and is a valid callback. - unsafe { bindings::get_tree_nodev(fc, Some(Self::fill_super_callback)) } + match T::SUPER_TYPE { + // SAFETY: `fc` is valid per the callback contract. `fill_super_callback` also has + // the right type and is a valid callback. + Super::BlockDev => unsafe { + bindings::get_tree_bdev(fc, Some(Self::fill_super_callback)) + }, + // SAFETY: `fc` is valid per the callback contract. `fill_super_callback` also has + // the right type and is a valid callback. + Super::Independent => unsafe { + bindings::get_tree_nodev(fc, Some(Self::fill_super_callback)) + }, + } } unsafe extern "C" fn fill_super_callback( diff --git a/rust/kernel/fs/buffer.rs b/rust/kernel/fs/buffer.rs index 6052af8822b3f8..de23d0fee66c12 100644 --- a/rust/kernel/fs/buffer.rs +++ b/rust/kernel/fs/buffer.rs @@ -49,7 +49,6 @@ pub struct View { } impl View { - #[allow(dead_code)] pub(crate) fn new(head: ARef, offset: usize, size: usize) -> Self { Self { head, size, offset } } From b40e37be5eb05348e45bee2b45fe1693678258c7 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:12 -0300 Subject: [PATCH 27/29] rust: fs: allow per-inode data Allow Rust file systems to attach extra [typed] data to each inode. If no data is needed, use the regular inode kmem_cache, otherwise we create a new one. Signed-off-by: Wedson Almeida Filho --- rust/helpers.c | 7 +++ rust/kernel/fs.rs | 128 +++++++++++++++++++++++++++++++++++--- rust/kernel/mem_cache.rs | 2 - samples/rust/rust_rofs.rs | 9 ++- 4 files changed, 131 insertions(+), 15 deletions(-) diff --git a/rust/helpers.c b/rust/helpers.c index bc19f3b7b93e6e..7b12a6d4cf5ccd 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -222,6 +222,13 @@ void rust_helper_kunmap_local(const void *vaddr) } EXPORT_SYMBOL_GPL(rust_helper_kunmap_local); +void *rust_helper_alloc_inode_sb(struct super_block *sb, + struct kmem_cache *cache, gfp_t gfp) +{ + return alloc_inode_sb(sb, cache, gfp); +} +EXPORT_SYMBOL_GPL(rust_helper_alloc_inode_sb); + void rust_helper_i_uid_write(struct inode *inode, uid_t uid) { i_uid_write(inode, uid); diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index b1ad5c110dbbf3..b072037586745e 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -9,8 +9,12 @@ use crate::error::{code::*, from_result, to_result, Error, Result}; use crate::folio::{LockedFolio, UniqueFolio}; use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque, ScopeGuard}; -use crate::{bindings, init::PinInit, str::CStr, time::Timespec, try_pin_init, ThisModule}; -use core::{marker::PhantomData, marker::PhantomPinned, mem::ManuallyDrop, pin::Pin, ptr}; +use crate::{ + bindings, container_of, init::PinInit, mem_cache::MemCache, str::CStr, time::Timespec, + try_pin_init, ThisModule, +}; +use core::mem::{size_of, ManuallyDrop, MaybeUninit}; +use core::{marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr}; use macros::{pin_data, pinned_drop}; #[cfg(CONFIG_BUFFER_HEAD)] @@ -35,6 +39,9 @@ pub trait FileSystem { /// Data associated with each file system instance (super-block). type Data: ForeignOwnable + Send + Sync; + /// Type of data associated with each inode. + type INodeData: Send + Sync; + /// The name of the file system type. const NAME: &'static CStr; @@ -165,6 +172,7 @@ impl core::convert::TryFrom for DirEntryType { pub struct Registration { #[pin] fs: Opaque, + inode_cache: Option, #[pin] _pin: PhantomPinned, } @@ -182,6 +190,14 @@ impl Registration { pub fn new(module: &'static ThisModule) -> impl PinInit { try_pin_init!(Self { _pin: PhantomPinned, + inode_cache: if size_of::() == 0 { + None + } else { + Some(MemCache::try_new::>( + T::NAME, + Some(Self::inode_init_once_callback::), + )?) + }, fs <- Opaque::try_ffi_init(|fs_ptr: *mut bindings::file_system_type| { // SAFETY: `try_ffi_init` guarantees that `fs_ptr` is valid for write. unsafe { fs_ptr.write(bindings::file_system_type::default()) }; @@ -239,6 +255,16 @@ impl Registration { unsafe { T::Data::from_foreign(ptr) }; } } + + unsafe extern "C" fn inode_init_once_callback( + outer_inode: *mut core::ffi::c_void, + ) { + let ptr = outer_inode.cast::>(); + + // SAFETY: This is only used in `new`, so we know that we have a valid `INodeWithData` + // instance whose inode part can be initialised. + unsafe { bindings::inode_init_once(ptr::addr_of_mut!((*ptr).inode)) }; + } } #[pinned_drop] @@ -280,6 +306,15 @@ impl INode { unsafe { &*(*self.0.get()).i_sb.cast() } } + /// Returns the data associated with the inode. + pub fn data(&self) -> &T::INodeData { + let outerp = container_of!(self.0.get(), INodeWithData, inode); + // SAFETY: `self` is guaranteed to be valid by the existence of a shared reference + // (`&self`) to it. Additionally, we know `T::INodeData` is always initialised in an + // `INode`. + unsafe { &*(*outerp).data.as_ptr() } + } + /// Returns the size of the inode contents. pub fn size(&self) -> i64 { // SAFETY: `self` is guaranteed to be valid by the existence of a shared reference. @@ -300,15 +335,29 @@ unsafe impl AlwaysRefCounted for INode { } } +struct INodeWithData { + data: MaybeUninit, + inode: bindings::inode, +} + /// An inode that is locked and hasn't been initialised yet. #[repr(transparent)] pub struct NewINode(ARef>); impl NewINode { /// Initialises the new inode with the given parameters. - pub fn init(self, params: INodeParams) -> Result>> { - // SAFETY: This is a new inode, so it's safe to manipulate it mutably. - let inode = unsafe { &mut *self.0 .0.get() }; + pub fn init(self, params: INodeParams) -> Result>> { + let outerp = container_of!(self.0 .0.get(), INodeWithData, inode); + + // SAFETY: This is a newly-created inode. No other references to it exist, so it is + // safe to mutably dereference it. + let outer = unsafe { &mut *outerp.cast_mut() }; + + // N.B. We must always write this to a newly allocated inode because the free callback + // expects the data to be initialised and drops it. + outer.data.write(params.value); + + let inode = &mut outer.inode; let mode = match params.typ { INodeType::Dir => { @@ -424,7 +473,7 @@ pub enum INodeType { /// Required inode parameters. /// /// This is used when creating new inodes. -pub struct INodeParams { +pub struct INodeParams { /// The access mode. It's a mask that grants execute (1), write (2) and read (4) access to /// everyone, the owner group, and the owner. pub mode: u16, @@ -459,6 +508,9 @@ pub struct INodeParams { /// Last access time. pub atime: Timespec, + + /// Value to attach to this node. + pub value: T, } /// A file system super block. @@ -735,8 +787,12 @@ impl Tables { } const SUPER_BLOCK: bindings::super_operations = bindings::super_operations { - alloc_inode: None, - destroy_inode: None, + alloc_inode: if size_of::() != 0 { + Some(Self::alloc_inode_callback) + } else { + None + }, + destroy_inode: Some(Self::destroy_inode_callback), free_inode: None, dirty_inode: None, write_inode: None, @@ -766,6 +822,61 @@ impl Tables { shutdown: None, }; + unsafe extern "C" fn alloc_inode_callback( + sb: *mut bindings::super_block, + ) -> *mut bindings::inode { + // SAFETY: The callback contract guarantees that `sb` is valid for read. + let super_type = unsafe { (*sb).s_type }; + + // SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily + // embedded in a `Registration`, which is guaranteed to be valid because it has a + // superblock associated to it. + let reg = unsafe { &*container_of!(super_type, Registration, fs) }; + + // SAFETY: `sb` and `cache` are guaranteed to be valid by the callback contract and by + // the existence of a superblock respectively. + let ptr = unsafe { + bindings::alloc_inode_sb(sb, MemCache::ptr(®.inode_cache), bindings::GFP_KERNEL) + } + .cast::>(); + if ptr.is_null() { + return ptr::null_mut(); + } + ptr::addr_of_mut!((*ptr).inode) + } + + unsafe extern "C" fn destroy_inode_callback(inode: *mut bindings::inode) { + // SAFETY: By the C contract, `inode` is a valid pointer. + let is_bad = unsafe { bindings::is_bad_inode(inode) }; + + // SAFETY: The inode is guaranteed to be valid by the callback contract. Additionally, the + // superblock is also guaranteed to still be valid by the inode existence. + let super_type = unsafe { (*(*inode).i_sb).s_type }; + + // SAFETY: This callback is only used in `Registration`, so `super_type` is necessarily + // embedded in a `Registration`, which is guaranteed to be valid because it has a + // superblock associated to it. + let reg = unsafe { &*container_of!(super_type, Registration, fs) }; + let ptr = container_of!(inode, INodeWithData, inode).cast_mut(); + + if !is_bad { + // SAFETY: The code either initialises the data or marks the inode as bad. Since the + // inode is not bad, the data is initialised, and thus safe to drop. + unsafe { ptr::drop_in_place((*ptr).data.as_mut_ptr()) }; + } + + if size_of::() == 0 { + // SAFETY: When the size of `INodeData` is zero, we don't use a separate mem_cache, so + // it is allocated from the regular mem_cache, which is what `free_inode_nonrcu` uses + // to free the inode. + unsafe { bindings::free_inode_nonrcu(inode) }; + } else { + // The callback contract guarantees that the inode was previously allocated via the + // `alloc_inode_callback` callback, so it is safe to free it back to the cache. + unsafe { bindings::kmem_cache_free(MemCache::ptr(®.inode_cache), ptr.cast()) }; + } + } + unsafe extern "C" fn statfs_callback( dentry: *mut bindings::dentry, buf: *mut bindings::kstatfs, @@ -1120,6 +1231,7 @@ impl crate::InPlaceModule for Module { /// struct MyFs; /// impl fs::FileSystem for MyFs { /// type Data = (); +/// type INodeData =(); /// const NAME: &'static CStr = c_str!("myfs"); /// fn super_params(_: &NewSuperBlock) -> Result> { /// todo!() diff --git a/rust/kernel/mem_cache.rs b/rust/kernel/mem_cache.rs index 05e5f2bc9781e8..bf6ce2d2d3e1ab 100644 --- a/rust/kernel/mem_cache.rs +++ b/rust/kernel/mem_cache.rs @@ -20,7 +20,6 @@ impl MemCache { /// Allocates a new `kmem_cache` for type `T`. /// /// `init` is called by the C code when entries are allocated. - #[allow(dead_code)] pub(crate) fn try_new( name: &'static CStr, init: Option, @@ -43,7 +42,6 @@ impl MemCache { /// Returns the pointer to the `kmem_cache` instance, or null if it's `None`. /// /// This is a helper for functions like `alloc_inode_sb` where the cache is optional. - #[allow(dead_code)] pub(crate) fn ptr(c: &Option) -> *mut bindings::kmem_cache { match c { Some(m) => m.ptr.as_ptr(), diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 093425650f26d9..dfe74543984223 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -53,6 +53,7 @@ const ENTRIES: [Entry; 4] = [ struct RoFs; impl fs::FileSystem for RoFs { type Data = (); + type INodeData = &'static Entry; const NAME: &'static CStr = c_str!("rust-fs"); fn super_params(_sb: &NewSuperBlock) -> Result> { @@ -79,6 +80,7 @@ impl fs::FileSystem for RoFs { atime: UNIX_EPOCH, ctime: UNIX_EPOCH, mtime: UNIX_EPOCH, + value: &ENTRIES[0], }), } } @@ -122,6 +124,7 @@ impl fs::FileSystem for RoFs { atime: UNIX_EPOCH, ctime: UNIX_EPOCH, mtime: UNIX_EPOCH, + value: e, }), }; } @@ -131,11 +134,7 @@ impl fs::FileSystem for RoFs { } fn read_folio(inode: &INode, mut folio: LockedFolio<'_>) -> Result { - let data = match inode.ino() { - 2 => ENTRIES[2].contents, - 3 => ENTRIES[3].contents, - _ => return Err(EINVAL), - }; + let data = inode.data().contents; let pos = usize::try_from(folio.pos()).unwrap_or(usize::MAX); let copied = if pos >= data.len() { From 80fda666dab34418883c6fc07a735e6a98fa4cd5 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:13 -0300 Subject: [PATCH 28/29] rust: fs: export file type from mode constants Allow Rust file system modules to use these constants if needed. Signed-off-by: Wedson Almeida Filho --- rust/kernel/fs.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index b072037586745e..235a86ed1127d7 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -20,6 +20,33 @@ use macros::{pin_data, pinned_drop}; #[cfg(CONFIG_BUFFER_HEAD)] pub mod buffer; +/// Contains constants related to Linux file modes. +pub mod mode { + /// A bitmask used to the file type from a mode value. + pub const S_IFMT: u32 = bindings::S_IFMT; + + /// File type constant for block devices. + pub const S_IFBLK: u32 = bindings::S_IFBLK; + + /// File type constant for char devices. + pub const S_IFCHR: u32 = bindings::S_IFCHR; + + /// File type constant for directories. + pub const S_IFDIR: u32 = bindings::S_IFDIR; + + /// File type constant for pipes. + pub const S_IFIFO: u32 = bindings::S_IFIFO; + + /// File type constant for symbolic links. + pub const S_IFLNK: u32 = bindings::S_IFLNK; + + /// File type constant for regular files. + pub const S_IFREG: u32 = bindings::S_IFREG; + + /// File type constant for sockets. + pub const S_IFSOCK: u32 = bindings::S_IFSOCK; +} + /// Maximum size of an inode. pub const MAX_LFS_FILESIZE: i64 = bindings::MAX_LFS_FILESIZE; From 71891773e80ae6c8523cb39f4ced7cb08ecf0ec9 Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Fri, 29 Sep 2023 17:58:13 -0300 Subject: [PATCH 29/29] tarfs: introduce tar fs It is a file system based on tar files and an index appended to them (to facilitate finding fs entries without having to traverse the whole tar file). Signed-off-by: Wedson Almeida Filho --- fs/Kconfig | 1 + fs/Makefile | 1 + fs/tarfs/Kconfig | 16 ++ fs/tarfs/Makefile | 8 + fs/tarfs/defs.rs | 80 ++++++++ fs/tarfs/tar.rs | 322 ++++++++++++++++++++++++++++++ scripts/generate_rust_analyzer.py | 2 +- 7 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 fs/tarfs/Kconfig create mode 100644 fs/tarfs/Makefile create mode 100644 fs/tarfs/defs.rs create mode 100644 fs/tarfs/tar.rs diff --git a/fs/Kconfig b/fs/Kconfig index aa7e03cc1941cb..f4b8c33ea62432 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -331,6 +331,7 @@ source "fs/sysv/Kconfig" source "fs/ufs/Kconfig" source "fs/erofs/Kconfig" source "fs/vboxsf/Kconfig" +source "fs/tarfs/Kconfig" endif # MISC_FILESYSTEMS diff --git a/fs/Makefile b/fs/Makefile index f9541f40be4e08..e3389f8b049d6d 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -129,3 +129,4 @@ obj-$(CONFIG_EFIVAR_FS) += efivarfs/ obj-$(CONFIG_EROFS_FS) += erofs/ obj-$(CONFIG_VBOXSF_FS) += vboxsf/ obj-$(CONFIG_ZONEFS_FS) += zonefs/ +obj-$(CONFIG_TARFS_FS) += tarfs/ diff --git a/fs/tarfs/Kconfig b/fs/tarfs/Kconfig new file mode 100644 index 00000000000000..d3e19eb2adbcf8 --- /dev/null +++ b/fs/tarfs/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# + +config TARFS_FS + tristate "TAR file system support" + depends on RUST && BLOCK + select BUFFER_HEAD + help + This is a simple read-only file system intended for mounting + tar files that have had an index appened to them. + + To compile this file system support as a module, choose M here: the + module will be called tarfs. + + If you don't know whether you need it, then you don't need it: + answer N. diff --git a/fs/tarfs/Makefile b/fs/tarfs/Makefile new file mode 100644 index 00000000000000..011c5d64fbe391 --- /dev/null +++ b/fs/tarfs/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux tarfs filesystem routines. +# + +obj-$(CONFIG_TARFS_FS) += tarfs.o + +tarfs-y := tar.o diff --git a/fs/tarfs/defs.rs b/fs/tarfs/defs.rs new file mode 100644 index 00000000000000..7481b75aaab250 --- /dev/null +++ b/fs/tarfs/defs.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Definitions of tarfs structures. + +use kernel::types::LE; + +/// Flags used in [`Inode::flags`]. +pub mod inode_flags { + /// Indicates that the inode is opaque. + /// + /// When set, inode will have the "trusted.overlay.opaque" set to "y" at runtime. + pub const OPAQUE: u8 = 0x1; +} + +kernel::derive_readable_from_bytes! { + /// An inode in the tarfs inode table. + #[repr(C)] + pub struct Inode { + /// The mode of the inode. + /// + /// The bottom 9 bits are the rwx bits for owner, group, all. + /// + /// The bits in the [`S_IFMT`] mask represent the file mode. + pub mode: LE, + + /// Tarfs flags for the inode. + /// + /// Values are drawn from the [`inode_flags`] module. + pub flags: u8, + + /// The bottom 4 bits represent the top 4 bits of mtime. + pub hmtime: u8, + + /// The owner of the inode. + pub owner: LE, + + /// The group of the inode. + pub group: LE, + + /// The bottom 32 bits of mtime. + pub lmtime: LE, + + /// Size of the contents of the inode. + pub size: LE, + + /// Either the offset to the data, or the major and minor numbers of a device. + /// + /// For the latter, the 32 LSB are the minor, and the 32 MSB are the major numbers. + pub offset: LE, + } + + /// An entry in a tarfs directory entry table. + #[repr(C)] + pub struct DirEntry { + /// The inode number this entry refers to. + pub ino: LE, + + /// The offset to the name of the entry. + pub name_offset: LE, + + /// The length of the name of the entry. + pub name_len: LE, + + /// The type of entry. + pub etype: u8, + + /// Unused padding. + pub _padding: [u8; 7], + } + + /// The super-block of a tarfs instance. + #[repr(C)] + pub struct Header { + /// The offset to the beginning of the inode-table. + pub inode_table_offset: LE, + + /// The number of inodes in the file system. + pub inode_count: LE, + } +} diff --git a/fs/tarfs/tar.rs b/fs/tarfs/tar.rs new file mode 100644 index 00000000000000..1a71b1ccf8e75f --- /dev/null +++ b/fs/tarfs/tar.rs @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! File system based on tar files and an index. + +use core::mem::size_of; +use defs::*; +use kernel::fs::{ + DirEmitter, DirEntryType, INode, INodeParams, INodeType, NewSuperBlock, Stat, Super, + SuperBlock, SuperParams, +}; +use kernel::types::{ARef, Either, FromBytes}; +use kernel::{c_str, folio::Folio, folio::LockedFolio, fs, prelude::*}; + +pub mod defs; + +kernel::module_fs! { + type: TarFs, + name: "tarfs", + author: "Wedson Almeida Filho ", + description: "File system for indexed tar files", + license: "GPL", +} + +const SECTOR_SIZE: u64 = 512; +const TARFS_BSIZE: u64 = 1 << TARFS_BSIZE_BITS; +const TARFS_BSIZE_BITS: u8 = 12; +const SECTORS_PER_BLOCK: u64 = TARFS_BSIZE / SECTOR_SIZE; +const TARFS_MAGIC: u32 = 0x54415246; + +static_assert!(SECTORS_PER_BLOCK > 0); + +struct INodeData { + offset: u64, + flags: u8, +} + +struct TarFs { + data_size: u64, + inode_table_offset: u64, + inode_count: u64, +} + +impl TarFs { + fn iget(sb: &SuperBlock, ino: u64) -> Result>> { + // Check that the inode number is valid. + let h = sb.data(); + if ino == 0 || ino > h.inode_count { + return Err(ENOENT); + } + + // Create an inode or find an existing (cached) one. + let inode = match sb.get_or_create_inode(ino)? { + Either::Left(existing) => return Ok(existing), + Either::Right(new) => new, + }; + + static_assert!((TARFS_BSIZE as usize) % size_of::() == 0); + + // Load inode details from storage. + let offset = h.inode_table_offset + (ino - 1) * u64::try_from(size_of::())?; + + let bh = sb.bread(offset / TARFS_BSIZE)?; + let b = bh.data(); + let idata = Inode::from_bytes(b, (offset & (TARFS_BSIZE - 1)) as usize).ok_or(EIO)?; + + let mode = idata.mode.value(); + + // Ignore inodes that have unknown mode bits. + if (u32::from(mode) & !(fs::mode::S_IFMT | 0o777)) != 0 { + return Err(ENOENT); + } + + let doffset = idata.offset.value(); + let size = idata.size.value().try_into()?; + let secs = u64::from(idata.lmtime.value()) | (u64::from(idata.hmtime & 0xf) << 32); + let ts = kernel::time::Timespec::new(secs, 0)?; + let typ = match u32::from(mode) & fs::mode::S_IFMT { + fs::mode::S_IFREG => INodeType::Reg, + fs::mode::S_IFDIR => INodeType::Dir, + fs::mode::S_IFLNK => INodeType::Lnk, + fs::mode::S_IFSOCK => INodeType::Sock, + fs::mode::S_IFIFO => INodeType::Fifo, + fs::mode::S_IFCHR => INodeType::Chr((doffset >> 32) as u32, doffset as u32), + fs::mode::S_IFBLK => INodeType::Blk((doffset >> 32) as u32, doffset as u32), + _ => return Err(ENOENT), + }; + inode.init(INodeParams { + typ, + mode: mode & 0o777, + size, + blocks: (idata.size.value() + TARFS_BSIZE - 1) / TARFS_BSIZE, + nlink: 1, + uid: idata.owner.value(), + gid: idata.group.value(), + ctime: ts, + mtime: ts, + atime: ts, + value: INodeData { + offset: doffset, + flags: idata.flags, + }, + }) + } + + fn name_eq(sb: &SuperBlock, mut name: &[u8], offset: u64) -> Result { + for v in sb.read(offset, name.len().try_into()?)? { + let v = v?; + let b = v.data(); + if b != &name[..b.len()] { + return Ok(false); + } + name = &name[b.len()..]; + } + Ok(true) + } + + fn read_name(sb: &SuperBlock, mut name: &mut [u8], offset: u64) -> Result { + for v in sb.read(offset, name.len().try_into()?)? { + let v = v?; + let b = v.data(); + name[..b.len()].copy_from_slice(b); + name = &mut name[b.len()..]; + } + Ok(true) + } +} + +impl fs::FileSystem for TarFs { + type Data = Box; + type INodeData = INodeData; + const NAME: &'static CStr = c_str!("tar"); + const SUPER_TYPE: Super = Super::BlockDev; + + fn super_params(sb: &NewSuperBlock) -> Result> { + let scount = sb.sector_count()?; + if scount < SECTORS_PER_BLOCK { + pr_err!("Block device is too small: sector count={scount}\n"); + return Err(ENXIO); + } + + let tarfs = { + let mut folio = Folio::try_new(0)?; + sb.sread( + (scount / SECTORS_PER_BLOCK - 1) * SECTORS_PER_BLOCK, + SECTORS_PER_BLOCK as usize, + &mut folio, + )?; + let mapped = folio.map_page(0)?; + let hdr = + Header::from_bytes(&mapped, (TARFS_BSIZE - SECTOR_SIZE) as usize).ok_or(EIO)?; + Box::try_new(TarFs { + inode_table_offset: hdr.inode_table_offset.value(), + inode_count: hdr.inode_count.value(), + data_size: scount.checked_mul(SECTOR_SIZE).ok_or(ERANGE)?, + })? + }; + + // Check that the inode table starts within the device data and is aligned to the block + // size. + if tarfs.inode_table_offset >= tarfs.data_size { + pr_err!( + "inode table offset beyond data size: {} >= {}\n", + tarfs.inode_table_offset, + tarfs.data_size + ); + return Err(E2BIG); + } + + if tarfs.inode_table_offset % SECTOR_SIZE != 0 { + pr_err!( + "inode table offset not aligned to sector size: {}\n", + tarfs.inode_table_offset, + ); + return Err(EDOM); + } + + // Check that the last inode is within bounds (and that there is no overflow when + // calculating its offset). + let offset = tarfs + .inode_count + .checked_mul(u64::try_from(size_of::())?) + .ok_or(ERANGE)? + .checked_add(tarfs.inode_table_offset) + .ok_or(ERANGE)?; + if offset > tarfs.data_size { + pr_err!( + "inode table extends beyond the data size : {} > {}\n", + tarfs.inode_table_offset + (tarfs.inode_count * size_of::() as u64), + tarfs.data_size, + ); + return Err(E2BIG); + } + + Ok(SuperParams { + magic: TARFS_MAGIC, + blocksize_bits: TARFS_BSIZE_BITS, + maxbytes: fs::MAX_LFS_FILESIZE, + time_gran: 1000000000, + data: tarfs, + }) + } + + fn init_root(sb: &SuperBlock) -> Result>> { + Self::iget(sb, 1) + } + + fn read_dir(inode: &INode, emitter: &mut DirEmitter) -> Result { + let sb = inode.super_block(); + let mut name = Vec::::new(); + let pos = emitter.pos(); + + if pos < 0 || pos % size_of::() as i64 != 0 { + return Err(ENOENT); + } + + if pos >= inode.size() { + return Ok(()); + } + + // Make sure the inode data doesn't overflow the data area. + let size = u64::try_from(inode.size())?; + if inode.data().offset.checked_add(size).ok_or(EIO)? > sb.data().data_size { + return Err(EIO); + } + + for v in sb.read(inode.data().offset + pos as u64, size - pos as u64)? { + for e in DirEntry::from_bytes_to_slice(v?.data()).ok_or(EIO)? { + let name_len = usize::try_from(e.name_len.value())?; + if name_len > name.len() { + name.try_resize(name_len, 0)?; + } + + Self::read_name(sb, &mut name[..name_len], e.name_offset.value())?; + + if !emitter.emit( + size_of::() as i64, + &name[..name_len], + e.ino.value(), + DirEntryType::try_from(u32::from(e.etype))?, + ) { + return Ok(()); + } + } + } + + Ok(()) + } + + fn lookup(parent: &INode, name: &[u8]) -> Result>> { + let name_len = u64::try_from(name.len())?; + let sb = parent.super_block(); + + for v in sb.read(parent.data().offset, parent.size().try_into()?)? { + for e in DirEntry::from_bytes_to_slice(v?.data()).ok_or(EIO)? { + if e.name_len.value() != name_len || e.name_len.value() > usize::MAX as u64 { + continue; + } + if Self::name_eq(sb, name, e.name_offset.value())? { + return Self::iget(sb, e.ino.value()); + } + } + } + + Err(ENOENT) + } + + fn read_folio(inode: &INode, mut folio: LockedFolio<'_>) -> Result { + let pos = u64::try_from(folio.pos()).unwrap_or(u64::MAX); + let size = u64::try_from(inode.size())?; + let sb = inode.super_block(); + + let copied = if pos >= size { + 0 + } else { + let offset = inode.data().offset.checked_add(pos).ok_or(ERANGE)?; + let len = core::cmp::min(size - pos, folio.size().try_into()?); + let mut foffset = 0; + + if offset.checked_add(len).ok_or(ERANGE)? > sb.data().data_size { + return Err(EIO); + } + + for v in sb.read(offset, len)? { + let v = v?; + folio.write(foffset, v.data())?; + foffset += v.data().len(); + } + foffset + }; + + folio.zero_out(copied, folio.size() - copied)?; + folio.mark_uptodate(); + folio.flush_dcache(); + + Ok(()) + } + + fn read_xattr(inode: &INode, name: &CStr, outbuf: &mut [u8]) -> Result { + if inode.data().flags & inode_flags::OPAQUE == 0 + || name.as_bytes() != b"trusted.overlay.opaque" + { + return Err(ENODATA); + } + + if !outbuf.is_empty() { + outbuf[0] = b'y'; + } + + Ok(1) + } + + fn statfs(sb: &SuperBlock) -> Result { + let data = sb.data(); + Ok(Stat { + magic: TARFS_MAGIC, + namelen: i64::MAX, + bsize: TARFS_BSIZE as _, + blocks: data.inode_table_offset / TARFS_BSIZE, + files: data.inode_count, + }) + } +} diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index fc52bc41d3e7bd..8dc74991894edd 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -116,7 +116,7 @@ def is_root_crate(build_file, target): # Then, the rest outside of `rust/`. # # We explicitly mention the top-level folders we want to cover. - extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers")) + extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers", "fs")) if external_src is not None: extra_dirs = [external_src] for folder in extra_dirs: