Skip to main content

Detcore

Struct Detcore 

Source
pub struct Detcore<T = NoopTool> { /* private fields */ }
Expand description

The detcore tool and its per-process state.

Implementations§

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_openat<G>( &self, guest: &mut G, call: Openat, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Openat system call.

Source

pub async fn handle_close<G>( &self, guest: &mut G, call: Close, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_close system call.

Source

pub async fn handle_close_range<G>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Close a contiguous descriptor range and mirror successful closes in Detcore.

The pinned Reverie revision exposes close_range as Syscall::Other. The common flags=0 operation cannot block and is deterministic for the process-local descriptor table. CLOSE_RANGE_UNSHARE and CLOSE_RANGE_CLOEXEC need separate shared-table modeling, so return ENOSYS for nonzero flags rather than letting strict execution silently diverge.

Source

pub async fn handle_flock<G>( &self, guest: &mut G, call: Flock, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Advisory whole-file locks, forwarded to the kernel.

This was previously an unconditional no-op success, justified by the claim that “an advisory whole-file lock is never contended within the serialized container”. That is false: serializing guest threads stops them EXECUTING simultaneously, it does not stop their lock HOLD INTERVALS from overlapping. A holder that is descheduled – because it blocked, forked, or simply used up its timeslice – keeps holding while another process runs and observes the lock. Measured before this change, on both ptrace and DBI, two processes held the same LOCK_EX simultaneously while native correctly returned EWOULDBLOCK.

A no-op is the wrong failure direction for a determinism tool. It is deterministically wrong, so double-run verification cannot see it, and it silently removes mutual exclusion from every guest that uses a lockfile.

Forwarding is what fcntl already does for POSIX record locks, which is why those work. The guest’s descriptor is a real host descriptor, so the kernel supplies the whole contract for free and consistently with itself: shared vs exclusive, LOCK_NB, upgrade/downgrade (which Linux performs non-atomically – see below, this handler compensates), release on LOCK_UN, release when the last descriptor for the open file description is closed, and release on process exit.

Determinism, scoped to what is actually true. When every contender is inside the container the outcome is a function of which guest holds the lock, and that is fixed by Detcore’s deterministic schedule, so a given program and seed produce the same acquisition outcome every run.

The scope is not decoration. Because this forwards to the kernel, a process OUTSIDE the container holding a lock on a guest-visible file does change the guest’s result – measured: with a host flock -x holder, a guest LOCK_EX|LOCK_NB returns EWOULDBLOCK, and acquires without one. That is a host-state leak, it is faithful to Linux, and it is the same leak fcntl record locks have always had here. Hermit already declines to make a mutating external filesystem deterministic, and lock state on a shared file is part of that state. Do not restate this as “no host state enters the decision”: it does, and the previous bug in this very function came from writing down a determinism argument that was broader than the truth.

Note that the no-op this replaced was not host-independent in any useful sense either – it was host-independent by being wrong in all cases.

§Why a blocking request is probed non-blockingly, and what that costs

A guest thread parked inside a kernel flock is not visible to the deterministic scheduler as blocked, so nothing runs to release the lock and the whole container wedges – measured: a four-way contention guest that completes natively hung indefinitely under a plain forwarding implementation. So a blocking operation is rewritten to LOCK_NB and, if it turns out to be contended, refused rather than hung.

That rewrite is not free, and the cost is a lock the guest already owns. Linux converts an flock lock in place and the conversion is not atomic: flock_lock_inode deletes this open file description’s existing lock before it scans for a conflict, so a contended LOCK_SH -> LOCK_EX conversion leaves the caller holding nothing and then reports EWOULDBLOCK. Natively the guest never observes that intermediate state, because a blocking request would sleep and eventually acquire. Under the rewrite it would: the guest asked to wait, got told “no”, and silently lost the shared lock it was already relying on.

So this handler restores the prior mode before refusing a blocking conversion, making the refusal side-effect-free. It deliberately does not restore when the guest itself passed LOCK_NB: there the drop is exactly what Linux does, and re-acquiring would be a divergence in the other direction. DetFd::flock_mode is what makes the two cases distinguishable – it records the mode Detcore last saw the kernel grant for this open file description, so a first acquisition (nothing to lose) is not confused with a conversion (something to lose).

That cache covers locks Detcore granted while it had sole knowledge of the open file description. State becomes permanently unknown when the descriptor is inherited across a process fork, discovered after tracing begins, or received through SCM_RIGHTS (recorded unobserved), because another process can change that shared kernel lock without updating this cache. A blocking conversion in unknown state is refused before the nonblocking probe, so the refusal cannot destroy a lock Detcore cannot restore.

Source

pub async fn handle_read<G>( &self, guest: &mut G, call: Read, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_read system call (MAYHANG).

Source

pub async fn handle_pread64<G>( &self, guest: &mut G, call: Pread64, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_pread64 system call.

Source

pub async fn handle_lseek<G>( &self, guest: &mut G, call: Lseek, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_lseek system call.

Source

pub async fn handle_sendfile<G>( &self, guest: &mut G, call: Sendfile, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Copy data between tracked regular files or memfds.

The kernel advances the input offset (or the explicit offset pointer) and destination offset atomically with the copy. Detcore serializes destination writes while the strict scheduler orders the stable input read, and routes the syscall through record/replay so that the result and offset update stay ordered with other file operations. Socket and pipe destinations can block and need the nonblocking scheduler path; return ENOSYS for those endpoint types so libc/application fallbacks use Detcore’s existing read/write handlers instead.

Source

pub async fn handle_write<G>( &self, guest: &mut G, call: Write, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_write system call.

Source

pub async fn handle_pwrite64<G>( &self, guest: &mut G, call: Pwrite64, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_pwrite64 system call.

Source

pub async fn handle_writev<G>( &self, guest: &mut G, call: Writev, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_writev system call.

Preserve the initial writev as one kernel operation so its iovec order remains intact. Detcore adds open-file resource ordering and nonblocking scheduler integration; a blocking pipe short write is completed by the helper because Hermit injected O_NONBLOCK.

Source

pub async fn handle_readv<G>( &self, guest: &mut G, call: Readv, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_readv system call: the vectored form of read.

Mirrors Self::handle_writev for the read direction. Detcore adds open-file resource ordering and, for physically nonblocking pipe/socket fds, the nonblocking scheduler integration. Random devices use the shared canonical cursor; other descriptors retain their recorded kernel operation.

Source

pub async fn handle_preadv<G>( &self, guest: &mut G, call: Preadv, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_preadv system call: the vectored form of pread64.

RNG reads use the canonical stream at the explicit offset without advancing its shared cursor. Other files retain their kernel operation.

Source

pub async fn handle_preadv2<G>( &self, guest: &mut G, call: Preadv2, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_preadv2 system call: positioned vectors, or the shared stream when offset is -1. RNG flag validation precedes output copying.

Source

pub async fn handle_pwritev<G>( &self, guest: &mut G, call: Pwritev, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_pwritev system call: the vectored form of pwrite64.

Positioned writes target seekable files and do not block, so this mirrors Self::handle_pwrite64’s ordering, records/replays the single kernel operation, and bumps the virtual mtime on a successful write.

Source

pub async fn handle_pwritev2<G>( &self, guest: &mut G, call: Pwritev2, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_pwritev2 system call: pwritev with a trailing per-call flags argument, which record/replay forwards unchanged.

Source

pub async fn handle_mmap<G>( &self, guest: &mut G, call: Mmap, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_mmap system call.

Source

pub async fn handle_munmap<G>( &self, guest: &mut G, call: Munmap, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_munmap system call.

Source

pub async fn handle_mremap<G>( &self, guest: &mut G, call: Mremap, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

SYS_mremap system call.

Source

pub async fn handle_stat_family<G>( &self, guest: &mut G, call: StatFamily, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Handles all stat syscalls.

Source

pub async fn handle_statx<G>( &self, guest: &mut G, call: Statx, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

statx system call

Source

pub async fn handle_fcntl<G>( &self, guest: &mut G, call: Fcntl, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

fcntl system call

Source

pub async fn handle_ioctl<G>( &self, guest: &mut G, call: Ioctl, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

ioctl system call

Source

pub async fn handle_statfs<G>( &self, guest: &mut G, call: Statfs, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

statfs: report deterministic filesystem statistics.

The kernel’s statfs reflects live host state: the free-block counts (f_bfree, f_bavail), the free-inode count (f_ffree) and the device id (f_fsid) all vary between runs as the underlying host filesystem fills and drains, which makes a bare passthrough diverge under --verify (e.g. tar calls statfs on its target filesystem). The static geometry of the mount (f_type, f_bsize, f_blocks, f_namelen, …) is reproducible, so we run the real syscall and then canonicalize only the volatile fields.

Source

pub async fn handle_fstatfs<G>( &self, guest: &mut G, call: Fstatfs, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

fstatfs: same determinization as Self::handle_statfs, keyed on an fd.

Source

pub async fn handle_ownership_change_noop<G>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

The chown family (chown, fchown, fchownat, lchown).

Detcore presents a fixed virtual-root identity, so the permission answer must be the one a real root gets: success, for any uid. But root privilege affects only the ownership permission check — it does not waive pathname, descriptor, or flag errors. A real root’s chown("/does/not/exist", 0, 0) still fails with ENOENT.

So this does not fabricate a bare Ok(0). It translates the mutation into a side-effect-free metadata lookup with the same target-selection arguments, and reports success only if that lookup succeeds:

  • F_GETFL validates fchown’s descriptor and distinguishes an O_PATH descriptor (valid for fstat, invalid for fchown); newfstatat performs the corresponding path walk for the three pathname variants, preserving ENOENT, ENOTDIR, ELOOP, ENAMETOOLONG, EFAULT, and EBADF;
  • fchownat flags are checked explicitly before the lookup, so an unsupported flag still returns EINVAL rather than being accepted by a metadata syscall with a wider flag vocabulary;
  • the ownership assignment itself is not performed, but the other consequences of a successful chown are, because Linux applies them even when ownership does not change. Measured on this host with chown(path, -1, -1): mode 06755, 04755 and 02755 all become 0755; 02644 keeps S_ISGID because the file is not group-executable; a directory at 06755 keeps both bits; and ctime moves in every one of those cases, including the plain 0644 file with nothing to clear. Skipping that is a privilege-containment regression, not a bookkeeping one: a guest that builds a setuid binary and chowns it would see the setuid bit survive under hermit and be cleared on the kernel.

Rather than reimplement that rule, the consequence is delegated to the kernel by reissuing the same call from the same family with the (-1, -1) sentinel, which is precisely the operation whose only effects are ATTR_CTIME | ATTR_KILL_SUID | ATTR_KILL_SGID. Delegation gets the directory exemption, the group-executable condition on S_ISGID, and symlink handling right for free, and cannot drift from the kernel the way a transcribed rule would.

Routed through record_or_replay rather than inject, so a replay does not need the guest’s filesystem to still exist.

Semantic boundary, stated explicitly. Detcore does not model per-file ownership, so the success is not observable through a later stat. A guest that chowns to a foreign uid and reads the owner back sees the unchanged owner — a divergence a single-uid container cannot avoid, and strictly smaller than the status quo in which the guest believes it is root and cannot chown at all.

Residual, also stated. This emulates ownership permission and target validation, not every write-time filesystem policy. In particular a target on a read-only mount can pass the metadata lookup where a real chown would return EROFS. Path resolution also still requires search permission on the parent directories, so EACCES remains a function of the host identity under --no-namespace. That exposure is shared with every pass-through filesystem syscall (open, stat, chmod) and is not introduced here; it is recorded so the boundary is not overstated.

Residual introduced by the delegation, stated too. The sentinel call needs the same inode_owner_or_capable permission the mode change does, so on a target the guest does not own it returns EPERM and that errno is propagated. Reporting a successful chown while silently failing to apply the consequence the kernel guarantees would be the same defect in a narrower form, so this fails closed instead. It is not a regression: that is exactly the case in which the pass-through implementation also returned EPERM. The case this change exists to fix — a guest chowning a file it created — is the owning case, and it succeeds.

The behavioural contract is bracketed end to end by hermit-cli/tests/chown_virtual_root_identity.rs; the unit tests in syscall_classification pin membership only and cannot see this function’s result.

Source

pub async fn handle_dup<G>( &self, guest: &mut G, call: Dup, ) -> Result<i64, Errno>
where G: Guest<Detcore<T>>,

dup system call.

Source

pub async fn handle_dup2<G>( &self, guest: &mut G, call: Dup2, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

dup2 system call.

Source

pub async fn handle_dup3<G>( &self, guest: &mut G, call: Dup3, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

dup3 system call.

Source

pub async fn handle_pipe2<G>( &self, guest: &mut G, call: Pipe2, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

pipe2 system call.

Source

pub async fn handle_utime<G>( &self, guest: &mut G, call: Utime, ) -> Result<i64, Errno>
where G: Guest<Detcore<T>>,

utime syscall: update access/modification time on a file

Source

pub async fn handle_utimes<G>( &self, guest: &mut G, call: Utimes, ) -> Result<i64, Errno>
where G: Guest<Detcore<T>>,

utimes syscall

Source

pub async fn handle_utimensat<G>( &self, guest: &mut G, call: Utimensat, ) -> Result<i64, Errno>
where G: Guest<Detcore<T>>,

ustimensat syscall

Source

pub async fn handle_socket<G>( &self, guest: &mut G, call: Socket, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

socket system call.

Source

pub async fn handle_socketpair<G>( &self, guest: &mut G, call: Socketpair, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

socketpair system call.

Source

pub async fn handle_setsockopt<G>( &self, guest: &mut G, call: Setsockopt, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Apply a socket option to an already tracked socket. Record mode captures the result; replay re-applies a successful option before later socket I/O, which remains mediated by Detcore’s nonblocking scheduler paths.

Under record and replay a successful SO_RCVTIMEO also sets the socket’s modeled receive timeout (DetFd::receive_timeout), which a blocking accept in turn waits by. The value is read before the call, from the same guest memory in both phases.

Source

pub async fn handle_listen<G>( &self, guest: &mut G, call: Listen, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Transition an already tracked socket into listening state.

Source

pub async fn handle_getsockname<G>( &self, guest: &mut G, call: Getsockname, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return the local address of a tracked socket.

Source

pub async fn handle_getpeername<G>( &self, guest: &mut G, call: Getpeername, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return the peer address of a tracked socket.

Source

pub async fn handle_getsockopt<G>( &self, guest: &mut G, call: Getsockopt, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return an option value from a tracked socket. Hermit only promises normal run determinism for isolated guest networking; record/replay captures the result when external socket state is part of the recording boundary.

Source

pub async fn handle_shutdown<G>( &self, guest: &mut G, call: Shutdown, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Half-close the read and/or write direction of an already tracked socket. shutdown never blocks and returns no data; its effect is deterministic given the container’s socket state, so it forwards via record_or_replay exactly like the rest of the socket family (KVM ratchet round 12).

Shutting the read side marks the socket when Linux shuts it: on success, and on an unconnected socket, whose read side Linux shuts even as it reports ENOTCONN (inet_shutdown). A polled receive asks the kernel before it relies on the mark (finish_after_read_shutdown), since a TCP disconnect opens the read side again (tcp_disconnect).

Source

pub async fn handle_bind<G>( &self, guest: &mut G, call: Bind, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

bind system call.

Source

pub async fn handle_eventfd2<G>( &self, guest: &mut G, call: Eventfd2, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Create and register an event notification counter.

Determinism: strict execution serializes creation, so the initial counter, guest-visible flags, and descriptor number depend only on syscall arguments and the reconstructed file table. Any internally added nonblocking flag remains hidden from the guest.

Source

pub async fn handle_signalfd4<G>( &self, guest: &mut G, call: Signalfd4, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

signalfd4 system call.

Source

pub async fn handle_timerfd_create<G>( &self, guest: &mut G, call: TimerfdCreate, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Create and register a timer notification descriptor.

Determinism: strict execution serializes creation, which exposes only kernel validation, guest-visible flags, and a descriptor number; this operation does not read the clock.

Source

pub async fn handle_timerfd_settime<G>( &self, guest: &mut G, call: TimerfdSettime, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timerfd_settime system call. Under virtual time the setting is kept on Detcore’s clock and the host timerfd stays disarmed (crate::syscalls::timerfd).

Source

pub async fn handle_timerfd_gettime<G>( &self, guest: &mut G, call: TimerfdGettime, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timerfd_gettime system call, answered from the virtual clock for a timerfd Detcore keeps there.

Source

pub async fn handle_inotify_init1<G>( &self, guest: &mut G, call: InotifyInit1, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

inotify_init1 system call (and inotify_init, which is inotify_init1 with no flags).

As for an eventfd, the instance is made physically nonblocking whenever Detcore sequentializes threads outside record and replay, and the guest’s blocking mode is the logical one Detcore keeps on the open file description, which F_GETFL, F_SETFL and FIONBIO show and change (keeps_physically_nonblocking). A blocking read with nothing queued then waits in Detcore’s turns (IOAction::NonblockizeRetry) instead of in the kernel, where nothing orders the other guest tasks whose filesystem operations would end it (https://github.com/rrnewton/hermit/issues/4768).

Source

pub async fn handle_inotify_add_watch<G>( &self, guest: &mut G, call: InotifyAddWatch, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

inotify_add_watch system call.

Source

pub async fn handle_inotify_rm_watch<G>( &self, guest: &mut G, call: InotifyRmWatch, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

inotify_rm_watch system call.

Source

pub async fn handle_memfd_create<G>( &self, guest: &mut G, call: MemfdCreate, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

memfd_create system call.

Source

pub async fn handle_pidfd_open<G>( &self, guest: &mut G, call: PidfdOpen, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Create a pidfd through record/replay and synchronize the descriptor with Detcore’s metadata before fcntl, poll, close, or waitid can observe it.

Source

pub async fn handle_pidfd_send_signal<G>( &self, guest: &mut G, call: Syscall, pidfd: i32, signal: i32, flags: u32, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Deliver a signal to the process referred to by a pidfd.

pidfd_send_signal(pidfd, sig, info, flags) names its target by an open kernel descriptor rather than a numeric PID. Unlike kill(2), there is therefore no host-PID/virtual-PID ambiguity for Detcore to resolve: the pidfd was bound to one specific process at pidfd_open time. Signal generation runs inside this thread’s serialized scheduler turn, as for tgkill/tkill/rt_tgsigqueueinfo, which forward through record/replay and then tell the scheduler that the signal is pending for its receiver (notify_cross_task_signal), so that a receiver parked in a blocking call is woken. This handler does the same for a process with one live thread (https://github.com/rrnewton/hermit/issues/4099).

The flags and the descriptor are decided as Linux decides them, ahead of the forward (https://github.com/rrnewton/hermit/issues/4108):

  • an unknown flag bit, or more than one of the three scope flags, is EINVAL; PIDFD_SIGNAL_THREAD_GROUP is the scope a process pidfd has anyway, and PIDFD_SIGNAL_THREAD sends to the thread whose id is the pid, as tgkill(pid, pid) does; PIDFD_SIGNAL_PROCESS_GROUP is refused by name, even with signal 0, whose answer would come from the process group the guest inherits from Hermit;
  • a descriptor names a process when it is a pidfd from pidfd_open or a /proc/<pid> directory of the guest’s procfs (process_directory_descriptor); any other file is EBADF, and a descriptor Detcore cannot classify is refused by name.

A process-directed signal to a process with several live threads is refused by name, as kill refuses it (Detcore::process_signal_target).

call is the raw Syscall::Other; pidfd/flags are pre-extracted from its arguments by the dispatcher.

Source

pub async fn handle_pidfd_getfd<G>( &self, guest: &mut G, call: Syscall, pidfd: i32, targetfd: i32, flags: u32, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Duplicate a descriptor from the process referred to by a pidfd.

pidfd_getfd(pidfd, targetfd, flags) returns a fresh descriptor in the caller that aliases targetfd in the target process. The source pidfd names one specific process fixed at pidfd_open time, the returned descriptor number is chosen through record/replay (so it is stable across runs), and a successful modeled operation executes inside this thread’s serialized turn, so the result is deterministic. For zero flags, Detcore fails closed with EBADF unless it models the descriptor as a pidfd. Linux checks the kernel-reserved flags first, however, so a nonzero value takes the raw record/replay path and preserves the kernel’s exact EINVAL across valid and invalid descriptor combinations.

The modeled path is narrower than “same process”: the caller must be the thread-group leader named by the pidfd. CLONE_THREAD does not imply CLONE_FILES, so a nonleader can share the target’s TGID while using a different descriptor table. Requiring target == getpid() == gettid() proves that targetfd is resolved in the caller’s exact table. The returned descriptor is then modeled as a real alias of the source open file description. Broader support needs a cross-task OFD channel that Detcore does not have today, so every other target is refused with EOPNOTSUPP. Failed calls leave descriptor state unchanged.

Source

pub async fn handle_userfaultfd<G>( &self, guest: &mut G, call: Userfaultfd, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

userfaultfd system call. A backend that does not implement it declines it with a typed refusal, which is named rather than handed to the guest as its errno (Detcore::report_backend_refusal).

Source

pub async fn handle_accept4<G>( &self, guest: &mut G, call: Accept4, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

accept4 system call (MAYHANG).

§Category: External OR Internal IO

When do we know? We only know if an accept4 did an extra-container IO AFTER it returns. I.e. we could accept a connection from another endpoint in the container, or from the outside, and we don’t know which at the point where accept4 is called.

Source

pub async fn handle_getdents<G>( &self, guest: &mut G, call: Getdents, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

getdents system call.

Source

pub async fn handle_getdents64<G>( &self, guest: &mut G, call: Getdents64, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

getdents64 system call.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn record_or_replay_blocking<G>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Record or replay a BLOCKING syscall without stalling the current thread (and thus deadlocking). This uses a protocol of an extra resource request before/after the syscall to inform the scheduler that the thread is leaving/rejoining the runnable threads pool.

This is only valid to use (1) in hermit record/replay modes, or (2) when we’re in “hermit run”, but we’re NOT sequentializing threads, because in that case it’s ok to use the blocking versions of system calls.

Source

pub async fn record_or_replay_blocking_with_mask<G>( &self, guest: &mut G, call: Syscall, blocked_signal_mask: Option<u64>, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

record_or_replay_blocking for a call that sleeps under its own temporary signal mask (ppoll, pselect6, or an rt_sigsuspend that finds a signal already pending): blocked_signal_mask is the mask the kernel installs for the call, read in this thread’s turn (Resources::blocked_signal_mask). None means the call sleeps under the thread’s own mask.

Source

pub async fn record_or_replay_blocking_with_unknown_mask<G>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

record_or_replay_blocking for a call that sleeps under a temporary mask its turn cannot vouch for (Resources::blocked_signal_mask_unknown): the scheduler records no sleeping mask for it.

Source

pub async fn record_or_replay_blocking_with_unknown_mask_then<G>( &self, guest: &mut G, call: Syscall, after_grant: impl FnOnce(&mut G), ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

record_or_replay_blocking_with_unknown_mask, running after_grant once the scheduler has granted the call and before it runs: after any sibling that ran while the request waited. In replay nothing else runs until the served call finishes; in recording a sibling turn may still run before the call enters the kernel.

Source

pub async fn record_or_replay_rt_sigsuspend<G>( &self, guest: &mut G, call: RtSigsuspend, temporary_mask: u64, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Execute the real rt_sigsuspend outside the runnable set while preserving its signal-only completion condition for the scheduler. temporary_mask is the mask the call sleeps under, as Resources::blocked_signal_mask describes.

Source

pub async fn execute_nonblockable_fd_syscall<G, C>( &self, guest: &mut G, call: C, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>,

Executes a nonblockable syscall according to the following strategy:

  • Record mode: Execute possibly blocking syscall
  • Run mode: Transform the syscall to nonblocking if required before executing

These are fd-oriented syscalls in the sense that whether they block or not depends on whether NONBLOCK was set on the corresponding file descriptor.

Source

pub async fn execute_blocking_pipe_writev<G>( &self, guest: &mut G, call: Writev, expected_open_file: OpenFileId, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Complete a logically blocking pipe writev after Hermit has made the pipe physically nonblocking. A positive short write is an implementation artifact here: without O_NONBLOCK, Linux blocks until the full vector is written unless a signal or error interrupts it. Atomic vectors retain a private iovec snapshot for every retry; larger vectors advance a positive short-write remainder through scalar writes.

Source

pub async fn execute_blocking_pipe_write<G>( &self, guest: &mut G, call: Write, expected_open_file: OpenFileId, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Complete a logically blocking scalar pipe write after Hermit has made the pipe physically nonblocking.

Linux may return a positive short write for a request larger than PIPE_BUF, then continue blocking for the remainder. Hermit’s physical O_NONBLOCK is an internal scheduler mechanism, so exposing that first short write changes guest behavior. Retry the unconsumed suffix until the logical write completes, a signal arrives, or a real error occurs. A signal or error after progress returns the partial byte count, matching Linux.

A concurrent close/dup2 can replace the numeric fd while this helper is yielded. Linux keeps the original open-file description alive inside a blocking syscall, but Reverie does not yet expose a backend-neutral retained-fd handle. Detect replacement before a retry and fail closed rather than writing the suffix into an unrelated object.

Source

pub fn maybe_set_nonblocking_fd<G>(&self, guest: &G, fd: i32)
where G: Guest<Detcore<T>>,

Override physically_nonblocking to true for the file descriptor, if appropriate.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_poll<G>( &self, guest: &mut G, call: Poll, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

poll syscall (MAYHANG)

Source

pub async fn handle_pselect6<G>( &self, guest: &mut G, call: Pselect6, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

pselect6 syscall (MAYHANG).

Source

pub async fn handle_select<G>( &self, guest: &mut G, call: Select, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

select syscall (MAYHANG).

select is the classic timeval sibling of pselect6 (which is already Determinized). It reuses the pselect6 fd-set scratch machinery, but takes a struct timeval timeout (which Linux updates in place with the time not slept) and carries no signal mask.

Source

pub async fn handle_ppoll<G>( &self, guest: &mut G, call: Ppoll, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

ppoll syscall (MAYHANG)

Source

pub async fn handle_internal_poll<G>( &self, guest: &mut G, call: Poll, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Handle a guest-internal poll call that can be fully determinized.

Source

pub async fn handle_epoll_create1<G>( &self, guest: &mut G, call: EpollCreate1, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

epoll_create1 syscall

Source

pub async fn handle_epoll_ctl<G>( &self, guest: &mut G, call: EpollCtl, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Apply an ADD, MOD, or DEL mutation to an epoll interest list.

Determinism: strict execution serializes this mutation, so its result depends only on the operation, event payload, and deterministically reconstructed epoll/file-descriptor state. Record/replay records each answer and replays it; replay reapplies only the recorded successes, so its epoll sets hold what the recording’s held (https://github.com/rrnewton/hermit/issues/31).

Source

pub async fn handle_epoll_pwait<G>( &self, guest: &mut G, call: EpollPwait, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

epoll_pwait syscall (MAYHANG)

Source

pub async fn handle_internal_epoll_pwait<G>( &self, guest: &mut G, call: EpollPwait, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Handle a guest-internal epoll_pwait (NULL sigmask) that can be fully determinized. Mirrors handle_internal_epoll_wait.

Source

pub async fn handle_epoll_pwait2<G>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

epoll_pwait2 syscall (MAYHANG).

epoll_pwait2 is epoll_pwait with a struct timespec * timeout instead of an int-milliseconds timeout; recent glibc implements epoll_wait/ epoll_pwait via epoll_pwait2 when the kernel supports it. The pinned Reverie revision has no typed variant, so it arrives as a raw Syscall::Other and is dispatched here by Sysno. Detcore treats it like epoll_pwait: under record and replay a call that may wait waits outside the turn; under sequentialized threads it polls (handle_internal_epoll_pwait2); otherwise, and for a zero timeout, a scheduler yield point is followed by the raw call.

Source

pub async fn handle_epoll_wait<G>( &self, guest: &mut G, call: EpollWait, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

epoll_wait syscall (MAYHANG)

Source

pub async fn handle_internal_epoll_wait<G>( &self, guest: &mut G, call: EpollWait, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Handle a guest-internal epoll_wait call that can be fully determinized.

Source

pub async fn handle_connect<G>( &self, guest: &mut G, call: Connect, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Connect system call (MAYHANG) Note that connect waits until a TCP handshake but does not wait for accept() on the other end. Nevertheless, it can block for a long time while waiting for connection, unless the socket is already nonblocking.

Source

pub async fn handle_sendrecv<G, C>( &self, guest: &mut G, call: C, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>,

Handles sendto, sendmsg, and sendmmsg syscalls (MAYHANG).

Source

pub async fn handle_sendmsg<G>( &self, guest: &mut G, call: Sendmsg, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Sends one message and invalidates process-wide flock knowledge after success.

The guest can mutate shared message and control memory while this helper deschedules. Parsing before the syscall would not prove which descriptors the kernel later transferred, so a successful unbound send conservatively makes every cached open-file-description lock mode unknown.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_sendmmsg<G>( &self, guest: &mut G, call: Sendmmsg, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Sends a message batch and invalidates process-wide flock knowledge when the kernel reports at least one message sent. This intentionally includes descriptors named only by an unsent tail message: the mutable guest array is not stable across a possible deschedule, so narrower attribution is unsafe.

Source

pub async fn handle_recvmsg<G>( &self, guest: &mut G, call: Recvmsg, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Receive one message, then record the descriptors it received over SCM_RIGHTS (Detcore::record_received_descriptors) and read their locks (Detcore::refresh_description_locks). Both read only the control bytes Linux reports writing, at the control buffer the message named before the call (ReceiveGeometry).

Source

pub async fn handle_sock_diag_recvfrom<G>( &self, guest: &mut G, call: Recvfrom, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

recvfrom on a socket-diag descriptor: one destination buffer.

recv(2) has no syscall of its own on x86_64 — glibc lowers it to recvfrom with a null address — so this covers recv as well, which is what Python’s socket.recv() reaches.

Source

pub async fn handle_sock_diag_read<G>( &self, guest: &mut G, call: Read, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

read on a socket-diag descriptor: one destination buffer.

Source

pub async fn handle_sock_diag_readv<G>( &self, guest: &mut G, call: Readv, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

readv on a socket-diag descriptor: an iovec array, no message header.

Source

pub async fn handle_sock_diag_recvmmsg<G>( &self, guest: &mut G, call: Recvmmsg, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

recvmmsg on a socket-diag descriptor.

Each delivered mmsghdr is a separate datagram with its own byte count in msg_len, so each is gathered and sanitized independently; treating the batch as one buffer would let one message’s length run into the next message’s memory.

Source

pub async fn handle_socket_receive<G, C>( &self, guest: &mut G, call: C, fd: i32, zero_delivers_packet: bool, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>,

Handle a socket receive and retain one timestamp for every alias of its open file.

Source

pub async fn handle_recvmmsg<G>( &self, guest: &mut G, call: Recvmmsg, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Receive a message batch, then record the descriptors it received over SCM_RIGHTS (Detcore::record_received_descriptors) and read their locks, as for recvmsg.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_madvise<G>( &self, guest: &mut G, call: Madvise, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Apply a deterministic policy to madvise(2).

Hints and advice with guest-visible semantics are forwarded to the backend, on every backend. A backend that does not implement an advice answers ENOSYS with the typed refusal MemoryAdvice, which is logged as a warning naming the advice; the guest sees the ENOSYS. Record/replay accepts pure hints as no-ops and passes guest-semantic advice to the recorder and replayer, which record and restore the effects that differ because replay replaces file mappings with anonymous mappings. Reclaim and asynchronous VM-policy advice receives fixed success without exposing host memory pressure. Resource- dependent, backing-store, and hardware-failure operations receive fixed errors.

Source

pub async fn handle_mincore<G>( &self, guest: &mut G, call: Mincore, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Deterministic mincore(2).

Real page residency reflects host memory pressure and is therefore nondeterministic, which is why mincore was previously classified as an unsupported syscall. GNU grep (and other glibc consumers) invoke mincore under the KVM backend on a code path the ptrace backend does not take, so leaving it unsupported aborts the guest under --strict.

Inject the call first so the backend preserves Linux pointer and mapping validation, then report every mapped page as resident. The residency vector is only an advisory hint, so replacing those nondeterministic bits with a constant answer remains bitwise-identical across runs.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_seccomp<G>( &self, _guest: &mut G, call: Seccomp, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Validates seccomp capability probes without installing guest filters.

Source

pub async fn handle_arch_prctl<G>( &self, guest: &mut G, call: ArchPrctl, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Preserve thread-local bases while hiding host CPU feature controls.

Source

pub async fn handle_prctl<G>( &self, guest: &mut G, call: Prctl, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Preserve deterministic Ruby thread controls, report the container’s fixed capability bounding set, and reject options that expose unmodeled process or host state.

Source

pub async fn handle_getpriority<G>( &self, _guest: &mut G, call: Getpriority, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Report the deterministic default nice value for any scheduling target.

Under Hermit the Linux nice value is inert: the scheduler is virtualized and guest threads are serialized onto one virtual CPU, so a process’s, group’s, or user’s scheduling priority never affects guest-visible computation. Report the deterministic default nice (0) for every valid target regardless of who — real tools such as renice -p <pid> always pass an explicit pid, and the raw getpriority(2) never checks permissions on a read, so it must never return EPERM. An unknown which still faults with EINVAL, matching Linux.

Source

pub async fn handle_syslog<G>( &self, _guest: &mut G, call: Syslog, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Present an empty kernel ring buffer. Reads and size queries return zero, controls are inert, and invalid actions preserve Linux’s EINVAL boundary.

Source

pub async fn handle_setpriority<G>( &self, _guest: &mut G, call: Setpriority, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Accept any priority change as a deterministic no-op.

Nice values are inert under Hermit’s virtualized, serialized scheduler, so accept the request without touching host scheduling. The guest runs as a single uid-0 container principal, so a real setpriority(2) from the caller would succeed anyway; never fabricate EPERM for tools such as nice -n 5 <cmd>, renice -p <pid>, or Python’s os.nice. An unknown which still faults with EINVAL, matching Linux.

Source

pub fn handle_process_madvise(pidfd: usize, flags: usize) -> Result<i64, Error>

Reject cross-process memory advice without consulting host process state.

Source

pub async fn handle_uname<G>( &self, guest: &mut G, call: Uname, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

uname syscall

Source

pub async fn handle_getrandom<G>( &self, guest: &mut G, call: Getrandom, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Fill getrandom(2) requests from the current thread’s seeded deterministic PRNG. Supported blocking/source-selection flags share that always-ready stream; invalid Linux flag combinations are rejected before guest memory is touched.

Source

pub async fn handle_setsid<G>( &self, guest: &mut G, call: Setsid, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

setsid system call, answered as Linux answers it from Detcore’s model of the guest’s sessions and process groups (ThreadTree), the same on every backend (slice B3a-4c of https://github.com/rrnewton/hermit/issues/4266). The call then goes to the backend too, so that a kernel that keeps process groups (ptrace’s) stays in step; a backend that keeps none answers ENOSYS (KVM’s executor), and the model is the guest’s answer. Any other disagreement is refused by name, never answered with the backend’s errno. A caller in a nested PID namespace, whose ids are not the model’s, is answered by the kernel and mirrored, as before.

Source

pub async fn handle_group_or_session_getter<G>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

getpgrp, getpgid and getsid, answered from Detcore’s model of the guest’s process groups and sessions (ThreadTree), which setpgid and setsid keep, the same on every backend. The root process’s group and session come from outside the container’s PID namespace and read as 0, as on Linux (scheduler::OUTSIDE_NAMESPACE), and so do the namespace init’s, pid 1. A pid that names no task Linux still has (none registered, an exited thread, a process already reaped, automatically or by the namespace’s init) is ESRCH; a negative one too, as Linux finds no task for it (Scheduler::group_or_session_of). A caller in a nested PID namespace, whose ids are not the model’s, is answered by the kernel as before.

Source

pub async fn handle_setpgid<G>( &self, guest: &mut G, call: Setpgid, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

setpgid system call, answered as Linux answers it from Detcore’s model (ThreadTree::setpgid_decision), and mirrored to the backend, as handle_setsid. A caller in a nested PID namespace is answered by the kernel and the model mirrors it, as before: the kernel validates, and group-selecting waits read the model rather than host /proc state.

Source

pub async fn handle_membarrier<G>( &self, guest: &mut G, call: Membarrier, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

membarrier (system call).

membarrier(2) issues process-wide memory barriers so that userspace can use asymmetric fences (e.g. CPython’s QSBR, RCU-style reclamation). Detcore serializes all guest threads onto a single logical CPU with a total memory order, so any requested barrier is already satisfied and every command is a deterministic no-op. For MEMBARRIER_CMD_QUERY we report the set of commands we emulate so the guest stays on this controlled path instead of a host-dependent fallback; every other command returns success without doing anything.

Source

pub async fn handle_getcpu<G>( &self, guest: &mut G, call: Getcpu, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

getcpu system call: CPU 0 of Hermit’s historical single CPU, or under --virtual-cpus the thread’s virtual CPU (Detcore::virtual_cpu). The vDSO’s getcpu reaches here too: Reverie turns it into this system call.

Source

pub async fn handle_getresuid<G>( &self, guest: &mut G, call: Getresuid, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

getresuid under Hermit. Detcore presents a fixed virtual-root identity, so the real, effective, and saved user IDs are all the constant 0. Under the ptrace backend the guest runs inside a CLONE_NEWUSER namespace that already maps the host uid to 0; emulating the same constant here makes in-process backends (DBT) agree with that golden reference instead of leaking the host uid, and the fully emulated result is bitwise-identical across –verify and record/replay.

Source

pub async fn handle_getresgid<G>( &self, guest: &mut G, call: Getresgid, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

getresgid under Hermit. The group-ID counterpart of handle_getresuid: the real, effective, and saved group IDs are all the fixed virtual-root constant 0, matching the ptrace CLONE_NEWUSER identity and deterministic across –verify and record/replay.

Source

pub async fn handle_get_mempolicy<G>( &self, guest: &mut G, call: GetMempolicy, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

get_mempolicy under Hermit. The container exposes a single virtual NUMA node, so the effective policy is always the default and every address resolves to node 0. The result is fully emulated (never injected), so it is bitwise-identical across the two –verify runs and under record/replay, removing the host-NUMA-topology dependence a passthrough would introduce.

It follows Linux’s kernel_get_mempolicy and do_get_mempolicy (mm/mempolicy.c) for a kernel with one node (nr_node_ids 1):

  • a node mask with maxnode below 1 is EINVAL;
  • an unknown flag (any bit beyond MPOL_F_NODE, MPOL_F_ADDR and MPOL_F_MEMS_ALLOWED, including those above bit 31 of the raw argument, which Reverie’s typed call drops), MPOL_F_MEMS_ALLOWED with MPOL_F_NODE or MPOL_F_ADDR, and an address without MPOL_F_ADDR are EINVAL;
  • *policy is 0: MPOL_DEFAULT, or node 0 for MPOL_F_NODE;
  • the node mask is written as copy_nodes_to_user writes it: node 0 alone for MPOL_F_MEMS_ALLOWED, as /proc/<pid>/status reports Mems_allowed_list (https://github.com/rrnewton/hermit/issues/4214), and no node for the default policy, in ALIGN(maxnode - 1, 64) / 8 bytes (none for a maxnode of 1), EINVAL beyond a page;
  • both are written with the guest’s own access rights, so a read-only or unmapped buffer is EFAULT, as from put_user and copy_to_user.

Limits: set_mempolicy and mbind succeed without recording a policy, so this always reports the default policy, where Linux reports, say, MPOL_INTERLEAVE after set_mempolicy(MPOL_INTERLEAVE). For the same reason MPOL_F_NODE without MPOL_F_ADDR answers node 0, as Linux does for an interleave policy (libnuma’s numa_get_interleave_node), where it refuses it under the default policy. MPOL_F_ADDR does not check that the address is mapped, where Linux answers EFAULT for an unmapped one.

Source

pub async fn handle_move_pages<G>( &self, guest: &mut G, call: MovePages, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

move_pages under Hermit. On a single virtual NUMA node nothing can be relocated, so report every page as residing on node 0 and succeed. The answer is a fixed constant, so it is deterministic across –verify and record/replay.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Preserve Linux readlink errors and canonicalize procfs namespace identities.

Source

pub async fn handle_readlinkat<G>( &self, guest: &mut G, call: Readlinkat, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Preserve Linux readlinkat errors and canonicalize absolute procfs namespace identities.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_alarm<G>( &self, guest: &mut G, call: Alarm, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

We send the alarms to the global scheduler to handle.

Source

pub async fn handle_setitimer<G>( &self, guest: &mut G, call: Setitimer, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Schedule a one-shot or periodic real-time interval timer on Detcore logical time.

Source

pub async fn handle_getitimer<G>( &self, guest: &mut G, call: Getitimer, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return interval-timer state from Detcore’s logical scheduler.

Source

pub async fn handle_pause<G>( &self, guest: &mut G, call: Pause, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

A pause is really just an unbounded sleep.

Source

pub async fn handle_rt_sigsuspend<G>( &self, guest: &mut G, call: RtSigsuspend, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Run rt_sigsuspend, without holding the deterministic scheduler turn where the call sleeps.

The kernel must perform the temporary mask swap atomically and restore the original mask after signal delivery, so execute the real blocking syscall while marking this thread as blocked outside the runnable set.

On a backend whose rt_sigsuspend never sleeps (rt_sigsuspend_never_sleeps: KVM), the call instead runs in the caller’s own turn, and a refused wait yields the turn (https://github.com/rrnewton/hermit/issues/4360).

Source

pub async fn handle_rt_sigaction<G>( &self, guest: &mut G, call: RtSigaction, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

rt_sigaction

Source

pub async fn handle_rt_sigprocmask<G>( &self, guest: &mut G, call: RtSigprocmask, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

rt_sigprocmask

Source

pub async fn handle_rt_sigtimedwait<G>( &self, guest: &mut G, call: RtSigtimedwait, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

rt_sigtimedwait system call

This is handled by the scheduler and not passed to the record/replay layer, because currently signals are not recorded.

Source

pub async fn handle_kill<G>( &self, guest: &mut G, call: Kill, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Resolve signal-zero existence checks in the fixed PID namespace, then route an unambiguous positive-PID process signal through the backend. Backends that can execute with guest PIDs preserve process-directed delivery; DBT translates it to the sole live thread because its native process uses a host PID. An unmaskable SIGKILL to a specific process group is also safe to preserve on backends whose guests use real namespace PIDs; other process-group and broadcast delivery remains refused until Detcore models eligible signal masks.

Source

pub async fn handle_tgkill<G>( &self, guest: &mut G, call: Tgkill, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Send a thread-directed signal through the kernel. Guest PID/TID values are stable in the fresh PID namespace and delivery is scheduler-serialized.

Source

pub async fn handle_tkill<G>( &self, guest: &mut G, call: Tkill, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Send a thread-directed signal through the older two-argument tkill. Like its tgkill sibling, the target thread is addressed by a guest TID that is stable in the fresh PID namespace and delivery is scheduler-serialized, so forwarding the kernel call is deterministic.

Source

pub async fn handle_rt_tgsigqueueinfo<G>( &self, guest: &mut G, call: RtTgsigqueueinfo, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Queue a thread-directed signal with an accompanying siginfo_t. Like tgkill, the target is a specific thread named by stable guest TGID/TID and delivery is scheduler-serialized; the guest-supplied siginfo is deterministic input, so forwarding the kernel call is deterministic.

Source

pub async fn handle_rt_sigqueueinfo<G>( &self, guest: &mut G, call: RtSigqueueinfo, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Queue a process-directed signal with an accompanying siginfo_t. Mirrors handle_kill: preserve process-directed delivery when the backend accepts guest PIDs, otherwise route an unambiguous positive-PID target to its sole live thread via rt_tgsigqueueinfo. Ambiguous multithreaded process-directed delivery is refused until Detcore models eligible masks.

Source

pub async fn handle_rt_sigpending<G>( &self, guest: &mut G, call: RtSigpending, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Read the kernel pending-signal mask after Detcore has serialized all signal generation and delivery events that can change it.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_socket_timestamp_ioctl<G>( &self, guest: &mut G, call: Ioctl, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return the logical timestamp stored by the last successful socket receive.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_getrlimit<G>( &self, guest: &mut G, call: Getrlimit, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return one deterministic process resource limit through the legacy ABI.

Source

pub async fn handle_setrlimit<G>( &self, guest: &mut G, call: Setrlimit, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Update one virtual process resource limit through the legacy ABI.

Source

pub async fn handle_prlimit64<G>( &self, guest: &mut G, call: Prlimit64, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Virtualize prlimit64(2) for the current guest process.

Queries return process-local deterministic values. Exact no-op updates succeed for every valid resource, as on Linux. Changes are kept virtual and restricted to limits that do not grant access to host resources or affect host scheduling. Accepted changes update only guest-observable compatibility state; they are not a sandbox boundary and do not ask the host kernel to enforce the virtual limit.

Source

pub async fn handle_getrusage<G>( &self, guest: &mut G, call: Getrusage, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return a deterministic resource-usage snapshot.

ru_utime/ru_stime come from the SAME logical CPU accounting that backs times(2) (see Self::handle_times), not from host scheduler counters. Reporting them as zero, as this did previously, was both a fidelity bug and an internal contradiction: a guest that called times(2) saw advancing CPU time while getrusage(2) insisted the same process had consumed none. Deriving both from ProcessCpuSnapshot makes the two syscalls agree by construction rather than by coincidence.

The who values report different aggregates, matching Linux:

  • RUSAGE_SELF — this process, summed across its threads.
  • RUSAGE_THREAD — the calling thread alone. This reads the thread’s own logical CPU counters rather than the process totals; substituting the process aggregate would over-report for every multithreaded guest.
  • RUSAGE_CHILDREN — reaped children only, which is exactly what the children_* fields accumulate on wait.

ru_maxrss is populated for the process/thread cases with the guest’s peak resident set size so that programs which require a positive maximum RSS (e.g. rr’s rusage test) behave like they do on Linux. This remains a best-effort host-procfs observation on backends where [Guest::pid] names a host process; it is separate from the configured system-wide memory reported by sysinfo(2) and virtual /proc/meminfo.

Page-fault and context-switch counts remain zero: Detcore does not model them, and synthesizing a plausible-looking number would be worse than reporting none.

Source

pub async fn handle_times<G>( &self, guest: &mut G, call: Times, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Return deterministic elapsed ticks and process CPU accounting for times(2).

Linux’s host boot epoch and scheduler CPU counters are nondeterministic. Detcore instead derives the return value from its global logical clock. Per-process logical CPU accounting aggregates user instruction and syscall-system time across threads; forked processes start fresh counters and contribute their totals to the parent’s child counters when reaped.

Source

pub async fn handle_sysinfo<G>( &self, guest: &mut G, call: Sysinfo, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

handle sysinfo syscall

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn handle_clone_family<G>( &self, guest: &mut G, clone_family: CloneFamily, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Clone, clone3, fork, vfork system calls

Source

pub async fn handle_set_tid_address<G>( &self, guest: &mut G, call: SetTidAddress, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

set_tid_address system call.

Linux owns the guest-visible registration and return value. Detcore mirrors the accepted address into the scheduler so its modeled CHILD_CLEARTID wake targets the same word as the backend.

Source

pub async fn handle_set_robust_list<G>( &self, guest: &mut G, call: SetRobustList, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

set_robust_list system call.

Still a pass-through: Linux owns the registration and supplies the result, and Detcore only records the head address so it can replay exit_robust_list() when the thread dies (see Self::run_robust_list_owner_death). Recording happens only after the kernel accepts the call, so a rejected length or address never becomes Detcore state.

The call goes through record_or_replay, like every other pass-through. Using Guest::inject directly would keep the classification but drop the behavior it implies: the syscall would vanish from a hermit record trace, and a log recorded by a build that did record it would no longer replay.

Source

pub async fn exec_robust_list_wakes<G>( &self, guest: &mut G, ) -> Vec<(DetPid, FutexID)>
where G: Guest<Detcore<T>>,

The owner-death wakes a successful execve makes Linux deliver, in delivery order, for a backend that runs the native execve outside Detcore’s handler and so cannot stage them in this thread’s state (DBT, whose new image starts a fresh runtime). Read while the old image is mapped, before the native exec; none where Detcore does not model owner death (https://github.com/rrnewton/hermit/issues/4268).

Source

pub async fn handle_exit<G>( &self, guest: &mut G, call: Exit, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Exit system call

Source

pub async fn handle_exit_group<G>( &self, guest: &mut G, call: ExitGroup, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Exit_group system call

Source

pub async fn handle_futex<G>( &self, guest: &mut G, call: Futex, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Futex system call, which can block.

Source

pub fn exec_image_at<G>( &self, guest: &G, dirfd: i32, path: &Path, ) -> Option<ExecRecord>
where G: Guest<Detcore<T>>,

Self::exec_image for a path the caller already read from the guest: public for a backend that runs the native execve outside Detcore’s handler, prepares the exec itself (crate::prepare_exec) and reads guest memory its own way.

Source

pub async fn handle_execveat<G>( &self, guest: &mut G, call: Execveat, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Execveat system call. Doesn’t return if successful.

Source

pub async fn handle_sched_yield<G>( &self, guest: &mut G, call: SchedYield, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

End the current logical timeslice for a sequentialized sched_yield.

Source

pub async fn handle_wait4<G>( &self, guest: &mut G, call: Wait4, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

wait4 system call This is handled by the scheduler and not passed to the record/replay layer.

Source

pub async fn handle_waitid<G>( &self, guest: &mut G, call: Waitid, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

waitid system call This is handled by the scheduler and not passed to the record/replay layer.

Source

pub async fn handle_sched_setaffinity<G>( &self, guest: &mut G, call: SchedSetaffinity, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Accept valid affinity masks without changing the host scheduler. Under --virtual-cpus the mask is checked and kept as Linux does (virtual_sched_setaffinity); Hermit’s historical single CPU accepts any readable mask and stays CPU 0.

Source

pub async fn handle_sched_getaffinity<G>( &self, guest: &mut G, call: SchedGetaffinity, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Report that we are on cpu 0, irrespective of what physical CPU we are on.

Linux’s sched_getaffinity accepts any length whose bits cover nr_cpu_ids (one, for Hermit’s single virtual CPU) and that is a multiple of sizeof(long), copies min(len, cpumask_size()) bytes and returns that count. Detcore’s mask is VIRTUAL_CPUSET_BYTES long (kernel_max 127), so an 8-byte set, which CPU_ALLOC gives for the one CPU that sysconf(_SC_NPROCESSORS_CONF) reports, receives its first 8 bytes (https://github.com/rrnewton/hermit/issues/4160). Under --virtual-cpus it is the named task’s mask over the machine’s CPUs (virtual_sched_getaffinity).

Source

pub async fn handle_sched_getparam<G>( &self, guest: &mut G, call: SchedGetparam, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

sched_getparam under Hermit. Detcore replaces the Linux scheduler with its own deterministic one, so a thread’s Linux scheduling parameters are inert. Report a fixed SCHED_OTHER priority of 0. The value is emulated (never injected), so it is identical across –verify runs and record/replay.

Source

pub async fn handle_sched_rr_get_interval<G>( &self, guest: &mut G, call: SchedRrGetInterval, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

sched_rr_get_interval under Hermit. The round-robin quantum is a property of the Linux scheduler, which Detcore does not use, so report a fixed zero interval. Being a constant, it is deterministic across –verify and record/replay.

Source

pub async fn handle_sched_getattr<G>( &self, guest: &mut G, call: SchedGetattr, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

sched_getattr under Hermit. Detcore replaces the Linux scheduler with its own deterministic one, so a thread’s Linux scheduling attributes are inert. Report a fixed SCHED_OTHER policy with zeroed nice/priority/flags. The value is emulated (never injected), so it is identical across –verify runs and record/replay. Re-enables chrt under –strict.

Source

pub async fn handle_sched_setattr<G>( &self, guest: &mut G, call: SchedSetattr, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Linux scheduler attributes cannot affect Detcore’s replacement scheduler, so a well-formed request is accepted as a deterministic no-op, matching the existing sched_setscheduler and sched_setparam policy.

Suppressing the effect is not the same as accepting arguments Linux refuses, nor as refusing arguments Linux accepts. Both directions are guest-visible: a probe that expects EINVAL and sees success takes the wrong branch, and so does one that expects success and sees E2BIG.

The order below is the kernel’s, and it is guest-visible when two arguments are wrong at once, because the kernel returns the first applicable error. sched_setattr() screens uattr, pid and flags together; sched_copy_attr() then handles the size, the trailing bytes and the util-clamp size rule; the signed-policy test follows; the target pid is resolved there, in the middle; and only then does __sched_setscheduler() judge the policy, the flags, the priority and the deadline parameters. Putting any of that last group before the pid lookup makes a request against a nonexistent pid report EINVAL where Linux reports ESRCH.

§Determinism

Every check is a pure function of the guest’s own arguments. The one piece of state consulted is the pid lookup, which asks the scheduler’s own task table via thread_is_live – Detcore state, replayed identically – rather than the host’s process table, which would leak unrelated host processes into a guest-visible answer.

Specifically NOT tool_global::resolve_kill_targets, which models kill(2) and so recognises only thread-group leaders; asking it this question reports ESRCH for a live non-leader thread.

§Deliberately not emulated

Three behaviours are excluded, under one rule: an answer that depends on the host’s kernel configuration or the caller’s privileges is not reproduced, because a deterministic sandbox must not vary with the machine underneath it. Each was measured natively and would differ on a differently-built or differently-privileged host:

  • EPERM for a real-time priority, a SCHED_DEADLINE admission, or a negative nice, which depends on CAP_SYS_NICE and RLIMIT_RTPRIO.
  • EOPNOTSUPP for util-clamp on a VER1 buffer, which depends on CONFIG_UCLAMP_TASK.
  • The sysctl_sched_dl_period_{min,max} bound on SCHED_DEADLINE periods, which is runtime-tunable.

In all three Hermit accepts the request as the same no-op as any other well-formed one. The same rule is why SCHED_EXT (policy 7) is accepted unconditionally even though valid_policy() admits it only with CONFIG_SCHED_CLASS_EXT: picking one answer keeps the sandbox stable across hosts, and accepting is the choice consistent with suppressing the effect rather than refusing the request.

The bracketed regression test in hermit-cli/tests/sched_setattr_abi.rs compares Hermit against the running kernel case by case, and therefore deliberately omits exactly these cases – including them would make its verdict depend on the host it runs on.

Source

pub async fn handle_ioprio_set<G>( &self, _guest: &mut G, call: IoprioSet, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

ioprio_set under Hermit. Detcore serializes guest threads onto one virtual CPU, so the block-layer I/O scheduling class and priority cannot change guest-visible computation. Accept and suppress the request as a deterministic no-op success, mirroring how sched_setaffinity is handled. Re-enables ionice under –strict.

Source

pub async fn handle_ioprio_get<G>( &self, _guest: &mut G, call: IoprioGet, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

ioprio_get under Hermit. I/O priority is inert under Detcore’s serialized scheduler, so process queries observe the fixed raw IOPRIO_CLASS_NONE value while group/user queries observe the effective SCHED_OTHER default of IOPRIO_CLASS_BE/4, without consulting host block-scheduler state.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn sleep_request<G>(guest: &mut G, ns_delta: Duration) -> Resources
where G: Guest<Detcore<T>>,

Convenience function for constructing a sleep request with a nanosecond offset from “now”. A zero offset marks the request a zero-length sleep (Resources::zero_length_sleep).

Source

pub async fn sleep_request_abs<G>(guest: &mut G, time: LogicalTime) -> Resources
where G: Guest<Detcore<T>>,

Convenience function for constructing a sleep request with a absolute nanosecond value from the realtime clock.

Source

pub fn yield_request<G>(guest: &mut G) -> Resources
where G: Guest<Detcore<T>>,

Convenience function for constructing a thread yield request. Implemented as a sleep ending at the epoch (in the past).

Source

pub fn sched_yield_request<G>(guest: &mut G) -> Resources
where G: Guest<Detcore<T>>,

Construct a request for a strong, one-turn scheduler yield.

Source

pub fn random_priority_changepoint_request<G>( guest: &mut G, change_time: LogicalTime, ) -> Resources
where G: Guest<Detcore<T>>,

Construct a random PriorityChangePoint request using the local PRNG.

Source

pub fn priority_changepoint_request<G>( guest: &mut G, change_time: LogicalTime, new_priority: u64, ) -> Resources
where G: Guest<Detcore<T>>,

Construct a PriorityChangePoint request using the supplied time and priority.

Source

pub async fn handle_gettimeofday<G>( &self, guest: &mut G, call: Gettimeofday, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

gettimeofday

Source

pub async fn handle_time<G>( &self, guest: &mut G, call: Time, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

time

Source

pub async fn handle_clock_gettime<G>( &self, guest: &mut G, call: ClockGettime, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

clock_gettime. A CPU clock reads Detcore’s logical CPU accounting (crate::cpu_clock); every other clock reads the guest clock. Every accepted id first makes the guest-clock observation, a global request, as every clock read always has, so a CPU-clock read keeps its place in the schedule and only the value it returns is the CPU clock’s.

Source

pub async fn handle_clock_getres<G>( &self, guest: &mut G, call: ClockGetres, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

clock_getres

Source

pub async fn handle_adjtimex<G>( &self, guest: &mut G, call: Adjtimex, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Report Hermit’s virtual clock with a fixed unsynchronized discipline. Adjustment modes are capability-gated host mutations and receive EPERM.

Source

pub async fn handle_clock_adjtime<G>( &self, guest: &mut G, call: ClockAdjtime, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

Apply the adjtimex policy to CLOCK_REALTIME. Linux does not permit NTP adjustment of the other fixed clock IDs, so reject them with EOPNOTSUPP.

Source

pub async fn handle_nanosleep_family<R>( &self, guest: &mut R, call: NanosleepFamily, ) -> Result<i64, Error>
where R: Guest<Detcore<T>>,

clock_nanosleep and nanosleep

Source

pub async fn handle_timer_create<G>( &self, guest: &mut G, call: TimerCreate, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timer_create: allocate a per-process POSIX timer and hand back a deterministic id, retaining any scheduler-deliverable signal and, for SIGEV_THREAD_ID, the thread it notifies. The notification is checked as Linux’s good_sigevent does: SIGEV_SIGNAL and SIGEV_THREAD (which the kernel treats alike; libc implements the thread itself) need a signal from 1 to 64; SIGEV_THREAD_ID needs, first, a thread of the caller’s own process (https://github.com/rrnewton/hermit/issues/4164); any other value is EINVAL.

Source

pub async fn handle_timer_settime<G>( &self, guest: &mut G, call: TimerSettime, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timer_settime: arm or disarm a timer against the deterministic virtual clock. The old arming is reported through old_value when requested.

Source

pub async fn handle_timer_gettime<G>( &self, guest: &mut G, call: TimerGettime, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timer_gettime: report the time remaining until the next expiration and the reload interval, both computed from the virtual clock.

Source

pub async fn handle_timer_getoverrun<G>( &self, guest: &mut G, call: TimerGetoverrun, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timer_getoverrun: the expirations the timer’s last delivered signal missed (it_overrun_last), where the scheduler coalesces timers (https://github.com/rrnewton/hermit/issues/4262); 0 elsewhere.

Source

pub async fn handle_timer_delete<G>( &self, guest: &mut G, call: TimerDelete, ) -> Result<i64, Error>
where G: Guest<Detcore<T>>,

timer_delete: destroy a timer created by timer_create.

Source§

impl<T> Detcore<T>
where T: RecordOrReplay,

Source

pub async fn register_external_child<G>( &self, guest: &mut G, child_tid: Pid, child_tid_addr: usize, flags: CloneFlags, exit_signal: i32, physical_ids: Option<(i32, i32)>, )
where G: Guest<Detcore<T>>,

Registers a child whose native backend executed the clone syscall.

The caller must initialize the child’s local thread state from the same parent state and clone flags before the child enters its start hook. No backend that reaches this keeps a signal-state copy; one that does must call signal_shadow::before_clone before its native clone, as handle_clone_family does.

Trait Implementations§

Source§

impl<T> AsMut<T> for Detcore<T>
where T: RecordOrReplay,

Source§

fn as_mut(&mut self) -> &mut T

Converts this type into a mutable reference of the (usually inferred) input type.
Source§

impl<T> AsRef<T> for Detcore<T>
where T: RecordOrReplay,

Source§

fn as_ref(&self) -> &T

Converts this type into a shared reference of the (usually inferred) input type.
Source§

impl<T> Debug for Detcore<T>
where T: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl<T> Default for Detcore<T>

Source§

fn default() -> Detcore<T>

Returns the “default value” for a type. Read more
Source§

impl<'de, T> Deserialize<'de> for Detcore<T>
where T: Deserialize<'de>,

Source§

fn deserialize<__D>( __deserializer: __D, ) -> Result<Detcore<T>, <__D as Deserializer<'de>>::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl<T> Serialize for Detcore<T>
where T: Serialize,

Source§

fn serialize<__S>( &self, __serializer: __S, ) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl<T> Tool for Detcore<T>
where T: RecordOrReplay,

Source§

fn may_observe_signal_dequeues(config: &Config) -> bool

Only a sequentialized run on a backend with process signal control can install that control. Whether this run observes dequeues is decided after installation by GlobalState’s GlobalTool::observe_signal_dequeues.

Source§

fn requires_signal_control(config: &Config) -> bool

The same runs require the control: real-timer signals on such a run travel only through acknowledged shared dequeues. Reverie refuses it before any guest code, with ENOSYS on a runner that offers no control, as Detcore’s own installation did before this check moved into Reverie, and with EINVAL on a runner whose offer was not taken. So Detcore’s scheduler needs no refusal of its own for a capable backend without a control.

Source§

fn enforces_serialized_execution(config: &Config) -> bool

A sequentialized run lets one guest thread run guest code at a time, and Detcore refuses every way to install a seccomp filter: seccomp(2) (handle_seccomp, EOPNOTSUPP or an earlier argument error) and prctl(PR_SET_SECCOMP) (not a supported option, ENOSYS). Both refusals need the call intercepted, so the declaration also requires both in the subscriptions. The ptrace backend then keeps the guest’s SIGTRAP action and mask across its own single-steps (https://github.com/rrnewton/hermit/issues/4409).

One exception to “one at a time”: a call the scheduler runs in the background (BlockingExternalIO, BlockingRtSigsuspend) is injected with a returning inject, so its thread runs only that syscall, in the kernel, and stops before any guest instruction. It can install no filter. Its injection is single-stepped, though, and that step’s trap, at a host-timed moment, can reset the process’s shared SIGTRAP action while another thread’s injection is kept. An ignored, or caught and blocked, SIGTRAP can then end at SIG_DFL, where an undeclared run always leaves it; the blocked mask is per thread and is not affected.

Source§

fn new(pid: Pid, cfg: &Config) -> Detcore<T>

Constructor for Detcore process-local state.

Source§

fn observes_sigreturns(config: &Config) -> bool

The signal-state copy follows handler returns from the backend’s reports where it has them (syscalls::signal_shadow).

Source§

fn in_guest_signal_delivery(config: &Config) -> bool

The in-guest delivery gate exactly as the host prepared it (Config::in_guest_signal_delivery): Hermit sets it only where the whole protocol holds, and clears it in every other run.

Source§

fn note_unreported_signal( &self, thread_state: &mut <Detcore<T> as Tool>::ThreadState, signal: &UnreportedSignal, kept: bool, )

Counts each signal the backend passes on without reporting it, which could reach a guest handler, so the delivery counters show what no delivery check saw (A5 step 3.2b).

Source§

fn subscriptions(config: &Config) -> Subscription

NOTE: these subscriptions are used ONLY for hermit run mode. Hermit record has its own subscriptions specified in recorder/mod.rs.

Source§

fn handle_raw_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, signal: i32, ) -> Pin<Box<dyn Future<Output = Result<Option<i32>, Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

A realtime signal (32 to 64), which Signal cannot name: handled as any other (handle_inbound_signal), so it takes its turn in the schedule (https://github.com/rrnewton/reverie/issues/207).

Source§

fn announce_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, signal: i32, ) -> Pin<Box<dyn Future<Output = Result<Option<SignalAnnouncement>, Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

The first half of a delivery a backend announces before the kernel dequeues it (an in-guest backend’s prediction at a return to guest code): the delivery’s turn alone (inbound_turn with TurnScope::Announcement), kept under the announcement’s token (ThreadState::signal_announcements). Detcore never suppresses here: its drop decisions (a duplicate child-exit SIGCHLD, a stale POSIX timer entry) exist only in configurations it refuses. A signal outside 1 to 64 is EINVAL. A configuration in which the commit could not keep the protocol, or a thread holding a claimed SIGCHLD, is refused here, before any turn, with EPROTONOSUPPORT (two_phase_refusal): Detcore has opted in, and EOPNOTSUPP is left to mean that a Tool has not (Reverie’s contract, step 4).

Source§

fn commit_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, announcement: SignalAnnouncement, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

The second half of an announced delivery, once the backend’s final read confirmed it, in ptrace’s order: what the dequeue settles (inbound_dequeued), what the announcement’s turn left (inbound_turn_settled, which also gives the delivery its ordinal), what the granted delivery settles (inbound_delivered), the delivery check (check_delivery_to_record, whose Guest::deliver_signal is the backend’s check of its prediction and whose siginfo read gets none), and, if the check approves, the record (record_checked_delivery). An announcement this thread does not hold, signal and token both, is EINVAL; a drop decision, which only refused configurations make, is EPROTO.

It must not wait for a turn. The announcement refused every configuration in which one of its parts files a scheduler request; if the thread was granted a turn all the same, the backend’s final read no longer holds, so the commit fails closed with EDEADLK, before the delivery is recorded. It checks twice: after inbound_delivered’s effects, before the delivery check, and after the delivery check (whose Guest::deliver_signal must not wait for a turn), before the record. Any Err ends the run.

Source§

fn withdraw_signal_event( &self, thread_state: &mut <Detcore<T> as Tool>::ThreadState, announcement: SignalAnnouncement, )

An announced delivery that does not happen at the backend’s return: its turn stays taken (one more turn than ptrace, which never reports a delivery that does not happen), and nothing else was done for it, so the signal, still pending, is announced again at a later return.

Source§

fn exit_for_predicted_death<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, status: ExitStatus, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

A death the backend’s final read predicted, after its commits (A5 design 16.30; Reverie’s contract, step 3): the thread keeps its turn for its group’s exit (Scheduler::hold_turn_for_group_exit), so no other thread takes a turn between the decision and the grant, then files the group Exit, which that hold grants at the very next turn and which starts the physical-exit hold. This is what DBT’s fatal default action does (end_thread_group_before_fatal_delivery), here at the in-guest runtime’s return, where the kernel then ends the group.

Robust-futex owner-death wakes are not claimed: the group’s robust lists are not walked here, which stays the named gap of https://github.com/rrnewton/hermit/issues/4268 (D24, IR-11).

Refused with EPROTONOSUPPORT before anything is filed when the run does not take in-guest delivery (Config::in_guest_signal_delivery, the gate this protocol needs), and when the thread does not hold the turn, which leaves the decision unprotected: Detcore has opted in, so EOPNOTSUPP stays Reverie’s “not opted in”. A status that is not a death by a signal is EINVAL.

Source§

fn filter_unreported_signal<'life0, 'life1, 'async_trait, R>( &'life0 self, rpc: &'life1 R, unreported: UnreportedSignal, ) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, R: 'async_trait + GlobalRPC<<Detcore<T> as Tool>::GlobalState>, Detcore<T>: 'async_trait,

A SIGCHLD the backend passes on without reporting it is dropped when it is the kernel’s copy of a child-exit notification the scheduler sends itself (scheduler::child_exit_sigchld). A copy the scheduler sent is kept, with the siginfo the tracer gave it: this hook cannot replace it.

Either way, the scheduler’s record of copies Linux discarded when they were sent learns what became of the copy (consumed_signal_control, https://github.com/rrnewton/hermit/issues/4293).

Source§

fn handle_timer_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

A timer fires to preempt the guest and give other threads a turn.

Source§

type GlobalState = GlobalState

The type of the global half that goes along with this Local tool. By including this type, the Tool is actually a complete specification for an instrumentation tool.
Source§

type ThreadState = ThreadState<<T as Tool>::ThreadState>

Tool-state specific to each guest thread. If unset, this defaults to the unit type (), indicating that the tool does not have thread-level state. Read more
Source§

fn stream_role() -> ToolStreamRole

Pure static role of this Tool’s stream, independent of Config booleans.
Source§

fn observes_native_syscalls(config: &Config) -> bool

Pure opt-in to synchronous final-native-call observations. The default performs no additional native queries or descriptor pinning.
Source§

fn observe_native_syscall( &self, _state: &<Detcore<T> as Tool>::ThreadState, facts: &dyn NativeSyscallFacts, call: &NativeSyscallCall, ) -> Result<Option<Arc<dyn NativeSyscallObserver>>, Error>

Observe an actual request after all guest-capable preparation and before native issue. The provider cannot inject, resume or await. Returned cookies are retained by the backend, independently of callback lifetime.
Source§

fn facility_admission(config: &Config) -> Result<(), Error>

Operational facility admission before global initialization or launch. Backends call this explicitly; subscription queries must remain pure.
Source§

fn reserved_input_requirement( config: &Config, ) -> Option<ReservedInputRequirement>

Pure request for one fixed private input page, or no mapping at all. Backends additionally require serialized execution before enabling it.
Source§

fn native_mm_ownership(config: &Config) -> NativeMmOwnership

Declare native executor ownership for Tool code running in the observed address space. See [NativeMmOwnership] for the complete obligation. Read more
Source§

fn handle_signal_dequeue<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, dequeue: SignalDequeue, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

Acknowledges one irreversible pending removal before any later Tool/guest work. The backend retains the journal entry until this returns success. An error is terminal; it is never a rollback or an ordinary guest syscall errno. Notifications are process-wide FIFO, but each runs on its removing Guest. Another owner can wait here before posting its next Tool scheduler request. An opted-in Tool must complete this acknowledgment without requiring that waiting owner to make progress or relinquish its scheduler token. FIFO sequencing alone does not establish deterministic event membership.
Source§

fn handle_sigreturn( &self, thread_state: &mut <Detcore<T> as Tool>::ThreadState, report: SigreturnReport, )

Records one guest rt_sigreturn before it runs, for a Tool for which [Tool::observes_sigreturns] holds, on a backend that reports sigreturns. The backend then carries the call out exactly as it would without the hook. Read more
Source§

fn handle_cpuid_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, eax: u32, ecx: u32, ) -> Pin<Box<dyn Future<Output = Result<CpuIdResult, Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

CPUID is trapped, the tool should implement this function to return [eax, ebx, ecx, edx]. Read more
Source§

fn handle_rdtsc_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, request: Rdtsc, ) -> Pin<Box<dyn Future<Output = Result<RdtscResult, Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

rdtsc/rdtscp is trapped, the tool should implement this function to return the counter. Read more
Source§

fn handle_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, signal: Signal, ) -> Pin<Box<dyn Future<Output = Result<Option<Signal>, Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

Handles a guest’s signal before it is delivered to guest. Read more
Source§

fn init_thread_state( &self, tid: Pid, parent: Option<(Pid, &<Detcore<T> as Tool>::ThreadState)>, ) -> <Detcore<T> as Tool>::ThreadState

A guest process creates additional threads, which need their tool state initialized. This method returns a newly-allocated thread state. This method necessarily runs before the first instruction of a newly created guest thread. Read more
Source§

fn on_initial_image_attached( &self, tid: Pid, state: &mut <Detcore<T> as Tool>::ThreadState, ) -> Result<(), Error>

Bind a fresh root state to an initial image whose launch exec has already completed before this Tool’s first registration. Read more
Source§

fn handle_thread_start<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

Similar to handle_syscall_event, except this traps the first instruction executed by a new thread. Typical uses of this method include delaying thread execution or running initialization actions (injections or rpcs). Read more
Source§

fn handle_post_exec<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

Called upon a successful execve. In handle_syscall_event, after injecting execve, it is not possible to run code after a successful execve because it never returns. Read more
Source§

fn handle_syscall_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, call: Syscall, ) -> Pin<Box<dyn Future<Output = Result<i64, Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + Guest<Detcore<T>>, Detcore<T>: 'async_trait,

The tool receives an event from the guest, via the Reverie program instrumentation. A Reverie syscall handler fires in the moment before a guest syscall executes (like a “prehook”). Read more
Source§

fn on_exit_thread<'life0, 'life1, 'async_trait, G>( &'life0 self, tid: Pid, global_state: &'life1 G, thread_state: <Detcore<T> as Tool>::ThreadState, exit_status: ExitStatus, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, G: 'async_trait + GlobalRPC<<Detcore<T> as Tool>::GlobalState>, Detcore<T>: 'async_trait,

Called when a thread will exit shortly or has exited. That means there will be no more intercepted events on this thread. Read more
§

fn thread_ownership( _cfg: &<Self::GlobalState as GlobalTool>::Config, ) -> ThreadOwnership

How this tool’s guest threads (children created via CLONE_THREAD) are owned. See [ThreadOwnership]. Called once per tree, like [Tool::subscriptions]. Read more
§

fn on_thread_state_ready( &self, _tid: Pid, _global_state: &Self::GlobalState, _thread_state: &Self::ThreadState, ) -> Result<(), Error>

Observe this backend’s actual thread state after a successful handle_thread_start or handle_post_exec, before ordinary guest execution resumes. The ptrace backend supplies this synchronous hook; other backends must implement an equivalent owned lifecycle boundary before relying on it. The default performs no work. Read more
§

fn handle_exec_commit<'life0, 'life1, 'async_trait, T>( &'life0 self, _guest: &'life1 mut T, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, T: 'async_trait + Guest<Self>, Self: 'async_trait,

Called once an execve or execveat this Tool injected can no longer fail, on a backend that carries the exec out itself rather than through a host kernel exec (today the KVM backend). The backend has finished every step that could fail: it has read the path, the argument strings and the environment strings out of guest memory and validated the new image. The old image’s memory is still this guest’s, and the image is replaced only after this returns; handle_post_exec then follows in the new image, and the injection does not return. This is the point Linux’s begin_new_exec reaches past point_of_no_return, where exec_mmap runs exec_mm_release on the old address space (fs/exec.c), after do_execveat_common has copied the strings. A Tool that models what Linux does to the old image there, such as the robust-futex walk futex_exec_release makes, does it here, where the strings the new image receives can no longer change. Read more
§

fn handle_structured_signal_event<'life0, 'life1, 'async_trait, T>( &'life0 self, guest: &'life1 mut T, event: SignalEvent, ) -> Pin<Box<dyn Future<Output = Result<Option<SignalEvent>, Errno>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, T: 'async_trait + Guest<Self>, Self: 'async_trait,

Handles a structured guest signal immediately before a virtual backend delivers it. Read more
§

fn on_exit_process<'life0, 'async_trait, G>( self, _pid: Pid, _global_state: &'life0 G, _exit_status: ExitStatus, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where 'life0: 'async_trait, G: 'async_trait + GlobalRPC<Self::GlobalState>, Self: 'async_trait,

Called when a process will exit shortly or has exited. That means there will be no more intercepted events on from any thread within this process. Read more

Auto Trait Implementations§

§

impl<T> Freeze for Detcore<T>
where T: Freeze,

§

impl<T> RefUnwindSafe for Detcore<T>
where T: RefUnwindSafe,

§

impl<T> Send for Detcore<T>
where T: Send,

§

impl<T> Sync for Detcore<T>
where T: Sync,

§

impl<T> Unpin for Detcore<T>
where T: Unpin,

§

impl<T> UnsafeUnpin for Detcore<T>
where T: UnsafeUnpin,

§

impl<T> UnwindSafe for Detcore<T>
where T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> Conv for T

§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> FmtForward for T

§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Paint for T
where T: ?Sized,

§

fn fg(&self, value: Color) -> Painted<&T>

Returns a styled value derived from self with the foreground set to value.

This method should be used rarely. Instead, prefer to use color-specific builder methods like red() and green(), which have the same functionality but are pithier.

§Example

Set foreground color to white using fg():

use yansi::{Paint, Color};

painted.fg(Color::White);

Set foreground color to white using white().

use yansi::Paint;

painted.white();
§

fn primary(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Primary].

§Example
println!("{}", value.primary());
§

fn fixed(&self, color: u8) -> Painted<&T>

Returns self with the fg() set to [Color :: Fixed].

§Example
println!("{}", value.fixed(color));
§

fn rgb(&self, r: u8, g: u8, b: u8) -> Painted<&T>

Returns self with the fg() set to [Color :: Rgb].

§Example
println!("{}", value.rgb(r, g, b));
§

fn black(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Black].

§Example
println!("{}", value.black());
§

fn red(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Red].

§Example
println!("{}", value.red());
§

fn green(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Green].

§Example
println!("{}", value.green());
§

fn yellow(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Yellow].

§Example
println!("{}", value.yellow());
§

fn blue(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Blue].

§Example
println!("{}", value.blue());
§

fn magenta(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Magenta].

§Example
println!("{}", value.magenta());
§

fn cyan(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: Cyan].

§Example
println!("{}", value.cyan());
§

fn white(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: White].

§Example
println!("{}", value.white());
§

fn bright_black(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightBlack].

§Example
println!("{}", value.bright_black());
§

fn bright_red(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightRed].

§Example
println!("{}", value.bright_red());
§

fn bright_green(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightGreen].

§Example
println!("{}", value.bright_green());
§

fn bright_yellow(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightYellow].

§Example
println!("{}", value.bright_yellow());
§

fn bright_blue(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightBlue].

§Example
println!("{}", value.bright_blue());
§

fn bright_magenta(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightMagenta].

§Example
println!("{}", value.bright_magenta());
§

fn bright_cyan(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightCyan].

§Example
println!("{}", value.bright_cyan());
§

fn bright_white(&self) -> Painted<&T>

Returns self with the fg() set to [Color :: BrightWhite].

§Example
println!("{}", value.bright_white());
§

fn bg(&self, value: Color) -> Painted<&T>

Returns a styled value derived from self with the background set to value.

This method should be used rarely. Instead, prefer to use color-specific builder methods like on_red() and on_green(), which have the same functionality but are pithier.

§Example

Set background color to red using fg():

use yansi::{Paint, Color};

painted.bg(Color::Red);

Set background color to red using on_red().

use yansi::Paint;

painted.on_red();
§

fn on_primary(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Primary].

§Example
println!("{}", value.on_primary());
§

fn on_fixed(&self, color: u8) -> Painted<&T>

Returns self with the bg() set to [Color :: Fixed].

§Example
println!("{}", value.on_fixed(color));
§

fn on_rgb(&self, r: u8, g: u8, b: u8) -> Painted<&T>

Returns self with the bg() set to [Color :: Rgb].

§Example
println!("{}", value.on_rgb(r, g, b));
§

fn on_black(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Black].

§Example
println!("{}", value.on_black());
§

fn on_red(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Red].

§Example
println!("{}", value.on_red());
§

fn on_green(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Green].

§Example
println!("{}", value.on_green());
§

fn on_yellow(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Yellow].

§Example
println!("{}", value.on_yellow());
§

fn on_blue(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Blue].

§Example
println!("{}", value.on_blue());
§

fn on_magenta(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Magenta].

§Example
println!("{}", value.on_magenta());
§

fn on_cyan(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: Cyan].

§Example
println!("{}", value.on_cyan());
§

fn on_white(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: White].

§Example
println!("{}", value.on_white());
§

fn on_bright_black(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightBlack].

§Example
println!("{}", value.on_bright_black());
§

fn on_bright_red(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightRed].

§Example
println!("{}", value.on_bright_red());
§

fn on_bright_green(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightGreen].

§Example
println!("{}", value.on_bright_green());
§

fn on_bright_yellow(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightYellow].

§Example
println!("{}", value.on_bright_yellow());
§

fn on_bright_blue(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightBlue].

§Example
println!("{}", value.on_bright_blue());
§

fn on_bright_magenta(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightMagenta].

§Example
println!("{}", value.on_bright_magenta());
§

fn on_bright_cyan(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightCyan].

§Example
println!("{}", value.on_bright_cyan());
§

fn on_bright_white(&self) -> Painted<&T>

Returns self with the bg() set to [Color :: BrightWhite].

§Example
println!("{}", value.on_bright_white());
§

fn attr(&self, value: Attribute) -> Painted<&T>

Enables the styling [Attribute] value.

This method should be used rarely. Instead, prefer to use attribute-specific builder methods like bold() and underline(), which have the same functionality but are pithier.

§Example

Make text bold using attr():

use yansi::{Paint, Attribute};

painted.attr(Attribute::Bold);

Make text bold using using bold().

use yansi::Paint;

painted.bold();
§

fn bold(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Bold].

§Example
println!("{}", value.bold());
§

fn dim(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Dim].

§Example
println!("{}", value.dim());
§

fn italic(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Italic].

§Example
println!("{}", value.italic());
§

fn underline(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Underline].

§Example
println!("{}", value.underline());

Returns self with the attr() set to [Attribute :: Blink].

§Example
println!("{}", value.blink());

Returns self with the attr() set to [Attribute :: RapidBlink].

§Example
println!("{}", value.rapid_blink());
§

fn invert(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Invert].

§Example
println!("{}", value.invert());
§

fn conceal(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Conceal].

§Example
println!("{}", value.conceal());
§

fn strike(&self) -> Painted<&T>

Returns self with the attr() set to [Attribute :: Strike].

§Example
println!("{}", value.strike());
§

fn quirk(&self, value: Quirk) -> Painted<&T>

Enables the yansi [Quirk] value.

This method should be used rarely. Instead, prefer to use quirk-specific builder methods like mask() and wrap(), which have the same functionality but are pithier.

§Example

Enable wrapping using .quirk():

use yansi::{Paint, Quirk};

painted.quirk(Quirk::Wrap);

Enable wrapping using wrap().

use yansi::Paint;

painted.wrap();
§

fn mask(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Mask].

§Example
println!("{}", value.mask());
§

fn wrap(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Wrap].

§Example
println!("{}", value.wrap());
§

fn linger(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Linger].

§Example
println!("{}", value.linger());
§

fn clear(&self) -> Painted<&T>

👎Deprecated since 1.0.1:

renamed to resetting() due to conflicts with Vec::clear(). The clear() method will be removed in a future release.

Returns self with the quirk() set to [Quirk :: Clear].

§Example
println!("{}", value.clear());
§

fn resetting(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Resetting].

§Example
println!("{}", value.resetting());
§

fn bright(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: Bright].

§Example
println!("{}", value.bright());
§

fn on_bright(&self) -> Painted<&T>

Returns self with the quirk() set to [Quirk :: OnBright].

§Example
println!("{}", value.on_bright());
§

fn whenever(&self, value: Condition) -> Painted<&T>

Conditionally enable styling based on whether the [Condition] value applies. Replaces any previous condition.

See the crate level docs for more details.

§Example

Enable styling painted only when both stdout and stderr are TTYs:

use yansi::{Paint, Condition};

painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);
§

fn new(self) -> Painted<Self>
where Self: Sized,

Create a new [Painted] with a default [Style]. Read more
§

fn paint<S>(&self, style: S) -> Painted<&Self>
where S: Into<Style>,

Apply a style wholesale to self. Any previous style is replaced. Read more
§

impl<T> Pipe for T
where T: ?Sized,

§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> RecordOrReplay for T
where T: Tool<GlobalState = GlobalState>,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T> Tap for T

§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
§

impl<T> TryConv for T

§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more