// SPDX-License-Identifier: MPL-2.0 //! The context that can be accessed from the current task, thread or process. use core::cell::Ref; use inherit_methods_macro::inherit_methods; use ostd::{ mm::{Fallible, MAX_USERSPACE_VADDR, PodAtomic, VmIo, VmReader, VmWriter}, task::Task, }; use crate::{ prelude::*, process::{ Process, posix_thread::{PosixThread, ThreadLocal}, }, thread::Thread, vm::vmar::{VMAR_LOWEST_ADDR, Vmar}, }; /// The context that can be accessed from the current POSIX thread. #[derive(Clone)] pub struct Context<'a> { pub process: Arc, pub thread_local: &'a ThreadLocal, pub posix_thread: &'a PosixThread, pub thread: &'a Thread, pub task: &'a Task, } impl Context<'_> { /// Gets the userspace of the current task. pub fn user_space(&self) -> CurrentUserSpace<'_> { CurrentUserSpace(self.thread_local.vmar().borrow()) } } /// The user's memory space of the current task. /// /// It provides methods to read from or write to the user space efficiently. // // FIXME: With `impl VmIo for &CurrentUserSpace<'_>`, the Rust compiler seems to think that // `CurrentUserSpace` is a publicly exposed type, despite the fact that it is contained in a // private module and is never actually exposed. Consequently, it incorrectly suppresses many dead // code lints (for *lots of* types that are recursively reached via `CurrentUserSpace`'s APIs). As // a workaround, we mark the type as `pub(crate)`. We can restore it to `pub` once the compiler bug // is resolved. pub(crate) struct CurrentUserSpace<'a>(Ref<'a, Option>>); /// Gets the [`CurrentUserSpace`] from the current task. /// /// This is slower than [`Context::user_space`]. Don't use this getter /// If you get the access to the [`Context`]. #[macro_export] macro_rules! current_userspace { () => { $crate::context::CurrentUserSpace::new( $crate::process::posix_thread::AsThreadLocal::as_thread_local( &ostd::task::Task::current().unwrap(), ) .unwrap(), ) }; } impl<'a> CurrentUserSpace<'a> { /// Creates a new `CurrentUserSpace` from the current task. /// /// If you have access to a [`Context`], it is preferable to call [`Context::user_space`]. /// /// Otherwise, you can use the `current_userspace` macro /// to obtain an instance of `CurrentUserSpace` if it will only be used once. pub fn new(thread_local: &'a ThreadLocal) -> Self { let vmar_ref = thread_local.vmar().borrow(); Self(vmar_ref) } /// Returns the `Vmar` of the current userspace. /// /// # Panics /// /// This method will panic if the current process has cleared its `Vmar`. pub fn vmar(&self) -> &Vmar { self.0.as_ref().unwrap() } /// Returns whether the VMAR is shared with other processes or threads. pub fn is_vmar_shared(&self) -> bool { // If the VMAR is not shared, its reference count should be exactly 2: // one reference is held by `ThreadLocal` and the other by `ProcessVm` in `Process`. Arc::strong_count(self.0.as_ref().unwrap()) > 2 } /// Creates a reader to read data from the user space of the current task. /// /// Returns `Err` if `vaddr` and `len` do not represent a user space memory range. pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result> { // Do NOT attempt to call `check_vaddr_lowerbound` here. // // Linux has a **delayed buffer validation** behavior: // The Linux kernel assumes that a given user-space pointer is valid until it attempts to access it. // For example, the following invocation of the `read` system call with a `NULL` pointer as the buffer // // ```c // read(fd, NULL, 1); // ``` // // will return 0 (rather than an error) if the file referred to by `fd` has zero length. // // Asterinas's system call entry points follow a pattern of converting user-space pointers to // a reader/writer first and using the reader/writer later. // So adding any pointer check here would break Asterinas's delayed buffer validation behavior. Ok(self.vmar().vm_space().reader(vaddr, len)?) } /// Creates a writer to write data into the user space of the current task. /// /// Returns `Err` if `vaddr` and `len` do not represent a user space memory range. pub fn writer(&self, vaddr: Vaddr, len: usize) -> Result> { // Do NOT attempt to call `check_vaddr_lowerbound` here. // See the comments in the `reader` method. Ok(self.vmar().vm_space().writer(vaddr, len)?) } /// Creates a reader/writer pair to read data from or write data into the user space /// of the current task. /// /// Returns `Err` if `vaddr` and `len` do not represent a user space memory range. /// /// This method is semantically equivalent to calling [`Self::reader`] and [`Self::writer`] /// separately, but it avoids double checking the validity of the memory region. pub fn reader_writer( &self, vaddr: Vaddr, len: usize, ) -> Result<(VmReader<'_, Fallible>, VmWriter<'_, Fallible>)> { // Do NOT attempt to call `check_vaddr_lowerbound` here. // See the comments in the `reader` method. Ok(self.vmar().vm_space().reader_writer(vaddr, len)?) } /// Atomically loads a `PodAtomic` value with [`Ordering::Relaxed`] semantics. /// /// # Panics /// /// This method will panic if `vaddr` is not aligned on an `align_of::()`-byte boundary. /// /// [`Ordering::Relaxed`]: core::sync::atomic::Ordering::Relaxed pub fn atomic_load(&self, vaddr: Vaddr) -> Result { if size_of::() > 0 { check_vaddr_lowerbound(vaddr)?; } let user_reader = self.reader(vaddr, size_of::())?; Ok(user_reader.atomic_load()?) } /// Atomically updates a `PodAtomic` value with [`Ordering::Relaxed`] semantics. /// /// This method internally fetches the old value via [`atomic_load`], applies `op` to compute a /// new value, and updates the value via [`atomic_compare_exchange`]. If the value changes /// concurrently, this method will retry so the operation may be performed multiple times. /// /// If the update is completely successful, returns `Ok` with the old value (i.e., the value /// _before_ applying `op`). Otherwise, it returns `Err`. /// /// # Panics /// /// This method will panic if `vaddr` is not aligned on an `align_of::()`-byte boundary. /// /// [`Ordering::Relaxed`]: core::sync::atomic::Ordering::Relaxed /// [`atomic_load`]: VmReader::atomic_load /// [`atomic_compare_exchange`]: VmWriter::atomic_compare_exchange pub fn atomic_fetch_update(&self, vaddr: Vaddr, op: impl Fn(T) -> T) -> Result where T: PodAtomic + Eq, { if size_of::() > 0 { check_vaddr_lowerbound(vaddr)?; } let (reader, writer) = self.reader_writer(vaddr, size_of::())?; let mut old_val = reader.atomic_load()?; loop { match writer.atomic_compare_exchange(&reader, old_val, op(old_val))? { (_, true) => return Ok(old_val), (cur_val, false) => old_val = cur_val, } } } /// Reads a C string from the user space of the current process. /// /// The length of the string should not exceed `max_len`, including the final nul byte. /// Otherwise, this method will fail with [`Errno::ENAMETOOLONG`]. /// /// This method is commonly used to read a file name or path. In that case, when the nul byte /// cannot be found within `max_len` bytes, the correct error code is [`Errno::ENAMETOOLONG`]. /// However, in other cases, the caller may want to fix the error code manually. pub fn read_cstring(&self, vaddr: Vaddr, max_len: usize) -> Result { if max_len > 0 { check_vaddr_lowerbound(vaddr)?; } // Adjust `max_len` to ensure `vaddr + max_len` does not exceed `MAX_USERSPACE_VADDR`. // If `vaddr` is outside user address space, `userspace_max_len` will be set to zero and // further call to `self.reader` will return `EFAULT`. let userspace_max_len = MAX_USERSPACE_VADDR.saturating_sub(vaddr).min(max_len); let mut user_reader = self.reader(vaddr, userspace_max_len)?; user_reader.read_cstring_until_nul(userspace_max_len)? .ok_or_else(|| if userspace_max_len == max_len { // There may be more bytes in the userspace, but the length limit has been reached. Error::with_message( Errno::ENAMETOOLONG, "the C string does not end before reaching the maximum length" ) } else { // There cannot be any bytes in the userspace, but the C string still does not end. // This is the Linux behavior in its `do_strncpy_from_user` implementation. Error::with_message( Errno::EFAULT, "the C string does not end before reaching the maximum userspace virtual address" ) }) } } impl VmIo for CurrentUserSpace<'_> { fn read(&self, offset: usize, writer: &mut VmWriter) -> ostd::Result<()> { let copy_len = writer.avail(); if copy_len > 0 { check_vaddr_lowerbound(offset)?; } let mut user_reader = self.vmar().vm_space().reader(offset, copy_len)?; user_reader.read_fallible(writer).map_err(|err| err.0)?; Ok(()) } fn write(&self, offset: usize, reader: &mut VmReader) -> ostd::Result<()> { let copy_len = reader.remain(); if copy_len > 0 { check_vaddr_lowerbound(offset)?; } let mut user_writer = self.vmar().vm_space().writer(offset, copy_len)?; user_writer.write_fallible(reader).map_err(|err| err.0)?; Ok(()) } } #[inherit_methods(from = "(**self)")] impl VmIo for &CurrentUserSpace<'_> { fn read(&self, offset: usize, writer: &mut VmWriter) -> ostd::Result<()>; fn write(&self, offset: usize, reader: &mut VmReader) -> ostd::Result<()>; } /// Checks if the user space pointer is below the lowest userspace address. /// /// If a pointer is below the lowest userspace address, it is likely to be a /// NULL pointer. Reading from or writing to a NULL pointer should trigger a /// segmentation fault. /// /// If it is not checked here, a kernel page fault will happen and we would /// deny the access in the page fault handler anyway. It may save a page fault /// in some occasions. More importantly, double page faults may not be handled /// quite well on some platforms. fn check_vaddr_lowerbound(va: Vaddr) -> ostd::Result<()> { if va < VMAR_LOWEST_ADDR { return Err(ostd::Error::PageFault); } Ok(()) }