summaryrefslogtreecommitdiffstats
path: root/rust/kernel/src/file_operations.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/kernel/src/file_operations.rs')
-rw-r--r--rust/kernel/src/file_operations.rs219
1 files changed, 219 insertions, 0 deletions
diff --git a/rust/kernel/src/file_operations.rs b/rust/kernel/src/file_operations.rs
new file mode 100644
index 000000000000..100fb62281e9
--- /dev/null
+++ b/rust/kernel/src/file_operations.rs
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use core::convert::{TryFrom, TryInto};
+use core::{marker, mem, ptr};
+
+use alloc::boxed::Box;
+
+use crate::bindings;
+use crate::c_types;
+use crate::error::{Error, KernelResult};
+use crate::user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter};
+
+bitflags::bitflags! {
+ pub struct FileFlags: c_types::c_uint {
+ const NONBLOCK = bindings::O_NONBLOCK;
+ }
+}
+
+pub struct File {
+ ptr: *const bindings::file,
+}
+
+impl File {
+ unsafe fn from_ptr(ptr: *const bindings::file) -> File {
+ File { ptr }
+ }
+
+ pub fn pos(&self) -> u64 {
+ unsafe { (*self.ptr).f_pos as u64 }
+ }
+
+ pub fn flags(&self) -> FileFlags {
+ FileFlags::from_bits_truncate(unsafe { (*self.ptr).f_flags })
+ }
+}
+
+// Matches std::io::SeekFrom in the Rust stdlib
+pub enum SeekFrom {
+ Start(u64),
+ End(i64),
+ Current(i64),
+}
+
+unsafe extern "C" fn open_callback<T: FileOperations>(
+ _inode: *mut bindings::inode,
+ file: *mut bindings::file,
+) -> c_types::c_int {
+ let f = match T::open() {
+ Ok(f) => Box::new(f),
+ Err(e) => return e.to_kernel_errno(),
+ };
+ (*file).private_data = Box::into_raw(f) as *mut c_types::c_void;
+ 0
+}
+
+unsafe extern "C" fn read_callback<T: FileOperations>(
+ file: *mut bindings::file,
+ buf: *mut c_types::c_char,
+ len: c_types::c_size_t,
+ offset: *mut bindings::loff_t,
+) -> c_types::c_ssize_t {
+ let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
+ Ok(ptr) => ptr.writer(),
+ Err(e) => return e.to_kernel_errno().try_into().unwrap(),
+ };
+ let f = &*((*file).private_data as *const T);
+ // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63).
+ // See discussion in #113
+ let positive_offset = match (*offset).try_into() {
+ Ok(v) => v,
+ Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(),
+ };
+ let read = T::READ.unwrap();
+ match read(f, &File::from_ptr(file), &mut data, positive_offset) {
+ Ok(()) => {
+ let written = len - data.len();
+ (*offset) += bindings::loff_t::try_from(written).unwrap();
+ written.try_into().unwrap()
+ }
+ Err(e) => e.to_kernel_errno().try_into().unwrap(),
+ }
+}
+
+unsafe extern "C" fn write_callback<T: FileOperations>(
+ file: *mut bindings::file,
+ buf: *const c_types::c_char,
+ len: c_types::c_size_t,
+ offset: *mut bindings::loff_t,
+) -> c_types::c_ssize_t {
+ let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) {
+ Ok(ptr) => ptr.reader(),
+ Err(e) => return e.to_kernel_errno().try_into().unwrap(),
+ };
+ let f = &*((*file).private_data as *const T);
+ // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63).
+ // See discussion in #113
+ let positive_offset = match (*offset).try_into() {
+ Ok(v) => v,
+ Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(),
+ };
+ let write = T::WRITE.unwrap();
+ match write(f, &mut data, positive_offset) {
+ Ok(()) => {
+ let read = len - data.len();
+ (*offset) += bindings::loff_t::try_from(read).unwrap();
+ read.try_into().unwrap()
+ }
+ Err(e) => e.to_kernel_errno().try_into().unwrap(),
+ }
+}
+
+unsafe extern "C" fn release_callback<T: FileOperations>(
+ _inode: *mut bindings::inode,
+ file: *mut bindings::file,
+) -> c_types::c_int {
+ let ptr = mem::replace(&mut (*file).private_data, ptr::null_mut());
+ drop(Box::from_raw(ptr as *mut T));
+ 0
+}
+
+unsafe extern "C" fn llseek_callback<T: FileOperations>(
+ file: *mut bindings::file,
+ offset: bindings::loff_t,
+ whence: c_types::c_int,
+) -> bindings::loff_t {
+ let off = match whence as u32 {
+ bindings::SEEK_SET => match offset.try_into() {
+ Ok(v) => SeekFrom::Start(v),
+ Err(_) => return Error::EINVAL.to_kernel_errno().into(),
+ },
+ bindings::SEEK_CUR => SeekFrom::Current(offset),
+ bindings::SEEK_END => SeekFrom::End(offset),
+ _ => return Error::EINVAL.to_kernel_errno().into(),
+ };
+ let f = &*((*file).private_data as *const T);
+ let seek = T::SEEK.unwrap();
+ match seek(f, &File::from_ptr(file), off) {
+ Ok(off) => off as bindings::loff_t,
+ Err(e) => e.to_kernel_errno().into(),
+ }
+}
+
+pub(crate) struct FileOperationsVtable<T>(marker::PhantomData<T>);
+
+impl<T: FileOperations> FileOperationsVtable<T> {
+ pub(crate) const VTABLE: bindings::file_operations = bindings::file_operations {
+ open: Some(open_callback::<T>),
+ release: Some(release_callback::<T>),
+ read: if let Some(_) = T::READ {
+ Some(read_callback::<T>)
+ } else {
+ None
+ },
+ write: if let Some(_) = T::WRITE {
+ Some(write_callback::<T>)
+ } else {
+ None
+ },
+ llseek: if let Some(_) = T::SEEK {
+ Some(llseek_callback::<T>)
+ } else {
+ None
+ },
+
+ check_flags: None,
+ compat_ioctl: None,
+ copy_file_range: None,
+ fallocate: None,
+ fadvise: None,
+ fasync: None,
+ flock: None,
+ flush: None,
+ fsync: None,
+ get_unmapped_area: None,
+ iterate: None,
+ iterate_shared: None,
+ iopoll: None,
+ lock: None,
+ mmap: None,
+ mmap_supported_flags: 0,
+ owner: ptr::null_mut(),
+ poll: None,
+ read_iter: None,
+ remap_file_range: None,
+ sendpage: None,
+ setlease: None,
+ show_fdinfo: None,
+ splice_read: None,
+ splice_write: None,
+ unlocked_ioctl: None,
+ write_iter: None,
+ };
+}
+
+pub type ReadFn<T> = Option<fn(&T, &File, &mut UserSlicePtrWriter, u64) -> KernelResult<()>>;
+pub type WriteFn<T> = Option<fn(&T, &mut UserSlicePtrReader, u64) -> KernelResult<()>>;
+pub type SeekFn<T> = Option<fn(&T, &File, SeekFrom) -> KernelResult<u64>>;
+
+/// `FileOperations` corresponds to the kernel's `struct file_operations`. You
+/// implement this trait whenever you'd create a `struct file_operations`.
+/// File descriptors may be used from multiple threads (or processes)
+/// concurrently, so your type must be `Sync`.
+pub trait FileOperations: Sync + Sized {
+ /// Creates a new instance of this file. Corresponds to the `open` function
+ /// pointer in `struct file_operations`.
+ fn open() -> KernelResult<Self>;
+
+ /// Reads data from this file to userspace. Corresponds to the `read`
+ /// function pointer in `struct file_operations`.
+ const READ: ReadFn<Self> = None;
+
+ /// Writes data from userspace o this file. Corresponds to the `write`
+ /// function pointer in `struct file_operations`.
+ const WRITE: WriteFn<Self> = None;
+
+ /// Changes the position of the file. Corresponds to the `llseek` function
+ /// pointer in `struct file_operations`.
+ const SEEK: SeekFn<Self> = None;
+}