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