blob: 5c7c5425afa3358fb0cdfe0edc52b609cd5c09d5 [file] [log] [blame]
// 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(
&current_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")
}
}