@@ -164,6 +164,17 @@ struct file *rust_helper_get_file(struct file *f)
}
EXPORT_SYMBOL_GPL(rust_helper_get_file);
+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);
struct dentry *rust_helper_dget(struct dentry *dentry)
{
@@ -9,7 +9,7 @@
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::{ffi, marker::PhantomData, pin::Pin};
+use core::{ffi, marker::PhantomData, mem::ManuallyDrop, pin::Pin, ptr};
use macros::{pin_data, pinned_drop};
use sb::SuperBlock;
@@ -38,6 +38,12 @@ pub trait FileSystem {
/// Initialises the new superblock.
fn fill_super(sb: &mut SuperBlock<Self>) -> Result;
+
+ /// Initialises and returns the root inode of the given superblock.
+ ///
+ /// This is called during initialisation of a superblock after [`FileSystem::fill_super`] has
+ /// completed successfully.
+ fn init_root(sb: &SuperBlock<Self>) -> Result<dentry::Root<Self>>;
}
/// A file system that is unspecified.
@@ -51,6 +57,10 @@ impl FileSystem for UnspecifiedFS {
fn fill_super(_: &mut SuperBlock<Self>) -> Result {
Err(ENOTSUPP)
}
+
+ fn init_root(_: &SuperBlock<Self>) -> Result<dentry::Root<Self>> {
+ Err(ENOTSUPP)
+ }
}
/// A registration of a file system.
@@ -154,41 +164,18 @@ impl<T: FileSystem + ?Sized> Tables<T> {
T::fill_super(new_sb)?;
- // 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(sb) };
- if inode.is_null() {
- return Err(ENOMEM);
- }
-
- // SAFETY: `inode` is valid for write.
- unsafe { bindings::set_nlink(inode, 2) };
+ let root = T::init_root(new_sb)?;
- {
- // 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 };
+ // Reject root inode if it belongs to a different superblock.
+ if !ptr::eq(root.super_block(), new_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) };
- if dentry.is_null() {
- return Err(ENOMEM);
- }
+ let dentry = ManuallyDrop::new(root).0.get();
- sb.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)
})
@@ -253,7 +240,7 @@ fn init(module: &'static ThisModule) -> impl PinInit<Self, Error> {
///
/// ```
/// # mod module_fs_sample {
-/// use kernel::fs::{sb::SuperBlock, self};
+/// use kernel::fs::{dentry, inode::INode, sb::SuperBlock, self};
/// use kernel::prelude::*;
///
/// kernel::module_fs! {
@@ -270,6 +257,9 @@ fn init(module: &'static ThisModule) -> impl PinInit<Self, Error> {
/// fn fill_super(_: &mut SuperBlock<Self>) -> Result {
/// todo!()
/// }
+/// fn init_root(_sb: &SuperBlock<Self>) -> Result<dentry::Root<Self>> {
+/// todo!()
+/// }
/// }
/// # }
/// ```
@@ -7,8 +7,10 @@
//! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h)
use super::{sb::SuperBlock, FileSystem, Offset, UnspecifiedFS};
-use crate::bindings;
-use crate::types::{AlwaysRefCounted, Opaque};
+use crate::error::Result;
+use crate::types::{ARef, AlwaysRefCounted, Opaque};
+use crate::{bindings, block, time::Timespec};
+use core::mem::ManuallyDrop;
use core::{marker::PhantomData, ptr};
/// The number of an inode.
@@ -76,3 +78,108 @@ unsafe fn dec_ref(obj: ptr::NonNull<Self>) {
unsafe { bindings::iput(obj.as_ref().0.get()) }
}
}
+
+/// An inode that is locked and hasn't been initialised yet.
+///
+/// # Invariants
+///
+/// The inode is a new one, locked, and valid for write.
+pub struct New<T: FileSystem + ?Sized>(
+ pub(crate) ptr::NonNull<bindings::inode>,
+ pub(crate) PhantomData<T>,
+);
+
+impl<T: FileSystem + ?Sized> New<T> {
+ /// Initialises the new inode with the given parameters.
+ pub fn init(mut self, params: Params) -> Result<ARef<INode<T>>> {
+ // SAFETY: This is a new inode, so it's safe to manipulate it mutably.
+ let inode = unsafe { self.0.as_mut() };
+ let mode = match params.typ {
+ Type::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);
+ }
+
+ let manual = ManuallyDrop::new(self);
+ // SAFETY: We transferred ownership of the refcount to `ARef` by preventing `drop` from
+ // being called with the `ManuallyDrop` instance created above.
+ Ok(unsafe { ARef::from_raw(manual.0.cast::<INode<T>>()) })
+ }
+}
+
+impl<T: FileSystem + ?Sized> Drop for New<T> {
+ 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.as_ptr()) };
+ }
+}
+
+/// The type of an inode.
+#[derive(Copy, Clone)]
+pub enum Type {
+ /// Directory type.
+ Dir,
+}
+
+/// Required inode parameters.
+///
+/// This is used when creating new inodes.
+pub struct Params {
+ /// 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: Type,
+
+ /// Size of the contents of the inode.
+ ///
+ /// Its maximum value is [`super::MAX_LFS_FILESIZE`].
+ pub size: Offset,
+
+ /// Number of blocks.
+ pub blocks: block::Count,
+
+ /// 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,
+}
@@ -6,9 +6,12 @@
//!
//! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h)
+use super::inode::{self, INode, Ino};
use super::FileSystem;
-use crate::{bindings, types::Opaque};
-use core::marker::PhantomData;
+use crate::bindings;
+use crate::error::{code::*, Result};
+use crate::types::{ARef, Either, Opaque};
+use core::{marker::PhantomData, ptr};
/// A file system super block.
///
@@ -60,4 +63,45 @@ pub fn set_magic(&mut self, magic: usize) -> &mut Self {
unsafe { (*self.0.get()).s_magic = magic as core::ffi::c_ulong };
self
}
+
+ /// 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<Either<ARef<INode<T>>, inode::New<T>>> {
+ // SAFETY: All superblock-related state needed by `iget_locked` is initialised by C code
+ // before calling `fill_super_callback`, or by `fill_super_callback` itself before calling
+ // `super_params`, which is the first function to see a new superblock.
+ let inode =
+ ptr::NonNull::new(unsafe { bindings::iget_locked(self.0.get(), ino) }).ok_or(ENOMEM)?;
+
+ // SAFETY: `inode` is a valid pointer returned by `iget_locked`.
+ unsafe { bindings::spin_lock(ptr::addr_of_mut!((*inode.as_ptr()).i_lock)) };
+
+ // SAFETY: `inode` is valid and was locked by the previous lock.
+ let state = unsafe { *ptr::addr_of!((*inode.as_ptr()).i_state) };
+
+ // SAFETY: `inode` is a valid pointer returned by `iget_locked`.
+ unsafe { bindings::spin_unlock(ptr::addr_of_mut!((*inode.as_ptr()).i_lock)) };
+
+ 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
+ // `inode::New`.
+ Ok(Either::Right(inode::New(inode, PhantomData)))
+ }
+ }
+
+ /// Creates an inode with the given inode number.
+ ///
+ /// Fails with `EEXIST` if an inode with the given number already exists.
+ pub fn create_inode(&self, ino: Ino) -> Result<inode::New<T>> {
+ if let Either::Right(new) = self.get_or_create_inode(ino)? {
+ Ok(new)
+ } else {
+ Err(EEXIST)
+ }
+ }
}
@@ -2,9 +2,9 @@
//! Rust read-only file system sample.
-use kernel::fs::sb;
+use kernel::fs::{dentry, inode, sb::SuperBlock};
use kernel::prelude::*;
-use kernel::{c_str, fs};
+use kernel::{c_str, fs, time::UNIX_EPOCH, types::Either};
kernel::module_fs! {
type: RoFs,
@@ -18,8 +18,27 @@
impl fs::FileSystem for RoFs {
const NAME: &'static CStr = c_str!("rust_rofs");
- fn fill_super(sb: &mut sb::SuperBlock<Self>) -> Result {
+ fn fill_super(sb: &mut SuperBlock<Self>) -> Result {
sb.set_magic(0x52555354);
Ok(())
}
+
+ fn init_root(sb: &SuperBlock<Self>) -> Result<dentry::Root<Self>> {
+ let inode = match sb.get_or_create_inode(1)? {
+ Either::Left(existing) => existing,
+ Either::Right(new) => new.init(inode::Params {
+ typ: inode::Type::Dir,
+ mode: 0o555,
+ size: 1,
+ blocks: 1,
+ nlink: 2,
+ uid: 0,
+ gid: 0,
+ atime: UNIX_EPOCH,
+ ctime: UNIX_EPOCH,
+ mtime: UNIX_EPOCH,
+ })?,
+ };
+ dentry::Root::try_new(inode)
+ }
}