diff --git a/README.md b/README.md index cff65b5e0fd827..8709a7f55af1cc 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,17 @@ -# Rust for Linux +# Some Modules for Rust for Linux -The goal of this project is to add support for the Rust language to the Linux kernel. This repository contains the work that will be eventually submitted for review to the LKML. +This repository contains the [Rust-for-Linux/linux](https://github.com/Rust-for-Linux/linux) kernel with some additions. -Feel free to [contribute](https://github.com/Rust-for-Linux/linux/contribute)! To start, take a look at [`Documentation/rust`](https://github.com/Rust-for-Linux/linux/tree/rust/Documentation/rust). +- `fs/bs2ramfs` +- various additions to the kernel crate -General discussions, announcements, questions, etc. take place on the mailing list at rust-for-linux@vger.kernel.org ([subscribe](mailto:majordomo@vger.kernel.org?body=subscribe%20rust-for-linux), [instructions](http://vger.kernel.org/majordomo-info.html), [archive](https://lore.kernel.org/rust-for-linux/)). For chat, help, quick questions, informal discussion, etc. you may want to join our Zulip at https://rust-for-linux.zulipchat.com ([request an invitation](https://lore.kernel.org/rust-for-linux/CANiq72kW07hWjuc+dyvYH9NxyXoHsQLCtgvtR+8LT-VaoN1J_w@mail.gmail.com/T/)). +For the actual work on the kernel, please refer to [Rust-for-Linux/linux](https://github.com/Rust-for-Linux/linux) and the instructions there. -All contributors to this effort are understood to have agreed to the Linux kernel development process as explained in the different files under [`Documentation/process`](https://www.kernel.org/doc/html/latest/process/index.html). +# Contributors - +This project is part of the [Operating Systems 2 (german)](https://osm.hpi.de/bs2/2021/) lecture at HPI, Potsdam. Our working group consists of + +- [Benedikt Weber](https://github.com/bewee) +- [Niklas Mohrin](https://github.com/niklasmohrin) +- [Nils Lißner](https://github.com/TheGrayStone) +- [Tamino Bauknecht](https://github.com/taminob) diff --git a/b b/b new file mode 100755 index 00000000000000..2f49be46bbb24c --- /dev/null +++ b/b @@ -0,0 +1,8 @@ +#!/bin/sh + +set -x +REPO_DIR=$(dirname $0) +MOD_DIR=$(realpath $REPO_DIR/_mod_inst) +BUILD_DIR=$(realpath $REPO_DIR/bs2build/) + +RUSTUP_TOOLCHAIN="nightly-2021-05-29" make LLVM=1 ARCH=x86_64 INSTALL_MOD_PATH="$MOD_DIR" CLIPPY=1 $@ diff --git a/fs/Kconfig b/fs/Kconfig index 141a856c50e717..e486325a8697a8 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -20,6 +20,7 @@ if BLOCK config FS_IOMAP bool +source "fs/bs2ramfs/Kconfig" source "fs/ext2/Kconfig" source "fs/ext4/Kconfig" source "fs/jbd2/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 9c708e1fbe8fb3..d7d5e1c97d64fa 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_NETFS_SUPPORT) += netfs/ obj-$(CONFIG_FSCACHE) += fscache/ obj-$(CONFIG_REISERFS_FS) += reiserfs/ obj-$(CONFIG_EXT4_FS) += ext4/ +obj-$(CONFIG_BS2_RAMFS) += bs2ramfs/ # We place ext4 before ext2 so that clean ext3 root fs's do NOT mount using the # ext2 driver, which doesn't know about journalling! Explicitly request ext2 # by giving the rootfstype= parameter. diff --git a/fs/bs2ramfs/Kconfig b/fs/bs2ramfs/Kconfig new file mode 100644 index 00000000000000..c6b47f488849b4 --- /dev/null +++ b/fs/bs2ramfs/Kconfig @@ -0,0 +1,6 @@ +config BS2_RAMFS + tristate "BS2 ramfs" + depends on MMU && RUST + default n + help + Enables the rust implementation of ramfs. diff --git a/fs/bs2ramfs/Makefile b/fs/bs2ramfs/Makefile new file mode 100644 index 00000000000000..dbc55398e16511 --- /dev/null +++ b/fs/bs2ramfs/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_BS2_RAMFS) += bs2ramfs.o diff --git a/fs/bs2ramfs/bs2ramfs.rs b/fs/bs2ramfs/bs2ramfs.rs new file mode 100644 index 00000000000000..4b511103d18619 --- /dev/null +++ b/fs/bs2ramfs/bs2ramfs.rs @@ -0,0 +1,421 @@ +#![no_std] +#![feature(global_asm)] +#![deny(clippy::all)] +#![warn(clippy::pedantic)] + +use alloc::boxed::Box; +use core::{mem, ptr}; + +use kernel::{ + bindings, + c_types::*, + file::File, + file_operations::{FileOperations, SeekFrom}, + fs::{ + address_space::AddressSpace, + address_space_operations::AddressSpaceOperations, + dentry::Dentry, + inode::{Inode, UpdateATime, UpdateCTime, UpdateMTime}, + inode_operations::InodeOperations, + kiocb::Kiocb, + libfs_functions::{self, PageSymlinkInodeOperations, SimpleDirOperations}, + super_block::SuperBlock, + super_operations::{Kstatfs, SeqFile, SuperOperations}, + FileSystemBase, FileSystemType, + }, + iov_iter::IovIter, + prelude::*, + print::ExpectK, + str::CStr, + types::{Dev, FileSystemFlags, Iattr, Kstat, Page, Path, UserNamespace}, + Error, Mode, +}; + +const PAGE_SHIFT: u32 = 12; // x86 (maybe) +const MAX_LFS_FILESIZE: c_longlong = c_longlong::MAX; +const BS2RAMFS_MAGIC: u64 = 0x858458f6; + +extern "C" { + fn rust_helper_mapping_set_unevictable(mapping: *mut bindings::address_space); + fn rust_helper_mapping_set_gfp_mask( + mapping: *mut bindings::address_space, + mask: bindings::gfp_t, + ); + static RUST_HELPER_GFP_HIGHUSER: bindings::gfp_t; +} + +module! { + type: BS2Ramfs, + name: b"bs2ramfs", + author: b"Rust for Linux Contributors", + description: b"RAMFS", + license: b"GPL v2", +} + +struct BS2Ramfs; + +impl FileSystemBase for BS2Ramfs { + const NAME: &'static CStr = kernel::c_str!("bs2ramfs"); + const FS_FLAGS: FileSystemFlags = FileSystemFlags::FS_USERNS_MOUNT; + + fn mount( + _fs_type: &'_ mut FileSystemType, + flags: c_int, + _device_name: &CStr, + data: Option<&mut Self::MountOptions>, + ) -> Result<*mut bindings::dentry> { + libfs_functions::mount_nodev::(flags, data) + } + + fn kill_super(sb: &mut SuperBlock) { + let _ = unsafe { Box::from_raw(mem::replace(&mut sb.s_fs_info, ptr::null_mut())) }; + libfs_functions::kill_litter_super(sb); + } + + fn fill_super( + sb: &mut SuperBlock, + _data: Option<&mut Self::MountOptions>, + _silent: c_int, + ) -> Result { + sb.s_magic = BS2RAMFS_MAGIC; + let ops = Bs2RamfsSuperOps::default(); + + // TODO: investigate if this really has to be set to NULL in case we run out of memory + sb.s_root = ptr::null_mut(); + sb.s_root = ramfs_get_inode(sb, None, Mode::S_IFDIR | ops.mount_opts.mode, 0) + .and_then(Dentry::make_root) + .ok_or(Error::ENOMEM)? as *mut _ as *mut _; + + sb.set_super_operations(ops)?; + sb.s_maxbytes = MAX_LFS_FILESIZE; + sb.s_blocksize = kernel::PAGE_SIZE as _; + sb.s_blocksize_bits = PAGE_SHIFT as _; + sb.s_time_gran = 1; + + Ok(()) + } +} +kernel::declare_fs_type!(BS2Ramfs, BS2RAMFS_FS_TYPE); + +impl KernelModule for BS2Ramfs { + fn init() -> Result { + pr_info!("bs2 ramfs in action"); + libfs_functions::register_filesystem::().map(move |_| Self) + } +} + +impl Drop for BS2Ramfs { + fn drop(&mut self) { + let _ = libfs_functions::unregister_filesystem::(); + pr_info!("bs2 ramfs out of action"); + } +} + +struct RamfsMountOpts { + pub mode: Mode, +} + +impl Default for RamfsMountOpts { + fn default() -> Self { + Self { + mode: Mode::from_int(0o775), + } + } +} + +#[derive(Default)] +struct Bs2RamfsFileOps; + +impl FileOperations for Bs2RamfsFileOps { + kernel::declare_file_operations!( + read_iter, + write_iter, + mmap, + fsync, + splice_read, + splice_write, + seek + ); + + fn read_iter(&self, iocb: &mut Kiocb, iter: &mut IovIter) -> Result { + libfs_functions::generic_file_read_iter(iocb, iter) + } + + fn write_iter(&self, iocb: &mut Kiocb, iter: &mut IovIter) -> Result { + libfs_functions::generic_file_write_iter(iocb, iter) + } + + fn mmap(&self, file: &File, vma: &mut bindings::vm_area_struct) -> Result { + libfs_functions::generic_file_mmap(file, vma) + } + + fn fsync(&self, file: &File, start: u64, end: u64, datasync: bool) -> Result { + libfs_functions::noop_fsync(file, start, end, datasync) + } + + fn seek(&self, file: &File, pos: SeekFrom) -> Result { + libfs_functions::generic_file_llseek(file, pos) + } + + fn splice_read( + &self, + file: &File, + pos: *mut i64, + pipe: &mut bindings::pipe_inode_info, + len: usize, + flags: u32, + ) -> Result { + libfs_functions::generic_file_splice_read(file, pos, pipe, len, flags) + } + + fn splice_write( + &self, + pipe: &mut bindings::pipe_inode_info, + file: &File, + pos: *mut i64, + len: usize, + flags: u32, + ) -> Result { + libfs_functions::iter_file_splice_write(pipe, file, pos, len, flags) + } +} + +#[derive(Default)] +struct Bs2RamfsSuperOps { + mount_opts: RamfsMountOpts, +} + +impl SuperOperations for Bs2RamfsSuperOps { + kernel::declare_super_operations!(statfs, drop_inode, show_options); + + fn drop_inode(&self, inode: &mut Inode) -> Result { + libfs_functions::generic_delete_inode(inode) + } + + fn statfs(&self, root: &mut Dentry, buf: &mut Kstatfs) -> Result { + libfs_functions::simple_statfs(root, buf) + } + + fn show_options(&self, _s: &mut SeqFile, _root: &mut Dentry) -> Result { + // TODO: Do something + Ok(()) + } +} + +#[derive(Default)] +struct Bs2RamfsAOps; + +impl AddressSpaceOperations for Bs2RamfsAOps { + kernel::declare_address_space_operations!(readpage, write_begin, write_end, set_page_dirty); + + fn readpage(&self, file: &File, page: &mut Page) -> Result { + libfs_functions::simple_readpage(file, page) + } + + fn write_begin( + &self, + file: Option<&File>, + mapping: &mut AddressSpace, + pos: bindings::loff_t, + len: u32, + flags: u32, + pagep: *mut *mut Page, + fsdata: *mut *mut c_void, + ) -> Result { + libfs_functions::simple_write_begin(file, mapping, pos, len, flags, pagep, fsdata) + } + + fn write_end( + &self, + file: Option<&File>, + mapping: &mut AddressSpace, + pos: bindings::loff_t, + len: u32, + copied: u32, + page: &mut Page, + fsdata: *mut c_void, + ) -> Result { + libfs_functions::simple_write_end(file, mapping, pos, len, copied, page, fsdata) + } + + fn set_page_dirty(&self, page: &mut Page) -> Result { + libfs_functions::__set_page_dirty_nobuffers(page) + } +} + +#[derive(Default)] +struct Bs2RamfsFileInodeOps; + +impl InodeOperations for Bs2RamfsFileInodeOps { + kernel::declare_inode_operations!(setattr, getattr); + + fn setattr( + &self, + mnt_userns: &mut UserNamespace, + dentry: &mut Dentry, + iattr: &mut Iattr, + ) -> Result { + libfs_functions::simple_setattr(mnt_userns, dentry, iattr) + } + + fn getattr( + &self, + mnt_userns: &mut UserNamespace, + path: &Path, + stat: &mut Kstat, + request_mask: u32, + query_flags: u32, + ) -> Result { + libfs_functions::simple_getattr(mnt_userns, path, stat, request_mask, query_flags) + } +} + +#[derive(Default)] +struct Bs2RamfsDirInodeOps; + +impl InodeOperations for Bs2RamfsDirInodeOps { + kernel::declare_inode_operations!( + create, lookup, link, unlink, symlink, mkdir, rmdir, mknod, rename + ); + + fn create( + &self, + mnt_userns: &mut UserNamespace, + dir: &mut Inode, + dentry: &mut Dentry, + mode: Mode, + _excl: bool, + ) -> Result { + self.mknod(mnt_userns, dir, dentry, mode | Mode::S_IFREG, 0) + } + + fn lookup(&self, dir: &mut Inode, dentry: &mut Dentry, flags: c_uint) -> Result<*mut Dentry> { + libfs_functions::simple_lookup(dir, dentry, flags) + } + + fn link(&self, old_dentry: &mut Dentry, dir: &mut Inode, dentry: &mut Dentry) -> Result { + libfs_functions::simple_link(old_dentry, dir, dentry) + } + + fn unlink(&self, dir: &mut Inode, dentry: &mut Dentry) -> Result { + libfs_functions::simple_unlink(dir, dentry) + } + + fn symlink( + &self, + _mnt_userns: &mut UserNamespace, + dir: &mut Inode, + dentry: &mut Dentry, + symname: &'static CStr, + ) -> Result { + let inode = ramfs_get_inode( + dir.super_block_mut(), + Some(dir), + Mode::S_IFLNK | Mode::S_IRWXUGO, + 0, + ) + .ok_or(Error::ENOSPC)?; + + if let Err(e) = libfs_functions::page_symlink(inode, symname) { + inode.put(); + return Err(e); + } + + dentry.instantiate(inode); + dentry.get(); + dir.update_acm_time(UpdateATime::No, UpdateCTime::Yes, UpdateMTime::Yes); + Ok(()) + } + + fn mkdir( + &self, + mnt_userns: &mut UserNamespace, + dir: &mut Inode, + dentry: &mut Dentry, + mode: Mode, + ) -> Result { + if self + .mknod(mnt_userns, dir, dentry, mode | Mode::S_IFDIR, 0) + .is_err() + { + dir.inc_nlink(); + } + Ok(()) + } + + fn rmdir(&self, dir: &mut Inode, dentry: &mut Dentry) -> Result { + libfs_functions::simple_rmdir(dir, dentry) + } + + fn mknod( + &self, + _mnt_userns: &mut UserNamespace, + dir: &mut Inode, + dentry: &mut Dentry, + mode: Mode, + dev: Dev, + ) -> Result { + let inode = + ramfs_get_inode(dir.super_block_mut(), Some(dir), mode, dev).ok_or(Error::ENOSPC)?; + dentry.instantiate(inode); + dentry.get(); + dir.update_acm_time(UpdateATime::No, UpdateCTime::Yes, UpdateMTime::Yes); + Ok(()) + } + + fn rename( + &self, + mnt_userns: &mut UserNamespace, + old_dir: &mut Inode, + old_dentry: &mut Dentry, + new_dir: &mut Inode, + new_dentry: &mut Dentry, + flags: c_uint, + ) -> Result { + libfs_functions::simple_rename(mnt_userns, old_dir, old_dentry, new_dir, new_dentry, flags) + } +} + +pub fn ramfs_get_inode<'a>( + sb: &'a mut SuperBlock, + dir: Option<&'_ mut Inode>, + mode: Mode, + dev: bindings::dev_t, +) -> Option<&'a mut Inode> { + let inode = Inode::new(sb)?; + inode.i_ino = Inode::next_ino() as _; + inode.init_owner(unsafe { &mut bindings::init_user_ns }, dir, mode); + + inode + .mapping_mut() + .set_address_space_operations(Bs2RamfsAOps) + .expectk("Set address space operations"); + + // TODO: these should be functions on the AddressSpace + unsafe { + rust_helper_mapping_set_gfp_mask(inode.i_mapping, RUST_HELPER_GFP_HIGHUSER); + rust_helper_mapping_set_unevictable(inode.i_mapping); + } + + inode.update_acm_time(UpdateATime::Yes, UpdateCTime::Yes, UpdateMTime::Yes); + match mode & Mode::S_IFMT { + Mode::S_IFREG => { + inode.set_inode_operations(Bs2RamfsFileInodeOps); + inode.set_file_operations::(); + } + Mode::S_IFDIR => { + inode.set_inode_operations(Bs2RamfsDirInodeOps); + inode.set_file_operations::(); + inode.inc_nlink(); + } + Mode::S_IFLNK => { + inode.set_inode_operations(PageSymlinkInodeOperations); + inode.nohighmem(); + } + _ => { + inode.init_special(mode, dev); + } + } + + Some(inode) +} diff --git a/kernel.german.md b/kernel.german.md new file mode 100644 index 00000000000000..009760d550b0c0 --- /dev/null +++ b/kernel.german.md @@ -0,0 +1,107 @@ +# Custom Rust Kernel bauen + +## 1. Vorbereitung + +Kernel holen (z.B. [`github.com/niklasmohrin/linux`](https://github.com/niklasmohrin/linux)), [Arch Iso holen](https://archlinux.org/download/) (relativ neu, mit Installer) + +## 2. Kernel bauen + +Siehe [`quick-start.rst`](https://github.com/niklasmohrin/linux/blob/ramfs/Documentation/rust/quick-start.rst) für Toolchain. + +Wir haben die zwei Skripte `b` und `scp_all` zusammengestellt. Insbesondere ist `b` ein Wrapper um den `make` command, der die Compiler Optionen setzt (also `rustup default` nicht verändert werden muss). Falls man die nicht benutzen möchte, hier unser vorherige Anleitung: + +```console +$ mkdir -p builds/{kernel, modules} +$ cd mylinuxclone +$ make O=../builds/kernel defconfig # wir wollen das repo selbst nicht zum bauen benutzen +$ cd ../builds/kernel +$ rustup default nightly-2021-05-29 +$ make menuconfig # Rust Support auswählen, Rest erstmal egal +$ make ARCH=x86_64 CC=clang -j12 # baut Kernel und gewählte Module +... +something about bzImage # irgendwie sowas sollte am Ende stehen +... +$ make INSTALL_MOD_PATH=../modules modules_install # Alle Module sammeln +``` + +## 3. VM aufbauen + +- qemu und ovmf installieren (man braucht vor allem eine `OVMF.fd`) +- `qemu-img create -f qcow2 archinstaller-clean.qcow2 8G` +- VM launchen mit Befehl unten, wobei das `-cdrom ...` mit benutzt werden muss +- `archinstall` guided installer, Profil minimal (kein XOrg) +- root account passwort muss gesetzt werden für SSH +- falls später was fehlt `pacman -S neovim openssh tree` oder so + +## 4. Snapshots + +Jetzt bietet es sich an, einen Snapshot zu machen: + +``` +qemu-img create -b archinstaller-clean.qcow2 snapshot1.qcow2 +``` + +Hierbei ist wichtig, dass der Snapshot nicht mehr funktioniert, falls das Basis Image verändert wird (also nichtmal booten am besten). Falls man wieder da anfangen will, sollte ein neuer Snapshot mit der gleichen Basis erstellt werden. + +Es gibt auch noch die `-snapshot` Flag beim starten, da arbeitet man immer nur auf einem Snapshot der nach Ende der Session weggeschmissen wird (wir wissen aber nicht, ob es safe ist das Basis Image damit zu booten). + +## 5. VM launchen + +Hier sollte das `pool/current.qcow2` durch Pfad zum Disk Image und `OVMF.fd` durch vollen Pfad zur Datei ersetzt werden + +```sh +#!/bin/sh + +qemu-system-x86_64 \ + -drive file=pool/current.qcow2,format=qcow2 \ + -boot menu=on \ + -bios OVMF.fd \ + -nic user,hostfwd=tcp::2222-:22 \ + -m 4096 \ + -cpu host \ + -enable-kvm + # -nographic \ + # -cdrom archlinux-2021.05.01-x86_64.iso \ + # -snapshot \ + # -vga virtio -display sdl,gl=on \ + # -fsdev local,id=fs1,path=share,security_model=none \ + # -device virtio-9p-pci,fsdev=fs1,mount_tag=shared_folder \ + # -kernel ../linux/arch/x86/boot/bzImage \ + # -append "console=ttyS0" \ +``` + +## 6. SSHD konfigurieren + +Im Guest in `/etc/ssh/sshd_config` (`pacman -S openssh`) die Option `PermitRootLogin yes` setzen. +Dann noch mit `systemctl enable --now sshd` den Server aktivieren. + +## 7. Kernel und Module im Guest installieren + +Bevor wir `builds/modules` rekursiv in die VM kopieren, sollten wir den Symlink auf den Sourcecode entfernen, weil sonst kopieren wir den mit. WICHTIG: nicht die Pfade mit `/` enden lassen, sonst wird das Verzeichnis hinter dem Link gelöscht. + +```console +$ rm builds/modules/{source, build} +$ scp -P 2222 builds/kernel//bzImage root@127.0.0.1:/boot/vmlinuz-rust +$ scp -r -P 2222 builds/modules/lib/* root@127.0.0.1:/lib/ +``` + +## 8. Boot Manager anpassen + +Der Arch Installer hat uns systemd-boot installiert. + +```console +$ cd /boot/loader/entries/ +$ cp linux-rust.conf +$ nvim linux-rust.conf + +ESC ESC :wq here you go +$ bootctl install +$ reboot +``` + +## 9. Testen + +Am besten man hat `rust_minimal` als eingebautes Modul kompiliert, dann kann man nach dem reboot in `dmesg | less` nach Rust suchen (type: `/rust`, dann `n` zum nächsten Suchergebnis). +Mit `modprobe rust_print` wird das dynamische Modul geladen, das sollte jetzt in `dmesg | tail` und `lsmod | grep rust` auftauchen. + +Kernelmodule die nicht in `/lib/modules/.../` (`...` sollte durch den Output von `uname -r` / die Versionsnummer ersetzt werden; in unserem Fall `5.12.0-rc4+`) liegen, können mit `insmod example.ko` und `rmmod example.ko` geladen und entladen werden. diff --git a/rust/helpers.c b/rust/helpers.c index cf4ddd705f222c..3d9f92c5b2f076 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -2,10 +2,11 @@ #include #include -#include #include #include #include +#include +#include #include #include #include @@ -151,3 +152,30 @@ static_assert( __alignof__(size_t) == __alignof__(uintptr_t), "Rust code expects C size_t to match Rust usize" ); + +void rust_helper_dget(struct dentry *dentry) +{ + dget(dentry); +} +EXPORT_SYMBOL_GPL(rust_helper_dget); + +void rust_helper_mapping_set_unevictable(struct address_space *mapping) +{ + mapping_set_unevictable(mapping); +} +EXPORT_SYMBOL_GPL(rust_helper_mapping_set_unevictable); + +void rust_helper_mapping_set_gfp_mask(struct address_space *mapping, gfp_t mask) +{ + mapping_set_gfp_mask(mapping, mask); +} +EXPORT_SYMBOL_GPL(rust_helper_mapping_set_gfp_mask); + +const gfp_t RUST_HELPER_GFP_HIGHUSER = GFP_HIGHUSER; +EXPORT_SYMBOL_GPL(RUST_HELPER_GFP_HIGHUSER); + +#if !defined(CONFIG_ARM) +// See https://github.com/rust-lang/rust-bindgen/issues/1671 +static_assert(__builtin_types_compatible_p(size_t, uintptr_t), + "size_t must match uintptr_t, what architecture is this??"); +#endif diff --git a/rust/kernel/chrdev.rs b/rust/kernel/chrdev.rs index 631405920c29c7..41b620f6b85cc4 100644 --- a/rust/kernel/chrdev.rs +++ b/rust/kernel/chrdev.rs @@ -17,6 +17,7 @@ use crate::bindings; use crate::c_types; use crate::error::{Error, Result}; use crate::file_operations; +use crate::fs::BuildVtable; use crate::str::CStr; /// Character device. @@ -168,7 +169,7 @@ impl Registration<{ N }> { // SAFETY: The adapter doesn't retrieve any state yet, so it's compatible with any // registration. - let fops = unsafe { file_operations::FileOperationsVtable::::build() }; + let fops = unsafe { file_operations::FileOperationsVtable::::build_vtable() }; let mut cdev = Cdev::alloc(fops, &this.this_module)?; cdev.add(inner.dev + inner.used as bindings::dev_t, 1)?; inner.cdevs[inner.used].replace(cdev); diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 0b6e60adc9210c..fb2f79a37f0d1e 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -4,11 +4,10 @@ //! //! C header: [`include/uapi/asm-generic/errno-base.h`](../../../include/uapi/asm-generic/errno-base.h) -use crate::str::CStr; -use crate::{bindings, c_types}; +use crate::{bindings, c_types, declare_constant_from_bindings, macros::DO_NEGATE, str::CStr}; use alloc::{alloc::AllocError, collections::TryReserveError}; -use core::convert::From; -use core::fmt; +use core::convert::{From, TryInto}; +use core::fmt::{self, Debug}; use core::num::TryFromIntError; use core::str::{self, Utf8Error}; @@ -24,41 +23,19 @@ use core::str::{self, Utf8Error}; pub struct Error(c_types::c_int); impl Error { - /// Invalid argument. - pub const EINVAL: Self = Error(-(bindings::EINVAL as i32)); - - /// Out of memory. - pub const ENOMEM: Self = Error(-(bindings::ENOMEM as i32)); - - /// Bad address. - pub const EFAULT: Self = Error(-(bindings::EFAULT as i32)); - - /// Illegal seek. - pub const ESPIPE: Self = Error(-(bindings::ESPIPE as i32)); - - /// Try again. - pub const EAGAIN: Self = Error(-(bindings::EAGAIN as i32)); - - /// Device or resource busy. - pub const EBUSY: Self = Error(-(bindings::EBUSY as i32)); - - /// Restart the system call. - pub const ERESTARTSYS: Self = Error(-(bindings::ERESTARTSYS as i32)); - - /// Operation not permitted. - pub const EPERM: Self = Error(-(bindings::EPERM as i32)); - - /// No such process. - pub const ESRCH: Self = Error(-(bindings::ESRCH as i32)); - - /// No such file or directory. - pub const ENOENT: Self = Error(-(bindings::ENOENT as i32)); - - /// Interrupted system call. - pub const EINTR: Self = Error(-(bindings::EINTR as i32)); - - /// Bad file number. - pub const EBADF: Self = Error(-(bindings::EBADF as i32)); + pub fn parse_int(value: T) -> Result + where + T: TryInto + Copy, + >::Error: Debug, + { + match value + .try_into() + .expect("Couldn't convert value given to Error::parse_int into c_int") + { + e if e.is_negative() => Err(Error::from_kernel_errno(e)), + _ => Ok(value), + } + } /// Creates an [`Error`] from a kernel error code. /// @@ -94,6 +71,157 @@ impl Error { pub fn to_kernel_errno(self) -> c_types::c_int { self.0 } + + pub fn as_err_ptr(&self) -> *mut T { + self.0 as *mut _ + } +} + +macro_rules! declare_error { + ($name:ident, $doc:expr) => { + declare_constant_from_bindings!($name, $doc, i32, DO_NEGATE); + }; +} + +#[macro_export] +macro_rules! ret_err_ptr { + ($ex:expr) => { + match $ex { + Ok(val) => val, + Err(err) => return err.as_err_ptr(), + } + }; +} + +#[rustfmt::skip] +impl Error { + // See `man 3 errno`. + declare_error!(E2BIG, "Argument list too long (POSIX.1-2001)."); + declare_error!(EACCES, "Permission denied (POSIX.1-2001)."); + declare_error!(EADDRINUSE, "Address already in use (POSIX.1-2001)."); + declare_error!(EADDRNOTAVAIL, "Address not available (POSIX.1-2001)."); + declare_error!(EAFNOSUPPORT, "Address family not supported (POSIX.1-2001)."); + declare_error!(EAGAIN, "Resource temporarily unavailable (may be the same value as EWOULDBLOCK) (POSIX.1-2001)."); + declare_error!(EALREADY, "Connection already in progress (POSIX.1-2001)."); + declare_error!(EBADE, "Invalid exchange."); + declare_error!(EBADF, "Bad file descriptor (POSIX.1-2001)."); + declare_error!(EBADFD, "File descriptor in bad state."); + declare_error!(EBADMSG, "Bad message (POSIX.1-2001)."); + declare_error!(EBADR, "Invalid request descriptor."); + declare_error!(EBADRQC, "Invalid request code."); + declare_error!(EBADSLT, "Invalid slot."); + declare_error!(EBUSY, "Device or resource busy (POSIX.1-2001)."); + declare_error!(ECANCELED, "Operation canceled (POSIX.1-2001)."); + declare_error!(ECHILD, "No child processes (POSIX.1-2001)."); + declare_error!(ECHRNG, "Channel number out of range."); + declare_error!(ECOMM, "Communication error on send."); + declare_error!(ECONNABORTED, "Connection aborted (POSIX.1-2001)."); + declare_error!(ECONNREFUSED, "Connection refused (POSIX.1-2001)."); + declare_error!(ECONNRESET, "Connection reset (POSIX.1-2001)."); + declare_error!(EDEADLK, "Resource deadlock avoided (POSIX.1-2001)."); + declare_error!(EDEADLOCK, "On most architectures, a synonym for EDEADLK. On some architectures (e.g., Linux MIPS, PowerPC, SPARC), it is a separate error code \"File locking dead-lock error\"."); + declare_error!(EDESTADDRREQ, "Destination address required (POSIX.1-2001)."); + declare_error!(EDOM, "Mathematics argument out of domain of function (POSIX.1, C99)."); + declare_error!(EDQUOT, "Disk quota exceeded (POSIX.1-2001)."); + declare_error!(EEXIST, "File exists (POSIX.1-2001)."); + declare_error!(EFAULT, "Bad address (POSIX.1-2001)."); + declare_error!(EFBIG, "File too large (POSIX.1-2001)."); + declare_error!(EHOSTDOWN, "Host is down."); + declare_error!(EHOSTUNREACH, "Host is unreachable (POSIX.1-2001)."); + declare_error!(EHWPOISON, "Memory page has hardware error."); + declare_error!(EIDRM, "Identifier removed (POSIX.1-2001)."); + declare_error!(EILSEQ, "Invalid or incomplete multibyte or wide character (POSIX.1, C99)."); + declare_error!(EINPROGRESS, "Operation in progress (POSIX.1-2001)."); + declare_error!(EINTR, "Interrupted function call (POSIX.1-2001); see signal(7)."); + declare_error!(EINVAL, "Invalid argument (POSIX.1-2001)."); + declare_error!(EIO, "Input/output error (POSIX.1-2001)."); + declare_error!(EISCONN, "Socket is connected (POSIX.1-2001)."); + declare_error!(EISDIR, "Is a directory (POSIX.1-2001)."); + declare_error!(EISNAM, "Is a named type file."); + declare_error!(EKEYEXPIRED, "Key has expired."); + declare_error!(EKEYREJECTED, "Key was rejected by service."); + declare_error!(EKEYREVOKED, "Key has been revoked."); + declare_error!(EL2HLT, "Level 2 halted."); + declare_error!(EL2NSYNC, "Level 2 not synchronized."); + declare_error!(EL3HLT, "Level 3 halted."); + declare_error!(EL3RST, "Level 3 reset."); + declare_error!(ELIBACC, "Cannot access a needed shared library."); + declare_error!(ELIBBAD, "Accessing a corrupted shared library."); + declare_error!(ELIBMAX, "Attempting to link in too many shared libraries."); + declare_error!(ELIBSCN, ".lib section in a.out corrupted"); + declare_error!(ELIBEXEC, "Cannot exec a shared library directly."); + declare_error!(ELNRNG, "Link number out of range."); + declare_error!(ELOOP, "Too many levels of symbolic links (POSIX.1-2001)."); + declare_error!(EMEDIUMTYPE, "Wrong medium type."); + declare_error!(EMFILE, "Too many open files (POSIX.1-2001). Commonly caused by exceeding the RLIMIT_NOFILE resource limit described in getrlimit(2). Can also be caused by exceeding the limit specified in /proc/sys/fs/nr_open."); + declare_error!(EMLINK, "Too many links (POSIX.1-2001)."); + declare_error!(EMSGSIZE, "Message too long (POSIX.1-2001)."); + declare_error!(EMULTIHOP, "Multihop attempted (POSIX.1-2001)."); + declare_error!(ENAMETOOLONG, "Filename too long (POSIX.1-2001)."); + declare_error!(ENETDOWN, "Network is down (POSIX.1-2001)."); + declare_error!(ENETRESET, "Connection aborted by network (POSIX.1-2001)."); + declare_error!(ENETUNREACH, "Network unreachable (POSIX.1-2001)."); + declare_error!(ENFILE, "Too many open files in system (POSIX.1-2001). On Linux, this is probably a result of encountering the /proc/sys/fs/file-max limit (see proc(5))."); + declare_error!(ENOANO, "No anode."); + declare_error!(ENOBUFS, "No buffer space available (POSIX.1 (XSI STREAMS option))."); + declare_error!(ENODATA, "No message is available on the STREAM head read queue (POSIX.1-2001)."); + declare_error!(ENODEV, "No such device (POSIX.1-2001)."); + declare_error!(ENOENT, "No such file or directory (POSIX.1-2001)."); + declare_error!(ENOEXEC, "Exec format error (POSIX.1-2001)."); + declare_error!(ENOKEY, "Required key not available."); + declare_error!(ENOLCK, "No locks available (POSIX.1-2001)."); + declare_error!(ENOLINK, "Link has been severed (POSIX.1-2001)."); + declare_error!(ENOMEDIUM, "No medium found."); + declare_error!(ENOMEM, "Not enough space/cannot allocate memory (POSIX.1-2001)."); + declare_error!(ENOMSG, "No message of the desired type (POSIX.1-2001)."); + declare_error!(ENONET, "Machine is not on the network."); + declare_error!(ENOPKG, "Package not installed."); + declare_error!(ENOPROTOOPT, "Protocol not available (POSIX.1-2001)."); + declare_error!(ENOSPC, "No space left on device (POSIX.1-2001)."); + declare_error!(ENOSR, "No STREAM resources (POSIX.1 (XSI STREAMS option))."); + declare_error!(ENOSTR, "Not a STREAM (POSIX.1 (XSI STREAMS option))."); + declare_error!(ENOSYS, "Function not implemented (POSIX.1-2001)."); + declare_error!(ENOTBLK, "Block device required."); + declare_error!(ENOTCONN, "The socket is not connected (POSIX.1-2001)."); + declare_error!(ENOTDIR, "Not a directory (POSIX.1-2001)."); + declare_error!(ENOTEMPTY, "Directory not empty (POSIX.1-2001)."); + declare_error!(ENOTRECOVERABLE, "State not recoverable (POSIX.1-2008)."); + declare_error!(ENOTSOCK, "Not a socket (POSIX.1-2001)."); + declare_error!(ENOTTY, "Inappropriate I/O control operation (POSIX.1-2001)."); + declare_error!(ENOTUNIQ, "Name not unique on network."); + declare_error!(ENXIO, "No such device or address (POSIX.1-2001)."); + declare_error!(EOPNOTSUPP, "Operation not supported on socket (POSIX.1-2001)."); + declare_error!(EOVERFLOW, "Value too large to be stored in data type (POSIX.1-2001)."); + declare_error!(EOWNERDEAD, "Owner died (POSIX.1-2008)."); + declare_error!(EPERM, "Operation not permitted (POSIX.1-2001)."); + declare_error!(EPFNOSUPPORT, "Protocol family not supported."); + declare_error!(EPIPE, "Broken pipe (POSIX.1-2001)."); + declare_error!(EPROTO, "Protocol error (POSIX.1-2001)."); + declare_error!(EPROTONOSUPPORT, "Protocol not supported (POSIX.1-2001)."); + declare_error!(EPROTOTYPE, "Protocol wrong type for socket (POSIX.1-2001)."); + declare_error!(ERANGE, "Result too large (POSIX.1, C99)."); + declare_error!(EREMCHG, "Remote address changed."); + declare_error!(EREMOTE, "Object is remote."); + declare_error!(EREMOTEIO, "Remote I/O error."); + declare_error!(ERESTART, "Interrupted system call should be restarted."); + declare_error!(ERFKILL, "Operation not possible due to RF-kill."); + declare_error!(EROFS, "Read-only filesystem (POSIX.1-2001)."); + declare_error!(ESHUTDOWN, "Cannot send after transport endpoint shutdown."); + declare_error!(ESPIPE, "Invalid seek (POSIX.1-2001)."); + declare_error!(ESOCKTNOSUPPORT, "Socket type not supported."); + declare_error!(ESRCH, "No such process (POSIX.1-2001)."); + declare_error!(ESTALE, "Stale file handle (POSIX.1-2001)."); + declare_error!(ESTRPIPE, "Streams pipe error."); + declare_error!(ETIME, "Timer expired (POSIX.1 (XSI STREAMS option))."); + declare_error!(ETIMEDOUT, "Connection timed out (POSIX.1-2001)."); + declare_error!(ETOOMANYREFS, "Too many references: cannot splice."); + declare_error!(ETXTBSY, "Text file busy (POSIX.1-2001)."); + declare_error!(EUCLEAN, "Structure needs cleaning."); + declare_error!(EUNATCH, "Protocol driver not attached."); + declare_error!(EUSERS, "Too many users."); + declare_error!(EWOULDBLOCK, "Operation would block (may be same value as EAGAIN) (POSIX.1-2001)."); + declare_error!(EXDEV, "Improper link (POSIX.1-2001)."); + declare_error!(EXFULL, "Exchange full."); } impl fmt::Debug for Error { @@ -207,6 +335,7 @@ where #[macro_export] macro_rules! from_kernel_result { ($($tt:tt)*) => {{ + #[allow(clippy::redundant_closure_call)] $crate::error::from_kernel_result_helper((|| { $($tt)* })()) diff --git a/rust/kernel/file.rs b/rust/kernel/file.rs index 262a856fc4910f..b8a815d6a88365 100644 --- a/rust/kernel/file.rs +++ b/rust/kernel/file.rs @@ -5,7 +5,7 @@ //! C headers: [`include/linux/fs.h`](../../../../include/linux/fs.h) and //! [`include/linux/file.h`](../../../../include/linux/file.h) -use crate::{bindings, error::Error, Result}; +use crate::{bindings, error::Error, fs::inode::Inode, print::ExpectK, Result}; use core::{mem::ManuallyDrop, ops::Deref}; /// Wraps the kernel's `struct file`. @@ -44,6 +44,18 @@ impl File { // SAFETY: `File::ptr` is guaranteed to be valid by the type invariants. unsafe { (*self.ptr).f_flags & bindings::O_NONBLOCK == 0 } } + + pub fn inode(&self) -> &mut Inode { + unsafe { + self.ptr + .as_mut() + .expectk("File::ptr was null") + .f_inode + .as_mut() + .expectk("File had NULL inode") + .as_mut() + } + } } impl Drop for File { diff --git a/rust/kernel/file_operations.rs b/rust/kernel/file_operations.rs index 7891a61695089f..2571dd8b0caf01 100644 --- a/rust/kernel/file_operations.rs +++ b/rust/kernel/file_operations.rs @@ -14,6 +14,7 @@ use crate::{ error::{Error, Result}, file::{File, FileRef}, from_kernel_result, + fs::{kiocb::Kiocb, BuildVtable}, io_buffer::{IoBufferReader, IoBufferWriter}, iov_iter::IovIter, sync::CondVar, @@ -79,6 +80,16 @@ pub enum SeekFrom { Current(i64), } +impl SeekFrom { + pub fn into_pos_and_whence(self) -> (i64, u32) { + match self { + SeekFrom::Start(off) => (off as _, bindings::SEEK_SET), + SeekFrom::End(off) => (off, bindings::SEEK_END), + SeekFrom::Current(off) => (off, bindings::SEEK_CUR), + } + } +} + unsafe extern "C" fn open_callback>( inode: *mut bindings::inode, file: *mut bindings::file, @@ -113,13 +124,13 @@ unsafe extern "C" fn read_iter_callback( raw_iter: *mut bindings::iov_iter, ) -> isize { from_kernel_result! { - let mut iter = unsafe { IovIter::from_ptr(raw_iter) }; - let file = unsafe { (*iocb).ki_filp }; - let offset = unsafe { (*iocb).ki_pos }; - let f = unsafe { &*((*file).private_data as *const T) }; - let read = f.read(unsafe { &FileRef::from_ptr(file) }, &mut iter, offset.try_into()?)?; - unsafe { (*iocb).ki_pos += bindings::loff_t::try_from(read).unwrap() }; - Ok(read as _) + unsafe { + let mut iter = IovIter::from_ptr(raw_iter); + let file = (*iocb).ki_filp; + let f = &*((*file).private_data as *const T); + let read = f.read_iter(iocb.as_mut().unwrap().as_mut(), &mut iter)?; + Ok(read as _) + } } } @@ -145,13 +156,13 @@ unsafe extern "C" fn write_iter_callback( raw_iter: *mut bindings::iov_iter, ) -> isize { from_kernel_result! { - let mut iter = unsafe { IovIter::from_ptr(raw_iter) }; - let file = unsafe { (*iocb).ki_filp }; - let offset = unsafe { (*iocb).ki_pos }; - let f = unsafe { &*((*file).private_data as *const T) }; - let written = f.write(unsafe { &FileRef::from_ptr(file) }, &mut iter, offset.try_into()?)?; - unsafe { (*iocb).ki_pos += bindings::loff_t::try_from(written).unwrap() }; - Ok(written as _) + unsafe { + let mut iter = IovIter::from_ptr(raw_iter); + let file = (*iocb).ki_filp; + let f = &*((*file).private_data as *const T); + let written = f.write_iter(iocb.as_mut().unwrap().as_mut(), &mut iter)?; + Ok(written as _) + } } } @@ -239,6 +250,21 @@ unsafe extern "C" fn fsync_callback( } } +unsafe extern "C" fn get_unmapped_area_callback( + file: *mut bindings::file, + addr: c_types::c_ulong, + len: c_types::c_ulong, + pgoff: c_types::c_ulong, + flags: c_types::c_ulong, +) -> c_types::c_ulong { + let ret: i64 = from_kernel_result! { + let f = unsafe { &*((*file).private_data as *const T) }; + let res = f.get_unmapped_area(unsafe { &FileRef::from_ptr(file) }, addr, len, pgoff, flags)?; + Ok(res as _) + }; + ret as _ +} + unsafe extern "C" fn poll_callback( file: *mut bindings::file, wait: *mut bindings::poll_table_struct, @@ -252,6 +278,38 @@ unsafe extern "C" fn poll_callback( } } +unsafe extern "C" fn splice_read_callback( + file: *mut bindings::file, + ppos: *mut bindings::loff_t, + pipe: *mut bindings::pipe_inode_info, + len: c_types::c_size_t, + flags: c_types::c_uint, +) -> c_types::c_ssize_t { + from_kernel_result! { + unsafe { + let f = &*((*file).private_data as *const T); + let ret = f.splice_read(&FileRef::from_ptr(file), ppos, &mut *pipe, len, flags)?; + Ok(ret as _) + } + } +} + +unsafe extern "C" fn splice_write_callback( + pipe: *mut bindings::pipe_inode_info, + file: *mut bindings::file, + ppos: *mut bindings::loff_t, + len: c_types::c_size_t, + flags: c_types::c_uint, +) -> c_types::c_ssize_t { + unsafe { + from_kernel_result! { + let f = &*((*file).private_data as *const T); + let ret = f.splice_write(&mut *pipe, &FileRef::from_ptr(file), ppos, len, flags)?; + Ok(ret as _) + } + } +} + pub(crate) struct FileOperationsVtable(marker::PhantomData, marker::PhantomData); impl> FileOperationsVtable { @@ -291,7 +349,11 @@ impl> FileOperationsVtable { } else { None }, - get_unmapped_area: None, + get_unmapped_area: if T::TO_USE.get_unmapped_area { + Some(get_unmapped_area_callback::) + } else { + None + }, iterate: None, iterate_shared: None, iopoll: None, @@ -317,8 +379,16 @@ impl> FileOperationsVtable { sendpage: None, setlease: None, show_fdinfo: None, - splice_read: None, - splice_write: None, + splice_read: if T::TO_USE.splice_read { + Some(splice_read_callback::) + } else { + None + }, + splice_write: if T::TO_USE.splice_write { + Some(splice_write_callback::) + } else { + None + }, unlocked_ioctl: if T::TO_USE.ioctl { Some(unlocked_ioctl_callback::) } else { @@ -330,13 +400,12 @@ impl> FileOperationsVtable { None }, }; +} - /// Builds an instance of [`struct file_operations`]. - /// - /// # Safety - /// - /// The caller must ensure that the adapter is compatible with the way the device is registered. - pub(crate) const unsafe fn build() -> &'static bindings::file_operations { +impl> BuildVtable + for FileOperationsVtable +{ + fn build_vtable() -> &'static bindings::file_operations { &Self::VTABLE } } @@ -367,11 +436,20 @@ pub struct ToUse { /// The `fsync` field of [`struct file_operations`]. pub fsync: bool, + /// The `get_unmapped_area` field of [`struct file_operations`]. + pub get_unmapped_area: bool, + /// The `mmap` field of [`struct file_operations`]. pub mmap: bool, /// The `poll` field of [`struct file_operations`]. pub poll: bool, + + /// The `splice_read` field of [`struct file_operations`]. + pub splice_read: bool, + + /// The `splice_write` field of [`struct file_operations`]. + pub splice_write: bool, } /// A constant version where all values are to set to `false`, that is, all supported fields will @@ -385,8 +463,11 @@ pub const USE_NONE: ToUse = ToUse { ioctl: false, compat_ioctl: false, fsync: false, + get_unmapped_area: false, mmap: false, poll: false, + splice_read: false, + splice_write: false, }; /// Defines the [`FileOperations::TO_USE`] field based on a list of fields to be populated. @@ -396,6 +477,7 @@ macro_rules! declare_file_operations { const TO_USE: $crate::file_operations::ToUse = $crate::file_operations::USE_NONE; }; ($($i:ident),+) => { + #[allow(clippy::needless_update)] const TO_USE: kernel::file_operations::ToUse = $crate::file_operations::ToUse { $($i: true),+ , @@ -505,6 +587,18 @@ pub trait FileOpenAdapter { -> *const Self::Arg; } +pub struct NopFileOpenAdapter; +impl FileOpenAdapter for NopFileOpenAdapter { + type Arg = (); + + unsafe fn convert( + _inode: *mut bindings::inode, + _file: *mut bindings::file, + ) -> *const Self::Arg { + &() + } +} + /// Trait for implementers of kernel files. /// /// In addition to the methods in [`FileOperations`], implementers must also provide @@ -524,6 +618,12 @@ impl> + Default> FileOpener<()> for T { } } +impl> BuildVtable for T { + fn build_vtable() -> &'static bindings::file_operations { + FileOperationsVtable::::build_vtable() + } +} + /// Corresponds to the kernel's `struct file_operations`. /// /// You implement this trait whenever you would create a `struct file_operations`. @@ -548,11 +648,23 @@ pub trait FileOperations: Send + Sync + Sized { /// Reads data from this file to the caller's buffer. /// - /// Corresponds to the `read` and `read_iter` function pointers in `struct file_operations`. + /// Corresponds to the `read` function pointer in `struct file_operations`. fn read(&self, _file: &File, _data: &mut T, _offset: u64) -> Result { Err(Error::EINVAL) } + /// Reads data from this file to the caller's buffer. + /// + /// Corresponds to the `read_iter` function pointer in `struct file_operations`. + fn read_iter(&self, iocb: &mut Kiocb, iter: &mut IovIter) -> Result { + let file = iocb.get_file(); + let offset = iocb.get_offset(); + let read = self.read(&file, iter, offset)?; + let offset = iocb.get_offset(); + iocb.set_offset(offset + read as u64); + Ok(read) + } + /// Writes data from the caller's buffer to this file. /// /// Corresponds to the `write` and `write_iter` function pointers in `struct file_operations`. @@ -560,6 +672,18 @@ pub trait FileOperations: Send + Sync + Sized { Err(Error::EINVAL) } + /// Writes data from the caller's buffer to this file. + /// + /// Corresponds to the `write_iter` function pointer in `struct file_operations`. + fn write_iter(&self, iocb: &mut Kiocb, iter: &mut IovIter) -> Result { + let file = iocb.get_file(); + let offset = iocb.get_offset(); + let written = self.write(&file, iter, offset)?; + let offset = iocb.get_offset(); + iocb.set_offset(offset + written as u64); + Ok(written) + } + /// Changes the position of the file. /// /// Corresponds to the `llseek` function pointer in `struct file_operations`. @@ -588,6 +712,20 @@ pub trait FileOperations: Send + Sync + Sized { Err(Error::EINVAL) } + /// Unmapped area. + /// + /// Corresponds to the `get_unmapped_area` function pointer in `struct file_operations`. + fn get_unmapped_area( + &self, + _file: &File, + _addr: u64, + _len: u64, + _pgoff: u64, + _flags: u64, + ) -> Result { + Err(Error::EINVAL) + } + /// Maps areas of the caller's virtual memory with device/file memory. /// /// Corresponds to the `mmap` function pointer in `struct file_operations`. @@ -603,4 +741,32 @@ pub trait FileOperations: Send + Sync + Sized { fn poll(&self, _file: &File, _table: &PollTable) -> Result { Ok(bindings::POLLIN | bindings::POLLOUT | bindings::POLLRDNORM | bindings::POLLWRNORM) } + + /// Splice data from file to a pipe + /// + /// Corresponds to the `splice_read` function pointer in `struct file_operations`. + fn splice_read( + &self, + _file: &File, + _pos: *mut i64, + _pipe: &mut bindings::pipe_inode_info, + _len: usize, + _flags: u32, + ) -> Result { + Err(Error::EINVAL) + } + + /// Splice data from pipe to a file + /// + /// Corresponds to the `splice_write` function pointer in `struct file_operations`. + fn splice_write( + &self, + _pipe: &mut bindings::pipe_inode_info, + _file: &File, + _pos: *mut i64, + _len: usize, + _flags: u32, + ) -> Result { + Err(Error::EINVAL) + } } diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs new file mode 100644 index 00000000000000..e9d8dd550cae6d --- /dev/null +++ b/rust/kernel/fs.rs @@ -0,0 +1,126 @@ +pub mod address_space; +pub mod address_space_operations; +pub mod dentry; +pub mod inode; +pub mod inode_operations; +pub mod kiocb; +pub mod libfs_functions; +pub mod super_block; +pub mod super_operations; + +use core::ptr; + +use crate::{ + bindings, c_types::*, error::from_kernel_err_ptr, fs::super_block::SuperBlock, print::ExpectK, + ret_err_ptr, str::CStr, types::FileSystemFlags, Result, +}; + +pub trait BuildVtable { + fn build_vtable() -> &'static T; +} +#[macro_export] +macro_rules! declare_c_vtable { + ($O:ident, $T:ty, $val:expr $(,)?) => { + pub struct $O; + impl $crate::fs::BuildVtable<$T> for $O { + fn build_vtable() -> &'static $T { + unsafe { &($val) } + } + } + }; +} + +pub type FileSystemType = bindings::file_system_type; + +pub trait FileSystemBase { + type MountOptions = c_void; + + const NAME: &'static CStr; + const FS_FLAGS: FileSystemFlags; + const OWNER: *mut bindings::module = ptr::null_mut(); + + fn mount( + fs_type: &'_ mut FileSystemType, + flags: c_int, + device_name: &CStr, + data: Option<&mut Self::MountOptions>, + ) -> Result<*mut bindings::dentry>; + + fn kill_super(sb: &mut SuperBlock); + + fn fill_super( + sb: &mut SuperBlock, + data: Option<&mut Self::MountOptions>, + silent: c_int, + ) -> Result; +} + +pub trait DeclaredFileSystemType: FileSystemBase { + fn file_system_type() -> *mut bindings::file_system_type; +} + +#[macro_export] +macro_rules! declare_fs_type { + ($T:ty, $S:ident) => { + static mut $S: $crate::bindings::file_system_type = $crate::bindings::file_system_type { + name: <$T as $crate::fs::FileSystemBase>::NAME.as_char_ptr() as *const _, + fs_flags: <$T as $crate::fs::FileSystemBase>::FS_FLAGS.into_int(), + owner: <$T as $crate::fs::FileSystemBase>::OWNER, + mount: Some($crate::fs::mount_callback::<$T>), + kill_sb: Some($crate::fs::kill_superblock_callback::<$T>), + ..$crate::fs::DEFAULT_FS_TYPE + }; + impl $crate::fs::DeclaredFileSystemType for $T { + fn file_system_type() -> *mut $crate::bindings::file_system_type { + unsafe { &mut $S as *mut _ } + } + } + }; +} + +pub unsafe extern "C" fn mount_callback( + fs_type: *mut bindings::file_system_type, + flags: c_int, + device_name: *const c_char, + data: *mut c_void, +) -> *mut bindings::dentry { + unsafe { + let fs_type = &mut *fs_type; + let device_name = CStr::from_char_ptr(device_name); + let data = (data as *mut T::MountOptions).as_mut(); + ret_err_ptr!(T::mount(fs_type, flags, device_name, data)) + } +} + +pub unsafe extern "C" fn kill_superblock_callback( + sb: *mut bindings::super_block, +) { + unsafe { + let sb = sb + .as_mut() + .expectk("kill_superblock got NULL super block") + .as_mut(); + T::kill_super(sb); + } +} + +pub const DEFAULT_FS_TYPE: bindings::file_system_type = bindings::file_system_type { + name: ptr::null(), + fs_flags: 0, + init_fs_context: None, + parameters: ptr::null(), + mount: None, + kill_sb: None, + owner: ptr::null_mut(), + next: ptr::null_mut(), + fs_supers: bindings::hlist_head { + first: ptr::null_mut(), + }, + s_lock_key: bindings::lock_class_key {}, + s_umount_key: bindings::lock_class_key {}, + s_vfs_rename_key: bindings::lock_class_key {}, + s_writers_key: [bindings::lock_class_key {}; 3], + i_lock_key: bindings::lock_class_key {}, + i_mutex_key: bindings::lock_class_key {}, + i_mutex_dir_key: bindings::lock_class_key {}, +}; diff --git a/rust/kernel/fs/address_space.rs b/rust/kernel/fs/address_space.rs new file mode 100644 index 00000000000000..aead60446ee7ce --- /dev/null +++ b/rust/kernel/fs/address_space.rs @@ -0,0 +1,47 @@ +use crate::{bindings, fs::BuildVtable, Result}; +use alloc::boxed::Box; +use core::{ + mem, + ops::{Deref, DerefMut}, +}; + +#[repr(transparent)] +pub struct AddressSpace(bindings::address_space); + +impl Deref for AddressSpace { + type Target = bindings::address_space; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for AddressSpace { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl AsRef for bindings::address_space { + fn as_ref(&self) -> &AddressSpace { + unsafe { mem::transmute(self) } + } +} +impl AsMut for bindings::address_space { + fn as_mut(&mut self) -> &mut AddressSpace { + unsafe { mem::transmute(self) } + } +} + +impl AddressSpace { + pub fn as_ptr_mut(&mut self) -> *mut bindings::address_space { + self.deref_mut() as *mut _ + } + + pub fn set_address_space_operations>( + &mut self, + ops: Ops, + ) -> Result { + self.a_ops = Ops::build_vtable(); + self.private_data = Box::into_raw(Box::try_new(ops)?).cast(); + Ok(()) + } +} diff --git a/rust/kernel/fs/address_space_operations.rs b/rust/kernel/fs/address_space_operations.rs new file mode 100644 index 00000000000000..79809e136c6d39 --- /dev/null +++ b/rust/kernel/fs/address_space_operations.rs @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! AddressSpace operations. +//! +//! C header: [`include/linux/fs.h`](../../../../include/linux/fs.h) + +use core::marker; + +use crate::{ + bindings, c_types, + error::{Error, Result}, + file::{File, FileRef}, + from_kernel_result, + fs::{address_space::AddressSpace, BuildVtable}, + print::ExpectK, + types::Page, +}; + +/// Corresponds to the kernel's `struct adress_space_operations`. +/// +/// You implement this trait whenever you would create a `struct adress_space_operations`. +/// +/// File descriptors may be used from multiple threads/processes concurrently, so your type must be +/// [`Sync`]. It must also be [`Send`] because [`FileOperations::release`] will be called from the +/// thread that decrements that associated file's refcount to zero. +pub trait AddressSpaceOperations: Send + Sync + Sized + Default { + /// The methods to use to populate [`struct adress_space_operations`]. + const TO_USE: ToUse; + + fn readpage(&self, _file: &File, _page: &mut Page) -> Result { + Err(Error::EINVAL) + } + + fn write_begin( + &self, + _file: Option<&File>, + _mapping: &mut AddressSpace, + _pos: bindings::loff_t, + _len: u32, + _flags: u32, + _pagep: *mut *mut Page, + _fsdata: *mut *mut c_types::c_void, + ) -> Result { + Err(Error::EINVAL) + } + + fn write_end( + &self, + _file: Option<&File>, + _mapping: &mut AddressSpace, + _pos: bindings::loff_t, + _len: u32, + _copied: u32, + _page: &mut Page, + _fsdata: *mut c_types::c_void, + ) -> Result { + Err(Error::EINVAL) + } + + fn set_page_dirty(&self, _page: &mut Page) -> Result { + Err(Error::EINVAL) + } +} + +unsafe extern "C" fn readpage_callback( + file: *mut bindings::file, + page: *mut bindings::page, +) -> c_types::c_int { + unsafe { + let address_space = (*file).f_mapping; + let a_ops = &*((*address_space).private_data as *const T); + from_kernel_result! { + a_ops.readpage(&FileRef::from_ptr(file), &mut (*page)).map(|()| 0) + } + } +} + +unsafe extern "C" fn write_begin_callback( + file: *mut bindings::file, + mapping: *mut bindings::address_space, + pos: bindings::loff_t, + len: c_types::c_uint, + flags: c_types::c_uint, + pagep: *mut *mut Page, + fsdata: *mut *mut c_types::c_void, +) -> c_types::c_int { + unsafe { + let a_ops = &*((*mapping).private_data as *const T); + let file = (!file.is_null()).then(|| FileRef::from_ptr(file)); + from_kernel_result! { + a_ops.write_begin(file.as_deref(), mapping.as_mut().expectk("Got null mapping").as_mut(), pos, len, flags, pagep, fsdata).map(|()| 0) + } + } +} + +unsafe extern "C" fn write_end_callback( + file: *mut bindings::file, + mapping: *mut bindings::address_space, + pos: bindings::loff_t, + len: c_types::c_uint, + copied: c_types::c_uint, + page: *mut bindings::page, + fsdata: *mut c_types::c_void, +) -> c_types::c_int { + unsafe { + let a_ops = &*((*mapping).private_data as *const T); + let file = (!file.is_null()).then(|| FileRef::from_ptr(file)); + from_kernel_result! { + a_ops.write_end(file.as_deref(), mapping.as_mut().expectk("Got null mapping").as_mut(), pos, len, copied, &mut (*page), fsdata).map(|x| x as i32) + } + } +} + +unsafe extern "C" fn set_page_dirty_callback( + page: *mut bindings::page, +) -> c_types::c_int { + unsafe { + let address_space = (*page).__bindgen_anon_1.__bindgen_anon_1.mapping; + let a_ops = &*((*address_space).private_data as *const T); + from_kernel_result! { + a_ops.set_page_dirty(&mut (*page)).map(|x| x as i32) + } + } +} + +pub(crate) struct AddressSpaceOperationsVtable(marker::PhantomData); + +impl AddressSpaceOperationsVtable { + const VTABLE: bindings::address_space_operations = bindings::address_space_operations { + readpage: if T::TO_USE.readpage { + Some(readpage_callback::) + } else { + None + }, + write_begin: if T::TO_USE.write_begin { + Some(write_begin_callback::) + } else { + None + }, + write_end: if T::TO_USE.write_end { + Some(write_end_callback::) + } else { + None + }, + set_page_dirty: if T::TO_USE.set_page_dirty { + Some(set_page_dirty_callback::) + } else { + None + }, + writepage: None, + writepages: None, + readpages: None, + readahead: None, + bmap: None, + invalidatepage: None, + releasepage: None, + freepage: None, + direct_IO: None, + migratepage: None, + isolate_page: None, + putback_page: None, + launder_page: None, + is_partially_uptodate: None, + is_dirty_writeback: None, + error_remove_page: None, + swap_activate: None, + swap_deactivate: None, + }; +} + +impl BuildVtable + for AddressSpaceOperationsVtable +{ + fn build_vtable() -> &'static bindings::address_space_operations { + &Self::VTABLE + } +} + +impl BuildVtable for T { + fn build_vtable() -> &'static bindings::address_space_operations { + AddressSpaceOperationsVtable::::build_vtable() + } +} + +/// Represents which fields of [`struct address_space_operation`] should be populated with pointers. +pub struct ToUse { + /// The `readpage` field of [`struct address_space_operation`]. + pub readpage: bool, + /// The `write_begin` field of [`struct address_space_operation`]. + pub write_begin: bool, + /// The `write_begin` field of [`struct address_space_operation`]. + pub write_end: bool, + /// The `set_page_dirty` field of [`struct address_space_operation`]. + pub set_page_dirty: bool, +} + +/// A constant version where all values are to set to `false`, that is, all supported fields will +/// be set to null pointers. +pub const USE_NONE: ToUse = ToUse { + readpage: false, + write_begin: false, + write_end: false, + set_page_dirty: false, +}; + +#[macro_export] +macro_rules! declare_address_space_operations { + () => { + const TO_USE: $crate::fs::address_space_operations::ToUse = $crate::fs::address_space_operations::USE_NONE; + }; + ($($i:ident),+) => { + #[allow(clippy::needless_update)] + const TO_USE: kernel::fs::address_space_operations::ToUse = + $crate::fs::address_space_operations::ToUse { + $($i: true),+ , + ..$crate::fs::address_space_operations::USE_NONE + }; + }; +} diff --git a/rust/kernel/fs/dentry.rs b/rust/kernel/fs/dentry.rs new file mode 100644 index 00000000000000..ee8dc7486138c9 --- /dev/null +++ b/rust/kernel/fs/dentry.rs @@ -0,0 +1,85 @@ +use core::mem; +use core::ops::{Deref, DerefMut}; + +use crate::bindings; +use crate::fs::inode::Inode; + +extern "C" { + fn rust_helper_dget(dentry: *mut bindings::dentry); +} + +#[repr(transparent)] +pub struct Dentry(bindings::dentry); + +impl Deref for Dentry { + type Target = bindings::dentry; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Dentry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl AsRef for bindings::dentry { + fn as_ref(&self) -> &Dentry { + unsafe { mem::transmute(self) } + } +} +impl AsMut for bindings::dentry { + fn as_mut(&mut self) -> &mut Dentry { + unsafe { mem::transmute(self) } + } +} + +impl Dentry { + pub fn as_ptr_mut(&mut self) -> *mut bindings::dentry { + self.deref_mut() as *mut _ + } + + pub fn make_root(inode: &mut Inode) -> Option<&mut Self> { + unsafe { (bindings::d_make_root(inode.as_ptr_mut()) as *mut Self).as_mut() } + } + + pub fn lookup(&mut self, query: *const bindings::qstr) -> Option<&mut Self> { + unsafe { (bindings::d_lookup(self.as_ptr_mut(), query) as *mut Self).as_mut() } + } + + pub fn get(&mut self) { + // Note: while the original `dget` function also allows NULL as an argument, it doesn't do + // anything with it, so only wrapping the function for non-null pointers should be okay. + unsafe { rust_helper_dget(self.as_ptr_mut()) }; + } + + pub fn put(&mut self) { + unsafe { + bindings::dput(self.as_ptr_mut()); + } + } + + pub fn drop_dentry(&mut self) { + unsafe { + bindings::d_drop(self.as_ptr_mut()); + } + } + + pub fn delete_dentry(&mut self) { + unsafe { + bindings::d_delete(self.as_ptr_mut()); + } + } + + pub fn add(&mut self, inode: &mut Inode) { + unsafe { + bindings::d_add(self.as_ptr_mut(), inode.as_ptr_mut()); + } + } + + pub fn instantiate(&mut self, inode: &mut Inode) { + unsafe { + bindings::d_instantiate(self.as_ptr_mut(), inode.as_ptr_mut()); + } + } +} diff --git a/rust/kernel/fs/inode.rs b/rust/kernel/fs/inode.rs new file mode 100644 index 00000000000000..3198c133b63d2b --- /dev/null +++ b/rust/kernel/fs/inode.rs @@ -0,0 +1,172 @@ +use alloc::boxed::Box; +use core::{ + mem, + ops::{Deref, DerefMut}, + ptr, +}; + +use crate::{ + bindings, + fs::{address_space::AddressSpace, super_block::SuperBlock, BuildVtable}, + print::ExpectK, + types::{Dev, Mode}, +}; + +#[derive(PartialEq, Eq)] +pub enum UpdateATime { + Yes, + No, +} +#[derive(PartialEq, Eq)] +pub enum UpdateCTime { + Yes, + No, +} +#[derive(PartialEq, Eq)] +pub enum UpdateMTime { + Yes, + No, +} + +#[repr(transparent)] +pub struct Inode(bindings::inode); + +impl Inode { + pub fn as_ptr_mut(&mut self) -> *mut bindings::inode { + self.deref_mut() as *mut _ + } + + pub fn new(sb: &mut SuperBlock) -> Option<&mut Self> { + unsafe { + bindings::new_inode(sb.as_ptr_mut()) + .as_mut() + .map(AsMut::as_mut) + } + } + + pub fn next_ino() -> u32 { + unsafe { bindings::get_next_ino() } // FIXME: why do the bindings not return c_int here? + } + + pub fn super_block<'a, 'b>(&'a self) -> &'b SuperBlock { + unsafe { + self.i_sb + .as_mut() + .expectk("Inode had NULL super block") + .as_mut() + } + } + + pub fn super_block_mut<'a, 'b>(&'a mut self) -> &'b mut SuperBlock { + unsafe { + self.i_sb + .as_mut() + .expectk("Inode had NULL super block") + .as_mut() + } + } + + pub fn mapping(&mut self) -> &AddressSpace { + unsafe { + self.i_mapping + .as_mut() + .expectk("Inode had NULL mapping") + .as_mut() + } + } + + pub fn mapping_mut(&mut self) -> &mut AddressSpace { + unsafe { + self.i_mapping + .as_mut() + .expectk("Inode had NULL mapping") + .as_mut() + } + } + + pub fn init_owner( + &mut self, + ns: &mut bindings::user_namespace, + directory: Option<&mut Inode>, + mode: Mode, + ) { + unsafe { + bindings::inode_init_owner( + ns as *mut _, + self.as_ptr_mut(), + directory + .map(Inode::as_ptr_mut) + .unwrap_or_else(ptr::null_mut), + mode.as_int(), + ); + } + } + + pub fn update_acm_time(&mut self, a: UpdateATime, c: UpdateCTime, m: UpdateMTime) { + let time = unsafe { bindings::current_time(self.as_ptr_mut()) }; + if a == UpdateATime::Yes { + self.i_atime = time; + } + if c == UpdateCTime::Yes { + self.i_ctime = time; + } + if m == UpdateMTime::Yes { + self.i_mtime = time; + } + } + + pub fn inc_nlink(&mut self) { + unsafe { + bindings::inc_nlink(self.as_ptr_mut()); + } + } + pub fn nohighmem(&mut self) { + unsafe { + bindings::inode_nohighmem(self.as_ptr_mut()); + } + } + pub fn init_special(&mut self, mode: Mode, device: Dev) { + unsafe { + bindings::init_special_inode(self.as_ptr_mut(), mode.as_int(), device); + } + } + pub fn put(&mut self) { + unsafe { + bindings::iput(self.as_ptr_mut()); + } + } + + pub fn set_file_operations>(&mut self) { + self.__bindgen_anon_3.i_fop = V::build_vtable(); + } + + pub fn set_inode_operations>(&mut self, ops: Ops) { + self.i_op = Ops::build_vtable(); + // TODO: Box::try_new + // => probably shouzldn't allocate in this method anyways, revisit signature + self.i_private = Box::into_raw(Box::new(ops)).cast(); + } +} + +impl Deref for Inode { + type Target = bindings::inode; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Inode { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl AsRef for bindings::inode { + fn as_ref(&self) -> &Inode { + unsafe { mem::transmute(self) } + } +} +impl AsMut for bindings::inode { + fn as_mut(&mut self) -> &mut Inode { + unsafe { mem::transmute(self) } + } +} diff --git a/rust/kernel/fs/inode_operations.rs b/rust/kernel/fs/inode_operations.rs new file mode 100644 index 00000000000000..8217da23ffd1c4 --- /dev/null +++ b/rust/kernel/fs/inode_operations.rs @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Inode operations. +//! +//! C header: [`include/linux/fs.h`](../../../../include/linux/fs.h) + +use core::marker; + +use crate::{ + bindings, c_types, + error::{Error, Result}, + from_kernel_result, + fs::{dentry::Dentry, inode::Inode, BuildVtable}, + print::ExpectK, + str::CStr, + types::{Dev, Iattr, Kstat, Mode, ModeInt, Path, UserNamespace}, +}; + +/// Corresponds to the kernel's `struct inode_operations`. +/// +/// You implement this trait whenever you would create a `struct inode_operations`. +/// +/// File descriptors may be used from multiple threads/processes concurrently, so your type must be +/// [`Sync`]. It must also be [`Send`] because [`FileOperations::release`] will be called from the +/// thread that decrements that associated file's refcount to zero. +pub trait InodeOperations: Send + Sync + Sized + Default { + /// The methods to use to populate [`struct inode_operations`]. + const TO_USE: ToUse; + + fn getattr( + &self, + _mnt_userns: &mut UserNamespace, + _path: &Path, + _stat: &mut Kstat, + _request_mask: u32, + _query_flags: u32, + ) -> Result { + Err(Error::EINVAL) + } + + fn setattr( + &self, + _mnt_userns: &mut UserNamespace, + _dentry: &mut Dentry, + _iattr: &mut Iattr, + ) -> Result { + Err(Error::EINVAL) + } + + fn create( + &self, + _mnt_userns: &mut UserNamespace, + _dir: &mut Inode, + _dentry: &mut Dentry, + _mode: Mode, + _excl: bool, + ) -> Result { + Err(Error::EINVAL) + } + fn lookup( + &self, + _dir: &mut Inode, + _dentry: &mut Dentry, + _flags: c_types::c_uint, + ) -> Result<*mut Dentry> { + Err(Error::EINVAL) + } + fn link(&self, _old_dentry: &mut Dentry, _dir: &mut Inode, _dentry: &mut Dentry) -> Result { + Err(Error::EINVAL) + } + fn unlink(&self, _dir: &mut Inode, _dentry: &mut Dentry) -> Result { + Err(Error::EINVAL) + } + fn symlink( + &self, + _mnt_userns: &mut UserNamespace, + _dir: &mut Inode, + _dentry: &mut Dentry, + _symname: &'static CStr, + ) -> Result { + Err(Error::EINVAL) + } + fn mkdir( + &self, + _mnt_userns: &mut UserNamespace, + _dir: &mut Inode, + _dentry: &mut Dentry, + _mode: Mode, + ) -> Result { + Err(Error::EINVAL) + } + fn rmdir(&self, _dir: &mut Inode, _dentry: &mut Dentry) -> Result { + Err(Error::EINVAL) + } + fn mknod( + &self, + _mnt_userns: &mut UserNamespace, + _dir: &mut Inode, + _dentry: &mut Dentry, + _mode: Mode, + _dev: Dev, + ) -> Result { + Err(Error::EINVAL) + } + fn rename( + &self, + _mnt_userns: &mut UserNamespace, + _old_dir: &mut Inode, + _old_dentry: &mut Dentry, + _new_dir: &mut Inode, + _new_dentry: &mut Dentry, + _flags: c_types::c_uint, + ) -> Result { + Err(Error::EINVAL) + } +} + +unsafe extern "C" fn setattr_callback( + mnt_userns: *mut bindings::user_namespace, + dentry: *mut bindings::dentry, + iattr: *mut bindings::iattr, +) -> c_types::c_int { + unsafe { + let dentry = dentry.as_mut().expectk("setattr got null dentry").as_mut(); + let inode = dentry.d_inode; // use d_inode method instead? + let i_ops = &*((*inode).i_private as *const T); + from_kernel_result! { + i_ops.setattr(&mut (*mnt_userns), dentry, &mut (*iattr)).map(|()| 0) + } + } +} + +unsafe extern "C" fn getattr_callback( + mnt_userns: *mut bindings::user_namespace, + path: *const bindings::path, + stat: *mut bindings::kstat, + request_mask: u32, + query_flags: u32, +) -> c_types::c_int { + unsafe { + let dentry = (*path).dentry; + let inode = (*dentry).d_inode; // use d_inode method instead? + let i_ops = &*((*inode).i_private as *const T); + from_kernel_result! { + i_ops.getattr(&mut (*mnt_userns), &(*path), &mut (*stat), request_mask, query_flags).map(|()| 0) + } + } +} + +unsafe extern "C" fn create_callback( + mnt_userns: *mut bindings::user_namespace, + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, + mode: ModeInt, + excl: bool, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("create got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("create got null dentry").as_mut(); + from_kernel_result! { + i_ops.create(&mut (*mnt_userns), dir, dentry, Mode::from_int(mode), excl).map(|()| 0) + } + } +} +unsafe extern "C" fn lookup_callback( + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, + flags: c_types::c_uint, +) -> *mut bindings::dentry { + unsafe { + let dir = dir.as_mut().expectk("lookup got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("lookup got null dentry").as_mut(); + ret_err_ptr!(i_ops.lookup(dir, dentry, flags).map(|p| p as *mut _)) + } +} +unsafe extern "C" fn link_callback( + old_dentry: *mut bindings::dentry, + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("link got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let old_dentry = old_dentry + .as_mut() + .expectk("link got null old_dentry") + .as_mut(); + let dentry = dentry.as_mut().expectk("link got null dentry").as_mut(); + from_kernel_result! { + i_ops.link(old_dentry, dir, dentry).map(|()| 0) + } + } +} +unsafe extern "C" fn unlink_callback( + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("unlink got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("unlink got null dentry").as_mut(); + from_kernel_result! { + i_ops.unlink(dir, dentry).map(|()| 0) + } + } +} +unsafe extern "C" fn symlink_callback( + mnt_userns: *mut bindings::user_namespace, + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, + symname: *const c_types::c_char, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("symlink got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("symlink got null dentry").as_mut(); + from_kernel_result! { + i_ops.symlink(&mut (*mnt_userns), dir, dentry, CStr::from_char_ptr(symname)).map(|()| 0) + } + } +} +unsafe extern "C" fn mkdir_callback( + mnt_userns: *mut bindings::user_namespace, + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, + mode: ModeInt, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("mkdir got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("mkdir got null dentry").as_mut(); + from_kernel_result! { + i_ops.mkdir(&mut (*mnt_userns), dir, dentry, Mode::from_int(mode)).map(|()| 0) // todo: mode_t is u32 but u16 in Mode? + } + } +} +unsafe extern "C" fn rmdir_callback( + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("rmdir got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("rmdir got null dentry").as_mut(); + from_kernel_result! { + i_ops.rmdir(dir, dentry).map(|()| 0) + } + } +} +unsafe extern "C" fn mknod_callback( + mnt_userns: *mut bindings::user_namespace, + dir: *mut bindings::inode, + dentry: *mut bindings::dentry, + mode: ModeInt, + dev: bindings::dev_t, +) -> c_types::c_int { + unsafe { + let dir = dir.as_mut().expectk("mknod got null dir").as_mut(); + let i_ops = &*(dir.i_private as *const T); + let dentry = dentry.as_mut().expectk("mknod got null dentry").as_mut(); + from_kernel_result! { + i_ops.mknod(&mut (*mnt_userns), dir, dentry, Mode::from_int(mode), dev).map(|()| 0) + } + } +} +unsafe extern "C" fn rename_callback( + mnt_userns: *mut bindings::user_namespace, + old_dir: *mut bindings::inode, + old_dentry: *mut bindings::dentry, + new_dir: *mut bindings::inode, + new_dentry: *mut bindings::dentry, + flags: c_types::c_uint, +) -> c_types::c_int { + unsafe { + let old_dir = old_dir.as_mut().expectk("rename got null dir").as_mut(); + let i_ops = &*(old_dir.i_private as *const T); + let old_dentry = old_dentry + .as_mut() + .expectk("rename got null dentry") + .as_mut(); + let new_dir = new_dir.as_mut().expectk("rename got null dir").as_mut(); + let new_dentry = new_dentry + .as_mut() + .expectk("rename got null dentry") + .as_mut(); + from_kernel_result! { + i_ops.rename(&mut (*mnt_userns), old_dir, old_dentry, new_dir, new_dentry, flags).map(|()| 0) + } + } +} + +pub(crate) struct InodeOperationsVtable(marker::PhantomData); + +impl InodeOperationsVtable { + const VTABLE: bindings::inode_operations = bindings::inode_operations { + getattr: if T::TO_USE.getattr { + Some(getattr_callback::) + } else { + None + }, + setattr: if T::TO_USE.setattr { + Some(setattr_callback::) + } else { + None + }, + lookup: if T::TO_USE.lookup { + Some(lookup_callback::) + } else { + None + }, + get_link: None, + permission: None, + get_acl: None, + readlink: None, + create: if T::TO_USE.create { + Some(create_callback::) + } else { + None + }, + link: if T::TO_USE.link { + Some(link_callback::) + } else { + None + }, + unlink: if T::TO_USE.unlink { + Some(unlink_callback::) + } else { + None + }, + symlink: if T::TO_USE.symlink { + Some(symlink_callback::) + } else { + None + }, + mkdir: if T::TO_USE.mkdir { + Some(mkdir_callback::) + } else { + None + }, + rmdir: if T::TO_USE.rmdir { + Some(rmdir_callback::) + } else { + None + }, + mknod: if T::TO_USE.mknod { + Some(mknod_callback::) + } else { + None + }, + rename: if T::TO_USE.rename { + Some(rename_callback::) + } else { + None + }, + listxattr: None, + fiemap: None, + update_time: None, + atomic_open: None, + tmpfile: None, + set_acl: None, + fileattr_get: None, + fileattr_set: None, + }; +} + +impl BuildVtable for InodeOperationsVtable { + fn build_vtable() -> &'static bindings::inode_operations { + &Self::VTABLE + } +} + +impl BuildVtable for T { + fn build_vtable() -> &'static bindings::inode_operations { + InodeOperationsVtable::::build_vtable() + } +} + +/// Represents which fields of [`struct inode_block_operations`] should be populated with pointers. +pub struct ToUse { + /// The `lookup` field of [`struct inode_operations`]. + pub lookup: bool, + + /// The `get_link` field of [`struct inode_operations`]. + pub get_link: bool, + + /// The `permission` field of [`struct inode_operations`]. + pub permission: bool, + + /// The `get_acl` field of [`struct inode_operations`]. + pub get_acl: bool, + + /// The `readlink` field of [`struct inode_operations`]. + pub readlink: bool, + + /// The `create` field of [`struct inode_operations`]. + pub create: bool, + + /// The `link` field of [`struct inode_operations`]. + pub link: bool, + + /// The `unlink` field of [`struct inode_operations`]. + pub unlink: bool, + + /// The `symlink` field of [`struct inode_operations`]. + pub symlink: bool, + + /// The `mkdir` field of [`struct inode_operations`]. + pub mkdir: bool, + + /// The `rmdir` field of [`struct inode_operations`]. + pub rmdir: bool, + + /// The `mknod` field of [`struct inode_operations`]. + pub mknod: bool, + + /// The `rename` field of [`struct inode_operations`]. + pub rename: bool, + + /// The `listxattr` field of [`struct inode_operations`]. + pub listxattr: bool, + + /// The `fiemap` field of [`struct inode_operations`]. + pub fiemap: bool, + + /// The `update_time` field of [`struct inode_operations`]. + pub update_time: bool, + + /// The `atomic_open` field of [`struct inode_operations`]. + pub atomic_open: bool, + + /// The `tmpfile` field of [`struct inode_operations`]. + pub tmpfile: bool, + + /// The `set_acl` field of [`struct inode_operations`]. + pub set_acl: bool, + + /// The `getattr` field of [`struct inode_operations`]. + pub getattr: bool, + + /// The `setattr` field of [`struct inode_operations`]. + pub setattr: bool, +} + +/// A constant version where all values are to set to `false`, that is, all supported fields will +/// be set to null pointers. +pub const USE_NONE: ToUse = ToUse { + lookup: false, + get_link: false, + permission: false, + get_acl: false, + readlink: false, + create: false, + link: false, + unlink: false, + symlink: false, + mkdir: false, + rmdir: false, + mknod: false, + rename: false, + listxattr: false, + fiemap: false, + update_time: false, + atomic_open: false, + tmpfile: false, + set_acl: false, + getattr: false, + setattr: false, +}; + +#[macro_export] +macro_rules! declare_inode_operations { + () => { + const TO_USE: $crate::fs::inode_operations::ToUse = $crate::fs::inode_operations::USE_NONE; + }; + ($($i:ident),+) => { + #[allow(clippy::needless_update)] + const TO_USE: kernel::fs::inode_operations::ToUse = + $crate::fs::inode_operations::ToUse { + $($i: true),+ , + ..$crate::fs::inode_operations::USE_NONE + }; + }; +} diff --git a/rust/kernel/fs/kiocb.rs b/rust/kernel/fs/kiocb.rs new file mode 100644 index 00000000000000..2b38587505ba83 --- /dev/null +++ b/rust/kernel/fs/kiocb.rs @@ -0,0 +1,54 @@ +use core::{ + mem, + ops::{Deref, DerefMut}, +}; + +use crate::{bindings, file::File}; + +#[repr(transparent)] +pub struct Kiocb(bindings::kiocb); + +impl Deref for Kiocb { + type Target = bindings::kiocb; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Kiocb { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl AsRef for bindings::kiocb { + fn as_ref(&self) -> &Kiocb { + unsafe { mem::transmute(self) } + } +} +impl AsMut for bindings::kiocb { + fn as_mut(&mut self) -> &mut Kiocb { + unsafe { mem::transmute(self) } + } +} + +impl Kiocb { + pub fn as_ptr_mut(&mut self) -> *mut bindings::kiocb { + self.deref_mut() as *mut _ + } + + pub fn get_file(&mut self) -> File { + let file = unsafe { (*(self.as_ptr_mut())).ki_filp }; + File { ptr: file } + } + + pub fn get_offset(&mut self) -> u64 { + let offset = unsafe { (*(self.as_ptr_mut())).ki_pos }; + offset as _ + } + + pub fn set_offset(&mut self, offset: u64) { + unsafe { + (*(self.as_ptr_mut())).ki_pos = offset as _; + } + } +} diff --git a/rust/kernel/fs/libfs_functions.rs b/rust/kernel/fs/libfs_functions.rs new file mode 100644 index 00000000000000..248844ef4c3e7d --- /dev/null +++ b/rust/kernel/fs/libfs_functions.rs @@ -0,0 +1,283 @@ +use core::ptr; + +use crate::{ + bindings, + c_types::*, + error::Error, + file::File, + file_operations::SeekFrom, + fs::{ + address_space::AddressSpace, dentry::Dentry, from_kernel_err_ptr, inode::Inode, + kiocb::Kiocb, super_block::SuperBlock, super_operations::Kstatfs, DeclaredFileSystemType, + FileSystemBase, + }, + iov_iter::IovIter, + print::ExpectK, + str::CStr, + types::{Iattr, Kstat, Page, Path, UserNamespace}, + Result, +}; + +pub fn generic_file_read_iter(iocb: &mut Kiocb, iter: &mut IovIter) -> Result { + Error::parse_int(unsafe { bindings::generic_file_read_iter(iocb.as_ptr_mut(), iter.ptr) as _ }) +} + +pub fn generic_file_write_iter(iocb: &mut Kiocb, iter: &mut IovIter) -> Result { + Error::parse_int(unsafe { bindings::generic_file_write_iter(iocb.as_ptr_mut(), iter.ptr) as _ }) +} + +pub fn generic_file_mmap(file: &File, vma: &mut bindings::vm_area_struct) -> Result { + Error::parse_int(unsafe { bindings::generic_file_mmap(file.ptr, vma as *mut _) }).map(|_| ()) +} + +pub fn noop_fsync(file: &File, start: u64, end: u64, datasync: bool) -> Result { + let start = start as _; + let end = end as _; + let datasync = if datasync { 1 } else { 0 }; + let res = unsafe { bindings::noop_fsync(file.ptr, start, end, datasync) }; + if res == 0 { + Ok(0) + } else { + Err(Error::EINVAL) + // Err(Error::from_kernel_errno(bindings::errno)) + } +} + +pub fn generic_file_llseek(file: &File, pos: SeekFrom) -> Result { + let (offset, whence) = pos.into_pos_and_whence(); + Error::parse_int( + unsafe { bindings::generic_file_llseek(file.ptr, offset as _, whence as _) } as _, + ) +} + +pub fn generic_file_splice_read( + file: &File, + pos: *mut i64, + pipe: &mut bindings::pipe_inode_info, + len: usize, + flags: u32, +) -> Result { + Error::parse_int(unsafe { + bindings::generic_file_splice_read(file.ptr, pos, pipe as *mut _, len, flags) as _ + }) +} + +pub fn iter_file_splice_write( + pipe: &mut bindings::pipe_inode_info, + file: &File, + pos: *mut i64, + len: usize, + flags: u32, +) -> Result { + Error::parse_int(unsafe { + bindings::iter_file_splice_write(pipe as *mut _, file.ptr, pos, len, flags) as _ + }) +} + +pub fn generic_delete_inode(inode: &mut Inode) -> Result { + Error::parse_int(unsafe { bindings::generic_delete_inode(inode.as_ptr_mut()) }).map(|_| ()) +} + +pub fn simple_statfs(root: &mut Dentry, buf: &mut Kstatfs) -> Result { + Error::parse_int(unsafe { bindings::simple_statfs(root.as_ptr_mut(), buf as *mut _) }) + .map(|_| ()) +} + +pub fn simple_setattr( + mnt_userns: &mut UserNamespace, + dentry: &mut Dentry, + iattr: &mut Iattr, +) -> Result { + Error::parse_int(unsafe { + bindings::simple_setattr(mnt_userns as *mut _, dentry.as_ptr_mut(), iattr as *mut _) + }) + .map(|_| ()) +} + +pub fn simple_getattr( + mnt_userns: &mut UserNamespace, + path: &Path, + stat: &mut Kstat, + request_mask: u32, + query_flags: u32, +) -> Result { + Error::parse_int(unsafe { + bindings::simple_getattr( + mnt_userns as *mut _, + path as *const _, + stat as *mut _, + request_mask, + query_flags, + ) + }) + .map(|_| ()) +} + +pub fn simple_lookup(dir: &mut Inode, dentry: &mut Dentry, flags: c_uint) -> Result<*mut Dentry> { + // todo: return type ptr vs ref? + from_kernel_err_ptr(unsafe { + bindings::simple_lookup(dir.as_ptr_mut(), dentry.as_ptr_mut(), flags) as *mut _ + }) +} + +pub fn simple_link(old_dentry: &mut Dentry, dir: &mut Inode, dentry: &mut Dentry) -> Result { + Error::parse_int(unsafe { + bindings::simple_link( + old_dentry.as_ptr_mut(), + dir.as_ptr_mut(), + dentry.as_ptr_mut(), + ) + }) + .map(|_| ()) +} + +pub fn simple_unlink(dir: &mut Inode, dentry: &mut Dentry) -> Result { + Error::parse_int(unsafe { bindings::simple_unlink(dir.as_ptr_mut(), dentry.as_ptr_mut()) }) + .map(|_| ()) +} + +pub fn simple_rmdir(dir: &mut Inode, dentry: &mut Dentry) -> Result { + Error::parse_int(unsafe { bindings::simple_rmdir(dir.as_ptr_mut(), dentry.as_ptr_mut()) }) + .map(|_| ()) +} + +pub fn simple_rename( + mnt_userns: &mut UserNamespace, + old_dir: &mut Inode, + old_dentry: &mut Dentry, + new_dir: &mut Inode, + new_dentry: &mut Dentry, + flags: c_uint, +) -> Result { + Error::parse_int(unsafe { + bindings::simple_rename( + mnt_userns as *mut _, + old_dir.as_ptr_mut(), + old_dentry.as_ptr_mut(), + new_dir.as_ptr_mut(), + new_dentry.as_ptr_mut(), + flags, + ) + }) + .map(|_| ()) +} + +pub fn page_symlink(inode: &mut Inode, symname: &'static CStr) -> Result { + Error::parse_int(unsafe { + bindings::page_symlink( + inode.as_ptr_mut(), + symname.as_ptr() as _, + symname.len_with_nul() as _, + ) + }) + .map(|_| ()) +} + +pub fn register_filesystem() -> Result { + Error::parse_int(unsafe { bindings::register_filesystem(T::file_system_type()) }).map(|_| ()) +} + +pub fn unregister_filesystem() -> Result { + Error::parse_int(unsafe { bindings::unregister_filesystem(T::file_system_type()) }).map(|_| ()) +} + +pub fn mount_nodev( + flags: c_int, + data: Option<&mut T::MountOptions>, +) -> Result<*mut bindings::dentry> { + from_kernel_err_ptr(unsafe { + bindings::mount_nodev( + T::file_system_type(), + flags, + data.map(|p| p as *mut _ as *mut _) + .unwrap_or_else(ptr::null_mut), + Some(fill_super_callback::), + ) + }) +} + +unsafe extern "C" fn fill_super_callback( + sb: *mut bindings::super_block, + data: *mut c_void, + silent: c_int, +) -> c_int { + unsafe { + let sb = sb.as_mut().expectk("SuperBlock was null").as_mut(); + let data = (data as *mut T::MountOptions).as_mut(); + T::fill_super(sb, data, silent) + .map(|_| 0) + .unwrap_or_else(|e| e.to_kernel_errno()) + } +} + +pub fn kill_litter_super(sb: &mut SuperBlock) { + unsafe { + bindings::kill_litter_super(sb.as_ptr_mut()); + } +} + +pub fn simple_readpage(file: &File, page: &mut Page) -> Result { + Error::parse_int(unsafe { bindings::simple_readpage(file.ptr, page as *mut _) }).map(|_| ()) +} + +pub fn simple_write_begin( + file: Option<&File>, + mapping: &mut AddressSpace, + pos: bindings::loff_t, + len: u32, + flags: u32, + pagep: *mut *mut Page, + fsdata: *mut *mut c_void, +) -> Result { + Error::parse_int(unsafe { + bindings::simple_write_begin( + file.map(|f| f.ptr).unwrap_or(ptr::null_mut()), + mapping.as_ptr_mut(), + pos, + len, + flags, + pagep, + fsdata, + ) + }) + .map(|_| ()) +} + +pub fn simple_write_end( + file: Option<&File>, + mapping: &mut AddressSpace, + pos: bindings::loff_t, + len: u32, + copied: u32, + page: &mut Page, + fsdata: *mut c_void, +) -> Result { + Error::parse_int(unsafe { + bindings::simple_write_end( + file.map(|f| f.ptr).unwrap_or(ptr::null_mut()), + mapping.as_ptr_mut(), + pos, + len, + copied, + page, + fsdata, + ) + }) + .map(|x| x as u32) +} + +pub fn __set_page_dirty_nobuffers(page: &mut Page) -> Result { + Error::parse_int(unsafe { bindings::__set_page_dirty_nobuffers(page as *mut _) }) + .map(|x| x != 0) +} + +crate::declare_c_vtable!( + SimpleDirOperations, + bindings::file_operations, + bindings::simple_dir_operations, +); +crate::declare_c_vtable!( + PageSymlinkInodeOperations, + bindings::inode_operations, + bindings::page_symlink_inode_operations, +); diff --git a/rust/kernel/fs/super_block.rs b/rust/kernel/fs/super_block.rs new file mode 100644 index 00000000000000..e2ebbc7ba35700 --- /dev/null +++ b/rust/kernel/fs/super_block.rs @@ -0,0 +1,49 @@ +use alloc::boxed::Box; +use core::{ + mem, + ops::{Deref, DerefMut}, +}; + +use crate::{ + bindings, + fs::super_operations::{SuperOperations, SuperOperationsVtable}, + Result, +}; + +#[repr(transparent)] +pub struct SuperBlock(bindings::super_block); + +impl SuperBlock { + pub fn as_ptr_mut(&mut self) -> *mut bindings::super_block { + self.deref_mut() as *mut _ + } + + pub fn set_super_operations(&mut self, ops: OPS) -> Result { + self.s_op = unsafe { SuperOperationsVtable::::build() }; + self.s_fs_info = Box::into_raw(Box::try_new(ops)?).cast(); + Ok(()) + } +} + +impl Deref for SuperBlock { + type Target = bindings::super_block; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for SuperBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl AsRef for bindings::super_block { + fn as_ref(&self) -> &SuperBlock { + unsafe { mem::transmute(self) } + } +} +impl AsMut for bindings::super_block { + fn as_mut(&mut self) -> &mut SuperBlock { + unsafe { mem::transmute(self) } + } +} diff --git a/rust/kernel/fs/super_operations.rs b/rust/kernel/fs/super_operations.rs new file mode 100644 index 00000000000000..802de0b3bca9da --- /dev/null +++ b/rust/kernel/fs/super_operations.rs @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Super operations. +//! +//! C header: [`include/linux/fs.h`](../../../../include/linux/fs.h) + +use core::marker; + +use crate::{ + bindings, c_types, + error::{Error, Result}, + from_kernel_result, + fs::dentry::Dentry, + fs::inode::Inode, + print::ExpectK, +}; + +pub type SeqFile = bindings::seq_file; +pub type Kstatfs = bindings::kstatfs; + +// unsafe extern "C" fn alloc_inode_callback( +// sb: *mut bindings::super_block, +// ) -> *mut bindings::inode { +// let s_ops = &*((*sb).s_fs_info as *const T); +// let inode = s_ops.alloc_inode(&SuperBlock::from_ptr(sb)); // TODO SuperBlock, Inode +// inode.map(|i| Inode::into_ptr(inode)) +// } + +// unsafe extern "C" fn destroy_inode_callback( +// inode: *mut bindings::inode, +// ) { +// let sb = (*inode).i_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// let inode = Inode::from_ptr(inode); +// s_ops.destroy_inode(inode); +// } + +// unsafe extern "C" fn free_inode_callback( +// inode: *mut bindings::inode, +// ) { +// let sb = (*inode).i_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// let inode = Inode::from_ptr(inode); +// s_ops.free_inode(inode); +// } + +// unsafe extern "C" fn dirty_inode_callback( +// inode: *mut bindings::inode, +// flags: c_types::c_int, +// ) { +// let sb = (*inode).i_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// let inode = Inode::from_ptr(inode); +// s_ops.dirty_inode(inode, flags); +// } + +// unsafe extern "C" fn write_inode_callback( +// inode: *mut bindings::inode, +// wbc: *mut bindings::writeback_control, // TODO +// ) -> c_types::c_int { +// let sb = (*inode).i_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// let inode = Inode::from_ptr(inode); +// let wbc = WritebackControl::from_ptr(wbc); +// from_kernel_result! { +// s_ops.write_inode(inode, wbc)?; +// Ok(0) +// } +// } + +unsafe extern "C" fn drop_inode_callback( + inode: *mut bindings::inode, +) -> c_types::c_int { + unsafe { + let sb = (*inode).i_sb as *const bindings::super_block; + let s_ops = &*((*sb).s_fs_info as *const T); + let inode = inode.as_mut().expectk("drop_inode got null inode").as_mut(); + from_kernel_result! { + s_ops.drop_inode(inode)?; + Ok(0) + } + } +} + +// unsafe extern "C" fn evict_inode_callback( +// inode: *mut bindings::inode, +// ) { +// let sb = (*inode).i_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// let inode = Inode::from_ptr(inode); +// s_ops.evict_inode(inode); +// } + +// unsafe extern "C" fn put_super_callback( +// sb: *mut bindings::super_block, +// ) { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.put_super(&SuperBlock::from_ptr(sb)); +// } + +// unsafe extern "C" fn sync_fs_callback( +// sb: *mut bindings::super_block, +// wait: c_types::c_int, +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.sync_fs(&SuperBlock::from_ptr(sb), wait)?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn freeze_super_callback( +// sb: *mut bindings::super_block, +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.sync_fs(&SuperBlock::from_ptr(sb))?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn freeze_fs_callback( +// sb: *mut bindings::super_block, +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.freeze_fs(&SuperBlock::from_ptr(sb))?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn thaw_super_callback( +// sb: *mut bindings::super_block, +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.thaw_super(&SuperBlock::from_ptr(sb))?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn unfreeze_fs_callback( +// sb: *mut bindings::super_block, +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.unfreeze_fs(&SuperBlock::from_ptr(sb))?; +// Ok(0) +// } +// } + +unsafe extern "C" fn statfs_callback( + root: *mut bindings::dentry, + buf: *mut bindings::kstatfs, +) -> c_types::c_int { + from_kernel_result! { + unsafe { + let sb = (*root).d_sb as *const bindings::super_block; + let s_ops = &*((*sb).s_fs_info as *const T); + s_ops.statfs(root.as_mut().expectk("Statfs got null dentry").as_mut(), &mut *buf)?; + Ok(0) + } + } +} + +// unsafe extern "C" fn remount_fs_callback( +// sb: *mut bindings::super_block, +// flags: *mut c_types::c_int, +// data: *mut c_types::c_char, +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.remount_fs( +// &SuperBlock::from_ptr(sb), +// flags, +// &CStr::from_ptr(data), // TODO +// )?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn umount_begin_callback( +// sb: *mut bindings::super_block, +// ) { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.umount_begin(&SuperBlock::from_ptr(sb)); +// } + +unsafe extern "C" fn show_options_callback( + s: *mut bindings::seq_file, + root: *mut bindings::dentry, +) -> c_types::c_int { + from_kernel_result! { + unsafe { + let sb = (*root).d_sb as *const bindings::super_block; + let s_ops = &*((*sb).s_fs_info as *const T); + s_ops.show_options(&mut *s, root.as_mut().expectk("show_options got null dentry").as_mut())?; + Ok(0) + } + } +} + +// unsafe extern "C" fn show_devname_callback( +// s: *mut bindings::seq_file, +// root: *mut bindings::dentry, +// ) -> c_types::c_int { +// from_kernel_result! { +// let sb = (*root).d_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.show_devname(&SeqFile::from_ptr(s), &Dentry::from_ptr(root))?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn show_path_callback( +// s: *mut bindings::seq_file, +// root: *mut bindings::dentry, +// ) -> c_types::c_int { +// from_kernel_result! { +// let sb = (*root).d_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.show_path(&SeqFile::from_ptr(s), &Dentry::from_ptr(root))?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn show_stats_callback( +// s: *mut bindings::seq_file, +// root: *mut bindings::dentry, +// ) -> c_types::c_int { +// from_kernel_result! { +// let sb = (*root).d_sb as *const bindings::super_block; +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.show_stats(&SeqFile::from_ptr(s), &Dentry::from_ptr(root))?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn bdev_try_to_free_page_callback( +// sb: *mut bindings::super_block, +// page: *mut bindings::page, // TODO +// wait: bindings::gfp_t, // TODO +// ) -> c_types::c_int { +// from_kernel_result! { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.show_stats(&SuperBlock::from_ptr(sb), &Page::from_ptr(page), wait)?; +// Ok(0) +// } +// } + +// unsafe extern "C" fn nr_cached_objects_callback( +// sb: *mut bindings::super_block, +// sc: *mut bindings::shrink_control, // TODO +// ) -> c_types::c_long { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.nr_cached_objects(&SuperBlock::from_ptr(sb), &ShrinkControl::from_ptr(sc)) +// } + +// unsafe extern "C" fn free_cached_objects_callback( +// sb: *mut bindings::super_block, +// sc: *mut bindings::shrink_control, // TODO +// ) -> c_types::c_long { +// let s_ops = &*((*sb).s_fs_info as *const T); +// s_ops.free_cached_objects(&SuperBlock::from_ptr(sb), &ShrinkControl::from_ptr(sc)) +// } + +pub(crate) struct SuperOperationsVtable(marker::PhantomData); + +impl SuperOperationsVtable { + const VTABLE: bindings::super_operations = bindings::super_operations { + // alloc_inode: T::TO_USE.alloc_inode { + // Some(alloc_inode_callback::) + // } else { + // None + // }, + // destroy_inode: if T::TO_USE.destroy_inode { + // Some(destroy_inode_callback::) + // } else { + // None + // }, + // free_inode: if T::TO_USE.free_inode { + // Some(free_inode_callback::) + // } else { + // None + // }, + // dirty_inode: if T::TO_USE.dirty_inode { + // Some(dirty_inode_callback::) + // } else { + // None + // }, + // write_inode: if T::TO_USE.write_inode { + // Some(write_inode_callback::) + // } else { + // None + // }, + drop_inode: if T::TO_USE.drop_inode { + Some(drop_inode_callback::) + } else { + None + }, + // evict_inode: if T::TO_USE.evict_inode { + // Some(evict_inode_callback::) + // } else { + // None + // }, + // put_super: if T::TO_USE.put_super { + // Some(put_super_callback::) + // } else { + // None + // }, + // sync_fs: if T::TO_USE.sync_fs { + // Some(sync_fs_callback::) + // } else { + // None + // }, + // freeze_super: if T::TO_USE.freeze_super { + // Some(freeze_super_callback::) + // } else { + // None + // }, + // freeze_fs: if T::TO_USE.freeze_fs { + // Some(freeze_fs_callback::) + // } else { + // None + // }, + // thaw_super: if T::TO_USE.thaw_super { + // Some(thaw_super_callback::) + // } else { + // None + // }, + // unfreeze_fs: if T::TO_USE.unfreeze_fs { + // Some(unfreeze_fs_callback::) + // } else { + // None + // }, + statfs: if T::TO_USE.statfs { + Some(statfs_callback::) + } else { + None + }, + // remount_fs: if T::TO_USE.remount_fs { + // Some(remount_fs_callback::) + // } else { + // None + // }, + // umount_begin: if T::TO_USE.umount_begin { + // Some(umount_begin_callback::) + // } else { + // None + // }, + show_options: if T::TO_USE.show_options { + Some(show_options_callback::) + } else { + None + }, + // show_devname: if T::TO_USE.show_devname { + // Some(show_devname_callback::) + // } else { + // None + // }, + // show_path: if T::TO_USE.show_path { + // Some(show_path_callback::) + // } else { + // None + // }, + // show_stats: if T::TO_USE.show_stats { + // Some(show_stats_callback::) + // } else { + // None + // }, + // bdev_try_to_free_page: if T::TO_USE.bdev_try_to_free_page { + // Some(bdev_try_to_free_page_callback::) + // } else { + // None + // }, + // nr_cached_objects: if T::TO_USE.nr_cached_objects { + // Some(nr_cached_objects_callback::) + // } else { + // None + // }, + // free_cached_objects: if T::TO_USE.free_cached_objects { + // Some(free_cached_objects_callback::) + // } else { + // None + // }, + alloc_inode: None, + destroy_inode: None, + free_inode: None, + dirty_inode: None, + write_inode: None, + evict_inode: None, + put_super: None, + sync_fs: None, + freeze_super: None, + freeze_fs: None, + thaw_super: None, + unfreeze_fs: None, + remount_fs: None, + umount_begin: None, + show_devname: None, + show_path: None, + show_stats: None, + bdev_try_to_free_page: None, + nr_cached_objects: None, + free_cached_objects: None, + get_dquots: None, + quota_read: None, + quota_write: None, + }; + + /// Builds an instance of [`struct super_operations`]. + /// + /// # Safety + /// + /// The caller must ensure that the adapter is compatible with the way the device is registered. + pub(crate) const unsafe fn build() -> &'static bindings::super_operations { + &Self::VTABLE + } +} + +/// Represents which fields of [`struct super_block_operations`] should be populated with pointers. +pub struct ToUse { + /// The `alloc_inode` field of [`struct super_operations`]. + pub alloc_inode: bool, + + /// The `destroy_inode` field of [`struct super_operations`]. + pub destroy_inode: bool, + + /// The `free_inode` field of [`struct super_operations`]. + pub free_inode: bool, + + /// The `dirty_inode` field of [`struct super_operations`]. + pub dirty_inode: bool, + + /// The `write_inode` field of [`struct super_operations`]. + pub write_inode: bool, + + /// The `drop_inode` field of [`struct super_operations`]. + pub drop_inode: bool, + + /// The `evict_inode` field of [`struct super_operations`]. + pub evict_inode: bool, + + /// The `put_super` field of [`struct super_operations`]. + pub put_super: bool, + + /// The `sync_fs` field of [`struct super_operations`]. + pub sync_fs: bool, + + /// The `freeze_super` field of [`struct super_operations`]. + pub freeze_super: bool, + + /// The `freeze_fs` field of [`struct super_operations`]. + pub freeze_fs: bool, + + /// The `thaw_super` field of [`struct super_operations`]. + pub thaw_super: bool, + + /// The `unfreeze_fs` field of [`struct super_operations`]. + pub unfreeze_fs: bool, + + /// The `statfs` field of [`struct super_operations`]. + pub statfs: bool, + + /// The `remount_fs` field of [`struct super_operations`]. + pub remount_fs: bool, + + /// The `umount_begin` field of [`struct super_operations`]. + pub umount_begin: bool, + + /// The `show_options` field of [`struct super_operations`]. + pub show_options: bool, + + /// The `show_devname` field of [`struct super_operations`]. + pub show_devname: bool, + + /// the `show_path` field of [`struct super_operations`]. + pub show_path: bool, + + /// the `show_stats` field of [`struct super_operations`]. + pub show_stats: bool, + + /// the `bdev_try_to_free_page` field of [`struct super_operations`]. + pub bdev_try_to_free_page: bool, + + /// the `nr_cached_objects` field of [`struct super_operations`]. + pub nr_cached_objects: bool, + + /// the `free_cached_objects` field of [`struct super_operations`]. + pub free_cached_objects: bool, +} + +/// A constant version where all values are to set to `false`, that is, all supported fields will +/// be set to null pointers. +pub const USE_NONE: ToUse = ToUse { + alloc_inode: false, + destroy_inode: false, + free_inode: false, + dirty_inode: false, + write_inode: false, + drop_inode: false, + evict_inode: false, + put_super: false, + sync_fs: false, + freeze_super: false, + freeze_fs: false, + thaw_super: false, + unfreeze_fs: false, + statfs: false, + remount_fs: false, + umount_begin: false, + show_options: false, + show_devname: false, + show_path: false, + show_stats: false, + bdev_try_to_free_page: false, + nr_cached_objects: false, + free_cached_objects: false, +}; + +#[macro_export] +macro_rules! declare_super_operations { + () => { + const TO_USE: $crate::fs::super_operations::ToUse = $crate::fs::super_operations::USE_NONE; + }; + ($($i:ident),+) => { + #[allow(clippy::needless_update)] + const TO_USE: kernel::fs::super_operations::ToUse = + $crate::fs::super_operations::ToUse { + $($i: true),+ , + ..$crate::fs::super_operations::USE_NONE + }; + }; +} + +/// Corresponds to the kernel's `struct super_operations`. +/// +/// You implement this trait whenever you would create a `struct super_operations`. +/// +/// File descriptors may be used from multiple threads/processes concurrently, so your type must be +/// [`Sync`]. It must also be [`Send`] because [`FileOperations::release`] will be called from the +/// thread that decrements that associated file's refcount to zero. +pub trait SuperOperations: Send + Sync + Sized { + /// The methods to use to populate [`struct super_operations`]. + const TO_USE: ToUse; + + // fn alloc_inode(&self, _sb: &SuperBlock) -> Option { + // None + // } + + // fn destroy_inode(&self, _inode: &Inode) {} + + // fn free_inode(&self, _inode: &Inode) {} + + // fn dirty_inode(&self, _inode: &Inode, _flags: i32) {} + + // fn write_inode(&self, _inode: &Inode, _wbc: &WritebackControl) -> Result { + // Err(Error::EINVAL) + // } + + fn drop_inode(&self, _inode: &mut Inode) -> Result { + Err(Error::EINVAL) + } + + // fn evict_inode(&self, _inode: &Inode) {} + + // fn put_super(&self, _sb: &SuperBlock) {} + + // fn sync_fs(&self, _sb: &SuperBlock, wait: i32) -> Result { + // Err(Error::EINVAL) + // } + + // fn freeze_super(&self, _sb: &SuperBlock) -> Result { + // Err(Error::EINVAL) + // } + + // fn freeze_fs(&self, _sb: &SuperBlock) -> Result { + // Err(Error::EINVAL) + // } + + // fn thaw_super(&self, _sb: &SuperBlock) -> Result { + // Err(Error::EINVAL) + // } + + // fn unfreeze_fs(&self, _sb: &SuperBlock) -> Result { + // Err(Error::EINVAL) + // } + + fn statfs(&self, _root: &mut Dentry, _buf: &mut Kstatfs) -> Result { + Err(Error::EINVAL) + } + + // fn remount_fs(&self, _sb: &SuperBlock, _flags: i32, _data: &CStr) -> Result { + // Err(Error::EINVAL) + // } + + // fn umount_begin(&self, _sb: &SuperBlock) {} + + fn show_options(&self, _s: &mut SeqFile, _root: &mut Dentry) -> Result { + Err(Error::EINVAL) + } + + // fn show_devname(&self, _s: &SeqFile, _root: &Dentry) -> Result { + // Err(Error::EINVAL) + // } + + // fn show_path(&self, _s: &SeqFile, _root: &Dentry) -> Result { + // Err(Error::EINVAL) + // } + + // fn show_stats(&self, _s: &SeqFile, _root: &Dentry) -> Result { + // Err(Error::EINVAL) + // } + + // fn bdev_try_to_free_page(&self, _sb: &SuperBlock, _page: &Page, _wait: GfpT) -> Result { + // Err(Error::EINVAL) + // } + + // fn nr_cached_objects(&self, _sb: &SuperBlock, _sc: &ShrinkControl) -> i64 { + // 0 + // } + + // fn free_cached_objects(&self, _sb: &SuperBlock, _sc: &ShrinkControl) -> i64 { + // 0 + // } +} diff --git a/rust/kernel/iov_iter.rs b/rust/kernel/iov_iter.rs index 4a6f63e1650c19..0b3fcdcb6d6bd3 100644 --- a/rust/kernel/iov_iter.rs +++ b/rust/kernel/iov_iter.rs @@ -31,7 +31,7 @@ extern "C" { /// /// The pointer [`IovIter::ptr`] is non-null and valid. pub struct IovIter { - ptr: *mut bindings::iov_iter, + pub(crate) ptr: *mut bindings::iov_iter, } impl IovIter { diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 7ceb8985f83656..7c68b8d7846604 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -23,10 +23,10 @@ const_unreachable_unchecked, try_reserve )] -#![deny(clippy::complexity)] -#![deny(clippy::correctness)] +#![warn(clippy::complexity)] +#![warn(clippy::correctness)] #![deny(clippy::perf)] -#![deny(clippy::style)] +#![warn(clippy::style)] #![deny(rust_2018_idioms)] // Ensure conditional compilation based on the kernel configuration works; @@ -44,9 +44,11 @@ pub mod bindings; pub mod buffer; pub mod c_types; pub mod chrdev; +#[macro_use] mod error; pub mod file; pub mod file_operations; +pub mod fs; pub mod miscdev; pub mod pages; pub mod str; @@ -70,9 +72,11 @@ pub mod sysctl; pub mod io_buffer; pub mod iov_iter; +#[macro_use] +mod macros; pub mod of; pub mod platdev; -mod types; +pub mod types; pub mod user_ptr; #[doc(hidden)] diff --git a/rust/kernel/macros.rs b/rust/kernel/macros.rs new file mode 100644 index 00000000000000..27c6762c396592 --- /dev/null +++ b/rust/kernel/macros.rs @@ -0,0 +1,19 @@ +#![allow(dead_code)] + +pub const DO_NEGATE: bool = true; +pub const DO_NOT_NEGATE: bool = false; + +#[macro_export] +macro_rules! declare_constant_from_bindings { + ($name:ident, $doc:expr) => { + #[doc=$doc] + #[allow(unused)] + pub const $name: Self = Self(crate::bindings::$name as _); + }; + ($name:ident, $doc:expr, $intermediate:ty, $negate:expr) => { + #[doc=$doc] + #[allow(unused)] + pub const $name: Self = + Self(if $negate { -1 } else { 1 } * (crate::bindings::$name as $intermediate)); + }; +} diff --git a/rust/kernel/miscdev.rs b/rust/kernel/miscdev.rs index e4d94d7416efce..2874d806a1073e 100644 --- a/rust/kernel/miscdev.rs +++ b/rust/kernel/miscdev.rs @@ -9,6 +9,7 @@ use crate::bindings; use crate::error::{Error, Result}; use crate::file_operations::{FileOpenAdapter, FileOpener, FileOperationsVtable}; +use crate::fs::BuildVtable; use crate::str::CStr; use alloc::boxed::Box; use core::marker::PhantomPinned; @@ -68,7 +69,7 @@ impl Registration { } // SAFETY: The adapter is compatible with `misc_register`. - this.mdev.fops = unsafe { FileOperationsVtable::::build() }; + this.mdev.fops = unsafe { FileOperationsVtable::::build_vtable() }; this.mdev.name = name.as_char_ptr(); this.mdev.minor = minor.unwrap_or(bindings::MISC_DYNAMIC_MINOR as i32); diff --git a/rust/kernel/print.rs b/rust/kernel/print.rs index 07cfbcb653a510..77a42f6897a04b 100644 --- a/rust/kernel/print.rs +++ b/rust/kernel/print.rs @@ -7,7 +7,7 @@ //! Reference: use core::cmp; -use core::fmt; +use core::fmt::{self, Debug}; use crate::bindings; use crate::c_types::{c_char, c_void}; @@ -410,3 +410,64 @@ macro_rules! pr_cont ( $crate::print_macro!($crate::print::format_strings::CONT, true, $($arg)*) ) ); + +/// Equivalent to `std::dbg`, but uses `pr_info`. +#[macro_export] +macro_rules! dbg { + // Comment from original implementation: + // NOTE: We cannot use `concat!` to make a static string as a format argument + // of `eprintln!` because `file!` could contain a `{` or + // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!` + // will be malformed. + () => { + $crate::pr_info!("[{}:{}]", core::file!(), core::line!()); + }; + ($val:expr $(,)?) => { + // Use of `match` here is intentional because it affects the lifetimes + // of temporaries - https://stackoverflow.com/a/48732525/1063961 + match $val { + tmp => { + $crate::pr_info!("[{}:{}] {} = {:#?}", + core::file!(), core::line!(), core::stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} + +/// Extension trait for the `expect` method on `Option` and `Result` that prints an error message +/// with `printk` before panicking. +pub trait ExpectK { + fn expectk(self, msg: &str) -> T; +} + +impl ExpectK for Option { + fn expectk(self, msg: &str) -> T { + match self { + Some(val) => val, + None => { + pr_emerg!("Called expectk on a None value: {}", msg); + panic!(); + } + } + } +} + +impl ExpectK for Result { + fn expectk(self, msg: &str) -> T { + match self { + Ok(val) => val, + Err(err) => { + pr_emerg!( + "Called expectk on a Err value: {}\nThe value was {:?}", + msg, + err + ); + panic!(); + } + } + } +} diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index 027a91812c7b0c..942041180367f5 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -4,33 +4,157 @@ //! //! C header: [`include/linux/types.h`](../../../../include/linux/types.h) -use core::{ops::Deref, pin::Pin}; +use core::{ + ops::{BitAnd, BitOr, Deref}, + pin::Pin, +}; use alloc::{boxed::Box, sync::Arc}; use crate::bindings; use crate::c_types; +use crate::declare_constant_from_bindings; use crate::sync::{Ref, RefCounted}; +pub type UserNamespace = bindings::user_namespace; +pub type Iattr = bindings::iattr; +pub type Path = bindings::path; +pub type Kstat = bindings::kstat; +pub type Dev = bindings::dev_t; +pub type Page = bindings::page; + +macro_rules! impl_flag_methods { + ($T:ty, $V:ty) => { + impl $T { + pub const fn from_int(val: $V) -> Self { + Self(val) + } + pub const fn into_int(self) -> $V { + self.0 + } + pub const fn is_empty(&self) -> bool { + self.0 == 0 + } + pub const fn has(self, other: Self) -> bool { + self.0 & other.0 != 0 + } + pub const fn with(self, other: Self) -> Self { + Self(self.0 | other.0) + } + pub const fn without(self, other: Self) -> Self { + Self(self.0 & !other.0) + } + } + }; +} + +pub struct FileSystemFlags(c_types::c_int); + +#[rustfmt::skip] +impl FileSystemFlags { + /// Not a virtual file system. An actual underlying block device is required. + pub const FS_REQUIRES_DEV: Self = Self::from_int(bindings::FS_REQUIRES_DEV as _); + + /// Mount data is binary, and cannot be handled by the standard option parser + pub const FS_BINARY_MOUNTDATA: Self = Self::from_int(bindings::FS_BINARY_MOUNTDATA as _); + + /// Has subtype + pub const FS_HAS_SUBTYPE: Self = Self::from_int(bindings::FS_HAS_SUBTYPE as _); + + /// Can be mounted by userns root + pub const FS_USERNS_MOUNT: Self = Self::from_int(bindings::FS_USERNS_MOUNT as _); + + /// Disable fanotify permission events + pub const FS_DISALLOW_NOTIFY_PERM: Self = Self::from_int(bindings::FS_DISALLOW_NOTIFY_PERM as _); + + /// FS has been updated to handle vfs idmappings + pub const FS_ALLOW_IDMAP: Self = Self::from_int(bindings::FS_ALLOW_IDMAP as _); + + /// Remove once all fs converted + pub const FS_THP_SUPPORT: Self = Self::from_int(bindings::FS_THP_SUPPORT as _); + + /// FS will handle d_move() during rename() internally + pub const FS_RENAME_DOES_D_MOVE: Self = Self::from_int(bindings::FS_RENAME_DOES_D_MOVE as _); +} + +impl_flag_methods!(FileSystemFlags, c_types::c_int); + /// Permissions. /// /// C header: [`include/uapi/linux/stat.h`](../../../../include/uapi/linux/stat.h) /// /// C header: [`include/linux/stat.h`](../../../../include/linux/stat.h) +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Mode(bindings::umode_t); +pub type ModeInt = u16; impl Mode { /// Creates a [`Mode`] from an integer. - pub fn from_int(m: u16) -> Mode { + pub const fn from_int(m: ModeInt) -> Mode { Mode(m) } /// Returns the mode as an integer. - pub fn as_int(&self) -> u16 { + pub fn as_int(&self) -> ModeInt { self.0 } } +#[rustfmt::skip] +impl Mode { + // See `man 7 inode`. + + // file type + declare_constant_from_bindings!(S_IFMT, "bit mask for the file type bit field"); + + declare_constant_from_bindings!(S_IFSOCK, "socket"); + declare_constant_from_bindings!(S_IFLNK, "symbolic link"); + declare_constant_from_bindings!(S_IFREG, "regular file"); + declare_constant_from_bindings!(S_IFBLK, "block device"); + declare_constant_from_bindings!(S_IFDIR, "directory"); + declare_constant_from_bindings!(S_IFCHR, "character device"); + declare_constant_from_bindings!(S_IFIFO, "FIFO"); + + // file mode component of the st_mode field + declare_constant_from_bindings!(S_ISUID, "set-user-ID bit (see execve(2))"); + declare_constant_from_bindings!(S_ISGID, "set-group-ID bit (see below)"); + declare_constant_from_bindings!(S_ISVTX, "sticky bit (see below)"); + + declare_constant_from_bindings!(S_IRWXU, "owner has read, write, and execute permission"); + declare_constant_from_bindings!(S_IRUSR, "owner has read permission"); + declare_constant_from_bindings!(S_IWUSR, "owner has write permission"); + declare_constant_from_bindings!(S_IXUSR, "owner has execute permission"); + + declare_constant_from_bindings!(S_IRWXG, "group has read, write, and execute permission"); + declare_constant_from_bindings!(S_IRGRP, "group has read permission"); + declare_constant_from_bindings!(S_IWGRP, "group has write permission"); + declare_constant_from_bindings!(S_IXGRP, "group has execute permission"); + + declare_constant_from_bindings!(S_IRWXO, "others (not in group) have read, write, and execute permission"); + declare_constant_from_bindings!(S_IROTH, "others have read permission"); + declare_constant_from_bindings!(S_IWOTH, "others have write permission"); + declare_constant_from_bindings!(S_IXOTH, "others have execute permission"); + + // extras + declare_constant_from_bindings!(S_IRWXUGO, ""); +} + +impl BitAnd for Mode { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } +} + +impl BitOr for Mode { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + /// Used to convert an object into a raw pointer that represents it. /// /// It can eventually be converted back into the object. This is used to store objects as pointers diff --git a/scp_all b/scp_all new file mode 100755 index 00000000000000..126cfc458e173e --- /dev/null +++ b/scp_all @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e +set -x + +REPO_DIR=`dirname $0` + +$REPO_DIR/b -j12 +$REPO_DIR/b modules_install +rm $REPO_DIR/_mod_inst/lib/modules/*/{build,source} +scp -P 2222 $REPO_DIR/arch/x86_64/boot/bzImage root@127.0.0.1:/boot/vmlinuz-rust +scp -r -P 2222 $REPO_DIR/_mod_inst/lib/* root@127.0.0.1:/lib/ +echo "reboot" | ssh -p 2222 root@127.0.0.1 diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index 2a7b22be642bbe..4eaa5440bee841 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -98,7 +98,7 @@ def append_crate(display_name, root_module, is_workspace_member, deps, cfg): # Then, the rest outside of `rust/`. # # We explicitly mention the top-level folders we want to cover. - for folder in ("samples", "drivers"): + for folder in ("samples", "drivers", "fs"): for path in (srctree / folder).rglob("*.rs"): logging.info("Checking %s", path) name = path.name.replace(".rs", "")