| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::device::mem::new_null_file; |
| use crate::execution::{ |
| create_init_child_process, create_init_process, create_system_task, |
| execute_task_with_prerun_result, |
| }; |
| use crate::fs::fuchsia::RemoteFs; |
| use crate::fs::tmpfs::TmpFs; |
| use crate::mm::syscalls::{do_mmap, sys_mremap}; |
| use crate::mm::{MemoryAccessor, MemoryAccessorExt, PAGE_SIZE}; |
| use crate::security; |
| use crate::task::container_namespace::ContainerNamespace; |
| use crate::task::{CurrentTask, Kernel, KernelOrTask, SchedulerManager, Task, TaskBuilder}; |
| use crate::vfs::buffers::{InputBuffer, OutputBuffer}; |
| use crate::vfs::{ |
| Anon, CacheMode, DirEntry, FdNumber, FileHandle, FileObject, FileOps, FileSystem, |
| FileSystemHandle, FileSystemOps, FileSystemOptions, FsContext, FsNode, FsNodeHandle, |
| FsNodeInfo, FsNodeOps, FsStr, Namespace, NamespaceNode, fileops_impl_nonseekable, |
| fileops_impl_noop_sync, fs_node_impl_not_dir, |
| }; |
| use fidl_fuchsia_io as fio; |
| use selinux::SecurityServer; |
| use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked}; |
| use starnix_syscalls::{SyscallArg, SyscallResult}; |
| use starnix_types::arch::ArchWidth; |
| use starnix_types::vfs::default_statfs; |
| use starnix_uapi::auth::FsCred; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::file_mode::mode; |
| use starnix_uapi::open_flags::OpenFlags; |
| use starnix_uapi::user_address::{ArchSpecific, UserAddress}; |
| use starnix_uapi::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE, statfs}; |
| use std::ffi::CString; |
| use std::mem::MaybeUninit; |
| use std::ops::Deref; |
| use std::sync::{Arc, mpsc}; |
| use zerocopy::{Immutable, IntoBytes}; |
| |
| /// Create a FileSystemHandle for use in testing. |
| /// |
| /// Open "/pkg" and returns an FsContext rooted in that directory. |
| fn create_pkgfs<L>(locked: &mut Locked<L>, kernel: &Kernel) -> FileSystemHandle |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE; |
| let (server, client) = zx::Channel::create(); |
| fdio::open("/pkg", rights, server).expect("failed to open /pkg"); |
| RemoteFs::new_fs( |
| locked, |
| kernel, |
| client, |
| FileSystemOptions { source: "/pkg".into(), ..Default::default() }, |
| rights, |
| ) |
| .unwrap() |
| } |
| |
| /// An old way of creating a task for testing |
| /// |
| /// This way of creating a task has problems because the test isn't actually run with that task |
| /// being current, which means that functions that expect a CurrentTask to actually be mapped into |
| /// memory can operate incorrectly. |
| /// |
| /// Please use `spawn_kernel_and_run` instead. If there isn't a variant of `spawn_kernel_and_run` |
| /// for this use case, please consider adding one that follows the new pattern of actually running |
| /// the test on the spawned task. |
| pub fn create_kernel_task_and_unlocked_with_pkgfs() |
| -> (Arc<Kernel>, AutoReleasableTask, &'static mut Locked<Unlocked>) { |
| create_kernel_task_and_unlocked_with_fs(create_pkgfs) |
| } |
| |
| /// An old way of creating a task for testing |
| /// |
| /// This way of creating a task has problems because the test isn't actually run with that task |
| /// being current, which means that functions that expect a CurrentTask to actually be mapped into |
| /// memory can operate incorrectly. |
| /// |
| /// Please use `spawn_kernel_and_run` instead. If there isn't a variant of `spawn_kernel_and_run` |
| /// for this use case, please consider adding one that follows the new pattern of actually running |
| /// the test on the spawned task. |
| pub fn create_kernel_and_task() -> (Arc<Kernel>, AutoReleasableTask) { |
| let (kernel, task, _) = create_kernel_task_and_unlocked(); |
| (kernel, task) |
| } |
| |
| /// Create a Kernel object and run the given callback in the init process for that kernel. |
| /// |
| /// This function is useful if you want to test code that requires a CurrentTask because |
| /// your callback is called with the init process as the CurrentTask. |
| pub fn spawn_kernel_and_run<F>(callback: F) |
| where |
| F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) + Send + Sync + 'static, |
| { |
| spawn_kernel_and_run_internal(callback, None, TmpFs::new_fs) |
| } |
| |
| /// Create a Kernel object and run the given callback in the init process for that kernel. |
| /// The task is rooted in a `pkgfs` instance. |
| /// |
| /// This function is useful if you want to test code that requires a CurrentTask because |
| /// your callback is called with the init process as the CurrentTask. |
| pub fn spawn_kernel_and_run_with_pkgfs<F>(callback: F) |
| where |
| F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) + Send + Sync + 'static, |
| { |
| spawn_kernel_and_run_internal(callback, None, create_pkgfs) |
| } |
| |
| /// Variant of `spawn_kernel_and_run()` that configures the kernel with SELinux enabled. |
| /// The supplied `callback` is invoked with an additional argument providing test access to the |
| /// SELinux security-server. |
| // TODO: https://fxbug.dev/335397745 - Only provide an admin/test API to the test, so that tests |
| // must generally exercise hooks via public entrypoints. |
| pub fn spawn_kernel_with_selinux_and_run<F>(callback: F) |
| where |
| F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask, &Arc<SecurityServer>) |
| + Send |
| + Sync |
| + 'static, |
| { |
| let security_server = SecurityServer::new_default(); |
| let security_server_for_callback = security_server.clone(); |
| spawn_kernel_and_run_internal( |
| move |unlocked, current_task| { |
| security::selinuxfs_init_null( |
| current_task, |
| &new_null_file(unlocked, current_task, OpenFlags::empty()), |
| ); |
| callback(unlocked, current_task, &security_server_for_callback) |
| }, |
| Some(security_server), |
| TmpFs::new_fs, |
| ) |
| } |
| |
| /// Create a Kernel object, with the optional caller-supplied `security_server`, and run the given |
| /// callback in the init process for that kernel. |
| fn spawn_kernel_and_run_internal<F, FS>( |
| callback: F, |
| security_server: Option<Arc<SecurityServer>>, |
| fs_factory: FS, |
| ) where |
| F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) + Send + Sync + 'static, |
| FS: FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle, |
| { |
| #[allow( |
| clippy::undocumented_unsafe_blocks, |
| reason = "Force documented unsafe blocks in Starnix" |
| )] |
| let locked = unsafe { Unlocked::new() }; |
| let kernel = create_test_kernel(locked, security_server); |
| let fs = create_test_fs_context(locked, &kernel, fs_factory); |
| let init_task = create_test_init_task(locked, &kernel, fs); |
| let (sender, receiver) = mpsc::channel(); |
| execute_task_with_prerun_result( |
| locked, |
| init_task, |
| |locked, current_task| { |
| callback(locked, current_task); |
| Ok(()) |
| }, |
| move |result| { |
| sender.send(result).expect("send task result"); |
| }, |
| None, |
| ) |
| .expect("successfully started task"); |
| let _ = receiver.recv().expect("received task result"); |
| } |
| |
| /// An old way of creating a task for testing |
| /// |
| /// This way of creating a task has problems because the test isn't actually run with that task |
| /// being current, which means that functions that expect a CurrentTask to actually be mapped into |
| /// memory can operate incorrectly. |
| /// |
| /// Please use `spawn_kernel_and_run` instead. If there isn't a variant of `spawn_kernel_and_run` |
| /// for this use case, please consider adding one that follows the new pattern of actually running |
| /// the test on the spawned task. |
| pub fn create_kernel_task_and_unlocked() |
| -> (Arc<Kernel>, AutoReleasableTask, &'static mut Locked<Unlocked>) { |
| create_kernel_task_and_unlocked_with_fs(TmpFs::new_fs) |
| } |
| |
| fn create_test_kernel( |
| _locked: &mut Locked<Unlocked>, |
| security_server: Option<Arc<SecurityServer>>, |
| ) -> Arc<Kernel> { |
| Kernel::new( |
| b"".into(), |
| Default::default(), |
| ContainerNamespace::new(), |
| SchedulerManager::empty_for_tests(), |
| None, |
| fuchsia_inspect::Node::default(), |
| security::testing::kernel_state(security_server), |
| Vec::new(), |
| /* time_adjustment_proxy=*/ None, |
| ) |
| .expect("failed to create kernel") |
| } |
| |
| fn create_test_fs_context( |
| locked: &mut Locked<Unlocked>, |
| kernel: &Kernel, |
| create_fs: impl FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle, |
| ) -> Arc<FsContext> { |
| FsContext::new(Namespace::new(create_fs(locked, kernel))) |
| } |
| |
| fn create_test_init_task( |
| locked: &mut Locked<Unlocked>, |
| kernel: &Kernel, |
| fs: Arc<FsContext>, |
| ) -> TaskBuilder { |
| let init_pid = kernel.pids.write().allocate_pid(); |
| assert_eq!(init_pid, 1); |
| let init_task = create_init_process( |
| locked, |
| &kernel.weak_self.upgrade().unwrap(), |
| init_pid, |
| CString::new("test-task").unwrap(), |
| fs.fork(), |
| &[], |
| ) |
| .expect("failed to create first task"); |
| init_task.mm().unwrap().initialize_mmap_layout_for_test(ArchWidth::Arch64); |
| |
| let system_task = create_system_task(locked, &kernel.weak_self.upgrade().unwrap(), fs) |
| .expect("create system task"); |
| kernel.kthreads.init(system_task).expect("failed to initialize kthreads"); |
| |
| let system_task = kernel.kthreads.system_task(); |
| kernel.hrtimer_manager.init(&system_task).expect("init hrtimer manager worker thread"); |
| |
| // Take the lock on thread group and task in the correct order to ensure any wrong ordering |
| // will trigger the tracing-mutex at the right call site. |
| { |
| let _l1 = init_task.thread_group().read(); |
| let _l2 = init_task.read(); |
| } |
| init_task |
| } |
| |
| fn create_kernel_task_and_unlocked_with_fs( |
| create_fs: impl FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle, |
| ) -> (Arc<Kernel>, AutoReleasableTask, &'static mut Locked<Unlocked>) { |
| #[allow( |
| clippy::undocumented_unsafe_blocks, |
| reason = "Force documented unsafe blocks in Starnix" |
| )] |
| let locked = unsafe { Unlocked::new() }; |
| let kernel = create_test_kernel(locked, None); |
| let fs = create_fs(locked, &kernel); |
| let fs_context = create_test_fs_context(locked, &kernel, |_, _| fs.clone()); |
| let init_task = create_test_init_task(locked, &kernel, fs_context); |
| (kernel, init_task.into(), locked) |
| } |
| |
| /// An old way of creating a task for testing |
| /// |
| /// This way of creating a task has problems because the test isn't actually run with that task |
| /// being current, which means that functions that expect a CurrentTask to actually be mapped into |
| /// memory can operate incorrectly. |
| /// |
| /// Please use `spawn_kernel_and_run` instead. If there isn't a variant of `spawn_kernel_and_run` |
| /// for this use case, please consider adding one that follows the new pattern of actually running |
| /// the test on the spawned task. |
| pub fn create_task( |
| locked: &mut Locked<Unlocked>, |
| kernel: &Kernel, |
| task_name: &str, |
| ) -> AutoReleasableTask { |
| let task = create_init_child_process( |
| locked, |
| &kernel.weak_self.upgrade().unwrap(), |
| &CString::new(task_name).unwrap(), |
| Some(&CString::new("#kernel").unwrap()), |
| ) |
| .expect("failed to create second task"); |
| task.mm().unwrap().initialize_mmap_layout_for_test(ArchWidth::Arch64); |
| |
| // Take the lock on thread group and task in the correct order to ensure any wrong ordering |
| // will trigger the tracing-mutex at the right call site. |
| { |
| let _l1 = task.thread_group().read(); |
| let _l2 = task.read(); |
| } |
| |
| task.into() |
| } |
| |
| /// Maps a region of mery at least `len` bytes long with `PROT_READ | PROT_WRITE`, |
| /// `MAP_ANONYMOUS | MAP_PRIVATE`, returning the mapped address. |
| pub fn map_memory_anywhere<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| len: u64, |
| ) -> UserAddress |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| map_memory(locked, current_task, UserAddress::NULL, len) |
| } |
| |
| /// Maps a region of memory large enough for the object with `PROT_READ | PROT_WRITE`, |
| /// `MAP_ANONYMOUS | MAP_PRIVATE` and writes the object to it, returning the mapped address. |
| /// |
| /// Useful for syscall in-pointer parameters. |
| pub fn map_object_anywhere<L, T>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| object: &T, |
| ) -> UserAddress |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| T: IntoBytes + Immutable, |
| { |
| let addr = map_memory_anywhere(locked, current_task, std::mem::size_of::<T>() as u64); |
| current_task.write_object(addr.into(), object).expect("could not write object"); |
| addr |
| } |
| |
| /// Maps `length` at `address` with `PROT_READ | PROT_WRITE`, `MAP_ANONYMOUS | MAP_PRIVATE`. |
| /// |
| /// Returns the address returned by `sys_mmap`. |
| pub fn map_memory<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| address: UserAddress, |
| length: u64, |
| ) -> UserAddress |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| map_memory_with_flags(locked, current_task, address, length, MAP_ANONYMOUS | MAP_PRIVATE) |
| } |
| |
| /// Maps `length` at `address` with `PROT_READ | PROT_WRITE` and the specified flags. |
| /// |
| /// Returns the address returned by `sys_mmap`. |
| pub fn map_memory_with_flags<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| address: UserAddress, |
| length: u64, |
| flags: u32, |
| ) -> UserAddress |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| do_mmap( |
| locked, |
| current_task, |
| address, |
| length as usize, |
| PROT_READ | PROT_WRITE, |
| flags, |
| FdNumber::from_raw(-1), |
| 0, |
| ) |
| .expect("Could not map memory") |
| } |
| |
| /// Convenience wrapper around [`sys_mremap`] which extracts the returned [`UserAddress`] from |
| /// the generic [`SyscallResult`]. |
| pub fn remap_memory( |
| locked: &mut Locked<Unlocked>, |
| current_task: &CurrentTask, |
| old_addr: UserAddress, |
| old_length: u64, |
| new_length: u64, |
| flags: u32, |
| new_addr: UserAddress, |
| ) -> Result<UserAddress, Errno> { |
| sys_mremap( |
| locked, |
| current_task, |
| old_addr, |
| old_length as usize, |
| new_length as usize, |
| flags, |
| new_addr, |
| ) |
| } |
| |
| /// Fills one page in the `current_task`'s address space starting at `addr` with the ASCII character |
| /// `data`. Panics if the write failed. |
| /// |
| /// This method uses the `#[track_caller]` attribute, which will display the caller's file and line |
| /// number in the event of a panic. This makes it easier to find test regressions. |
| #[track_caller] |
| pub fn fill_page(current_task: &CurrentTask, addr: UserAddress, data: char) { |
| let data = [data as u8].repeat(*PAGE_SIZE as usize); |
| if let Err(err) = current_task.write_memory(addr, &data) { |
| panic!("write page: failed to fill page @ {addr:?} with {data:?}: {err:?}"); |
| } |
| } |
| |
| /// Checks that the page in `current_task`'s address space starting at `addr` is readable. |
| /// Panics if the read failed, or the page was not filled with the ASCII character `data`. |
| /// |
| /// This method uses the `#[track_caller]` attribute, which will display the caller's file and line |
| /// number in the event of a panic. This makes it easier to find test regressions. |
| #[track_caller] |
| pub fn check_page_eq(current_task: &CurrentTask, addr: UserAddress, data: char) { |
| let buf = match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) { |
| Ok(b) => b, |
| Err(err) => panic!("read page: failed to read page @ {addr:?}: {err:?}"), |
| }; |
| assert!( |
| buf.into_iter().all(|c| c == data as u8), |
| "unexpected payload: page @ {addr:?} should be filled with {data:?}" |
| ); |
| } |
| |
| /// Checks that the page in `current_task`'s address space starting at `addr` is readable. |
| /// Panics if the read failed, or the page *was* filled with the ASCII character `data`. |
| /// |
| /// This method uses the `#[track_caller]` attribute, which will display the caller's file and line |
| /// number in the event of a panic. This makes it easier to find test regressions. |
| #[track_caller] |
| pub fn check_page_ne(current_task: &CurrentTask, addr: UserAddress, data: char) { |
| let buf = match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) { |
| Ok(b) => b, |
| Err(err) => panic!("read page: failed to read page @ {addr:?}: {err:?}"), |
| }; |
| assert!( |
| !buf.into_iter().all(|c| c == data as u8), |
| "unexpected payload: page @ {addr:?} should not be filled with {data:?}" |
| ); |
| } |
| |
| /// Checks that the page in `current_task`'s address space starting at `addr` is unmapped. |
| /// Panics if the read succeeds, or if an error other than `EFAULT` occurs. |
| /// |
| /// This method uses the `#[track_caller]` attribute, which will display the caller's file and line |
| /// number in the event of a panic. This makes it easier to find test regressions. |
| #[track_caller] |
| pub fn check_unmapped(current_task: &CurrentTask, addr: UserAddress) { |
| match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) { |
| Ok(_) => panic!("read page: page @ {addr:?} should be unmapped"), |
| Err(err) if err == starnix_uapi::errors::EFAULT => {} |
| Err(err) => { |
| panic!("read page: expected EFAULT reading page @ {addr:?} but got {err:?} instead") |
| } |
| } |
| } |
| |
| /// An FsNodeOps implementation that panics if you try to open it. Useful as a stand-in for testing |
| /// APIs that require a FsNodeOps implementation but don't actually use it. |
| pub struct PanickingFsNode; |
| |
| impl FsNodeOps for PanickingFsNode { |
| fs_node_impl_not_dir!(); |
| |
| fn create_file_ops( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _node: &FsNode, |
| _current_task: &CurrentTask, |
| _flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| panic!("should not be called") |
| } |
| } |
| |
| /// An implementation of [`FileOps`] that panics on any read, write, or ioctl operation. |
| pub struct PanickingFile; |
| |
| impl PanickingFile { |
| /// Creates a [`FileObject`] whose implementation panics on reads, writes, and ioctls. |
| pub fn new_file(locked: &mut Locked<Unlocked>, current_task: &CurrentTask) -> FileHandle { |
| anon_test_file(locked, current_task, Box::new(PanickingFile), OpenFlags::RDWR) |
| } |
| } |
| |
| impl FileOps for PanickingFile { |
| fileops_impl_nonseekable!(); |
| fileops_impl_noop_sync!(); |
| |
| fn write( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _data: &mut dyn InputBuffer, |
| ) -> Result<usize, Errno> { |
| panic!("write called on TestFile") |
| } |
| |
| fn read( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _offset: usize, |
| _data: &mut dyn OutputBuffer, |
| ) -> Result<usize, Errno> { |
| panic!("read called on TestFile") |
| } |
| |
| fn ioctl( |
| &self, |
| _locked: &mut Locked<Unlocked>, |
| _file: &FileObject, |
| _current_task: &CurrentTask, |
| _request: u32, |
| _arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| panic!("ioctl called on TestFile") |
| } |
| } |
| |
| /// Returns a new anonymous test file with the specified `ops` and `flags`. |
| pub fn anon_test_file<L>( |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| ops: Box<dyn FileOps>, |
| flags: OpenFlags, |
| ) -> FileHandle |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| // TODO: https://fxbug.dev/404739824 - Confirm whether to handle this as a "private" node. |
| Anon::new_private_file(locked, current_task, ops, flags, "[fuchsia:test_file]") |
| } |
| |
| /// Helper to write out data to a task's memory sequentially. |
| pub struct UserMemoryWriter<'a> { |
| // The task's memory manager. |
| mm: &'a Task, |
| // The address to which to write the next bit of data. |
| current_addr: UserAddress, |
| } |
| |
| impl<'a> UserMemoryWriter<'a> { |
| /// Constructs a new `UserMemoryWriter` to write to `task`'s memory at `addr`. |
| pub fn new(task: &'a Task, addr: UserAddress) -> Self { |
| Self { mm: task, current_addr: addr } |
| } |
| |
| /// Writes all of `data` to the current address in the task's address space, incrementing the |
| /// current address by the size of `data`. Returns the address at which the data starts. |
| /// Panics on failure. |
| pub fn write(&mut self, data: &[u8]) -> UserAddress { |
| let bytes_written = self.mm.write_memory(self.current_addr, data).unwrap(); |
| assert_eq!(bytes_written, data.len()); |
| let start_addr = self.current_addr; |
| self.current_addr = (self.current_addr + bytes_written).unwrap(); |
| start_addr |
| } |
| |
| /// Writes `object` to the current address in the task's address space, incrementing the |
| /// current address by the size of `object`. Returns the address at which the data starts. |
| /// Panics on failure. |
| pub fn write_object<T: IntoBytes + Immutable>(&mut self, object: &T) -> UserAddress { |
| self.write(object.as_bytes()) |
| } |
| |
| /// Returns the current address at which data will be next written. |
| pub fn current_address(&self) -> UserAddress { |
| self.current_addr |
| } |
| } |
| |
| #[derive(Debug)] |
| pub struct AutoReleasableTask(Option<CurrentTask>); |
| |
| impl AutoReleasableTask { |
| fn as_ref(this: &Self) -> &CurrentTask { |
| this.0.as_ref().unwrap() |
| } |
| |
| fn as_mut(this: &mut Self) -> &mut CurrentTask { |
| this.0.as_mut().unwrap() |
| } |
| } |
| |
| impl From<CurrentTask> for AutoReleasableTask { |
| fn from(task: CurrentTask) -> Self { |
| Self(Some(task)) |
| } |
| } |
| |
| impl From<TaskBuilder> for AutoReleasableTask { |
| fn from(builder: TaskBuilder) -> Self { |
| CurrentTask::from(builder).into() |
| } |
| } |
| |
| impl<'a> KernelOrTask<'a> for &'a AutoReleasableTask { |
| fn kernel(&self) -> &'a Kernel { |
| (self as &Task).kernel() |
| } |
| fn maybe_task(&self) -> Option<&'a CurrentTask> { |
| Some(&self) |
| } |
| } |
| |
| impl Drop for AutoReleasableTask { |
| fn drop(&mut self) { |
| // TODO(mariagl): Find a way to avoid creating a new locked context here. |
| #[allow( |
| clippy::undocumented_unsafe_blocks, |
| reason = "Force documented unsafe blocks in Starnix" |
| )] |
| let locked = unsafe { Unlocked::new() }; |
| self.0.take().unwrap().release(locked); |
| } |
| } |
| |
| impl std::ops::Deref for AutoReleasableTask { |
| type Target = CurrentTask; |
| |
| fn deref(&self) -> &Self::Target { |
| AutoReleasableTask::as_ref(self) |
| } |
| } |
| |
| impl std::ops::DerefMut for AutoReleasableTask { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| AutoReleasableTask::as_mut(self) |
| } |
| } |
| |
| impl std::borrow::Borrow<CurrentTask> for AutoReleasableTask { |
| fn borrow(&self) -> &CurrentTask { |
| AutoReleasableTask::as_ref(self) |
| } |
| } |
| |
| impl std::convert::AsRef<CurrentTask> for AutoReleasableTask { |
| fn as_ref(&self) -> &CurrentTask { |
| AutoReleasableTask::as_ref(self) |
| } |
| } |
| |
| impl ArchSpecific for AutoReleasableTask { |
| fn is_arch32(&self) -> bool { |
| self.deref().is_arch32() |
| } |
| } |
| |
| impl MemoryAccessor for AutoReleasableTask { |
| fn read_memory<'a>( |
| &self, |
| addr: UserAddress, |
| bytes: &'a mut [MaybeUninit<u8>], |
| ) -> Result<&'a mut [u8], Errno> { |
| (**self).read_memory(addr, bytes) |
| } |
| fn read_memory_partial_until_null_byte<'a>( |
| &self, |
| addr: UserAddress, |
| bytes: &'a mut [MaybeUninit<u8>], |
| ) -> Result<&'a mut [u8], Errno> { |
| (**self).read_memory_partial_until_null_byte(addr, bytes) |
| } |
| fn read_memory_partial<'a>( |
| &self, |
| addr: UserAddress, |
| bytes: &'a mut [MaybeUninit<u8>], |
| ) -> Result<&'a mut [u8], Errno> { |
| (**self).read_memory_partial(addr, bytes) |
| } |
| fn write_memory(&self, addr: UserAddress, bytes: &[u8]) -> Result<usize, Errno> { |
| (**self).write_memory(addr, bytes) |
| } |
| fn write_memory_partial(&self, addr: UserAddress, bytes: &[u8]) -> Result<usize, Errno> { |
| (**self).write_memory_partial(addr, bytes) |
| } |
| fn zero(&self, addr: UserAddress, length: usize) -> Result<usize, Errno> { |
| (**self).zero(addr, length) |
| } |
| } |
| |
| struct TestFs; |
| impl FileSystemOps for TestFs { |
| fn statfs( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _fs: &FileSystem, |
| _current_task: &CurrentTask, |
| ) -> Result<statfs, Errno> { |
| Ok(default_statfs(0)) |
| } |
| fn name(&self) -> &'static FsStr { |
| "test".into() |
| } |
| } |
| |
| pub fn create_testfs(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> FileSystemHandle { |
| FileSystem::new(locked, &kernel, CacheMode::Uncached, TestFs, Default::default()) |
| .expect("testfs constructed with valid options") |
| } |
| |
| pub fn create_testfs_with_root( |
| locked: &mut Locked<Unlocked>, |
| kernel: &Kernel, |
| ops: impl FsNodeOps, |
| ) -> FileSystemHandle { |
| let test_fs = create_testfs(locked, kernel); |
| let root_ino = test_fs.allocate_ino(); |
| test_fs.create_root(root_ino, ops); |
| test_fs |
| } |
| |
| pub fn create_fs_node_for_testing(fs: &FileSystemHandle, ops: impl FsNodeOps) -> FsNodeHandle { |
| let ino = fs.allocate_ino(); |
| let info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred::root()); |
| FsNode::new_uncached(ino, ops, fs, info) |
| } |
| |
| pub fn create_namespace_node_for_testing( |
| fs: &FileSystemHandle, |
| ops: impl FsNodeOps, |
| ) -> NamespaceNode { |
| let node = create_fs_node_for_testing(fs, ops); |
| NamespaceNode::new_anonymous(DirEntry::new_unrooted(node)) |
| } |