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