Skip to main content

starnix_core/
testing.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::device::mem::new_null_file;
6use crate::execution::{
7    create_init_child_process, create_init_process, create_system_task,
8    execute_task_with_prerun_result,
9};
10use crate::fs::fuchsia::RemoteFs;
11use crate::fs::tmpfs::TmpFs;
12use crate::mm::syscalls::{do_mmap, sys_mremap};
13use crate::mm::{MemoryAccessor, MemoryAccessorExt, MemoryManager, PAGE_SIZE};
14use crate::security;
15use crate::task::container_namespace::ContainerNamespace;
16use crate::task::{
17    CurrentTask, ExitStatus, Kernel, KernelFeatures, SchedulerManager, SystemLimits, Task,
18    TaskBuilder,
19};
20use crate::vfs::buffers::{InputBuffer, OutputBuffer};
21use crate::vfs::{
22    Anon, CacheMode, DirEntry, FdNumber, FileHandle, FileObject, FileOps, FileSystem,
23    FileSystemHandle, FileSystemOps, FileSystemOptions, FsContext, FsNode, FsNodeFlags,
24    FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, Namespace, NamespaceNode, fileops_impl_nonseekable,
25    fileops_impl_noop_sync, fs_node_impl_not_dir,
26};
27use fidl_fuchsia_io as fio;
28use fuchsia_async as fasync;
29use fuchsia_async::LocalExecutor;
30use selinux::SecurityServer;
31use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
32use starnix_syscalls::{SyscallArg, SyscallResult};
33use starnix_task_command::TaskCommand;
34use starnix_types::arch::ArchWidth;
35use starnix_types::vfs::default_statfs;
36use starnix_uapi::auth::{Credentials, FsCred};
37use starnix_uapi::errors::Errno;
38use starnix_uapi::file_mode::mode;
39use starnix_uapi::open_flags::OpenFlags;
40use starnix_uapi::user_address::{ArchSpecific, UserAddress};
41use starnix_uapi::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE, errno, error, statfs};
42use std::ffi::CString;
43use std::future::Future;
44use std::mem::MaybeUninit;
45use std::ops::Deref;
46use std::sync::{Arc, mpsc};
47use zerocopy::{Immutable, IntoBytes};
48
49/// Create a FileSystemHandle for use in testing.
50///
51/// Open "/pkg" and returns an FsContext rooted in that directory.
52fn create_pkgfs<L>(locked: &mut Locked<L>, kernel: &Kernel) -> FileSystemHandle
53where
54    L: LockEqualOrBefore<FileOpsCore>,
55{
56    let rights = fio::PERM_READABLE | fio::PERM_EXECUTABLE;
57    let (server, client) = zx::Channel::create();
58    fdio::open("/pkg", rights, server).expect("failed to open /pkg");
59    RemoteFs::new_fs(
60        locked,
61        kernel,
62        client,
63        FileSystemOptions { source: "/pkg".into(), ..Default::default() },
64        rights,
65    )
66    .unwrap()
67}
68
69/// Create a Kernel object and run the given callback in the init process for that kernel.
70///
71/// This function is useful if you want to test code that requires a CurrentTask because
72/// your callback is called with the init process as the CurrentTask.
73pub fn spawn_kernel_and_run<F, R>(callback: F) -> impl Future<Output = R>
74where
75    F: AsyncFnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
76    R: Send + Sync + 'static,
77{
78    spawn_kernel_and_run_internal(callback, None, TmpFs::new_fs, KernelFeatures::default())
79}
80
81/// Create and run a kernel with non-default feature settings.
82pub fn spawn_kernel_with_features_and_run<F, R>(
83    callback: F,
84    features: KernelFeatures,
85) -> impl Future<Output = R>
86where
87    F: AsyncFnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
88    R: Send + Sync + 'static,
89{
90    spawn_kernel_and_run_internal(callback, None, TmpFs::new_fs, features)
91}
92
93/// Create a Kernel object and run the given synchronous callback in the init process for that kernel.
94///
95/// This function is useful if you want to test code that requires a CurrentTask because
96/// your callback is called with the init process as the CurrentTask.
97pub fn spawn_kernel_and_run_sync<F, R>(callback: F) -> impl Future<Output = R>
98where
99    F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
100    R: Send + Sync + 'static,
101{
102    spawn_kernel_and_run_internal_sync(
103        callback,
104        None,
105        TmpFs::new_fs,
106        KernelFeatures::default(),
107        SchedulerManager::empty_for_tests(),
108    )
109}
110
111/// Create a Kernel object with a custom SchedulerManager and run the given synchronous callback.
112pub fn spawn_kernel_with_scheduler_and_run_sync<F, R>(
113    scheduler: SchedulerManager,
114    callback: F,
115) -> impl Future<Output = R>
116where
117    F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
118    R: Send + Sync + 'static,
119{
120    spawn_kernel_and_run_internal_sync(
121        callback,
122        None,
123        TmpFs::new_fs,
124        KernelFeatures::default(),
125        scheduler,
126    )
127}
128
129/// Create a Kernel object and run the given callback in the init process for that kernel.
130/// The task is rooted in a `pkgfs` instance.
131///
132/// This function is useful if you want to test code that requires a CurrentTask because
133/// your callback is called with the init process as the CurrentTask.
134pub fn spawn_kernel_and_run_with_pkgfs<F, R>(callback: F) -> impl Future<Output = R>
135where
136    F: AsyncFnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
137    R: Send + Sync + 'static,
138{
139    spawn_kernel_and_run_internal(callback, None, create_pkgfs, KernelFeatures::default())
140}
141
142/// Variant of `spawn_kernel_and_run()` that configures the kernel with SELinux enabled.
143/// The supplied `callback` is invoked with an additional argument providing test access to the
144/// SELinux security-server.
145// TODO: https://fxbug.dev/335397745 - Only provide an admin/test API to the test, so that tests
146// must generally exercise hooks via public entrypoints.
147pub async fn spawn_kernel_with_selinux_and_run<F, R>(callback: F) -> R
148where
149    F: AsyncFnOnce(&mut Locked<Unlocked>, &mut CurrentTask, &Arc<SecurityServer>) -> R
150        + Send
151        + Sync
152        + 'static,
153    R: Send + Sync + 'static,
154{
155    let security_server = SecurityServer::new_default();
156    let security_server_for_callback = security_server.clone();
157    spawn_kernel_and_run_internal(
158        async move |unlocked, current_task| {
159            security::selinuxfs_init_null(
160                current_task,
161                &new_null_file(unlocked, current_task, OpenFlags::empty()),
162            );
163            callback(unlocked, current_task, &security_server_for_callback).await
164        },
165        Some(security_server),
166        TmpFs::new_fs,
167        KernelFeatures::default(),
168    )
169    .await
170}
171
172/// Create a Kernel object, with the optional caller-supplied `security_server`, and run the given
173/// callback in the init process for that kernel.
174fn spawn_kernel_and_run_internal<F, FS, R>(
175    callback: F,
176    security_server: Option<Arc<SecurityServer>>,
177    fs_factory: FS,
178    features: KernelFeatures,
179) -> impl Future<Output = R>
180where
181    R: Send + Sync + 'static,
182    F: AsyncFnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
183    FS: FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle,
184{
185    spawn_kernel_and_run_internal_sync(
186        move |locked, current_task| {
187            LocalExecutor::default().run_singlethreaded(callback(locked, current_task))
188        },
189        security_server,
190        fs_factory,
191        features,
192        SchedulerManager::empty_for_tests(),
193    )
194}
195
196/// Create a Kernel object, with the optional caller-supplied `security_server`, and run the given
197/// synchronous callback in the init process for that kernel.
198fn spawn_kernel_and_run_internal_sync<F, FS, R>(
199    callback: F,
200    security_server: Option<Arc<SecurityServer>>,
201    fs_factory: FS,
202    features: KernelFeatures,
203    scheduler: SchedulerManager,
204) -> impl Future<Output = R>
205where
206    R: Send + Sync + 'static,
207    F: FnOnce(&mut Locked<Unlocked>, &mut CurrentTask) -> R + Send + Sync + 'static,
208    FS: FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle,
209{
210    #[allow(
211        clippy::undocumented_unsafe_blocks,
212        reason = "Force documented unsafe blocks in Starnix"
213    )]
214    let locked = unsafe { Unlocked::new() };
215    let kernel = create_test_kernel(locked, security_server, features, scheduler);
216    let fs = create_test_fs_context(locked, &kernel, fs_factory);
217    let init_task = create_test_init_task(locked, &kernel, fs);
218    fasync::unblock(move || {
219        let (sender, receiver) = mpsc::sync_channel(1);
220        let error = execute_task_with_prerun_result(
221            locked,
222            init_task,
223            move |locked, current_task| -> Result<(), Errno> {
224                let result = callback(locked, current_task);
225                current_task.write().set_exit_status_if_not_already(ExitStatus::Exit(0));
226                sender.send(result).map_err(|e| errno!(EIO, e))?;
227                error!(EHWPOISON)
228            },
229            |_| {},
230            None,
231        )
232        .unwrap_err();
233        // EHWPOISON is expected from the pre_run task, any other error is returned.
234        assert_eq!(error, errno!(EHWPOISON));
235        receiver.recv().expect("recv")
236    })
237}
238
239#[deprecated = "Do not add new callers, use spawn_kernel_and_run() instead."]
240pub fn create_kernel_task_and_unlocked()
241-> (Arc<Kernel>, AutoReleasableTask, &'static mut Locked<Unlocked>) {
242    create_kernel_task_and_unlocked_with_fs(TmpFs::new_fs)
243}
244
245fn create_test_kernel(
246    _locked: &mut Locked<Unlocked>,
247    security_server: Option<Arc<SecurityServer>>,
248    features: KernelFeatures,
249    scheduler: SchedulerManager,
250) -> Arc<Kernel> {
251    Kernel::new(
252        b"".into(),
253        features,
254        SystemLimits::default(),
255        ContainerNamespace::new(),
256        scheduler,
257        None,
258        fuchsia_inspect::Node::default(),
259        security::testing::kernel_state(security_server),
260        /* time_adjustment_proxy=*/ None,
261        /* device_tree=*/ None,
262    )
263    .expect("failed to create kernel")
264}
265
266fn create_test_fs_context(
267    locked: &mut Locked<Unlocked>,
268    kernel: &Kernel,
269    create_fs: impl FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle,
270) -> Arc<FsContext> {
271    FsContext::new(Namespace::new(create_fs(locked, kernel)))
272}
273
274/// Initializes a 64-bit address-space for the specified `task`.
275fn create_test_mm(task: &Task) -> Result<Arc<MemoryManager>, Errno> {
276    let arch_width = ArchWidth::Arch64;
277    let mm =
278        MemoryManager::new_for_test(task.thread_group().root_vmar.unowned(), ArchWidth::Arch64);
279    let fake_executable_addr = mm.get_random_base_for_executable(arch_width, 0)?;
280    mm.initialize_brk_origin(arch_width, fake_executable_addr)?;
281    task.running_state()?.mm.update(Some(mm.clone()));
282    Ok(mm)
283}
284
285fn create_test_init_task(
286    locked: &mut Locked<Unlocked>,
287    kernel: &Kernel,
288    fs: Arc<FsContext>,
289) -> TaskBuilder {
290    let init_pid = kernel.pids.write().allocate_pid();
291    assert_eq!(init_pid, 1);
292    let init_task = create_init_process(
293        locked,
294        &kernel.weak_self.upgrade().unwrap(),
295        init_pid,
296        TaskCommand::new(b"test-task"),
297        fs.fork(),
298        &[],
299    )
300    .expect("failed to create first task");
301    create_test_mm(&init_task).expect("failed to create MM");
302
303    let system_task = create_system_task(locked, &kernel.weak_self.upgrade().unwrap(), fs)
304        .expect("create system task");
305    kernel.kthreads.init(system_task).expect("failed to initialize kthreads");
306
307    let system_task = kernel.kthreads.system_task();
308    kernel.hrtimer_manager.init(&system_task).expect("init hrtimer manager worker thread");
309
310    // Take the lock on thread group and task in the correct order to ensure any wrong ordering
311    // will trigger the tracing-mutex at the right call site.
312    {
313        let _l1 = init_task.thread_group().read();
314        let _l2 = init_task.read();
315    }
316    init_task
317}
318
319fn create_kernel_task_and_unlocked_with_fs(
320    create_fs: impl FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle,
321) -> (Arc<Kernel>, AutoReleasableTask, &'static mut Locked<Unlocked>) {
322    #[allow(
323        clippy::undocumented_unsafe_blocks,
324        reason = "Force documented unsafe blocks in Starnix"
325    )]
326    let locked = unsafe { Unlocked::new() };
327    let kernel = create_test_kernel(
328        locked,
329        None,
330        KernelFeatures::default(),
331        SchedulerManager::empty_for_tests(),
332    );
333    let fs = create_fs(locked, &kernel);
334    let fs_context = create_test_fs_context(locked, &kernel, |_, _| fs.clone());
335    let init_task = create_test_init_task(locked, &kernel, fs_context);
336    (kernel, init_task.into(), locked)
337}
338
339/// An old way of creating a task for testing
340///
341/// This way of creating a task has problems because the test isn't actually run with that task
342/// being current, which means that functions that expect a CurrentTask to actually be mapped into
343/// memory can operate incorrectly.
344///
345/// Please use `spawn_kernel_and_run` instead. If there isn't a variant of `spawn_kernel_and_run`
346/// for this use case, please consider adding one that follows the new pattern of actually running
347/// the test on the spawned task.
348pub fn create_task(
349    locked: &mut Locked<Unlocked>,
350    kernel: &Kernel,
351    task_name: &str,
352) -> AutoReleasableTask {
353    create_task_with_security_context(locked, kernel, task_name, &CString::new("#kernel").unwrap())
354}
355
356/// An old way of creating a task for testing, with a given security context.
357///
358/// See caveats on `create_task`.
359pub fn create_task_with_security_context(
360    locked: &mut Locked<Unlocked>,
361    kernel: &Kernel,
362    task_name: &str,
363    security_context: &CString,
364) -> AutoReleasableTask {
365    let task = create_init_child_process(
366        locked,
367        &kernel.weak_self.upgrade().unwrap(),
368        TaskCommand::new(task_name.as_bytes()),
369        Credentials::with_ids(0, 0),
370        Some(security_context),
371    )
372    .expect("failed to create second task");
373    create_test_mm(&task).expect("failed to create MM");
374
375    // Take the lock on thread group and task in the correct order to ensure any wrong ordering
376    // will trigger the tracing-mutex at the right call site.
377    {
378        let _l1 = task.thread_group().read();
379        let _l2 = task.read();
380    }
381
382    task.into()
383}
384
385/// Maps a region of mery at least `len` bytes long with `PROT_READ | PROT_WRITE`,
386/// `MAP_ANONYMOUS | MAP_PRIVATE`, returning the mapped address.
387pub fn map_memory_anywhere<L>(
388    locked: &mut Locked<L>,
389    current_task: &CurrentTask,
390    len: u64,
391) -> UserAddress
392where
393    L: LockEqualOrBefore<FileOpsCore> + starnix_sync::LockBefore<starnix_sync::ThreadGroupLimits>,
394{
395    map_memory(locked, current_task, UserAddress::NULL, len)
396}
397
398/// Maps a region of memory large enough for the object with `PROT_READ | PROT_WRITE`,
399/// `MAP_ANONYMOUS | MAP_PRIVATE` and writes the object to it, returning the mapped address.
400///
401/// Useful for syscall in-pointer parameters.
402pub fn map_object_anywhere<L, T>(
403    locked: &mut Locked<L>,
404    current_task: &CurrentTask,
405    object: &T,
406) -> UserAddress
407where
408    L: LockEqualOrBefore<FileOpsCore> + starnix_sync::LockBefore<starnix_sync::ThreadGroupLimits>,
409    T: IntoBytes + Immutable,
410{
411    let addr = map_memory_anywhere(locked, current_task, std::mem::size_of::<T>() as u64);
412    current_task.write_object(addr.into(), object).expect("could not write object");
413    addr
414}
415
416/// Maps `length` at `address` with `PROT_READ | PROT_WRITE`, `MAP_ANONYMOUS | MAP_PRIVATE`.
417///
418/// Returns the address returned by `sys_mmap`.
419pub fn map_memory<L>(
420    locked: &mut Locked<L>,
421    current_task: &CurrentTask,
422    address: UserAddress,
423    length: u64,
424) -> UserAddress
425where
426    L: LockEqualOrBefore<FileOpsCore> + starnix_sync::LockBefore<starnix_sync::ThreadGroupLimits>,
427{
428    map_memory_with_flags(locked, current_task, address, length, MAP_ANONYMOUS | MAP_PRIVATE)
429}
430
431/// Maps `length` at `address` with `PROT_READ | PROT_WRITE` and the specified flags.
432///
433/// Returns the address returned by `sys_mmap`.
434pub fn map_memory_with_flags<L>(
435    locked: &mut Locked<L>,
436    current_task: &CurrentTask,
437    address: UserAddress,
438    length: u64,
439    flags: u32,
440) -> UserAddress
441where
442    L: LockEqualOrBefore<FileOpsCore> + starnix_sync::LockBefore<starnix_sync::ThreadGroupLimits>,
443{
444    do_mmap(
445        locked,
446        current_task,
447        address,
448        length as usize,
449        PROT_READ | PROT_WRITE,
450        flags,
451        FdNumber::from_raw(-1),
452        0,
453    )
454    .expect("Could not map memory")
455}
456
457/// Convenience wrapper around [`sys_mremap`] which extracts the returned [`UserAddress`] from
458/// the generic [`SyscallResult`].
459pub fn remap_memory(
460    locked: &mut Locked<Unlocked>,
461    current_task: &CurrentTask,
462    old_addr: UserAddress,
463    old_length: u64,
464    new_length: u64,
465    flags: u32,
466    new_addr: UserAddress,
467) -> Result<UserAddress, Errno> {
468    sys_mremap(
469        locked,
470        current_task,
471        old_addr,
472        old_length as usize,
473        new_length as usize,
474        flags,
475        new_addr,
476    )
477}
478
479/// Fills one page in the `current_task`'s address space starting at `addr` with the ASCII character
480/// `data`. Panics if the write failed.
481///
482/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
483/// number in the event of a panic. This makes it easier to find test regressions.
484#[track_caller]
485pub fn fill_page(current_task: &CurrentTask, addr: UserAddress, data: char) {
486    let data = [data as u8].repeat(*PAGE_SIZE as usize);
487    if let Err(err) = current_task.write_memory(addr, &data) {
488        panic!("write page: failed to fill page @ {addr:?} with {data:?}: {err:?}");
489    }
490}
491
492/// Checks that the page in `current_task`'s address space starting at `addr` is readable.
493/// Panics if the read failed, or the page was not filled with the ASCII character `data`.
494///
495/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
496/// number in the event of a panic. This makes it easier to find test regressions.
497#[track_caller]
498pub fn check_page_eq(current_task: &CurrentTask, addr: UserAddress, data: char) {
499    let buf = match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) {
500        Ok(b) => b,
501        Err(err) => panic!("read page: failed to read page @ {addr:?}: {err:?}"),
502    };
503    assert!(
504        buf.into_iter().all(|c| c == data as u8),
505        "unexpected payload: page @ {addr:?} should be filled with {data:?}"
506    );
507}
508
509/// Checks that the page in `current_task`'s address space starting at `addr` is readable.
510/// Panics if the read failed, or the page *was* filled with the ASCII character `data`.
511///
512/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
513/// number in the event of a panic. This makes it easier to find test regressions.
514#[track_caller]
515pub fn check_page_ne(current_task: &CurrentTask, addr: UserAddress, data: char) {
516    let buf = match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) {
517        Ok(b) => b,
518        Err(err) => panic!("read page: failed to read page @ {addr:?}: {err:?}"),
519    };
520    assert!(
521        !buf.into_iter().all(|c| c == data as u8),
522        "unexpected payload: page @ {addr:?} should not be filled with {data:?}"
523    );
524}
525
526/// Checks that the page in `current_task`'s address space starting at `addr` is unmapped.
527/// Panics if the read succeeds, or if an error other than `EFAULT` occurs.
528///
529/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
530/// number in the event of a panic. This makes it easier to find test regressions.
531#[track_caller]
532pub fn check_unmapped(current_task: &CurrentTask, addr: UserAddress) {
533    match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) {
534        Ok(_) => panic!("read page: page @ {addr:?} should be unmapped"),
535        Err(err) if err == starnix_uapi::errors::EFAULT => {}
536        Err(err) => {
537            panic!("read page: expected EFAULT reading page @ {addr:?} but got {err:?} instead")
538        }
539    }
540}
541
542/// An FsNodeOps implementation that panics if you try to open it. Useful as a stand-in for testing
543/// APIs that require a FsNodeOps implementation but don't actually use it.
544pub struct PanickingFsNode;
545
546impl FsNodeOps for PanickingFsNode {
547    fs_node_impl_not_dir!();
548
549    fn create_file_ops(
550        &self,
551        _locked: &mut Locked<FileOpsCore>,
552        _node: &FsNode,
553        _current_task: &CurrentTask,
554        _flags: OpenFlags,
555    ) -> Result<Box<dyn FileOps>, Errno> {
556        panic!("should not be called")
557    }
558}
559
560/// An implementation of [`FileOps`] that panics on any read, write, or ioctl operation.
561pub struct PanickingFile;
562
563impl PanickingFile {
564    /// Creates a [`FileObject`] whose implementation panics on reads, writes, and ioctls.
565    pub fn new_file(locked: &mut Locked<Unlocked>, current_task: &CurrentTask) -> FileHandle {
566        anon_test_file(locked, current_task, Box::new(PanickingFile), OpenFlags::RDWR)
567    }
568}
569
570impl FileOps for PanickingFile {
571    fileops_impl_nonseekable!();
572    fileops_impl_noop_sync!();
573
574    fn write(
575        &self,
576        _locked: &mut Locked<FileOpsCore>,
577        _file: &FileObject,
578        _current_task: &CurrentTask,
579        _offset: usize,
580        _data: &mut dyn InputBuffer,
581    ) -> Result<usize, Errno> {
582        panic!("write called on TestFile")
583    }
584
585    fn read(
586        &self,
587        _locked: &mut Locked<FileOpsCore>,
588        _file: &FileObject,
589        _current_task: &CurrentTask,
590        _offset: usize,
591        _data: &mut dyn OutputBuffer,
592    ) -> Result<usize, Errno> {
593        panic!("read called on TestFile")
594    }
595
596    fn ioctl(
597        &self,
598        _locked: &mut Locked<Unlocked>,
599        _file: &FileObject,
600        _current_task: &CurrentTask,
601        _request: u32,
602        _arg: SyscallArg,
603    ) -> Result<SyscallResult, Errno> {
604        panic!("ioctl called on TestFile")
605    }
606}
607
608/// Returns a new anonymous test file with the specified `ops` and `flags`.
609pub fn anon_test_file<L>(
610    locked: &mut Locked<L>,
611    current_task: &CurrentTask,
612    ops: Box<dyn FileOps>,
613    flags: OpenFlags,
614) -> FileHandle
615where
616    L: LockEqualOrBefore<FileOpsCore>,
617{
618    // TODO: https://fxbug.dev/404739824 - Confirm whether to handle this as a "private" node.
619    Anon::new_private_file(locked, current_task, ops, flags, "[fuchsia:test_file]")
620}
621
622/// Helper to write out data to a task's memory sequentially.
623pub struct UserMemoryWriter<'a> {
624    // The task's memory manager.
625    mm: &'a Task,
626    // The address to which to write the next bit of data.
627    current_addr: UserAddress,
628}
629
630impl<'a> UserMemoryWriter<'a> {
631    /// Constructs a new `UserMemoryWriter` to write to `task`'s memory at `addr`.
632    pub fn new(task: &'a Task, addr: UserAddress) -> Self {
633        Self { mm: task, current_addr: addr }
634    }
635
636    /// Writes all of `data` to the current address in the task's address space, incrementing the
637    /// current address by the size of `data`. Returns the address at which the data starts.
638    /// Panics on failure.
639    pub fn write(&mut self, data: &[u8]) -> UserAddress {
640        let bytes_written = self.mm.write_memory(self.current_addr, data).unwrap();
641        assert_eq!(bytes_written, data.len());
642        let start_addr = self.current_addr;
643        self.current_addr = (self.current_addr + bytes_written).unwrap();
644        start_addr
645    }
646
647    /// Writes `object` to the current address in the task's address space, incrementing the
648    /// current address by the size of `object`. Returns the address at which the data starts.
649    /// Panics on failure.
650    pub fn write_object<T: IntoBytes + Immutable>(&mut self, object: &T) -> UserAddress {
651        self.write(object.as_bytes())
652    }
653
654    /// Returns the current address at which data will be next written.
655    pub fn current_address(&self) -> UserAddress {
656        self.current_addr
657    }
658}
659
660#[derive(Debug)]
661pub struct AutoReleasableTask(Option<CurrentTask>);
662
663impl AutoReleasableTask {
664    fn as_ref(this: &Self) -> &CurrentTask {
665        this.0.as_ref().unwrap()
666    }
667
668    fn as_mut(this: &mut Self) -> &mut CurrentTask {
669        this.0.as_mut().unwrap()
670    }
671}
672
673impl From<CurrentTask> for AutoReleasableTask {
674    fn from(task: CurrentTask) -> Self {
675        Self(Some(task))
676    }
677}
678
679impl From<TaskBuilder> for AutoReleasableTask {
680    fn from(builder: TaskBuilder) -> Self {
681        CurrentTask::from(builder).into()
682    }
683}
684
685impl Drop for AutoReleasableTask {
686    fn drop(&mut self) {
687        // TODO(mariagl): Find a way to avoid creating a new locked context here.
688        #[allow(
689            clippy::undocumented_unsafe_blocks,
690            reason = "Force documented unsafe blocks in Starnix"
691        )]
692        let locked = unsafe { Unlocked::new() };
693        self.0.take().unwrap().release(locked);
694    }
695}
696
697impl std::ops::Deref for AutoReleasableTask {
698    type Target = CurrentTask;
699
700    fn deref(&self) -> &Self::Target {
701        AutoReleasableTask::as_ref(self)
702    }
703}
704
705impl std::ops::DerefMut for AutoReleasableTask {
706    fn deref_mut(&mut self) -> &mut Self::Target {
707        AutoReleasableTask::as_mut(self)
708    }
709}
710
711impl std::borrow::Borrow<CurrentTask> for AutoReleasableTask {
712    fn borrow(&self) -> &CurrentTask {
713        AutoReleasableTask::as_ref(self)
714    }
715}
716
717impl std::convert::AsRef<CurrentTask> for AutoReleasableTask {
718    fn as_ref(&self) -> &CurrentTask {
719        AutoReleasableTask::as_ref(self)
720    }
721}
722
723impl ArchSpecific for AutoReleasableTask {
724    fn is_arch32(&self) -> bool {
725        self.deref().is_arch32()
726    }
727}
728
729impl MemoryAccessor for AutoReleasableTask {
730    fn read_memory<'a>(
731        &self,
732        addr: UserAddress,
733        bytes: &'a mut [MaybeUninit<u8>],
734    ) -> Result<&'a mut [u8], Errno> {
735        (**self).read_memory(addr, bytes)
736    }
737    fn read_memory_partial_until_null_byte<'a>(
738        &self,
739        addr: UserAddress,
740        bytes: &'a mut [MaybeUninit<u8>],
741    ) -> Result<&'a mut [u8], Errno> {
742        (**self).read_memory_partial_until_null_byte(addr, bytes)
743    }
744    fn read_memory_partial<'a>(
745        &self,
746        addr: UserAddress,
747        bytes: &'a mut [MaybeUninit<u8>],
748    ) -> Result<&'a mut [u8], Errno> {
749        (**self).read_memory_partial(addr, bytes)
750    }
751    fn write_memory(&self, addr: UserAddress, bytes: &[u8]) -> Result<usize, Errno> {
752        (**self).write_memory(addr, bytes)
753    }
754    fn write_memory_partial(&self, addr: UserAddress, bytes: &[u8]) -> Result<usize, Errno> {
755        (**self).write_memory_partial(addr, bytes)
756    }
757    fn zero(&self, addr: UserAddress, length: usize) -> Result<usize, Errno> {
758        (**self).zero(addr, length)
759    }
760}
761
762struct TestFs;
763impl FileSystemOps for TestFs {
764    fn statfs(
765        &self,
766        _locked: &mut Locked<FileOpsCore>,
767        _fs: &FileSystem,
768        _current_task: &CurrentTask,
769    ) -> Result<statfs, Errno> {
770        Ok(default_statfs(0))
771    }
772    fn name(&self) -> &'static FsStr {
773        "test".into()
774    }
775}
776
777pub fn create_testfs(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> FileSystemHandle {
778    FileSystem::new(locked, &kernel, CacheMode::Uncached, TestFs, Default::default())
779        .expect("testfs constructed with valid options")
780}
781
782pub fn create_testfs_with_root(
783    locked: &mut Locked<Unlocked>,
784    kernel: &Kernel,
785    ops: impl FsNodeOps,
786) -> FileSystemHandle {
787    let test_fs = create_testfs(locked, kernel);
788    let root_ino = test_fs.allocate_ino();
789    test_fs.create_root(root_ino, ops);
790    test_fs
791}
792
793pub fn create_fs_node_for_testing(fs: &FileSystemHandle, ops: impl FsNodeOps) -> FsNodeHandle {
794    let ino = fs.allocate_ino();
795    let info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred::root());
796    FsNode::new_uncached(ino, ops, fs, info, FsNodeFlags::empty())
797}
798
799pub fn create_namespace_node_for_testing(
800    fs: &FileSystemHandle,
801    ops: impl FsNodeOps,
802) -> NamespaceNode {
803    let node = create_fs_node_for_testing(fs, ops);
804    NamespaceNode::new_anonymous(DirEntry::new_unrooted(node))
805}