blob: c20b9de961d3cbad90d97ed80d5506b50c12006a [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 crate::task::TaskBuilder;
use selinux::security_server::SecurityServer;
use starnix_sync::{FileOpsCore, Locked, Unlocked, WriteOps};
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::sync::Arc;
use zerocopy::{AsBytes, NoCell};
use {fidl_fuchsia_io as fio, fuchsia_zircon as zx};
use crate::device::init_common_devices;
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::task::{CurrentTask, Kernel, Task};
use crate::vfs::buffers::{InputBuffer, OutputBuffer};
use crate::vfs::{
fileops_impl_nonseekable, fs_node_impl_not_dir, Anon, CacheMode, FdNumber, FileHandle,
FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsContext,
FsNode, FsNodeOps, FsStr,
};
use starnix_syscalls::{SyscallArg, SyscallResult};
use starnix_uapi::errors::Errno;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::user_address::UserAddress;
use starnix_uapi::vfs::default_statfs;
use starnix_uapi::{statfs, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
/// Create a FileSystemHandle for use in testing.
///
/// Open "/pkg" and returns an FsContext rooted in that directory.
fn create_pkgfs(kernel: &Arc<Kernel>) -> FileSystemHandle {
let rights = fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE;
let (server, client) = zx::Channel::create();
fdio::open("/pkg", rights, server).expect("failed to open /pkg");
RemoteFs::new_fs(
kernel,
client,
FileSystemOptions { source: "/pkg".into(), ..Default::default() },
rights,
)
.unwrap()
}
/// Creates a `Kernel`, `Task`, and `Locked<Unlocked>` 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_task_and_unlocked_with_pkgfs<'l>(
) -> (Arc<Kernel>, AutoReleasableTask, Locked<'l, Unlocked>) {
create_kernel_task_and_unlocked_with_fs_and_selinux(create_pkgfs, None)
}
pub fn create_kernel_and_task() -> (Arc<Kernel>, AutoReleasableTask) {
let (kernel, task, _) = create_kernel_task_and_unlocked();
(kernel, task)
}
pub fn create_kernel_and_task_with_selinux(
security_server: Arc<SecurityServer>,
) -> (Arc<Kernel>, AutoReleasableTask) {
let (kernel, task, _) =
create_kernel_task_and_unlocked_with_fs_and_selinux(TmpFs::new_fs, Some(security_server));
(kernel, task)
}
pub fn create_kernel_task_and_unlocked<'l>(
) -> (Arc<Kernel>, AutoReleasableTask, Locked<'l, Unlocked>) {
create_kernel_task_and_unlocked_with_fs_and_selinux(TmpFs::new_fs, None)
}
pub fn create_kernel_task_and_unlocked_with_selinux<'l>(
security_server: Arc<SecurityServer>,
) -> (Arc<Kernel>, AutoReleasableTask, Locked<'l, Unlocked>) {
create_kernel_task_and_unlocked_with_fs_and_selinux(TmpFs::new_fs, Some(security_server))
}
/// Creates a `Kernel`, `Task`, and `Locked<Unlocked>` for testing purposes.
///
/// The `Task` is backed by a real process, and can be used to test syscalls.
fn create_kernel_task_and_unlocked_with_fs_and_selinux<'l>(
create_fs: impl FnOnce(&Arc<Kernel>) -> FileSystemHandle,
security_server: Option<Arc<SecurityServer>>,
) -> (Arc<Kernel>, AutoReleasableTask, Locked<'l, Unlocked>) {
let mut locked = Unlocked::new();
let kernel = Kernel::new(
b"".into(),
Default::default(),
None,
None,
None,
fuchsia_inspect::Node::default(),
None,
security_server,
)
.expect("failed to create kernel");
let init_pid = kernel.pids.write().allocate_pid();
assert_eq!(init_pid, 1);
let fs = FsContext::new(create_fs(&kernel));
let init_task = CurrentTask::create_init_process(
&mut locked,
&kernel,
init_pid,
CString::new("test-task").unwrap(),
fs.clone(),
&[],
)
.expect("failed to create first task");
let system_task =
CurrentTask::create_system_task(&mut locked, &kernel, fs).expect("create system task");
kernel.kthreads.init(system_task).expect("failed to initialize kthreads");
init_common_devices(&mut locked, &kernel.kthreads.system_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 = init_task.thread_group.read();
let _l2 = init_task.read();
}
(kernel, init_task.into(), locked)
}
/// 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(
locked: &mut Locked<'_, Unlocked>,
kernel: &Arc<Kernel>,
task_name: &str,
) -> AutoReleasableTask {
let task =
CurrentTask::create_init_child_process(locked, kernel, &CString::new(task_name).unwrap())
.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.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(current_task: &CurrentTask, len: u64) -> UserAddress {
map_memory(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<T: AsBytes + NoCell>(
current_task: &CurrentTask,
object: &T,
) -> UserAddress {
let addr = map_memory_anywhere(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(current_task: &CurrentTask, address: UserAddress, length: u64) -> UserAddress {
map_memory_with_flags(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(
current_task: &CurrentTask,
address: UserAddress,
length: u64,
flags: u32,
) -> UserAddress {
do_mmap(
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(current_task: &CurrentTask) -> FileHandle {
Anon::new_file(current_task, Box::new(PanickingFile), OpenFlags::RDWR)
}
}
impl FileOps for PanickingFile {
fileops_impl_nonseekable!();
fn write(
&self,
_locked: &mut Locked<'_, WriteOps>,
_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")
}
}
/// 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 += bytes_written;
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: AsBytes + NoCell>(&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 Drop for AutoReleasableTask {
fn drop(&mut self) {
let mut locked = Unlocked::new(); // TODO(mariagl): Find a way to avoid creating a new locked context here.
self.0.take().unwrap().release(&mut 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 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, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
Ok(default_statfs(0))
}
fn name(&self) -> &'static FsStr {
"test".into()
}
fn generate_node_ids(&self) -> bool {
false
}
}
pub fn create_fs(kernel: &Arc<Kernel>, ops: impl FsNodeOps) -> FileSystemHandle {
let test_fs = FileSystem::new(&kernel, CacheMode::Uncached, TestFs, Default::default())
.expect("testfs constructed with valid options");
let bus_dir_node = FsNode::new_root(ops);
test_fs.set_root_node(bus_dir_node);
test_fs
}