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        Vec::new(),
216        /* time_adjustment_proxy=*/ None,
217        /* device_tree=*/ None,
218    )
219    .expect("failed to create kernel")
220}
221
222fn create_test_fs_context(
223    locked: &mut Locked<Unlocked>,
224    kernel: &Kernel,
225    create_fs: impl FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle,
226) -> Arc<FsContext> {
227    FsContext::new(Namespace::new(create_fs(locked, kernel)))
228}
229
230fn create_test_init_task(
231    locked: &mut Locked<Unlocked>,
232    kernel: &Kernel,
233    fs: Arc<FsContext>,
234) -> TaskBuilder {
235    let init_pid = kernel.pids.write().allocate_pid();
236    assert_eq!(init_pid, 1);
237    let init_task = create_init_process(
238        locked,
239        &kernel.weak_self.upgrade().unwrap(),
240        init_pid,
241        TaskCommand::new(b"test-task"),
242        fs.fork(),
243        &[],
244    )
245    .expect("failed to create first task");
246    init_task.mm().unwrap().initialize_mmap_layout_for_test(ArchWidth::Arch64);
247
248    let system_task = create_system_task(locked, &kernel.weak_self.upgrade().unwrap(), fs)
249        .expect("create system task");
250    kernel.kthreads.init(system_task).expect("failed to initialize kthreads");
251
252    let system_task = kernel.kthreads.system_task();
253    kernel.hrtimer_manager.init(&system_task).expect("init hrtimer manager worker thread");
254
255    // Take the lock on thread group and task in the correct order to ensure any wrong ordering
256    // will trigger the tracing-mutex at the right call site.
257    {
258        let _l1 = init_task.thread_group().read();
259        let _l2 = init_task.read();
260    }
261    init_task
262}
263
264fn create_kernel_task_and_unlocked_with_fs(
265    create_fs: impl FnOnce(&mut Locked<Unlocked>, &Kernel) -> FileSystemHandle,
266) -> (Arc<Kernel>, AutoReleasableTask, &'static mut Locked<Unlocked>) {
267    #[allow(
268        clippy::undocumented_unsafe_blocks,
269        reason = "Force documented unsafe blocks in Starnix"
270    )]
271    let locked = unsafe { Unlocked::new() };
272    let kernel = create_test_kernel(locked, None);
273    let fs = create_fs(locked, &kernel);
274    let fs_context = create_test_fs_context(locked, &kernel, |_, _| fs.clone());
275    let init_task = create_test_init_task(locked, &kernel, fs_context);
276    (kernel, init_task.into(), locked)
277}
278
279/// An old way of creating a task for testing
280///
281/// This way of creating a task has problems because the test isn't actually run with that task
282/// being current, which means that functions that expect a CurrentTask to actually be mapped into
283/// memory can operate incorrectly.
284///
285/// Please use `spawn_kernel_and_run` instead. If there isn't a variant of `spawn_kernel_and_run`
286/// for this use case, please consider adding one that follows the new pattern of actually running
287/// the test on the spawned task.
288pub fn create_task(
289    locked: &mut Locked<Unlocked>,
290    kernel: &Kernel,
291    task_name: &str,
292) -> AutoReleasableTask {
293    let task = create_init_child_process(
294        locked,
295        &kernel.weak_self.upgrade().unwrap(),
296        TaskCommand::new(task_name.as_bytes()),
297        Some(&CString::new("#kernel").unwrap()),
298    )
299    .expect("failed to create second task");
300    task.mm().unwrap().initialize_mmap_layout_for_test(ArchWidth::Arch64);
301
302    // Take the lock on thread group and task in the correct order to ensure any wrong ordering
303    // will trigger the tracing-mutex at the right call site.
304    {
305        let _l1 = task.thread_group().read();
306        let _l2 = task.read();
307    }
308
309    task.into()
310}
311
312/// Maps a region of mery at least `len` bytes long with `PROT_READ | PROT_WRITE`,
313/// `MAP_ANONYMOUS | MAP_PRIVATE`, returning the mapped address.
314pub fn map_memory_anywhere<L>(
315    locked: &mut Locked<L>,
316    current_task: &CurrentTask,
317    len: u64,
318) -> UserAddress
319where
320    L: LockEqualOrBefore<FileOpsCore>,
321{
322    map_memory(locked, current_task, UserAddress::NULL, len)
323}
324
325/// Maps a region of memory large enough for the object with `PROT_READ | PROT_WRITE`,
326/// `MAP_ANONYMOUS | MAP_PRIVATE` and writes the object to it, returning the mapped address.
327///
328/// Useful for syscall in-pointer parameters.
329pub fn map_object_anywhere<L, T>(
330    locked: &mut Locked<L>,
331    current_task: &CurrentTask,
332    object: &T,
333) -> UserAddress
334where
335    L: LockEqualOrBefore<FileOpsCore>,
336    T: IntoBytes + Immutable,
337{
338    let addr = map_memory_anywhere(locked, current_task, std::mem::size_of::<T>() as u64);
339    current_task.write_object(addr.into(), object).expect("could not write object");
340    addr
341}
342
343/// Maps `length` at `address` with `PROT_READ | PROT_WRITE`, `MAP_ANONYMOUS | MAP_PRIVATE`.
344///
345/// Returns the address returned by `sys_mmap`.
346pub fn map_memory<L>(
347    locked: &mut Locked<L>,
348    current_task: &CurrentTask,
349    address: UserAddress,
350    length: u64,
351) -> UserAddress
352where
353    L: LockEqualOrBefore<FileOpsCore>,
354{
355    map_memory_with_flags(locked, current_task, address, length, MAP_ANONYMOUS | MAP_PRIVATE)
356}
357
358/// Maps `length` at `address` with `PROT_READ | PROT_WRITE` and the specified flags.
359///
360/// Returns the address returned by `sys_mmap`.
361pub fn map_memory_with_flags<L>(
362    locked: &mut Locked<L>,
363    current_task: &CurrentTask,
364    address: UserAddress,
365    length: u64,
366    flags: u32,
367) -> UserAddress
368where
369    L: LockEqualOrBefore<FileOpsCore>,
370{
371    do_mmap(
372        locked,
373        current_task,
374        address,
375        length as usize,
376        PROT_READ | PROT_WRITE,
377        flags,
378        FdNumber::from_raw(-1),
379        0,
380    )
381    .expect("Could not map memory")
382}
383
384/// Convenience wrapper around [`sys_mremap`] which extracts the returned [`UserAddress`] from
385/// the generic [`SyscallResult`].
386pub fn remap_memory(
387    locked: &mut Locked<Unlocked>,
388    current_task: &CurrentTask,
389    old_addr: UserAddress,
390    old_length: u64,
391    new_length: u64,
392    flags: u32,
393    new_addr: UserAddress,
394) -> Result<UserAddress, Errno> {
395    sys_mremap(
396        locked,
397        current_task,
398        old_addr,
399        old_length as usize,
400        new_length as usize,
401        flags,
402        new_addr,
403    )
404}
405
406/// Fills one page in the `current_task`'s address space starting at `addr` with the ASCII character
407/// `data`. Panics if the write failed.
408///
409/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
410/// number in the event of a panic. This makes it easier to find test regressions.
411#[track_caller]
412pub fn fill_page(current_task: &CurrentTask, addr: UserAddress, data: char) {
413    let data = [data as u8].repeat(*PAGE_SIZE as usize);
414    if let Err(err) = current_task.write_memory(addr, &data) {
415        panic!("write page: failed to fill page @ {addr:?} with {data:?}: {err:?}");
416    }
417}
418
419/// Checks that the page in `current_task`'s address space starting at `addr` is readable.
420/// Panics if the read failed, or the page was not filled with the ASCII character `data`.
421///
422/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
423/// number in the event of a panic. This makes it easier to find test regressions.
424#[track_caller]
425pub fn check_page_eq(current_task: &CurrentTask, addr: UserAddress, data: char) {
426    let buf = match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) {
427        Ok(b) => b,
428        Err(err) => panic!("read page: failed to read page @ {addr:?}: {err:?}"),
429    };
430    assert!(
431        buf.into_iter().all(|c| c == data as u8),
432        "unexpected payload: page @ {addr:?} should be filled with {data:?}"
433    );
434}
435
436/// Checks that the page in `current_task`'s address space starting at `addr` is readable.
437/// Panics if the read failed, or the page *was* filled with the ASCII character `data`.
438///
439/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
440/// number in the event of a panic. This makes it easier to find test regressions.
441#[track_caller]
442pub fn check_page_ne(current_task: &CurrentTask, addr: UserAddress, data: char) {
443    let buf = match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) {
444        Ok(b) => b,
445        Err(err) => panic!("read page: failed to read page @ {addr:?}: {err:?}"),
446    };
447    assert!(
448        !buf.into_iter().all(|c| c == data as u8),
449        "unexpected payload: page @ {addr:?} should not be filled with {data:?}"
450    );
451}
452
453/// Checks that the page in `current_task`'s address space starting at `addr` is unmapped.
454/// Panics if the read succeeds, or if an error other than `EFAULT` occurs.
455///
456/// This method uses the `#[track_caller]` attribute, which will display the caller's file and line
457/// number in the event of a panic. This makes it easier to find test regressions.
458#[track_caller]
459pub fn check_unmapped(current_task: &CurrentTask, addr: UserAddress) {
460    match current_task.read_memory_to_vec(addr, *PAGE_SIZE as usize) {
461        Ok(_) => panic!("read page: page @ {addr:?} should be unmapped"),
462        Err(err) if err == starnix_uapi::errors::EFAULT => {}
463        Err(err) => {
464            panic!("read page: expected EFAULT reading page @ {addr:?} but got {err:?} instead")
465        }
466    }
467}
468
469/// An FsNodeOps implementation that panics if you try to open it. Useful as a stand-in for testing
470/// APIs that require a FsNodeOps implementation but don't actually use it.
471pub struct PanickingFsNode;
472
473impl FsNodeOps for PanickingFsNode {
474    fs_node_impl_not_dir!();
475
476    fn create_file_ops(
477        &self,
478        _locked: &mut Locked<FileOpsCore>,
479        _node: &FsNode,
480        _current_task: &CurrentTask,
481        _flags: OpenFlags,
482    ) -> Result<Box<dyn FileOps>, Errno> {
483        panic!("should not be called")
484    }
485}
486
487/// An implementation of [`FileOps`] that panics on any read, write, or ioctl operation.
488pub struct PanickingFile;
489
490impl PanickingFile {
491    /// Creates a [`FileObject`] whose implementation panics on reads, writes, and ioctls.
492    pub fn new_file(locked: &mut Locked<Unlocked>, current_task: &CurrentTask) -> FileHandle {
493        anon_test_file(locked, current_task, Box::new(PanickingFile), OpenFlags::RDWR)
494    }
495}
496
497impl FileOps for PanickingFile {
498    fileops_impl_nonseekable!();
499    fileops_impl_noop_sync!();
500
501    fn write(
502        &self,
503        _locked: &mut Locked<FileOpsCore>,
504        _file: &FileObject,
505        _current_task: &CurrentTask,
506        _offset: usize,
507        _data: &mut dyn InputBuffer,
508    ) -> Result<usize, Errno> {
509        panic!("write called on TestFile")
510    }
511
512    fn read(
513        &self,
514        _locked: &mut Locked<FileOpsCore>,
515        _file: &FileObject,
516        _current_task: &CurrentTask,
517        _offset: usize,
518        _data: &mut dyn OutputBuffer,
519    ) -> Result<usize, Errno> {
520        panic!("read called on TestFile")
521    }
522
523    fn ioctl(
524        &self,
525        _locked: &mut Locked<Unlocked>,
526        _file: &FileObject,
527        _current_task: &CurrentTask,
528        _request: u32,
529        _arg: SyscallArg,
530    ) -> Result<SyscallResult, Errno> {
531        panic!("ioctl called on TestFile")
532    }
533}
534
535/// Returns a new anonymous test file with the specified `ops` and `flags`.
536pub fn anon_test_file<L>(
537    locked: &mut Locked<L>,
538    current_task: &CurrentTask,
539    ops: Box<dyn FileOps>,
540    flags: OpenFlags,
541) -> FileHandle
542where
543    L: LockEqualOrBefore<FileOpsCore>,
544{
545    // TODO: https://fxbug.dev/404739824 - Confirm whether to handle this as a "private" node.
546    Anon::new_private_file(locked, current_task, ops, flags, "[fuchsia:test_file]")
547}
548
549/// Helper to write out data to a task's memory sequentially.
550pub struct UserMemoryWriter<'a> {
551    // The task's memory manager.
552    mm: &'a Task,
553    // The address to which to write the next bit of data.
554    current_addr: UserAddress,
555}
556
557impl<'a> UserMemoryWriter<'a> {
558    /// Constructs a new `UserMemoryWriter` to write to `task`'s memory at `addr`.
559    pub fn new(task: &'a Task, addr: UserAddress) -> Self {
560        Self { mm: task, current_addr: addr }
561    }
562
563    /// Writes all of `data` to the current address in the task's address space, incrementing the
564    /// current address by the size of `data`. Returns the address at which the data starts.
565    /// Panics on failure.
566    pub fn write(&mut self, data: &[u8]) -> UserAddress {
567        let bytes_written = self.mm.write_memory(self.current_addr, data).unwrap();
568        assert_eq!(bytes_written, data.len());
569        let start_addr = self.current_addr;
570        self.current_addr = (self.current_addr + bytes_written).unwrap();
571        start_addr
572    }
573
574    /// Writes `object` to the current address in the task's address space, incrementing the
575    /// current address by the size of `object`. Returns the address at which the data starts.
576    /// Panics on failure.
577    pub fn write_object<T: IntoBytes + Immutable>(&mut self, object: &T) -> UserAddress {
578        self.write(object.as_bytes())
579    }
580
581    /// Returns the current address at which data will be next written.
582    pub fn current_address(&self) -> UserAddress {
583        self.current_addr
584    }
585}
586
587#[derive(Debug)]
588pub struct AutoReleasableTask(Option<CurrentTask>);
589
590impl AutoReleasableTask {
591    fn as_ref(this: &Self) -> &CurrentTask {
592        this.0.as_ref().unwrap()
593    }
594
595    fn as_mut(this: &mut Self) -> &mut CurrentTask {
596        this.0.as_mut().unwrap()
597    }
598}
599
600impl From<CurrentTask> for AutoReleasableTask {
601    fn from(task: CurrentTask) -> Self {
602        Self(Some(task))
603    }
604}
605
606impl From<TaskBuilder> for AutoReleasableTask {
607    fn from(builder: TaskBuilder) -> Self {
608        CurrentTask::from(builder).into()
609    }
610}
611
612impl<'a> KernelOrTask<'a> for &'a AutoReleasableTask {
613    fn kernel(&self) -> &'a Kernel {
614        (self as &Task).kernel()
615    }
616    fn maybe_task(&self) -> Option<&'a CurrentTask> {
617        Some(&self)
618    }
619}
620
621impl Drop for AutoReleasableTask {
622    fn drop(&mut self) {
623        // TODO(mariagl): Find a way to avoid creating a new locked context here.
624        #[allow(
625            clippy::undocumented_unsafe_blocks,
626            reason = "Force documented unsafe blocks in Starnix"
627        )]
628        let locked = unsafe { Unlocked::new() };
629        self.0.take().unwrap().release(locked);
630    }
631}
632
633impl std::ops::Deref for AutoReleasableTask {
634    type Target = CurrentTask;
635
636    fn deref(&self) -> &Self::Target {
637        AutoReleasableTask::as_ref(self)
638    }
639}
640
641impl std::ops::DerefMut for AutoReleasableTask {
642    fn deref_mut(&mut self) -> &mut Self::Target {
643        AutoReleasableTask::as_mut(self)
644    }
645}
646
647impl std::borrow::Borrow<CurrentTask> for AutoReleasableTask {
648    fn borrow(&self) -> &CurrentTask {
649        AutoReleasableTask::as_ref(self)
650    }
651}
652
653impl std::convert::AsRef<CurrentTask> for AutoReleasableTask {
654    fn as_ref(&self) -> &CurrentTask {
655        AutoReleasableTask::as_ref(self)
656    }
657}
658
659impl ArchSpecific for AutoReleasableTask {
660    fn is_arch32(&self) -> bool {
661        self.deref().is_arch32()
662    }
663}
664
665impl MemoryAccessor for AutoReleasableTask {
666    fn read_memory<'a>(
667        &self,
668        addr: UserAddress,
669        bytes: &'a mut [MaybeUninit<u8>],
670    ) -> Result<&'a mut [u8], Errno> {
671        (**self).read_memory(addr, bytes)
672    }
673    fn read_memory_partial_until_null_byte<'a>(
674        &self,
675        addr: UserAddress,
676        bytes: &'a mut [MaybeUninit<u8>],
677    ) -> Result<&'a mut [u8], Errno> {
678        (**self).read_memory_partial_until_null_byte(addr, bytes)
679    }
680    fn read_memory_partial<'a>(
681        &self,
682        addr: UserAddress,
683        bytes: &'a mut [MaybeUninit<u8>],
684    ) -> Result<&'a mut [u8], Errno> {
685        (**self).read_memory_partial(addr, bytes)
686    }
687    fn write_memory(&self, addr: UserAddress, bytes: &[u8]) -> Result<usize, Errno> {
688        (**self).write_memory(addr, bytes)
689    }
690    fn write_memory_partial(&self, addr: UserAddress, bytes: &[u8]) -> Result<usize, Errno> {
691        (**self).write_memory_partial(addr, bytes)
692    }
693    fn zero(&self, addr: UserAddress, length: usize) -> Result<usize, Errno> {
694        (**self).zero(addr, length)
695    }
696}
697
698struct TestFs;
699impl FileSystemOps for TestFs {
700    fn statfs(
701        &self,
702        _locked: &mut Locked<FileOpsCore>,
703        _fs: &FileSystem,
704        _current_task: &CurrentTask,
705    ) -> Result<statfs, Errno> {
706        Ok(default_statfs(0))
707    }
708    fn name(&self) -> &'static FsStr {
709        "test".into()
710    }
711}
712
713pub fn create_testfs(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> FileSystemHandle {
714    FileSystem::new(locked, &kernel, CacheMode::Uncached, TestFs, Default::default())
715        .expect("testfs constructed with valid options")
716}
717
718pub fn create_testfs_with_root(
719    locked: &mut Locked<Unlocked>,
720    kernel: &Kernel,
721    ops: impl FsNodeOps,
722) -> FileSystemHandle {
723    let test_fs = create_testfs(locked, kernel);
724    let root_ino = test_fs.allocate_ino();
725    test_fs.create_root(root_ino, ops);
726    test_fs
727}
728
729pub fn create_fs_node_for_testing(fs: &FileSystemHandle, ops: impl FsNodeOps) -> FsNodeHandle {
730    let ino = fs.allocate_ino();
731    let info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred::root());
732    FsNode::new_uncached(ino, ops, fs, info)
733}
734
735pub fn create_namespace_node_for_testing(
736    fs: &FileSystemHandle,
737    ops: impl FsNodeOps,
738) -> NamespaceNode {
739    let node = create_fs_node_for_testing(fs, ops);
740    NamespaceNode::new_anonymous(DirEntry::new_unrooted(node))
741}