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: RecordOrReplay> Detcore<T>

Source

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

Openat system call.

Source

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

SYS_close system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Flock, ) -> Result<i64, Error>

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, 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: Guest<Self>>( &self, guest: &mut G, call: Read, ) -> Result<i64, Error>

SYS_read system call (MAYHANG).

Source

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

SYS_pread64 system call.

Source

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

SYS_lseek system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Write, ) -> Result<i64, Error>

SYS_write system call.

Source

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

SYS_pwrite64 system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Readv, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Preadv, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Preadv2, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Pwritev, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Pwritev2, ) -> Result<i64, Error>

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

Source

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

SYS_mmap system call.

Source

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

SYS_munmap system call.

Source

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

SYS_mremap system call.

Source

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

Handles all stat syscalls.

Source

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

statx system call

Source

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

fcntl system call

Source

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

ioctl system call

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Fstatfs, ) -> Result<i64, Error>

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

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Dup, ) -> Result<i64, Errno>

dup system call.

Source

pub async fn handle_dup2<G: Guest<Self>>( &self, guest: &mut G, call: Dup2, ) -> Result<i64, Errno>

dup2 system call.

Source

pub async fn handle_dup3<G: Guest<Self>>( &self, guest: &mut G, call: Dup3, ) -> Result<i64, Errno>

dup3 system call.

Source

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

pipe2 system call.

Source

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

utime syscall: update access/modification time on a file

Source

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

utimes syscall

Source

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

ustimensat syscall

Source

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

socket system call.

Source

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

socketpair system call.

Source

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

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.

Source

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

Transition an already tracked socket into listening state.

Source

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

Return the local address of a tracked socket.

Source

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

Return the peer address of a tracked socket.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Shutdown, ) -> Result<i64, Error>

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).

Source

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

bind system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Signalfd4, ) -> Result<i64, Error>

signalfd4 system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: TimerfdSettime, ) -> Result<i64, Error>

timerfd_settime system call.

Source

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

timerfd_gettime system call.

Source

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

inotify_init1 system call.

Source

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

inotify_add_watch system call.

Source

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

inotify_rm_watch system call.

Source

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

memfd_create system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Syscall, pidfd: RawFd, flags: u32, ) -> Result<i64, Error>

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, exactly like tgkill/tkill/rt_tgsigqueueinfo (which also just forward through record/replay), so forwarding the kernel call is deterministic by construction. This handler adds deterministic argument validation ahead of the forward: a descriptor that Detcore does not model as a pidfd fails closed with EBADF, and the flags field the current kernel reserves is required to be zero (EINVAL otherwise), so the guest-visible errno is fixed and host-independent.

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: Guest<Self>>( &self, guest: &mut G, call: Syscall, pidfd: RawFd, targetfd: RawFd, flags: u32, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Userfaultfd, ) -> Result<i64, Error>

userfaultfd system call.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Getdents, ) -> Result<i64, Error>

getdents system call.

Source

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

getdents64 system call.

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

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_rt_sigsuspend<G: Guest<Self>>( &self, guest: &mut G, call: RtSigsuspend, ) -> Result<i64, Error>

Execute the real rt_sigsuspend outside the runnable set while preserving its signal-only completion condition for the scheduler.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Writev, expected_open_file: OpenFileId, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Write, expected_open_file: OpenFileId, ) -> Result<i64, Error>

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: Guest<Self>>(&self, guest: &G, fd: i32)

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

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

poll syscall (MAYHANG)

Source

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

pselect6 syscall (MAYHANG).

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Ppoll, ) -> Result<i64, Error>

ppoll syscall (MAYHANG)

Source

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

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

Source

pub async fn handle_external_poll<G: Guest<Self>>( &self, guest: &mut G, call: Poll, ) -> Result<i64, Error>

Handle a poll syscall that deponds on external, nondeterminstic IO.

Source

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

epoll_create1 syscall

Source

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

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 rebuilds that state by reinjecting the same control operations.

Source

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

epoll_pwait syscall (MAYHANG)

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Syscall, ) -> Result<i64, Error>

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 exactly like epoll_pwait: a scheduler yield point followed by record/replay-aware forwarding of the raw call.

Source

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

epoll_wait syscall (MAYHANG)

Source

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

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

Source

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

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: Guest<Self>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>>( &self, guest: &mut G, call: C, ) -> Result<i64, Error>

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

Source

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

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

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

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: Guest<Self>>( &self, guest: &mut G, call: Recvmsg, ) -> Result<i64, Error>

Receive one message and replace host socket timestamps with logical time.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Read, ) -> Result<i64, Error>

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

Source

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

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

Source

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

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: Guest<Self>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>>( &self, guest: &mut G, call: C, fd: i32, zero_delivers_packet: bool, ) -> Result<i64, Error>

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

Source

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

Receive a message batch and replace every host socket timestamp with logical time.

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

Apply a deterministic policy to madvise(2).

Ptrace/DBT forward hints and supported advice with guest-visible semantics. 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. KVM accepts pure hints as no-ops and reports ENOSYS for guest-visible semantics its executor cannot provide.

Source

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

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: RecordOrReplay> Detcore<T>

Source

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

Validates seccomp capability probes without installing guest filters.

Source

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

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

Source

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

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: Guest<Self>>( &self, _guest: &mut G, call: Getpriority, ) -> Result<i64, Error>

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: Guest<Self>>( &self, _guest: &mut G, call: Syslog, ) -> Result<i64, Error>

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: Guest<Self>>( &self, _guest: &mut G, call: Setpriority, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Uname, ) -> Result<i64, Error>

uname syscall

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Setsid, ) -> Result<i64, Error>

setsid system call

Source

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

setpgid system call. The kernel remains authoritative for validation; after success Detcore mirrors the guest-visible process-group change so group-selecting waits do not consult host /proc state.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Getcpu, ) -> Result<i64, Error>

getcpu system call

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Getresgid, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: GetMempolicy, ) -> Result<i64, Error>

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.

Source

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

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: RecordOrReplay> Detcore<T>

Preserve Linux readlink errors and canonicalize procfs namespace identities.

Source

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

Preserve Linux readlinkat errors and canonicalize absolute procfs namespace identities.

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

We send the alarms to the global scheduler to handle.

Source

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

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

Source

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

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

Source

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

A pause is really just an unbounded sleep.

Source

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

Run rt_sigsuspend without holding the deterministic scheduler turn.

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.

Source

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

rt_sigaction

Source

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

rt_sigprocmask

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Kill, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Tgkill, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Tkill, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: RtTgsigqueueinfo, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: RtSigqueueinfo, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: RtSigpending, ) -> Result<i64, Error>

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

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

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

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

Return one deterministic process resource limit through the legacy ABI.

Source

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

Update one virtual process resource limit through the legacy ABI.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: Getrusage, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Times, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: Sysinfo, ) -> Result<i64, Error>

handle sysinfo syscall

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

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

Clone, clone3, fork, vfork system calls

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: SetRobustList, ) -> Result<i64, Error>

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 handle_exit<G: Guest<Self>>( &self, guest: &mut G, call: Exit, ) -> Result<i64, Error>

Exit system call

Source

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

Exit_group system call

Source

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

Futex system call, which can block.

Source

pub async fn handle_futex_blocking<G: Guest<Self>>( &self, guest: &mut G, call: Futex, init_val: i32, ) -> Result<i64, Error>

Blocking (precise) Futex implementation. Here we use a two-phase request to the scheduler: before and after the futex wait/wake side effects. We EMULATE futex calls and NEVER run them inside the kernel.

Source

pub async fn handle_futex_polling<G: Guest<Self>>( &self, guest: &mut G, call: Futex, init_val: i32, ) -> Result<i64, Error>

Futex system call, alternative implemenattion where we treat futexes as InternalIOPolling operations.

Source

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

Execveat system call. Doesn’t return if successful.

Source

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

End the current logical timeslice for a sequentialized sched_yield.

Source

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

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

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: SchedSetaffinity, ) -> Result<i64, Error>

Accept valid affinity masks without changing the host scheduler.

Source

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

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

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: SchedRrGetInterval, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: SchedGetattr, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: SchedSetattr, ) -> Result<i64, Error>

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: Guest<Self>>( &self, _guest: &mut G, call: IoprioSet, ) -> Result<i64, Error>

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: Guest<Self>>( &self, _guest: &mut G, call: IoprioGet, ) -> Result<i64, Error>

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: RecordOrReplay> Detcore<T>

Source

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

Convenience function for constructing a sleep request with a nanosecond offset from “now”.

Source

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

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

Source

pub fn yield_request<G: Guest<Self>>(guest: &mut G) -> Resources

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<Self>>(guest: &mut G) -> Resources

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

Source

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

Construct a random PriorityChangePoint request using the local PRNG.

Source

pub fn priority_changepoint_request<G: Guest<Self>>( guest: &mut G, change_time: LogicalTime, new_priority: Priority, ) -> Resources

Construct a PriorityChangePoint request using the supplied time and priority.

Source

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

gettimeofday

Source

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

time

Source

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

clock_gettime

Source

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

clock_gettime

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: ClockAdjtime, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut R, call: NanosleepFamily, ) -> Result<i64, Error>

clock_nanosleep and nanosleep

Source

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

timer_create: allocate a per-process POSIX timer and hand back a deterministic id, retaining any scheduler-deliverable signal.

Source

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

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: Guest<Self>>( &self, guest: &mut G, call: TimerGettime, ) -> Result<i64, Error>

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: Guest<Self>>( &self, guest: &mut G, call: TimerGetoverrun, ) -> Result<i64, Error>

timer_getoverrun: coalesced expiration accounting is not modeled, so the overrun count is always 0 for a live timer.

Source

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

timer_delete: destroy a timer created by timer_create.

Source§

impl<T: RecordOrReplay> Detcore<T>

Source

pub async fn register_external_child<G: Guest<Self>>( &self, guest: &mut G, child_tid: Tid, child_tid_addr: usize, flags: CloneFlags, exit_signal: c_int, physical_ids: Option<(i32, i32)>, )

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.

Trait Implementations§

Source§

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

Source§

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

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

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

Source§

fn as_ref(&self) -> &T

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

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

Source§

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

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

impl<T> Default for Detcore<T>

Source§

fn default() -> Self

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<Self, __D::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::Ok, __S::Error>
where __S: Serializer,

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

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

Source§

fn new(pid: Pid, cfg: &Config) -> Self

Constructor for Detcore process-local state.

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_timer_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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 observe_signal_dequeues(config: &Config) -> bool

Enables acknowledgment of real KVM pending removals before thread start. Other Tools retain their existing pending-state behavior by default. The static-ELF runner requires effective ThreadOwnership::Tool, including caller overrides, and rejects an incompatible Host choice before initializing GlobalState or consuming/executing the installed ELF.
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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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_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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

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

fn init_thread_state( &self, tid: Tid, parent: Option<(Tid, &Self::ThreadState)>, ) -> Self::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 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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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 G: 'async_trait + Guest<Self>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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: Tid, global_state: &'life1 G, thread_state: Self::ThreadState, exit_status: ExitStatus, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
where G: 'async_trait + GlobalRPC<Self::GlobalState>, Self: 'async_trait, 'life0: 'async_trait, 'life1: '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
Source§

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
Source§

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
Source§

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
Source§

impl<T> Conv for T

Source§

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>,

Source§

impl<T> FmtForward for T

Source§

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

Source§

impl<T> Instrument for T

Source§

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

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

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.

Source§

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

Source§

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();
Source§

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

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

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

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

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

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

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));
Source§

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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();
Source§

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

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

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

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

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

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

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));
Source§

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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();
Source§

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

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

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

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

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

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

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

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

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

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());
Source§

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

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

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

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

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

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

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

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

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

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();
Source§

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

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

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

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

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

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

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

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

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

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());
Source§

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

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

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

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

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

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

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

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

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

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);
Source§

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

Create a new Painted with a default Style. Read more
Source§

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
Source§

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

Source§

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
Source§

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
Source§

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
Source§

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
Source§

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
Source§

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.
Source§

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.
Source§

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.
Source§

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
Source§

impl<T> Tap for T

Source§

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

Immutable access to a value. Read more
Source§

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

Mutable access to a value. Read more
Source§

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
Source§

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
Source§

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
Source§

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
Source§

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
Source§

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
Source§

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

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

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

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

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.
Source§

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.
Source§

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.
Source§

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.
Source§

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.
Source§

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.
Source§

impl<T> TryConv for T

Source§

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 = !

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

fn try_from(value: U) -> Result<T, !>

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.
Source§

impl<T> WithSubscriber for T

Source§

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
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

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