Skip to main content

starnix_core/
testing.rs

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