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