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/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 */ diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index c91a3c24f6070a..8403f13d4d4851 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -7,13 +7,30 @@ */ #include +#include +#include #include +#include +#include #include +#include +#include #include #include #include +#include /* `bindgen` gets confused at certain things. */ 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; + +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 9bcbea04dac305..a96b7f08e57dae 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -51,3 +51,13 @@ 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; + +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 4c86fe4a7e05e7..7b12a6d4cf5ccd 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -21,11 +21,18 @@ */ #include +#include +#include #include #include +#include #include #include +#include +#include +#include #include +#include #include #include #include @@ -144,6 +151,141 @@ 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_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); +} +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); +} +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); + +unsigned int rust_helper_MKDEV(unsigned int major, unsigned int minor) +{ + return MKDEV(major, 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); +} +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 + +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/error.rs b/rust/kernel/error.rs index 05fcab6abfe63e..829756cf6c48d9 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -81,6 +81,9 @@ 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."); + declare_err!(ENOSYS, "Invalid system call number."); } /// Generic integer kernel error. @@ -131,7 +134,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 _ } @@ -320,8 +322,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/folio.rs b/rust/kernel/folio.rs new file mode 100644 index 00000000000000..b7f80291b0e110 --- /dev/null +++ b/rust/kernel/folio.rs @@ -0,0 +1,214 @@ +// 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. + 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/fs.rs b/rust/kernel/fs.rs new file mode 100644 index 00000000000000..235a86ed1127d7 --- /dev/null +++ b/rust/kernel/fs.rs @@ -0,0 +1,1290 @@ +// 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, Result}; +use crate::folio::{LockedFolio, UniqueFolio}; +use crate::types::{ARef, AlwaysRefCounted, Either, ForeignOwnable, Opaque, ScopeGuard}; +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)] +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; + +/// 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). + 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; + + /// 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>; + + /// 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>>; + + /// 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; + + /// 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; + + /// 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) + } + + /// 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`]. +#[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::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, + } + } +} + +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. +#[pin_data(PinnedDrop)] +pub struct Registration { + #[pin] + fs: Opaque, + inode_cache: Option, + #[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, + 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()) }; + + // 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 = 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`). + 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(|| { + // 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, + ) { + 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 }; + 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) }; + } + } + + 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] +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()) }; + } +} + +/// 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 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. + 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()) } + } +} + +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>> { + 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 => { + inode.__bindgen_anon_3.i_fop = &Tables::::DIR_FILE_OPERATIONS; + 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 + } + 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)?; + 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 { + /// 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. +/// +/// 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, + + /// Value to attach to this node. + pub value: T, +} + +/// A file system super block. +/// +/// Wraps the kernel's `struct super_block`. +#[repr(transparent)] +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 + // 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()) + }))) + } + } + + /// 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. +/// +/// 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, + + /// Data to be associated with the superblock. + pub data: T, +} + +/// 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); + +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 { + 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 { + 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( + 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_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; + 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; + + // 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() }; + let root = T::init_root(sb)?; + + // 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(ManuallyDrop::new(root).0.get()) }; + if dentry.is_null() { + return Err(ENOMEM); + } + + // 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) + }) + } + + const SUPER_BLOCK: bindings::super_operations = bindings::super_operations { + 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, + 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: Some(Self::statfs_callback), + 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, + }; + + 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, + ) -> 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 { + 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), + 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) + } + } + } + }) + } + + 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) + }, + } + } + + 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, + 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. +/// +/// 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`. +#[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::fs::{DirEmitter, INode, NewSuperBlock, SuperBlock, SuperParams}; +/// use kernel::prelude::*; +/// use kernel::{c_str, folio::LockedFolio, fs, types::ARef}; +/// +/// 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 { +/// type Data = (); +/// type INodeData =(); +/// const NAME: &'static CStr = c_str!("myfs"); +/// fn super_params(_: &NewSuperBlock) -> Result> { +/// todo!() +/// } +/// fn init_root(_sb: &SuperBlock) -> Result>> { +/// todo!() +/// } +/// fn read_dir(_: &INode, _: &mut DirEmitter) -> Result { +/// todo!() +/// } +/// fn lookup(_: &INode, _: &[u8]) -> Result>> { +/// todo!() +/// } +/// fn read_folio(_: &INode, _: LockedFolio<'_>) -> Result { +/// todo!() +/// } +/// } +/// # } +/// ``` +#[macro_export] +macro_rules! module_fs { + (type: $type:ty, $($f:tt)*) => { + type ModuleType = $crate::fs::Module<$type>; + $crate::macros::module! { + type: ModuleType, + $($f)* + } + } +} diff --git a/rust/kernel/fs/buffer.rs b/rust/kernel/fs/buffer.rs new file mode 100644 index 00000000000000..de23d0fee66c12 --- /dev/null +++ b/rust/kernel/fs/buffer.rs @@ -0,0 +1,60 @@ +// 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 { + 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] + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index e8811700239aaf..0e85b380da6449 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -16,7 +16,9 @@ #![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)] // Ensure conditional compilation based on the kernel configuration works; @@ -32,10 +34,13 @@ 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; #[cfg(CONFIG_KUNIT)] pub mod kunit; +pub mod mem_cache; pub mod prelude; pub mod print; mod static_assert; @@ -44,6 +49,7 @@ pub mod std_vendor; pub mod str; pub mod sync; pub mod task; +pub mod time; pub mod types; #[doc(hidden)] @@ -60,7 +66,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 +76,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` @@ -89,6 +118,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/rust/kernel/mem_cache.rs b/rust/kernel/mem_cache.rs new file mode 100644 index 00000000000000..bf6ce2d2d3e1ab --- /dev/null +++ b/rust/kernel/mem_cache.rs @@ -0,0 +1,62 @@ +// 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. + 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. + 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()) }; + } +} 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 + } +} diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index fdb778e65d79d3..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, }; @@ -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. @@ -387,3 +395,170 @@ 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()) + } +} + +/// 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>();)* + }; + )* + }; +} 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/samples/rust/Kconfig b/samples/rust/Kconfig index b0f74a81c8f9ad..2f26c5c5281382 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 @@ -30,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 03086dabbea44f..df1e4341ae9587 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -1,6 +1,8 @@ # 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 +obj-$(CONFIG_SAMPLE_RUST_ROFS) += rust_rofs.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/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs new file mode 100644 index 00000000000000..dfe74543984223 --- /dev/null +++ b/samples/rust/rust_rofs.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust read-only file system sample. + +use kernel::fs::{ + DirEmitter, INode, INodeParams, INodeType, NewSuperBlock, SuperBlock, SuperParams, +}; +use kernel::prelude::*; +use kernel::{c_str, folio::LockedFolio, fs, time::UNIX_EPOCH, types::ARef, types::Either}; + +kernel::module_fs! { + type: RoFs, + name: "rust_rofs", + author: "Rust for Linux Contributors", + description: "Rust read-only file system sample", + license: "GPL", +} + +struct Entry { + name: &'static [u8], + ino: u64, + etype: INodeType, + contents: &'static [u8], +} + +const ENTRIES: [Entry; 4] = [ + Entry { + name: b".", + ino: 1, + etype: INodeType::Dir, + contents: b"", + }, + Entry { + name: b"..", + ino: 1, + etype: INodeType::Dir, + contents: b"", + }, + Entry { + name: b"test.txt", + ino: 2, + etype: INodeType::Reg, + contents: b"hello\n", + }, + Entry { + name: b"link.txt", + ino: 3, + etype: INodeType::Lnk, + contents: b"./test.txt", + }, +]; + +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> { + Ok(SuperParams { + magic: 0x52555354, + blocksize_bits: 12, + maxbytes: fs::MAX_LFS_FILESIZE, + time_gran: 1, + data: (), + }) + } + + 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: ENTRIES.len().try_into()?, + blocks: 1, + nlink: 2, + uid: 0, + gid: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, + value: &ENTRIES[0], + }), + } + } + + 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(()) + } + + fn lookup(parent: &INode, name: &[u8]) -> Result>> { + if parent.ino() != 1 { + return 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, + value: e, + }), + }; + } + } + + Err(ENOENT) + } + + fn read_folio(inode: &INode, mut folio: LockedFolio<'_>) -> Result { + let data = inode.data().contents; + + 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(()) + } +} diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 82e3fb19fdafc9..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 +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 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: