From: Ariel Miculas <amiculas@cisco.com>
To: rust-for-linux@vger.kernel.org
Cc: Wedson Almeida Filho <wedsonaf@google.com>
Subject: [PATCH 08/80] WIP: rust: allow fs to be populated
Date: Fri, 9 Jun 2023 09:30:06 +0300 [thread overview]
Message-ID: <20230609063118.24852-9-amiculas@cisco.com> (raw)
In-Reply-To: <20230609063118.24852-1-amiculas@cisco.com>
From: Wedson Almeida Filho <wedsonaf@google.com>
---
rust/bindings/bindings_helper.h | 4 +
rust/bindings/lib.rs | 3 +
rust/helpers.c | 7 +
rust/kernel/fs.rs | 777 +++++++++++++++++++++++++++++---
rust/kernel/fs/param.rs | 8 +-
rust/kernel/prelude.rs | 1 -
samples/rust/rust_fs.rs | 51 ++-
7 files changed, 778 insertions(+), 73 deletions(-)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index b4297f6cb99f..d15a698439e1 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -22,3 +22,7 @@ const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO;
const loff_t BINDINGS_MAX_LFS_FILESIZE = MAX_LFS_FILESIZE;
+
+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 cd1fceb31390..8655d73b6785 100644
--- a/rust/bindings/lib.rs
+++ b/rust/bindings/lib.rs
@@ -53,3 +53,6 @@ mod bindings_helper {
pub const __GFP_ZERO: gfp_t = BINDINGS___GFP_ZERO;
pub const MAX_LFS_FILESIZE: loff_t = BINDINGS_MAX_LFS_FILESIZE;
+
+pub const SLAB_RECLAIM_ACCOUNT: slab_flags_t = BINDINGS_SLAB_RECLAIM_ACCOUNT;
+pub const SLAB_MEM_SPREAD: slab_flags_t = BINDINGS_SLAB_MEM_SPREAD;
diff --git a/rust/helpers.c b/rust/helpers.c
index ffe62af5ee20..efbe9d917a57 100644
--- a/rust/helpers.c
+++ b/rust/helpers.c
@@ -191,6 +191,13 @@ void rust_helper_kunmap(struct page *page)
}
EXPORT_SYMBOL_GPL(rust_helper_kunmap);
+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);
+
/*
* We use `bindgen`'s `--size_t-is-usize` option to bind the C `size_t` type
* as the Rust `usize` type, so we can use it in contexts where Rust
diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs
index 86c306c19e0a..2a0267b3c0b6 100644
--- a/rust/kernel/fs.rs
+++ b/rust/kernel/fs.rs
@@ -4,11 +4,20 @@
//!
//! C headers: [`include/linux/fs.h`](../../../../include/linux/fs.h)
+use crate::error::{from_kernel_result, to_result, Error, Result};
+use crate::file;
+use crate::types::{ARef, AlwaysRefCounted, ForeignOwnable, ScopeGuard};
+use crate::{
+ bindings, container_of, delay::coarse_sleep, error::code::*, pr_warn, str::CStr, ThisModule,
+};
use alloc::boxed::Box;
-use crate::{bindings, error::code::*, str::CStr, ThisModule};
-use crate::error::{to_result, from_kernel_result, Error, Result};
-use crate::types::{AlwaysRefCounted, ForeignOwnable, ScopeGuard};
-use core::{cell::UnsafeCell, marker::PhantomData, marker::PhantomPinned, pin::Pin, ptr};
+use core::mem::{align_of, size_of, ManuallyDrop, MaybeUninit};
+use core::sync::atomic::{AtomicU64, Ordering};
+use core::time::Duration;
+use core::{
+ cell::UnsafeCell, marker::PhantomData, marker::PhantomPinned, ops::Deref, pin::Pin, ptr,
+};
+
use macros::vtable;
pub mod param;
@@ -81,7 +90,21 @@ fn tree_key(_data: &mut Self::Data) -> Result<T::Data> {
}
}
-struct Tables<T: Type + ?Sized>(T);
+/// An empty file system context.
+///
+/// That is, one that doesn't take any arguments and doesn't hold any state. It is a convenience
+/// type for file systems that don't need context for mounting/reconfiguring.
+pub struct EmptyContext;
+
+#[vtable]
+impl<T: Type + ?Sized> Context<T> for EmptyContext {
+ type Data = ();
+ fn try_new() -> Result {
+ Ok(())
+ }
+}
+
+pub(crate) struct Tables<T: Type + ?Sized>(T);
impl<T: Type + ?Sized> Tables<T> {
const CONTEXT: bindings::fs_context_operations = bindings::fs_context_operations {
free: Some(Self::free_callback),
@@ -292,10 +315,18 @@ impl<T: Type + ?Sized> Tables<T> {
}
}
- const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
- alloc_inode: None,
+ pub(crate) const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
+ alloc_inode: if size_of::<T::INodeData>() != 0 {
+ Some(Self::alloc_inode_callback)
+ } else {
+ None
+ },
destroy_inode: None,
- free_inode: None,
+ free_inode: if size_of::<T::INodeData>() != 0 {
+ Some(Self::free_inode_callback)
+ } else {
+ None
+ },
dirty_inode: None,
write_inode: None,
drop_inode: None,
@@ -322,16 +353,76 @@ impl<T: Type + ?Sized> Tables<T> {
nr_cached_objects: None,
free_cached_objects: 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 `reg.inode_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, reg.inode_cache, bindings::GFP_KERNEL) }
+ as *mut INodeWithData<T::INodeData>;
+ if ptr.is_null() {
+ return ptr::null_mut();
+ }
+ reg.alloc_count.fetch_add(1, Ordering::Relaxed);
+ ptr::addr_of_mut!((*ptr).inode)
+ }
+
+ unsafe extern "C" fn free_inode_callback(inode: *mut bindings::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<T::INodeData>, inode);
+
+ // SAFETY: The code in `try_new_inode` always initialises the inode data after allocating
+ // it, so it is safe to drop it here.
+ unsafe {
+ core::ptr::drop_in_place(
+ (*(ptr as *mut INodeWithData<T::INodeData>))
+ .data
+ .as_mut_ptr(),
+ )
+ };
+
+ // 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(reg.inode_cache, ptr as _) };
+
+ reg.alloc_count.fetch_sub(1, Ordering::Release);
+ }
}
/// A file system type.
pub trait Type {
/// The context used to build fs configuration before it is mounted or reconfigured.
- type Context: Context<Self> + ?Sized;
+ type Context: Context<Self> + ?Sized = EmptyContext;
+
+ /// Type of data allocated for each inode.
+ type INodeData: Send + Sync = ();
/// Data associated with each file system instance.
type Data: ForeignOwnable + Send + Sync = ();
+ /// Determines whether the filesystem is based on the dcache.
+ ///
+ /// When this is `true`, adding a dentry results in an increased refcount. Removing them
+ /// results in a matching decrement, and `kill_litter_super` is used when killing the
+ /// superblock so that these extra references are removed.
+ const DCACHE_BASED: bool = false;
+
/// Determines how superblocks for this file system type are keyed.
const SUPER_TYPE: Super;
@@ -377,10 +468,11 @@ pub mod flags {
}
/// A file system registration.
-#[derive(Default)]
pub struct Registration {
is_registered: bool,
fs: UnsafeCell<bindings::file_system_type>,
+ inode_cache: *mut bindings::kmem_cache,
+ alloc_count: AtomicU64,
_pin: PhantomPinned,
}
@@ -401,6 +493,8 @@ pub fn new() -> Self {
Self {
is_registered: false,
fs: UnsafeCell::new(bindings::file_system_type::default()),
+ inode_cache: ptr::null_mut(),
+ alloc_count: AtomicU64::new(0),
_pin: PhantomPinned,
}
}
@@ -418,6 +512,29 @@ pub fn register<T: Type + ?Sized>(self: Pin<&mut Self>, module: &'static ThisMod
return Err(EINVAL);
}
+ if this.inode_cache.is_null() {
+ let size = size_of::<T::INodeData>();
+ if size != 0 {
+ // We only create the cache if the size is non-zero.
+ //
+ // SAFETY: `NAME` is static, so always valid.
+ this.inode_cache = unsafe {
+ bindings::kmem_cache_create(
+ T::NAME.as_char_ptr(),
+ size_of::<INodeWithData<T::INodeData>>() as _,
+ align_of::<INodeWithData<T::INodeData>>() as _,
+ bindings::SLAB_RECLAIM_ACCOUNT
+ | bindings::SLAB_MEM_SPREAD
+ | bindings::SLAB_ACCOUNT,
+ Some(Self::inode_init_once_callback::<T>),
+ )
+ };
+ if this.inode_cache.is_null() {
+ return Err(ENOMEM);
+ }
+ }
+ }
+
let mut fs = this.fs.get_mut();
fs.owner = module.0;
fs.name = T::NAME.as_char_ptr();
@@ -496,6 +613,13 @@ unsafe fn unregister_keys(fs: *mut bindings::file_system_type) {
// to call `kill_block_super`. Additionally, the callback contract guarantees that
// `sb_ptr` is valid.
unsafe { bindings::kill_block_super(sb_ptr) }
+ } else if T::DCACHE_BASED {
+ // SAFETY: We always call a `get_tree_nodev` variant from `get_tree_callback` without a
+ // device when `T::SUPER_TYPE` is not `BlockDev`, so we never have a device in such
+ // cases, therefore it is ok to call the function below. Additionally, the callback
+ // contract guarantees that `sb_ptr` is valid, and we have all positive dentries biased
+ // by +1 when `T::DCACHE_BASED`.
+ unsafe { bindings::kill_litter_super(sb_ptr) }
} else {
// SAFETY: We always call a `get_tree_nodev` variant from `get_tree_callback` without a
// device when `T::SUPER_TYPE` is not `BlockDev`, so we never have a device in such
@@ -519,6 +643,35 @@ unsafe fn unregister_keys(fs: *mut bindings::file_system_type) {
unsafe { T::Data::from_foreign(ptr) };
}
}
+
+ unsafe extern "C" fn inode_init_once_callback<T: Type + ?Sized>(
+ outer_inode: *mut core::ffi::c_void,
+ ) {
+ let ptr = outer_inode as *mut INodeWithData<T::INodeData>;
+ // This is only used in `register`, 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)) };
+ }
+
+ fn has_super_blocks(&self) -> bool {
+ unsafe extern "C" fn fs_cb(_: *mut bindings::super_block, ptr: *mut core::ffi::c_void) {
+ // SAFETY: This function is only called below, while `ptr` is known to `has_sb`.
+ unsafe { *(ptr as *mut bool) = true };
+ }
+
+ let mut has_sb = false;
+ // SAFETY: `fs` is valid, and `fs_cb` only touches `has_sb` during the call.
+ unsafe {
+ bindings::iterate_supers_type(self.fs.get(), Some(fs_cb), (&mut has_sb) as *mut _ as _)
+ }
+ has_sb
+ }
+}
+
+impl Default for Registration {
+ fn default() -> Self {
+ Self::new()
+ }
}
impl Drop for Registration {
@@ -527,10 +680,59 @@ fn drop(&mut self) {
// SAFETY: When `is_registered` is `true`, a previous call to `register_filesystem` has
// succeeded, so it is safe to unregister here.
unsafe { bindings::unregister_filesystem(self.fs.get()) };
+
+ // TODO: Test this.
+ if self.has_super_blocks() {
+ // If there are mounted superblocks of this registration, we cannot release the
+ // memory because it may be referenced, which would be a memory violation.
+ pr_warn!(
+ "Attempting to unregister a file system (0x{:x}) with mounted super blocks\n",
+ self.fs.get() as usize
+ );
+ while self.has_super_blocks() {
+ pr_warn!("Sleeping 1s before retrying...\n");
+ coarse_sleep(Duration::from_secs(1));
+ }
+ }
+ }
+
+ if !self.inode_cache.is_null() {
+ // Check if all inodes have been freed. If that's not the case, we may run into
+ // user-after-frees of the registration and kmem cache, so wait for it to drop to zero
+ // before proceeding.
+ //
+ // The expectation is that developers will fix this if they run into this warning.
+ if self.alloc_count.load(Ordering::Acquire) > 0 {
+ pr_warn!(
+ "Attempting to unregister a file system (0x{:x}) with allocated inodes\n",
+ self.fs.get() as usize
+ );
+ while self.alloc_count.load(Ordering::Acquire) > 0 {
+ pr_warn!("Sleeping 1s before retrying...\n");
+ coarse_sleep(Duration::from_secs(1));
+ }
+ }
+
+ // SAFETY: Just an FFI call with no additional safety requirements.
+ unsafe { bindings::rcu_barrier() };
+
+ // SAFETY: We know there are no more allocations in this cache and that it won't be
+ // used to allocate anymore because the filesystem is unregistered (so new mounts can't
+ // be created) and there are no more superblocks nor inodes.
+ //
+ // TODO: Can a dentry keep a file system alive? It looks like the answer is yes because
+ // it has a pointer to the superblock. How do we keep it alive? `d_init` may be an
+ // option to increment some count.
+ unsafe { bindings::kmem_cache_destroy(self.inode_cache) };
}
}
}
+struct INodeWithData<T> {
+ data: MaybeUninit<T>,
+ inode: bindings::inode,
+}
+
/// State of [`NewSuperBlock`] that indicates that [`NewSuperBlock::init`] needs to be called
/// eventually.
pub struct NeedsInit;
@@ -574,8 +776,10 @@ impl SuperParams {
///
/// The superblock is a newly-created one and this is the only active pointer to it.
pub struct NewSuperBlock<'a, T: Type + ?Sized, S = NeedsInit> {
- sb: *mut bindings::super_block,
- _p: PhantomData<(&'a T, S)>,
+ sb: &'a mut SuperBlock<T>,
+
+ // This also forces `'a` to be invariant.
+ _p: PhantomData<&'a mut &'a S>,
}
impl<'a, T: Type + ?Sized> NewSuperBlock<'a, T, NeedsInit> {
@@ -587,7 +791,8 @@ impl<'a, T: Type + ?Sized> NewSuperBlock<'a, T, NeedsInit> {
unsafe fn new(sb: *mut bindings::super_block) -> Self {
// INVARIANT: The invariants are satisfied by the safety requirements of this function.
Self {
- sb,
+ // SAFETY: The safety requirements ensure that `sb` is valid for dereference.
+ sb: unsafe { &mut *sb.cast() },
_p: PhantomData,
}
}
@@ -598,9 +803,7 @@ pub fn init(
data: T::Data,
params: &SuperParams,
) -> Result<NewSuperBlock<'a, T, NeedsRoot>> {
- // SAFETY: The type invariant guarantees that `self.sb` is the only pointer to a
- // newly-allocated superblock, so it is safe to mutably reference it.
- let sb = unsafe { &mut *self.sb };
+ let sb = self.sb.0.get_mut();
sb.s_magic = params.magic as _;
sb.s_op = &Tables::<T>::SUPER_BLOCK;
@@ -635,56 +838,214 @@ pub fn init(
impl<'a, T: Type + ?Sized> NewSuperBlock<'a, T, NeedsRoot> {
/// Initialises the root of the superblock.
- pub fn init_root(self) -> Result<&'a SuperBlock<T>> {
- // The following is temporary code to create the root inode and dentry. It will be replaced
- // once we allow inodes and dentries to be created directly from Rust code.
+ pub fn init_root(self, dentry: RootDEntry<T>) -> Result<&'a SuperBlock<T>> {
+ self.sb.0.get_mut().s_root = ManuallyDrop::new(dentry).ptr;
+ Ok(self.sb)
+ }
- // SAFETY: `sb` is initialised (`NeedsRoot` typestate implies it), so it is safe to pass it
- // to `new_inode`.
- let inode = unsafe { bindings::new_inode(self.sb) };
- if inode.is_null() {
- return Err(ENOMEM);
+ fn populate_dir(
+ &self,
+ parent: &DEntry<T>,
+ ino: &mut u64,
+ entries: &[Entry<'_, T>],
+ recursion: usize,
+ ) -> Result
+ where
+ T::INodeData: Clone,
+ {
+ if recursion == 0 {
+ return Err(E2BIG);
}
- {
- // 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 };
+ for e in entries {
+ *ino += 1;
+ match e {
+ Entry::File(name, mode, value, inode_create) => {
+ let params = INodeParams {
+ mode: *mode,
+ ino: *ino,
+ value: value.clone(),
+ };
+ let inode = inode_create(self, params)?;
+ self.try_new_dentry(inode, parent, name)?;
+ }
+ Entry::Special(name, mode, value, typ, dev) => {
+ let params = INodeParams {
+ mode: *mode,
+ ino: *ino,
+ value: value.clone(),
+ };
+ let inode = self.sb.try_new_special_inode(*typ, *dev, params)?;
+ self.try_new_dentry(inode, parent, name)?;
+ }
+ Entry::Directory(name, mode, value, dir_entries) => {
+ let params = INodeParams {
+ mode: *mode,
+ ino: *ino,
+ value: value.clone(),
+ };
+ let inode = self.sb.try_new_dcache_dir_inode(params)?;
+ let new_parent = self.try_new_dentry(inode, parent, name)?;
+ self.populate_dir(&new_parent, ino, dir_entries, recursion - 1)?;
+ }
+ }
+ }
- // SAFETY: `current_time` requires that `inode.sb` be valid, which is the case here
- // since we allocated the inode through the superblock.
- let time = unsafe { bindings::current_time(inode) };
- inode.i_ino = 1;
- inode.i_mode = (bindings::S_IFDIR | 0o755) as _;
- inode.i_mtime = time;
- inode.i_atime = time;
- inode.i_ctime = time;
+ Ok(())
+ }
- // SAFETY: `simple_dir_operations` never changes, it's safe to reference it.
- inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations };
+ /// Creates a new root dentry populated with the given entries.
+ pub fn try_new_populated_root_dentry(
+ &self,
+ root_value: T::INodeData,
+ entries: &[Entry<'_, T>],
+ ) -> Result<RootDEntry<T>>
+ where
+ T::INodeData: Clone,
+ {
+ let root_inode = self.sb.try_new_dcache_dir_inode(INodeParams {
+ mode: 0o755,
+ ino: 1,
+ value: root_value,
+ })?;
+ let root = self.try_new_root_dentry(root_inode)?;
+ let mut ino = 1u64;
+ self.populate_dir(&root, &mut ino, entries, 10)?;
+ Ok(root)
+ }
- // SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it.
- inode.i_op = unsafe { &bindings::simple_dir_inode_operations };
+ /// Creates a new empty root dentry.
+ pub fn try_new_root_dentry(&self, inode: ARef<INode<T>>) -> Result<RootDEntry<T>> {
+ // SAFETY: The inode is referenced, so it is safe to read the read-only field `i_sb`.
+ if unsafe { (*inode.0.get()).i_sb } != self.sb.0.get() {
+ return Err(EINVAL);
+ }
- // SAFETY: `inode` is valid for write.
- unsafe { bindings::set_nlink(inode, 2) };
+ // SAFETY: The caller owns a reference to the inode, so it is valid. The reference is
+ // transferred to the callee.
+ let dentry =
+ ptr::NonNull::new(unsafe { bindings::d_make_root(ManuallyDrop::new(inode).0.get()) })
+ .ok_or(ENOMEM)?;
+ Ok(RootDEntry {
+ ptr: dentry.as_ptr(),
+ _p: PhantomData,
+ })
+ }
+
+ /// Creates a new dentry with the given name, under the given parent, and backed by the given
+ /// inode.
+ pub fn try_new_dentry(
+ &self,
+ inode: ARef<INode<T>>,
+ parent: &DEntry<T>,
+ name: &CStr,
+ ) -> Result<ARef<DEntry<T>>> {
+ // SAFETY: Both `inode` and `parent` are referenced, so it is safe to read the read-only
+ // fields `i_sb` and `d_sb`.
+ if unsafe { (*parent.0.get()).d_sb } != self.sb.0.get()
+ || unsafe { (*inode.0.get()).i_sb } != self.sb.0.get()
+ {
+ 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) };
- if dentry.is_null() {
- return Err(ENOMEM);
+ // SAFETY: `parent` is valid (we have a shared reference to it), and `name` is valid for
+ // the duration of the call (the callee makes a copy of the name).
+ let dentry = ptr::NonNull::new(unsafe {
+ bindings::d_alloc_name(parent.0.get(), name.as_char_ptr())
+ })
+ .ok_or(ENOMEM)?;
+
+ // SAFETY: `dentry` was just allocated so it is valid. The callee takes over the reference
+ // to the inode.
+ unsafe { bindings::d_add(dentry.as_ptr(), ManuallyDrop::new(inode).0.get()) };
+
+ // SAFETY: `dentry` was just allocated, and the caller holds a reference, which it
+ // transfers to `dref`.
+ let dref = unsafe { ARef::from_raw(dentry.cast::<DEntry<T>>()) };
+
+ if T::DCACHE_BASED {
+ // Bias the refcount by +1 when adding a positive dentry.
+ core::mem::forget(dref.clone());
}
- // SAFETY: The typestate guarantees that `self.sb` is valid.
- unsafe { (*self.sb).s_root = dentry };
+ Ok(dref)
+ }
+
+ /// Creates a new inode that is a directory.
+ ///
+ /// The directory is based on the dcache, implemented by `simple_dir_operations` and
+ /// `simple_dir_inode_operations`.
+ pub fn try_new_dcache_dir_inode(
+ &self,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ self.sb.try_new_dcache_dir_inode(params)
+ }
- // SAFETY: The typestate guarantees that `self.sb` is initialised and we just finished
- // setting its root, so it's a fully ready superblock.
- Ok(unsafe { &mut *self.sb.cast() })
+ /// Creates a new "special" inode.
+ pub fn try_new_special_inode(
+ &self,
+ typ: INodeSpecialType,
+ rdev: Option<u32>,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ self.sb.try_new_special_inode(typ, rdev, params)
+ }
+
+ /// Creates a new regular file inode.
+ pub fn try_new_file_inode<F: file::Operations<OpenData = T::INodeData>>(
+ &self,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ self.sb.try_new_file_inode::<F>(params)
+ }
+}
+
+/// The type of a special inode.
+///
+/// This is used in functions like [`SuperBlock::try_new_special_inode`] to specify the type of
+/// an special inode; in this example, it's for it to be created.
+#[derive(Clone, Copy)]
+#[repr(u16)]
+pub enum INodeSpecialType {
+ /// Character device.
+ Char = bindings::S_IFCHR as _,
+
+ /// Block device.
+ Block = bindings::S_IFBLK as _,
+
+ /// A pipe (FIFO, first-in first-out) inode.
+ Fifo = bindings::S_IFIFO as _,
+
+ /// A unix-domain socket.
+ Sock = bindings::S_IFSOCK as _,
+}
+
+/// Required inode parameters.
+///
+/// This is used when creating new inodes.
+pub struct INodeParams<T> {
+ /// 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,
+
+ /// Number of the inode.
+ pub ino: u64,
+
+ /// Value to attach to this node.
+ pub value: T,
+}
+
+struct FsAdapter<T: Type + ?Sized>(PhantomData<T>);
+impl<T: Type + ?Sized> file::OpenAdapter<T::INodeData> for FsAdapter<T> {
+ unsafe fn convert(
+ inode: *mut bindings::inode,
+ _file: *mut bindings::file,
+ ) -> *const T::INodeData {
+ let ptr = container_of!(inode, INodeWithData<T::INodeData>, inode);
+ // SAFETY: Add safety annotation.
+ let outer = unsafe { &*ptr };
+ outer.data.as_ptr()
}
}
@@ -697,6 +1058,95 @@ pub struct SuperBlock<T: Type + ?Sized>(
PhantomData<T>,
);
+impl<T: Type + ?Sized> SuperBlock<T> {
+ fn try_new_inode(
+ &self,
+ mode_type: u16,
+ params: INodeParams<T::INodeData>,
+ init: impl FnOnce(&mut bindings::inode),
+ ) -> Result<ARef<INode<T>>> {
+ // SAFETY: `sb` is initialised (`NeedsRoot` typestate implies it), so it is safe to pass it
+ // to `new_inode`.
+ let inode =
+ ptr::NonNull::new(unsafe { bindings::new_inode(self.0.get()) }).ok_or(ENOMEM)?;
+
+ {
+ let ptr = container_of!(inode.as_ptr(), INodeWithData<T::INodeData>, 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 *(ptr as *mut INodeWithData<T::INodeData>) };
+
+ // 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);
+
+ // SAFETY: `current_time` requires that `inode.sb` be valid, which is the case here
+ // since we allocated the inode through the superblock.
+ let time = unsafe { bindings::current_time(&mut outer.inode) };
+ outer.inode.i_mtime = time;
+ outer.inode.i_atime = time;
+ outer.inode.i_ctime = time;
+
+ outer.inode.i_ino = params.ino;
+ outer.inode.i_mode = params.mode & 0o777 | mode_type;
+
+ init(&mut outer.inode);
+ }
+
+ // SAFETY: `inode` only has one reference, and it's being relinquished to the `ARef`
+ // instance.
+ Ok(unsafe { ARef::from_raw(inode.cast()) })
+ }
+
+ /// Creates a new inode that is a directory.
+ ///
+ /// The directory is based on the dcache, implemented by `simple_dir_operations` and
+ /// `simple_dir_inode_operations`.
+ pub fn try_new_dcache_dir_inode(
+ &self,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ self.try_new_inode(bindings::S_IFDIR as _, params, |inode| {
+ // 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 };
+
+ // Directory inodes start off with i_nlink == 2 (for "." entry).
+ // SAFETY: `inode` is valid for write.
+ unsafe { bindings::inc_nlink(inode) };
+ })
+ }
+
+ /// Creates a new "special" inode.
+ pub fn try_new_special_inode(
+ &self,
+ typ: INodeSpecialType,
+ rdev: Option<u32>,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ // SAFETY: `inode` is valid as it's a mutable reference.
+ self.try_new_inode(typ as _, params, |inode| unsafe {
+ bindings::init_special_inode(inode, inode.i_mode, rdev.unwrap_or(0))
+ })
+ }
+
+ /// Creates a new regular file inode.
+ pub fn try_new_file_inode<F: file::Operations<OpenData = T::INodeData>>(
+ &self,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ self.try_new_inode(bindings::S_IFREG as _, params, |inode| {
+ // SAFETY: The adapter is compatible because it assumes an inode created by a `T` file
+ // system, which is the case here.
+ inode.__bindgen_anon_3.i_fop =
+ unsafe { file::OperationsVtable::<FsAdapter<T>, F>::build() };
+ })
+ }
+}
+
/// Wraps the kernel's `struct inode`.
///
/// # Invariants
@@ -704,10 +1154,19 @@ pub struct SuperBlock<T: Type + ?Sized>(
/// 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(pub(crate) UnsafeCell<bindings::inode>);
+pub struct INode<T: Type + ?Sized>(pub(crate) UnsafeCell<bindings::inode>, PhantomData<T>);
+
+impl<T: Type + ?Sized> INode<T> {
+ /// Returns the file-system-determined data associated with the inode.
+ pub fn fs_data(&self) -> &T::INodeData {
+ let ptr = container_of!(self.0.get(), INodeWithData<T::INodeData>, inode);
+ // SAFETY: Add safety annotation.
+ unsafe { (*ptr::addr_of!((*ptr).data)).assume_init_ref() }
+ }
+}
// SAFETY: The type invariants guarantee that `INode` is always ref-counted.
-unsafe impl AlwaysRefCounted for INode {
+unsafe impl<T: Type + ?Sized> AlwaysRefCounted for INode<T> {
fn inc_ref(&self) {
// SAFETY: The existence of a shared reference means that the refcount is nonzero.
unsafe { bindings::ihold(self.0.get()) };
@@ -726,10 +1185,10 @@ unsafe fn dec_ref(obj: ptr::NonNull<Self>) {
/// Instances of this type are always ref-counted, that is, a call to `dget` ensures that the
/// allocation remains valid at least until the matching call to `dput`.
#[repr(transparent)]
-pub struct DEntry(pub(crate) UnsafeCell<bindings::dentry>);
+pub struct DEntry<T: Type + ?Sized>(pub(crate) UnsafeCell<bindings::dentry>, PhantomData<T>);
// SAFETY: The type invariants guarantee that `DEntry` is always ref-counted.
-unsafe impl AlwaysRefCounted for DEntry {
+unsafe impl<T: Type + ?Sized> AlwaysRefCounted for DEntry<T> {
fn inc_ref(&self) {
// SAFETY: The existence of a shared reference means that the refcount is nonzero.
unsafe { bindings::dget(self.0.get()) };
@@ -741,6 +1200,45 @@ unsafe fn dec_ref(obj: ptr::NonNull<Self>) {
}
}
+/// A dentry that is meant to be used as the root of a file system.
+///
+/// We have a specific type for the root dentry because we may need to do extra work when it is
+/// dropped. For example, if [`Type::DCACHE_BASED`] is `true`, we need to remove the extra
+/// reference held on each child dentry.
+///
+/// # Invariants
+///
+/// `ptr` is always valid and ref-counted.
+pub struct RootDEntry<T: Type + ?Sized> {
+ ptr: *mut bindings::dentry,
+ _p: PhantomData<T>,
+}
+
+impl<T: Type + ?Sized> Deref for RootDEntry<T> {
+ type Target = DEntry<T>;
+
+ fn deref(&self) -> &Self::Target {
+ // SAFETY: Add safety annotation.
+ unsafe { &*self.ptr.cast() }
+ }
+}
+
+impl<T: Type + ?Sized> Drop for RootDEntry<T> {
+ fn drop(&mut self) {
+ if T::DCACHE_BASED {
+ // All dentries have an extra ref on them, so we use `d_genocide` to drop it.
+ // SAFETY: Add safety annotation.
+ unsafe { bindings::d_genocide(self.ptr) };
+
+ // SAFETY: Add safety annotation.
+ unsafe { bindings::shrink_dcache_parent(self.ptr) };
+ }
+
+ // SAFETY: Add safety annotation.
+ unsafe { bindings::dput(self.ptr) };
+ }
+}
+
/// Wraps the kernel's `struct filename`.
#[repr(transparent)]
pub struct Filename(pub(crate) UnsafeCell<bindings::filename>);
@@ -776,6 +1274,11 @@ fn init(_name: &'static CStr, module: &'static ThisModule) -> Result<Self> {
}
}
+/// Returns a device id from its major and minor components.
+pub const fn mkdev(major: u16, minor: u32) -> u32 {
+ (major as u32) << bindings::MINORBITS | minor
+}
+
/// Declares a kernel module that exposes a single file system.
///
/// The `type` argument must be a type which implements the [`Type`] trait. Also accepts various
@@ -797,16 +1300,7 @@ fn init(_name: &'static CStr, module: &'static ThisModule) -> Result<Self> {
///
/// struct MyFs;
///
-/// #[vtable]
-/// impl fs::Context<Self> for MyFs {
-/// type Data = ();
-/// fn try_new() -> Result {
-/// Ok(())
-/// }
-/// }
-///
/// impl fs::Type for MyFs {
-/// type Context = Self;
/// const SUPER_TYPE: fs::Super = fs::Super::Independent;
/// const NAME: &'static CStr = c_str!("example");
/// const FLAGS: i32 = 0;
@@ -819,7 +1313,13 @@ fn init(_name: &'static CStr, module: &'static ThisModule) -> Result<Self> {
/// ..fs::SuperParams::DEFAULT
/// },
/// )?;
-/// let sb = sb.init_root()?;
+/// let root_inode = sb.try_new_dcache_dir_inode(fs::INodeParams {
+/// mode: 0o755,
+/// ino: 1,
+/// value: (),
+/// })?;
+/// let root = sb.try_new_root_dentry(root_inode)?;
+/// let sb = sb.init_root(root)?;
/// Ok(sb)
/// }
/// }
@@ -834,3 +1334,144 @@ macro_rules! module_fs {
}
}
}
+
+/// Defines a slice of file system entries.
+///
+/// This is meant as a helper for the definition of file system entries in a more compact form than
+/// if declared directly using the types.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::prelude::*;
+/// use kernel::{c_str, file, fs};
+///
+/// struct MyFs;
+///
+/// impl fs::Type for MyFs {
+/// type INodeData = &'static [u8];
+///
+/// // ...
+/// # const SUPER_TYPE: fs::Super = fs::Super::Independent;
+/// # const NAME: &'static CStr = c_str!("example");
+/// # const FLAGS: i32 = fs::flags::USERNS_MOUNT;
+/// # const DCACHE_BASED: bool = true;
+/// #
+/// # fn fill_super(_: (), _: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBlock<Self>> {
+/// # todo!()
+/// # }
+/// }
+///
+/// struct MyFile;
+///
+/// #[vtable]
+/// impl file::Operations for MyFile {
+/// type OpenData = &'static [u8];
+///
+/// // ...
+/// # fn open(_context: &Self::OpenData, _file: &file::File) -> Result<Self::Data> {
+/// # Ok(())
+/// # }
+/// }
+///
+/// const ENTRIES: &[fs::Entry<'_, MyFs>] = kernel::fs_entries![
+/// file("test1", 0o600, "abc\n".as_bytes(), MyFile),
+/// file("test2", 0o600, "def\n".as_bytes(), MyFile),
+/// char("test3", 0o600, [].as_slice(), (10, 125)),
+/// sock("test4", 0o755, [].as_slice()),
+/// fifo("test5", 0o755, [].as_slice()),
+/// block("test6", 0o755, [].as_slice(), (1, 1)),
+/// dir(
+/// "dir1",
+/// 0o755,
+/// [].as_slice(),
+/// [
+/// file("test1", 0o600, "abc\n".as_bytes(), MyFile),
+/// file("test2", 0o600, "def\n".as_bytes(), MyFile),
+/// ],
+/// ),
+/// ];
+/// ```
+#[macro_export]
+macro_rules! fs_entries {
+ ($($kind:ident ($($t:tt)*)),* $(,)?) => {
+ &[
+ $($crate::fs_entries!(@single $kind($($t)*)),)*
+ ]
+ };
+ (@single file($name:literal, $mode:expr, $value:expr, $file_ops:ty $(,)?)) => {
+ $crate::fs::Entry::File(
+ $crate::c_str!($name),
+ $mode,
+ $value,
+ $crate::fs::file_creator::<_, $file_ops>(),
+ )
+ };
+ (@single dir($name:literal, $mode:expr, $value:expr, [$($t:tt)*] $(,)?)) => {
+ $crate::fs::Entry::Directory(
+ $crate::c_str!($name),
+ $mode,
+ $value,
+ $crate::fs_entries!($($t)*),
+ )
+ };
+ (@single nod($name:literal, $mode:expr, $value:expr, $nod_type:ident, $dev:expr $(,)?)) => {
+ $crate::fs::Entry::Special(
+ $crate::c_str!($name),
+ $mode,
+ $value,
+ $crate::fs::INodeSpecialType::$nod_type,
+ $dev,
+ )
+ };
+ (@single char($name:literal, $mode:expr, $value:expr, ($major:expr, $minor:expr) $(,)?)) => {
+ $crate::fs_entries!(
+ @single nod($name, $mode, $value, Char, Some($crate::fs::mkdev($major, $minor))))
+ };
+ (@single block($name:literal, $mode:expr, $value:expr, ($major:expr, $minor:expr) $(,)?)) => {
+ $crate::fs_entries!(
+ @single nod($name, $mode, $value, Block, Some($crate::fs::mkdev($major, $minor))))
+ };
+ (@single sock($name:literal, $mode:expr, $value:expr $(,)?)) => {
+ $crate::fs_entries!(@single nod($name, $mode, $value, Sock, None))
+ };
+ (@single fifo($name:literal, $mode:expr, $value:expr $(,)?)) => {
+ $crate::fs_entries!(@single nod($name, $mode, $value, Fifo, None))
+ };
+}
+
+/// A file system entry.
+///
+/// This is used statically describe the files and directories of a file system in functions that
+/// take such data as arguments, for example, [`NewSuperBlock::try_new_populated_root_dentry`].
+pub enum Entry<'a, T: Type + ?Sized> {
+ /// A regular file.
+ File(&'a CStr, u16, T::INodeData, INodeCreator<T>),
+
+ /// A directory and its children.
+ Directory(&'a CStr, u16, T::INodeData, &'a [Entry<'a, T>]),
+
+ /// A special file, the type of which is given by [`INodeSpecialType`].
+ Special(&'a CStr, u16, T::INodeData, INodeSpecialType, Option<u32>),
+}
+
+/// A function that creates and inode.
+pub type INodeCreator<T> = fn(
+ &NewSuperBlock<'_, T, NeedsRoot>,
+ INodeParams<<T as Type>::INodeData>,
+) -> Result<ARef<INode<T>>>;
+
+/// Returns an [`INodeCreator`] that creates a regular file with the given file operations.
+///
+/// This is used by the [`fs_entries`] macro to elide the type implementing the [`file::Operations`]
+/// trait.
+pub const fn file_creator<T: Type + ?Sized, F: file::Operations<OpenData = T::INodeData>>(
+) -> INodeCreator<T> {
+ fn file_creator<T: Type + ?Sized, F: file::Operations<OpenData = T::INodeData>>(
+ new_sb: &NewSuperBlock<'_, T, NeedsRoot>,
+ params: INodeParams<T::INodeData>,
+ ) -> Result<ARef<INode<T>>> {
+ new_sb.sb.try_new_file_inode::<F>(params)
+ }
+ file_creator::<T, F>
+}
diff --git a/rust/kernel/fs/param.rs b/rust/kernel/fs/param.rs
index 44b4e895a1eb..1a31130c6d1e 100644
--- a/rust/kernel/fs/param.rs
+++ b/rust/kernel/fs/param.rs
@@ -502,7 +502,13 @@ macro_rules! count_brace_items {
/// # ..fs::SuperParams::DEFAULT
/// # },
/// # )?;
-/// # let sb = sb.init_root()?;
+/// # let root_inode = sb.try_new_dcache_dir_inode(fs::INodeParams {
+/// # mode: 0o755,
+/// # ino: 1,
+/// # value: (),
+/// # })?;
+/// # let root = sb.try_new_root_dentry(root_inode)?;
+/// # let sb = sb.init_root(root)?;
/// # Ok(sb)
/// # }
/// # }
diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs
index 37789bc8a796..c28587d68ebc 100644
--- a/rust/kernel/prelude.rs
+++ b/rust/kernel/prelude.rs
@@ -28,7 +28,6 @@
pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
pub use super::{init, pin_init, try_init, try_pin_init};
-pub use super::{module_fs, module_misc_device};
pub use super::static_assert;
diff --git a/samples/rust/rust_fs.rs b/samples/rust/rust_fs.rs
index 064ead97dd98..18fd4542863b 100644
--- a/samples/rust/rust_fs.rs
+++ b/samples/rust/rust_fs.rs
@@ -3,7 +3,7 @@
//! Rust file system sample.
use kernel::prelude::*;
-use kernel::{c_str, fs};
+use kernel::{c_str, file, fs, io_buffer::IoBufferWriter};
module_fs! {
type: RustFs,
@@ -34,16 +34,17 @@ impl fs::Context<Self> for RustFs {
}
fn try_new() -> Result {
- pr_info!("context created!\n");
Ok(())
}
}
impl fs::Type for RustFs {
type Context = Self;
+ type INodeData = &'static [u8];
const SUPER_TYPE: fs::Super = fs::Super::Independent;
const NAME: &'static CStr = c_str!("rustfs");
const FLAGS: i32 = fs::flags::USERNS_MOUNT;
+ const DCACHE_BASED: bool = true;
fn fill_super(_data: (), sb: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBlock<Self>> {
let sb = sb.init(
@@ -53,7 +54,51 @@ fn fill_super(_data: (), sb: fs::NewSuperBlock<'_, Self>) -> Result<&fs::SuperBl
..fs::SuperParams::DEFAULT
},
)?;
- let sb = sb.init_root()?;
+ let root = sb.try_new_populated_root_dentry(
+ &[],
+ kernel::fs_entries![
+ file("test1", 0o600, "abc\n".as_bytes(), FsFile),
+ file("test2", 0o600, "def\n".as_bytes(), FsFile),
+ char("test3", 0o600, [].as_slice(), (10, 125)),
+ sock("test4", 0o755, [].as_slice()),
+ fifo("test5", 0o755, [].as_slice()),
+ block("test6", 0o755, [].as_slice(), (1, 1)),
+ dir(
+ "dir1",
+ 0o755,
+ [].as_slice(),
+ [
+ file("test1", 0o600, "abc\n".as_bytes(), FsFile),
+ file("test2", 0o600, "def\n".as_bytes(), FsFile),
+ ]
+ ),
+ ],
+ )?;
+ let sb = sb.init_root(root)?;
Ok(sb)
}
}
+
+struct FsFile;
+
+#[vtable]
+impl file::Operations for FsFile {
+ type OpenData = &'static [u8];
+
+ fn open(_context: &Self::OpenData, _file: &file::File) -> Result<Self::Data> {
+ Ok(())
+ }
+
+ fn read(
+ _data: (),
+ file: &file::File,
+ writer: &mut impl IoBufferWriter,
+ offset: u64,
+ ) -> Result<usize> {
+ file::read_from_slice(
+ file.inode::<RustFs>().ok_or(EINVAL)?.fs_data(),
+ writer,
+ offset,
+ )
+ }
+}
--
2.40.1
next prev parent reply other threads:[~2023-06-09 6:54 UTC|newest]
Thread overview: 134+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-06-09 6:29 [RFC PATCH 00/80] Rust PuzzleFS filesystem driver Ariel Miculas
2023-06-09 6:29 ` [PATCH 01/80] rust: add definitions for ref-counted inodes and dentries Ariel Miculas
2023-06-09 6:30 ` [PATCH 02/80] rust: add ability to register a file system Ariel Miculas
2023-06-09 9:23 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 03/80] rust: define fs context Ariel Miculas
2023-06-09 6:30 ` [PATCH 04/80] rust: add support for file system parameters Ariel Miculas
2023-06-09 6:30 ` [PATCH 05/80] rust: kernel: add libraries required by the filesystem abstractions Ariel Miculas
2023-06-09 9:46 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 06/80] rust: allow fs driver to initialise new superblocks Ariel Miculas
2023-06-09 6:30 ` [PATCH 07/80] rust: add `module_fs` macro Ariel Miculas
2023-06-09 6:30 ` Ariel Miculas [this message]
2023-06-09 6:30 ` [PATCH 09/80] rust: kernel: backport the delay module from the rust branch Ariel Miculas
2023-06-09 9:29 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 10/80] rust: kernel: add container_of macro Ariel Miculas
2023-06-09 6:30 ` [PATCH 11/80] rust: kernel: add offset_of macro Ariel Miculas
2023-06-09 6:30 ` [PATCH 12/80] drop: Add crate::pr_warn declaration Ariel Miculas
2023-06-09 9:29 ` Miguel Ojeda
2023-06-09 10:46 ` Ariel Miculas (amiculas)
2023-06-09 6:30 ` [PATCH 13/80] rust: kernel: rename from_kernel_errno to from_errno Ariel Miculas
2023-06-09 9:56 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 14/80] rust: kernel: Rename from_pointer to from_foreing and into_pointer to into_foreign Ariel Miculas
2023-06-09 9:57 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 15/80] rust: kernel: add count_paren_items macro, needed by define_fs_params macro Ariel Miculas
2023-06-09 6:30 ` [PATCH 16/80] rust: helpers: add missing rust helper 'alloc_pages' Ariel Miculas
2023-06-09 9:57 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 17/80] kernel: configs: add qemu-busybox-min.config Ariel Miculas
2023-06-09 9:39 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 18/80] rust: kernel: format the rust code Ariel Miculas
2023-06-09 9:21 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 19/80] samples: puzzlefs: add initial puzzlefs sample, copied from rust_fs.rs Ariel Miculas
2023-06-09 6:30 ` [PATCH 20/80] kernel: configs: enable rust samples in rust.config Ariel Miculas
2023-06-09 9:25 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 22/80] rust: proc-macro2: add SPDX License Identifiers Ariel Miculas
2023-06-09 6:30 ` [PATCH 23/80] rust: proc-macro2: remove `unicode_ident` dependency Ariel Miculas
2023-06-09 6:30 ` [PATCH 24/80] rust: quote: import crate Ariel Miculas
2023-06-09 6:30 ` [PATCH 25/80] rust: quote: add SPDX License Identifiers Ariel Miculas
2023-06-09 6:30 ` [PATCH 27/80] rust: syn: " Ariel Miculas
2023-06-09 6:30 ` [PATCH 28/80] rust: syn: remove `unicode-ident` dependency Ariel Miculas
2023-06-09 6:30 ` [PATCH 30/80] rust: serde: add `no_fp_fmt_parse` support Ariel Miculas
2023-06-09 6:30 ` [PATCH 31/80] rust: serde: add SPDX License Identifiers Ariel Miculas
2023-06-10 0:19 ` Kent Overstreet
2023-06-10 6:43 ` Greg KH
2023-06-10 13:18 ` Kent Overstreet
2023-06-10 15:28 ` Greg KH
2023-06-10 0:25 ` Kent Overstreet
2023-06-10 9:04 ` Andreas Hindborg (Samsung)
2023-06-10 13:20 ` Kent Overstreet
2023-06-12 8:56 ` Ariel Miculas
2023-06-10 9:33 ` Miguel Ojeda
2023-06-12 11:58 ` Ariel Miculas
2023-06-15 15:05 ` Ariel Miculas
2023-06-17 16:04 ` Kent Overstreet
2023-06-09 6:30 ` [PATCH 33/80] rust: serde_derive: " Ariel Miculas
2023-06-09 6:30 ` [PATCH 34/80] rust: Kbuild: enable `proc-macro2`, `quote`, `syn`, `serde` and `serde_derive` Ariel Miculas
2023-06-09 6:30 ` [PATCH 35/80] rust: test `serde` support Ariel Miculas
2023-06-09 6:30 ` [PATCH 36/80] Add SAMPLE_RUST_SERDE in rust.config Ariel Miculas
2023-06-09 6:30 ` [PATCH 37/80] rust: kernel: fix compile errors after rebase to rust-next Ariel Miculas
2023-06-09 9:38 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 39/80] rust: serde_cbor: add SPDX License Identifiers Ariel Miculas
2023-06-09 6:30 ` [PATCH 40/80] rust: serde_cbor: add no_fp_fmt_parse support Ariel Miculas
2023-06-09 6:30 ` [PATCH 41/80] rust: Kbuild: enable serde_cbor Ariel Miculas
2023-06-09 10:21 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 42/80] samples: rust: add cbor serialize/deserialize example Ariel Miculas
2023-06-09 6:30 ` [PATCH 43/80] rust: serde_cbor: add support for serde_cbor's from_slice method by using a custom alloc_kernel feature Ariel Miculas
2023-06-09 9:55 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 44/80] rust: serde: add support for deserializing Vec with kernel_alloc feature Ariel Miculas
2023-06-09 10:10 ` Miguel Ojeda
2023-06-09 6:30 ` [PATCH 45/80] rust: file: Replace UnsafeCell with Opaque for File Ariel Miculas
2023-06-09 6:30 ` [PATCH 46/80] rust: kernel: implement fmt::Debug for CString Ariel Miculas
2023-06-09 6:30 ` [PATCH 47/80] samples: puzzlefs: rename RustFs to PuzzleFs Ariel Miculas
2023-06-09 6:30 ` [PATCH 48/80] samples: puzzlefs: add basic deserializing support for the puzzlefs metadata Ariel Miculas
2023-06-09 6:30 ` [PATCH 49/80] rust: file: present the filesystem context to the open function Ariel Miculas
2023-06-09 6:30 ` [PATCH 50/80] rust: kernel: add an abstraction over vfsmount to allow cloning a new private mount Ariel Miculas
2023-06-09 6:30 ` [PATCH 51/80] rust: file: add from_path, from_path_in_root_mnt and read_with_offset methods to File Ariel Miculas
2023-06-09 6:30 ` [PATCH 52/80] samples: puzzlefs: pass the Vfsmount structure from open to read and return the contents of the data file inside /home/puzzlefs_oci Ariel Miculas
2023-06-09 6:30 ` [PATCH 53/80] rust: file: move from_path, from_path_in_root_mnt and read_with_offset methods to a RegularFile newtype Ariel Miculas
2023-06-09 6:30 ` [PATCH 54/80] rust: file: ensure RegularFile can only create regular files Ariel Miculas
2023-06-09 6:30 ` [PATCH 55/80] rust: file: add get_pos method to RegularFile Ariel Miculas
2023-06-09 6:30 ` [PATCH 56/80] rust: file: add methods read_to_end, get_file_size and update_pos " Ariel Miculas
2023-06-09 6:30 ` [PATCH 57/80] rust: file: define a minimal Read trait and implement it for RegularFile Ariel Miculas
2023-06-09 6:30 ` [PATCH 58/80] samples: puzzlefs: add cbor_get_array_size method Ariel Miculas
2023-06-09 6:30 ` [PATCH 59/80] samples: puzzlefs: add KernelError to WireFormatError and implement From conversion Ariel Miculas
2023-06-09 6:30 ` [PATCH 60/80] samples: puzzlefs: implement new for MetadataBlob Ariel Miculas
2023-06-09 6:30 ` [PATCH 61/80] samples: puzzlefs: build puzzlefs into the kernel, thus avoiding the need to export rust symbols Ariel Miculas
2023-06-09 6:31 ` [PATCH 62/80] rust: alloc: add try_clone for Vec<T> Ariel Miculas
2023-06-09 6:31 ` [PATCH 63/80] rust: alloc: add from_iter_fallible " Ariel Miculas
2023-06-09 10:06 ` Miguel Ojeda
2023-06-09 6:31 ` [PATCH 64/80] samples: puzzlefs: implement to_errno and from_errno for WireFormatError Ariel Miculas
2023-06-09 6:31 ` [PATCH 65/80] samples: puzzlefs: add TryReserveError (and from conversion) to WireFormatError Ariel Miculas
2023-06-09 6:31 ` [PATCH 66/80] samples: puzzlefs: add higher level inode related functionality Ariel Miculas
2023-06-09 6:31 ` [PATCH 67/80] samples: puzzlefs: populate the directory entries with the inodes from the puzzlefs metadata file Ariel Miculas
2023-06-09 6:31 ` [PATCH 68/80] rust: hex: import crate Ariel Miculas
2023-06-09 6:31 ` [PATCH 69/80] rust: hex: add SPDX license identifiers Ariel Miculas
2023-06-09 6:31 ` [PATCH 70/80] rust: Kbuild: enable `hex` Ariel Miculas
2023-06-09 6:31 ` [PATCH 71/80] rust: hex: implement FromHex trait and hex::decode using a custom kernel_alloc feature Ariel Miculas
2023-06-09 6:31 ` [PATCH 72/80] rust: hex: add encode_hex_iter and encode_hex_upper_iter methods Ariel Miculas
2023-06-09 6:31 ` [PATCH 73/80] rust: puzzlefs: add HexError to WireFormatError and implement the From conversion Ariel Miculas
2023-06-09 6:31 ` [PATCH 74/80] rust: puzzlefs: display the error value for WireFormatError::KernelError Ariel Miculas
2023-06-09 6:31 ` [PATCH 75/80] samples: puzzlefs: add Rootfs and Digest structs to types.rs Ariel Miculas
2023-06-09 6:31 ` [PATCH 76/80] samples: puzzlefs: implement the conversion from WireFormatError to kernel::error::Error Ariel Miculas
2023-06-09 6:31 ` [PATCH 77/80] rust: puzzlefs: read the puzzlefs image manifest instead of an individual metadata layer Ariel Miculas
2023-06-09 6:31 ` [PATCH 78/80] rust: puzzlefs: rename PuzzleFs to PuzzleFsModule to avoid confusion with the PuzzleFS struct Ariel Miculas
2023-06-09 6:31 ` [PATCH 79/80] rust: puzzlefs: add support for reading files Ariel Miculas
2023-06-09 6:31 ` [PATCH 80/80] rust: puzzlefs: add oci_root_dir and image_manifest filesystem parameters Ariel Miculas
2023-06-09 10:26 ` [RFC PATCH 00/80] Rust PuzzleFS filesystem driver Miguel Ojeda
2023-06-09 10:36 ` Christian Brauner
2023-06-09 11:42 ` Miguel Ojeda
[not found] ` <CH0PR11MB529981313ED5A1F815350E41CD51A@CH0PR11MB5299.namprd11.prod.outlook.com>
2023-06-09 11:45 ` Christian Brauner
2023-06-09 12:03 ` Ariel Miculas (amiculas)
2023-06-09 12:56 ` Gao Xiang
2023-06-09 12:07 ` Miguel Ojeda
2023-06-09 12:11 ` Ariel Miculas (amiculas)
2023-06-09 12:21 ` Greg KH
2023-06-09 13:05 ` Alice Ryhl
2023-06-09 12:20 ` Colin Walters
2023-06-09 12:42 ` Christian Brauner
2023-06-09 17:28 ` Serge Hallyn
2023-06-09 13:45 ` Ariel Miculas (amiculas)
2023-06-09 17:10 ` Trilok Soni
2023-06-09 17:16 ` Ariel Miculas (amiculas)
2023-06-09 17:41 ` Miguel Ojeda
2023-06-09 18:49 ` James Bottomley
2023-06-09 19:08 ` Miguel Ojeda
2023-06-09 19:11 ` Ariel Miculas
2023-06-09 20:01 ` James Bottomley
2023-06-10 9:34 ` Miguel Ojeda
2023-06-09 18:43 ` James Bottomley
2023-06-09 18:59 ` Ariel Miculas (amiculas)
2023-06-09 19:20 ` Ariel Miculas
2023-06-09 19:45 ` Trilok Soni
2023-06-09 19:53 ` Alice Ryhl
2023-06-09 23:52 ` Kent Overstreet
2023-06-10 9:40 ` Miguel Ojeda
2023-06-10 0:09 ` Kent Overstreet
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230609063118.24852-9-amiculas@cisco.com \
--to=amiculas@cisco.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=wedsonaf@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).