| // Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| //! Tracks the `CATEGORY_STARNIX_SYSCALLS` or `CATEGORY_STARNIX_SYSCALLS_FS` duration of the syscall | |
| //! running on the current thread, so that code inside the syscall can end that duration early. | |
| //! | |
| //! `TraceMarkerFile::write` uses this to end the syscall's slice before it records an | |
| //! `ftrace/print` (`atrace`) event. Otherwise the syscall slice ends after the `atrace` event on | |
| //! the same thread track, and Perfetto's strict slice nesting truncates the `atrace` slice. Linux | |
| //! `ftrace` traces avoid this the same way, in Perfetto's | |
| //! `SyscallTracker::MaybeTruncateOngoingWriteSlice`. | |
| use fuchsia_trace::{TraceCategoryContext, trace_site_t}; | |
| use starnix_logging::{CATEGORY_STARNIX_SYSCALLS, CATEGORY_STARNIX_SYSCALLS_FS}; | |
| use starnix_syscalls::decls::SyscallDecl; | |
| use std::cell::Cell; | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| enum SyscallTraceKind { | |
| /// Emitted under `CATEGORY_STARNIX_SYSCALLS`. | |
| Syscalls, | |
| /// Emitted under `CATEGORY_STARNIX_SYSCALLS_FS` when `confirmed` is `true` (if enabled), or | |
| /// under `CATEGORY_STARNIX_SYSCALLS` if `fallback_to_syscalls` is `true`. | |
| Fs { confirmed: bool, fallback_to_syscalls: bool }, | |
| } | |
| #[derive(Clone, Copy)] | |
| struct ActiveSyscallTrace { | |
| name: &'static str, | |
| start_time: zx::BootTicks, | |
| kind: SyscallTraceKind, | |
| } | |
| thread_local! { | |
| /// The active syscall duration that is open on the current thread, if any. | |
| /// | |
| /// This is a `const`-initialized thread-local without a destructor, so accessing it is cheap. | |
| /// A `CurrentTask` runs syscalls only on its own thread, so this holds the state of that task's | |
| /// current syscall. | |
| static SYSCALL_TRACE: Cell<Option<ActiveSyscallTrace>> = const { Cell::new(None) }; | |
| } | |
| /// Ends the syscall duration started by [`begin_syscall_trace`] when dropped, unless | |
| /// [`complete_syscall_trace`] already ended it. | |
| #[must_use = "the syscall duration ends when the scope is dropped"] | |
| pub struct SyscallTraceScope(()); | |
| impl Drop for SyscallTraceScope { | |
| fn drop(&mut self) { | |
| complete_syscall_trace(); | |
| } | |
| } | |
| /// Starts a duration for `syscall_decl` on the current thread under its trace category | |
| /// (`CATEGORY_STARNIX_SYSCALLS_FS` or `CATEGORY_STARNIX_SYSCALLS`). | |
| /// | |
| /// Returns `None`, without calling `syscall_decl.trace_name()`, if neither applicable category is | |
| /// enabled. Otherwise the duration ends when the returned scope is dropped, or earlier at a call to | |
| /// [`complete_syscall_trace`]. | |
| #[inline] | |
| pub fn begin_syscall_trace(syscall_decl: SyscallDecl) -> Option<SyscallTraceScope> { | |
| // The context is dropped right away. `complete_syscall_trace` acquires it again, so the trace | |
| // buffers can be flushed while the syscall runs, as with `fuchsia_trace::duration!`. | |
| static SYSCALLS_CACHE: trace_site_t = trace_site_t::new(0); | |
| static FS_CACHE: trace_site_t = trace_site_t::new(0); | |
| let syscalls_enabled = | |
| TraceCategoryContext::acquire_cached(CATEGORY_STARNIX_SYSCALLS, &SYSCALLS_CACHE).is_some(); | |
| let kind = if syscall_decl.is_fs() | |
| && TraceCategoryContext::acquire_cached(CATEGORY_STARNIX_SYSCALLS_FS, &FS_CACHE).is_some() | |
| { | |
| // Filesystem syscalls start unconfirmed under `CATEGORY_STARNIX_SYSCALLS_FS` and are | |
| // confirmed when operating on a storage-backed filesystem (such as `ext4`). Unconfirmed | |
| // filesystem syscalls on in-memory or pseudo filesystems (such as `tmpfs` or `procfs`, or | |
| // pipes/sockets) fall back to `CATEGORY_STARNIX_SYSCALLS` if enabled. | |
| SyscallTraceKind::Fs { confirmed: false, fallback_to_syscalls: syscalls_enabled } | |
| } else if syscalls_enabled { | |
| SyscallTraceKind::Syscalls | |
| } else { | |
| return None; | |
| }; | |
| SYSCALL_TRACE.set(Some(ActiveSyscallTrace { | |
| name: syscall_decl.trace_name(), | |
| start_time: zx::BootTicks::get(), | |
| kind, | |
| })); | |
| Some(SyscallTraceScope(())) | |
| } | |
| /// Confirms that the current syscall operates on a storage-backed filesystem, so a | |
| /// `CATEGORY_STARNIX_SYSCALLS_FS` syscall duration will be emitted when completed. | |
| #[inline] | |
| pub fn confirm_fs_syscall_trace() { | |
| confirm_fs_syscall_trace_if(|| true); | |
| } | |
| /// Confirms an unconfirmed `CATEGORY_STARNIX_SYSCALLS_FS` syscall duration on the current thread if | |
| /// `is_storage_backed()` returns `true`. | |
| /// | |
| /// Checks whether an unconfirmed trace is active on the current thread before invoking | |
| /// `is_storage_backed()`, avoiding virtual dispatch on `FileSystemOps::is_storage_backed` when | |
| /// tracing is disabled or the current syscall trace is already confirmed. | |
| #[inline] | |
| pub fn confirm_fs_syscall_trace_if(is_storage_backed: impl FnOnce() -> bool) { | |
| SYSCALL_TRACE.with(|cell| { | |
| if let Some(mut trace) = cell.get() { | |
| if let SyscallTraceKind::Fs { ref mut confirmed, .. } = trace.kind { | |
| if !*confirmed && is_storage_backed() { | |
| *confirmed = true; | |
| cell.set(Some(trace)); | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| /// Ends the syscall duration that is open on the current thread, if any, and writes it to the | |
| /// trace. | |
| /// | |
| /// Called early by `TraceMarkerFile::write`, right before it records an `ftrace/print` event, so | |
| /// that the syscall slice ends at or before the event's timestamp. | |
| #[inline] | |
| pub fn complete_syscall_trace() { | |
| if let Some(trace) = SYSCALL_TRACE.take() { | |
| static SYSCALLS_CACHE: trace_site_t = trace_site_t::new(0); | |
| static FS_CACHE: trace_site_t = trace_site_t::new(0); | |
| let category = match trace.kind { | |
| SyscallTraceKind::Fs { confirmed: true, .. } | |
| if TraceCategoryContext::acquire_cached( | |
| CATEGORY_STARNIX_SYSCALLS_FS, | |
| &FS_CACHE, | |
| ) | |
| .is_some() => | |
| { | |
| CATEGORY_STARNIX_SYSCALLS_FS | |
| } | |
| SyscallTraceKind::Syscalls | |
| | SyscallTraceKind::Fs { fallback_to_syscalls: true, .. } | |
| if TraceCategoryContext::acquire_cached( | |
| CATEGORY_STARNIX_SYSCALLS, | |
| &SYSCALLS_CACHE, | |
| ) | |
| .is_some() => | |
| { | |
| CATEGORY_STARNIX_SYSCALLS | |
| } | |
| _ => return, | |
| }; | |
| fuchsia_trace::complete_duration(category, trace.name, trace.start_time, &[]); | |
| } | |
| } | |
| /// Opens a syscall duration on the current thread even if `CATEGORY_STARNIX_SYSCALLS` is | |
| /// disabled, so tests can check that code ends it. Drop the returned scope to clear it. | |
| pub fn begin_syscall_trace_for_testing(name: &'static str) -> SyscallTraceScope { | |
| SYSCALL_TRACE.set(Some(ActiveSyscallTrace { | |
| name, | |
| start_time: zx::BootTicks::get(), | |
| kind: SyscallTraceKind::Syscalls, | |
| })); | |
| SyscallTraceScope(()) | |
| } | |
| /// Returns whether a syscall duration is open on the current thread. | |
| pub fn has_active_syscall_trace_for_testing() -> bool { | |
| SYSCALL_TRACE.with(|trace| trace.get().is_some()) | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| use crate::fs::tmpfs::TmpFs; | |
| use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_with_pkgfs}; | |
| use crate::vfs::pipe::new_pipe; | |
| use crate::vfs::{FdFlags, FdNumber, LookupContext, WhatToMount}; | |
| use starnix_syscalls::SyscallArg; | |
| use starnix_types::arch::ArchWidth; | |
| use starnix_uapi::file_mode::FileMode; | |
| use starnix_uapi::mount_flags::MountpointFlags; | |
| use starnix_uapi::open_flags::OpenFlags; | |
| use starnix_uapi::user_address::UserAddress; | |
| use starnix_uapi::vfs::ResolveFlags; | |
| fn begin_unconfirmed_fs_trace_for_testing(name: &'static str) -> SyscallTraceScope { | |
| SYSCALL_TRACE.set(Some(ActiveSyscallTrace { | |
| name, | |
| start_time: zx::BootTicks::get(), | |
| kind: SyscallTraceKind::Fs { confirmed: false, fallback_to_syscalls: false }, | |
| })); | |
| SyscallTraceScope(()) | |
| } | |
| fn active_syscall_trace_kind() -> Option<SyscallTraceKind> { | |
| SYSCALL_TRACE.with(|trace| trace.get().map(|t| t.kind)) | |
| } | |
| fn is_active_fs_trace_confirmed() -> bool { | |
| matches!(active_syscall_trace_kind(), Some(SyscallTraceKind::Fs { confirmed: true, .. })) | |
| } | |
| #[::fuchsia::test] | |
| fn test_confirm_fs_syscall_trace() { | |
| assert_eq!(active_syscall_trace_kind(), None); | |
| // No-op when no trace is active; closure is not evaluated. | |
| let mut called = false; | |
| confirm_fs_syscall_trace_if(|| { | |
| called = true; | |
| true | |
| }); | |
| assert!(!called); | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("read"); | |
| assert_eq!( | |
| active_syscall_trace_kind(), | |
| Some(SyscallTraceKind::Fs { confirmed: false, fallback_to_syscalls: false }) | |
| ); | |
| // Non-storage-backed check leaves trace unconfirmed. | |
| confirm_fs_syscall_trace_if(|| false); | |
| assert_eq!( | |
| active_syscall_trace_kind(), | |
| Some(SyscallTraceKind::Fs { confirmed: false, fallback_to_syscalls: false }) | |
| ); | |
| // Storage-backed check confirms the trace. | |
| confirm_fs_syscall_trace_if(|| true); | |
| assert_eq!( | |
| active_syscall_trace_kind(), | |
| Some(SyscallTraceKind::Fs { confirmed: true, fallback_to_syscalls: false }) | |
| ); | |
| // Once confirmed, subsequent checks short-circuit without calling the predicate. | |
| let mut called_after_confirm = false; | |
| confirm_fs_syscall_trace_if(|| { | |
| called_after_confirm = true; | |
| true | |
| }); | |
| assert!(!called_after_confirm); | |
| } | |
| #[::fuchsia::test] | |
| fn test_syscall_decl_is_fs() { | |
| let read_decl = SyscallDecl::from_number(starnix_uapi::__NR_read as u64, ArchWidth::Arch64); | |
| assert!(read_decl.is_fs()); | |
| let openat_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_openat as u64, ArchWidth::Arch64); | |
| assert!(openat_decl.is_fs()); | |
| let execve_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_execve as u64, ArchWidth::Arch64); | |
| assert!(execve_decl.is_fs()); | |
| let execveat_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_execveat as u64, ArchWidth::Arch64); | |
| assert!(execveat_decl.is_fs()); | |
| let inotify_add_watch_decl = SyscallDecl::from_number( | |
| starnix_uapi::__NR_inotify_add_watch as u64, | |
| ArchWidth::Arch64, | |
| ); | |
| assert!(inotify_add_watch_decl.is_fs()); | |
| let io_uring_enter_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_io_uring_enter as u64, ArchWidth::Arch64); | |
| assert!(io_uring_enter_decl.is_fs()); | |
| let ioctl_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_ioctl as u64, ArchWidth::Arch64); | |
| assert!(ioctl_decl.is_fs()); | |
| let mmap_decl = SyscallDecl::from_number(starnix_uapi::__NR_mmap as u64, ArchWidth::Arch64); | |
| assert!(mmap_decl.is_fs()); | |
| let getpid_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_getpid as u64, ArchWidth::Arch64); | |
| assert!(!getpid_decl.is_fs()); | |
| let umask_decl = | |
| SyscallDecl::from_number(starnix_uapi::__NR_umask as u64, ArchWidth::Arch64); | |
| assert!(!umask_decl.is_fs()); | |
| for num in 0..512 { | |
| let decl = SyscallDecl::from_number(num, ArchWidth::Arch64); | |
| if decl.is_fs() { | |
| assert_ne!( | |
| decl.trace_name(), | |
| "<unknown syscall>", | |
| "Arch64 FS syscall {num} is missing a trace_name" | |
| ); | |
| } | |
| } | |
| #[cfg(target_arch = "aarch64")] | |
| { | |
| for nr in [ | |
| starnix_uapi::arch32::__NR__llseek, | |
| starnix_uapi::arch32::__NR_fstatat64, | |
| starnix_uapi::arch32::__NR_chmod, | |
| starnix_uapi::arch32::__NR_creat, | |
| starnix_uapi::arch32::__NR_execve, | |
| starnix_uapi::arch32::__NR_execveat, | |
| starnix_uapi::arch32::__NR_inotify_add_watch, | |
| starnix_uapi::arch32::__NR_io_uring_enter, | |
| starnix_uapi::arch32::__NR_ioctl, | |
| starnix_uapi::arch32::__NR_link, | |
| starnix_uapi::arch32::__NR_mmap2, | |
| starnix_uapi::arch32::__NR_readlink, | |
| starnix_uapi::arch32::__NR_rename, | |
| starnix_uapi::arch32::__NR_rmdir, | |
| starnix_uapi::arch32::__NR_symlink, | |
| ] { | |
| let decl = SyscallDecl::from_number(nr as u64, ArchWidth::Arch32); | |
| assert!(decl.is_fs(), "expected arch32 syscall {nr} to be is_fs()"); | |
| assert_ne!(decl.trace_name(), "<unknown syscall>"); | |
| } | |
| for nr in [starnix_uapi::arch32::__NR_getpid, starnix_uapi::arch32::__NR_umask] { | |
| let decl = SyscallDecl::from_number(nr as u64, ArchWidth::Arch32); | |
| assert!(!decl.is_fs(), "expected arch32 syscall {nr} not to be is_fs()"); | |
| } | |
| for num in 0..512 { | |
| let decl = SyscallDecl::from_number(num, ArchWidth::Arch32); | |
| if decl.is_fs() { | |
| assert_ne!( | |
| decl.trace_name(), | |
| "<unknown syscall>", | |
| "Arch32 FS syscall {num} is missing a trace_name" | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| #[::fuchsia::test] | |
| async fn test_vfs_confirmation_tmpfs_and_pipe() { | |
| spawn_kernel_and_run(async |current_task| { | |
| // Creating and looking up files on TmpFs should not confirm `starnix:syscalls:fs`. | |
| let tmp_fd = { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| let file = current_task | |
| .open_file_at( | |
| FdNumber::AT_FDCWD, | |
| "tmpfile.txt".into(), | |
| OpenFlags::RDWR | OpenFlags::CREAT, | |
| FileMode::ALLOW_ALL, | |
| ResolveFlags::empty(), | |
| ) | |
| .expect("create tmpfs file"); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| current_task.add_file(file, FdFlags::empty()).expect("add tmpfs fd") | |
| }; | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("lseek"); | |
| crate::vfs::syscalls::sys_lseek(current_task, tmp_fd, 0, starnix_uapi::SEEK_CUR) | |
| .expect("lseek tmpfs fd"); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("ioctl"); | |
| let _ = | |
| crate::vfs::syscalls::sys_ioctl(current_task, tmp_fd, 0, SyscallArg::default()); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("mmap"); | |
| crate::mm::syscalls::do_mmap( | |
| current_task, | |
| UserAddress::NULL, | |
| *crate::mm::PAGE_SIZE as usize, | |
| starnix_uapi::PROT_READ, | |
| starnix_uapi::MAP_PRIVATE | starnix_uapi::MAP_ANONYMOUS, | |
| FdNumber::from_raw(-1), | |
| 0, | |
| ) | |
| .expect("anonymous mmap"); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("mmap"); | |
| crate::mm::syscalls::do_mmap( | |
| current_task, | |
| UserAddress::NULL, | |
| *crate::mm::PAGE_SIZE as usize, | |
| starnix_uapi::PROT_READ, | |
| starnix_uapi::MAP_PRIVATE, | |
| tmp_fd, | |
| 0, | |
| ) | |
| .expect("tmpfs mmap"); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| assert!(current_task.open_file("missing.txt".into(), OpenFlags::RDONLY).is_err()); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("execve"); | |
| let _ = current_task.open_file_for_exec( | |
| FdNumber::AT_FDCWD, | |
| "tmpfile.txt".into(), | |
| OpenFlags::RDONLY, | |
| ); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| // Pipe file descriptors should not confirm `starnix:syscalls:fs`. | |
| let (read_pipe, _write_pipe) = new_pipe(current_task).expect("new_pipe"); | |
| let pipe_fd = current_task.add_file(read_pipe, FdFlags::empty()).expect("add pipe fd"); | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("lseek"); | |
| let _ = crate::vfs::syscalls::sys_lseek( | |
| current_task, | |
| pipe_fd, | |
| 0, | |
| starnix_uapi::SEEK_CUR, | |
| ); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| }) | |
| .await; | |
| } | |
| #[::fuchsia::test] | |
| async fn test_vfs_confirmation_pkgfs_and_submount() { | |
| spawn_kernel_and_run_with_pkgfs(async |current_task| { | |
| // Opening a file on storage-backed `pkgfs` (`RemoteFs`) confirms `starnix:syscalls:fs`. | |
| let pkg_fd = { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| let file = current_task | |
| .open_file("data/testfile.txt".into(), OpenFlags::RDONLY) | |
| .expect("open pkgfs file"); | |
| assert!(is_active_fs_trace_confirmed()); | |
| current_task.add_file(file, FdFlags::empty()).expect("add pkgfs fd") | |
| }; | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("execve"); | |
| let _ = current_task.open_file_for_exec( | |
| FdNumber::AT_FDCWD, | |
| "data/testfile.txt".into(), | |
| OpenFlags::RDONLY, | |
| ); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| // Operating on a storage-backed fd confirms `starnix:syscalls:fs`. | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("lseek"); | |
| crate::vfs::syscalls::sys_lseek(current_task, pkg_fd, 0, starnix_uapi::SEEK_CUR) | |
| .expect("lseek pkgfs fd"); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("ioctl"); | |
| let _ = | |
| crate::vfs::syscalls::sys_ioctl(current_task, pkg_fd, 0, SyscallArg::default()); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("mmap"); | |
| crate::mm::syscalls::do_mmap( | |
| current_task, | |
| UserAddress::NULL, | |
| *crate::mm::PAGE_SIZE as usize, | |
| starnix_uapi::PROT_READ, | |
| starnix_uapi::MAP_PRIVATE, | |
| pkg_fd, | |
| 0, | |
| ) | |
| .expect("pkgfs mmap"); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| // `FIOCLEX` only updates `FdTable` flags and should not confirm. | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("ioctl"); | |
| crate::vfs::syscalls::sys_ioctl( | |
| current_task, | |
| pkg_fd, | |
| starnix_uapi::FIOCLEX, | |
| SyscallArg::default(), | |
| ) | |
| .expect("FIOCLEX"); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| // Failed lookups on a storage-backed parent or intermediate directory confirm. | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| assert!( | |
| current_task.open_file("data/no_such_file".into(), OpenFlags::RDONLY).is_err() | |
| ); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| assert!( | |
| current_task.open_file("no_such_dir/file".into(), OpenFlags::RDONLY).is_err() | |
| ); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| // `ENOTDIR` on a storage-backed `dir_fd` in `resolve_dir_fd` confirms. | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| assert!( | |
| current_task | |
| .open_file_at( | |
| pkg_fd, | |
| "child".into(), | |
| OpenFlags::RDONLY, | |
| FileMode::default(), | |
| ResolveFlags::empty(), | |
| ) | |
| .is_err() | |
| ); | |
| assert!(is_active_fs_trace_confirmed()); | |
| } | |
| // Traversing from a storage-backed `dir_fd` into a `TmpFs` submount leaves the trace | |
| // unconfirmed. | |
| let root_file = | |
| current_task.open_file("/".into(), OpenFlags::RDONLY | OpenFlags::PATH).unwrap(); | |
| let root_fd = current_task.add_file(root_file, FdFlags::empty()).unwrap(); | |
| let mut context = LookupContext::default(); | |
| let bin_dir = current_task | |
| .fs() | |
| .root() | |
| .lookup_child(current_task, &mut context, "bin".into()) | |
| .expect("lookup bin"); | |
| bin_dir | |
| .mount( | |
| WhatToMount::Fs(TmpFs::new_fs(current_task.kernel())), | |
| MountpointFlags::empty(), | |
| ) | |
| .expect("mount tmpfs on bin"); | |
| { | |
| let _scope = begin_unconfirmed_fs_trace_for_testing("openat"); | |
| let _tmp_in_submount = current_task | |
| .open_file_at( | |
| root_fd, | |
| "bin/submount_tmp.txt".into(), | |
| OpenFlags::RDWR | OpenFlags::CREAT, | |
| FileMode::ALLOW_ALL, | |
| ResolveFlags::empty(), | |
| ) | |
| .expect("create file in tmpfs submount"); | |
| assert!(!is_active_fs_trace_confirmed()); | |
| } | |
| }) | |
| .await; | |
| } | |
| } |