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