@@ -288,6 +288,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);
+
unsigned long rust_helper_copy_to_user(void __user *to, const void *from,
unsigned long n)
{
@@ -307,6 +313,13 @@ void rust_helper_inode_unlock_shared(struct inode *inode)
}
EXPORT_SYMBOL_GPL(rust_helper_inode_unlock_shared);
+void rust_helper_set_delayed_call(struct delayed_call *call,
+ void (*fn)(void *), void *arg)
+{
+ set_delayed_call(call, fn, arg);
+}
+EXPORT_SYMBOL_GPL(rust_helper_set_delayed_call);
+
/*
* `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.
@@ -521,8 +521,13 @@ pub enum DirEntryType {
impl From<inode::Type> for DirEntryType {
fn from(value: inode::Type) -> Self {
match value {
+ inode::Type::Fifo => DirEntryType::Fifo,
+ inode::Type::Chr(_, _) => DirEntryType::Chr,
inode::Type::Dir => DirEntryType::Dir,
+ inode::Type::Blk(_, _) => DirEntryType::Blk,
inode::Type::Reg => DirEntryType::Reg,
+ inode::Type::Lnk => DirEntryType::Lnk,
+ inode::Type::Sock => DirEntryType::Sock,
}
}
}
@@ -10,8 +10,8 @@
address_space, dentry, dentry::DEntry, file, sb::SuperBlock, FileSystem, Offset, UnspecifiedFS,
};
use crate::error::{code::*, Result};
-use crate::types::{ARef, AlwaysRefCounted, Lockable, Locked, Opaque};
-use crate::{bindings, block, time::Timespec};
+use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Lockable, Locked, Opaque};
+use crate::{bindings, block, str::CStr, str::CString, time::Timespec};
use core::mem::ManuallyDrop;
use core::{marker::PhantomData, ptr};
use macros::vtable;
@@ -25,6 +25,18 @@ pub trait Operations {
/// File system that these operations are compatible with.
type FileSystem: FileSystem + ?Sized;
+ /// Returns the string that represents the name of the file a symbolic link inode points to.
+ ///
+ /// When `dentry` is `None`, `get_link` is called with the RCU read-side lock held, so it may
+ /// not sleep. Implementations must return `Err(ECHILD)` for it to be called again without
+ /// holding the RCU lock.
+ fn get_link<'a>(
+ _dentry: Option<&DEntry<Self::FileSystem>>,
+ _inode: &'a INode<Self::FileSystem>,
+ ) -> Result<Either<CString, &'a CStr>> {
+ Err(ENOTSUPP)
+ }
+
/// Returns the inode corresponding to the directory entry with the given name.
fn lookup(
_parent: &Locked<&INode<Self::FileSystem>, ReadSem>,
@@ -134,6 +146,52 @@ pub fn init(mut self, params: Params) -> Result<ARef<INode<T>>> {
unsafe { bindings::mapping_set_large_folios(inode.i_mapping) };
bindings::S_IFREG
}
+ Type::Lnk => {
+ // If we are using `page_get_link`, we need to prevent the use of high mem.
+ if !inode.i_op.is_null() {
+ // SAFETY: We just checked that `i_op` is non-null, and we always just set it
+ // to valid values.
+ if unsafe {
+ (*inode.i_op).get_link == bindings::page_symlink_inode_operations.get_link
+ } {
+ // SAFETY: `inode` is valid for write as it's a new inode.
+ unsafe { bindings::inode_nohighmem(inode) };
+ }
+ }
+ bindings::S_IFLNK
+ }
+ Type::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
+ }
+ Type::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
+ }
+ Type::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
+ }
+ Type::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)?;
@@ -194,11 +252,26 @@ fn drop(&mut self) {
/// The type of an inode.
#[derive(Copy, Clone)]
pub enum Type {
+ /// 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.
@@ -245,6 +318,15 @@ pub struct Params {
pub struct Ops<T: FileSystem + ?Sized>(*const bindings::inode_operations, PhantomData<T>);
impl<T: FileSystem + ?Sized> Ops<T> {
+ /// Returns inode operations for symbolic links that are stored in a single page.
+ pub fn page_symlink_inode() -> Self {
+ // SAFETY: This is a constant in C, it never changes.
+ Self(
+ unsafe { &bindings::page_symlink_inode_operations },
+ PhantomData,
+ )
+ }
+
/// Creates the inode operations from a type that implements the [`Operations`] trait.
pub const fn new<U: Operations<FileSystem = T> + ?Sized>() -> Self {
struct Table<T: Operations + ?Sized>(PhantomData<T>);
@@ -255,7 +337,11 @@ impl<T: Operations + ?Sized> Table<T> {
} else {
None
},
- get_link: None,
+ get_link: if T::HAS_GET_LINK {
+ Some(Self::get_link_callback)
+ } else {
+ None
+ },
permission: None,
get_inode_acl: None,
readlink: None,
@@ -303,6 +389,45 @@ extern "C" fn lookup_callback(
Ok(Some(ret)) => ManuallyDrop::new(ret).0.get(),
}
}
+
+ extern "C" fn get_link_callback(
+ dentry_ptr: *mut bindings::dentry,
+ inode_ptr: *mut bindings::inode,
+ delayed_call: *mut bindings::delayed_call,
+ ) -> *const core::ffi::c_char {
+ extern "C" fn drop_cstring(ptr: *mut core::ffi::c_void) {
+ // SAFETY: The argument came from a previous call to `into_foreign` below.
+ unsafe { CString::from_foreign(ptr) };
+ }
+
+ let dentry = if dentry_ptr.is_null() {
+ None
+ } else {
+ // SAFETY: The C API guarantees that `dentry_ptr` is a valid dentry when it's
+ // non-null.
+ Some(unsafe { DEntry::from_raw(dentry_ptr) })
+ };
+
+ // SAFETY: The C API guarantees that `parent_ptr` is a valid inode.
+ let inode = unsafe { INode::from_raw(inode_ptr) };
+
+ match T::get_link(dentry, inode) {
+ Err(e) => e.to_ptr::<core::ffi::c_char>(),
+ Ok(Either::Right(str)) => str.as_char_ptr(),
+ Ok(Either::Left(str)) => {
+ let ptr = str.into_foreign();
+ unsafe {
+ bindings::set_delayed_call(
+ delayed_call,
+ Some(drop_cstring),
+ ptr.cast_mut(),
+ )
+ };
+
+ ptr.cast::<core::ffi::c_char>()
+ }
+ }
+ }
}
Self(&Table::<U>::TABLE, PhantomData)
}
@@ -7,7 +7,7 @@
};
use kernel::prelude::*;
use kernel::types::{ARef, Either, Locked};
-use kernel::{c_str, folio::Folio, folio::PageCache, fs, time::UNIX_EPOCH, user};
+use kernel::{c_str, folio::Folio, folio::PageCache, fs, str::CString, time::UNIX_EPOCH, user};
kernel::module_fs! {
type: RoFs,
@@ -24,7 +24,7 @@ struct Entry {
contents: &'static [u8],
}
-const ENTRIES: [Entry; 3] = [
+const ENTRIES: [Entry; 4] = [
Entry {
name: b".",
ino: 1,
@@ -43,11 +43,18 @@ struct Entry {
etype: inode::Type::Reg,
contents: b"hello world\n",
},
+ Entry {
+ name: b"link.txt",
+ ino: 3,
+ etype: inode::Type::Lnk,
+ contents: b"./test.txt",
+ },
];
const DIR_FOPS: file::Ops<RoFs> = file::Ops::new::<RoFs>();
const DIR_IOPS: inode::Ops<RoFs> = inode::Ops::new::<RoFs>();
const FILE_AOPS: address_space::Ops<RoFs> = address_space::Ops::new::<RoFs>();
+const LNK_IOPS: inode::Ops<RoFs> = inode::Ops::new::<Link>();
struct RoFs;
@@ -68,6 +75,11 @@ fn iget(sb: &sb::SuperBlock<Self>, e: &'static Entry) -> Result<ARef<INode<Self>
.set_aops(FILE_AOPS);
(0o444, 1, e.contents.len().try_into()?)
}
+ inode::Type::Lnk => {
+ new.set_iops(LNK_IOPS);
+ (0o444, 1, e.contents.len().try_into()?)
+ }
+ _ => return Err(ENOENT),
};
new.init(inode::Params {
@@ -123,6 +135,33 @@ fn lookup(
}
}
+struct Link;
+#[vtable]
+impl inode::Operations for Link {
+ type FileSystem = RoFs;
+
+ fn get_link<'a>(
+ dentry: Option<&DEntry<RoFs>>,
+ inode: &'a INode<RoFs>,
+ ) -> Result<Either<CString, &'a CStr>> {
+ if dentry.is_none() {
+ return Err(ECHILD);
+ }
+
+ let name_buf = match inode.ino() {
+ 3 => ENTRIES[3].contents,
+ _ => return Err(EINVAL),
+ };
+ let mut name = Box::new_slice(
+ name_buf.len().checked_add(1).ok_or(ENOMEM)?,
+ b'\0',
+ GFP_NOFS,
+ )?;
+ name[..name_buf.len()].copy_from_slice(name_buf);
+ Ok(Either::Left(name.try_into()?))
+ }
+}
+
#[vtable]
impl address_space::Operations for RoFs {
type FileSystem = Self;