| // 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 fidl_fuchsia_io as fio; |
| use fuchsia_zircon as zx; |
| use std::ffi::CString; |
| use std::sync::Arc; |
| |
| use crate::fs::fuchsia::RemoteFs; |
| use crate::fs::tmpfs::TmpFs; |
| use crate::fs::*; |
| use crate::mm::{ |
| syscalls::{sys_mmap, sys_mremap}, |
| PAGE_SIZE, |
| }; |
| use crate::task::*; |
| use crate::types::*; |
| |
| /// Create an FsContext for use in testing. |
| /// |
| /// Open "/pkg" and returns an FsContext rooted in that directory. |
| fn create_pkgfs() -> Arc<FsContext> { |
| let rights = fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE; |
| let (server, client) = zx::Channel::create().expect("failed to create channel"); |
| fdio::open("/pkg", rights, server).expect("failed to open /pkg"); |
| return FsContext::new(RemoteFs::new(client, rights).unwrap()); |
| } |
| |
| /// Creates a `Kernel` and `Task` with the package file system for testing purposes. |
| /// |
| /// The `Task` is backed by a real process, and can be used to test syscalls. |
| pub fn create_kernel_and_task_with_pkgfs() -> (Arc<Kernel>, CurrentTask) { |
| create_kernel_and_task_with_fs(Some(create_pkgfs())) |
| } |
| |
| pub fn create_kernel_and_task() -> (Arc<Kernel>, CurrentTask) { |
| create_kernel_and_task_with_fs(None) |
| } |
| /// Creates a `Kernel` and `Task` for testing purposes. |
| /// |
| /// The `Task` is backed by a real process, and can be used to test syscalls. |
| pub fn create_kernel_and_task_with_fs( |
| mut fs: Option<Arc<FsContext>>, |
| ) -> (Arc<Kernel>, CurrentTask) { |
| let kernel = Arc::new( |
| Kernel::new(&CString::new("test-kernel").unwrap(), &Vec::new()) |
| .expect("failed to create kernel"), |
| ); |
| |
| if fs.is_none() { |
| fs = Some(FsContext::new(TmpFs::new(&kernel))); |
| } |
| |
| let task = Task::create_process_without_parent( |
| &kernel, |
| CString::new("test-task").unwrap(), |
| fs.unwrap(), |
| ) |
| .expect("failed to create first task"); |
| |
| // 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(); |
| } |
| |
| (kernel, task) |
| } |
| |
| /// Creates a new `Task` in the provided kernel. |
| /// |
| /// The `Task` is backed by a real process, and can be used to test syscalls. |
| pub fn create_task(kernel: &Arc<Kernel>, task_name: &str) -> CurrentTask { |
| let task = Task::create_process_without_parent( |
| kernel, |
| CString::new(task_name).unwrap(), |
| create_pkgfs(), |
| ) |
| .expect("failed to create second task"); |
| |
| // 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 |
| } |
| |
| /// Maps `length` at `address` with `PROT_READ | PROT_WRITE`, `MAP_ANONYMOUS | MAP_PRIVATE`. |
| /// |
| /// Returns the address returned by `sys_mmap`. |
| pub fn map_memory(current_task: &CurrentTask, address: UserAddress, length: u64) -> UserAddress { |
| sys_mmap( |
| ¤t_task, |
| address, |
| length as usize, |
| PROT_READ | PROT_WRITE, |
| MAP_ANONYMOUS | MAP_PRIVATE, |
| 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( |
| current_task: &CurrentTask, |
| old_addr: UserAddress, |
| old_length: u64, |
| new_length: u64, |
| flags: u32, |
| new_addr: UserAddress, |
| ) -> Result<UserAddress, Errno> { |
| sys_mremap(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.mm.write_memory(addr, &data) { |
| panic!("write page: failed to fill page @ {:?} with {:?}: {:?}", addr, 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 mut buf = Vec::with_capacity(*PAGE_SIZE as usize); |
| buf.resize(*PAGE_SIZE as usize, 0u8); |
| if let Err(err) = current_task.mm.read_memory(addr, &mut buf) { |
| panic!("read page: failed to read page @ {:?}: {:?}", addr, err); |
| } |
| assert!( |
| buf.into_iter().all(|c| c == data as u8), |
| "unexpected payload: page @ {:?} should be filled with {:?}", |
| addr, |
| 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 mut buf = Vec::with_capacity(*PAGE_SIZE as usize); |
| buf.resize(*PAGE_SIZE as usize, 0u8); |
| if let Err(err) = current_task.mm.read_memory(addr, &mut buf) { |
| panic!("read page: failed to read page @ {:?}: {:?}", addr, err); |
| } |
| assert!( |
| !buf.into_iter().all(|c| c == data as u8), |
| "unexpected payload: page @ {:?} should not be filled with {:?}", |
| addr, |
| 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) { |
| let mut buf = Vec::with_capacity(*PAGE_SIZE as usize); |
| buf.resize(*PAGE_SIZE as usize, 0u8); |
| match current_task.mm.read_memory(addr, &mut buf) { |
| Ok(()) => panic!("read page: page @ {:?} should be unmapped", addr), |
| Err(err) if err.value() == crate::types::uapi::EFAULT as i32 => {} |
| Err(err) => { |
| panic!("read page: expected EFAULT reading page @ {:?} but got {:} instead", addr, err) |
| } |
| } |
| } |
| |
| /// 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 PlaceholderFsNodeOps; |
| |
| impl FsNodeOps for PlaceholderFsNodeOps { |
| fn open(&self, _node: &FsNode, _flags: OpenFlags) -> Result<Box<dyn FileOps>, Errno> { |
| panic!("should not be called") |
| } |
| } |