pub struct Detcore<T = NoopTool> { /* private fields */ }Expand description
The detcore tool and its per-process state.
Implementations§
Source§impl<T: RecordOrReplay> Detcore<T>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_openat<G: Guest<Self>>(
&self,
guest: &mut G,
call: Openat,
) -> Result<i64, Error>
pub async fn handle_openat<G: Guest<Self>>( &self, guest: &mut G, call: Openat, ) -> Result<i64, Error>
Openat system call.
Sourcepub async fn handle_close<G: Guest<Self>>(
&self,
guest: &mut G,
call: Close,
) -> Result<i64, Error>
pub async fn handle_close<G: Guest<Self>>( &self, guest: &mut G, call: Close, ) -> Result<i64, Error>
SYS_close system call.
Sourcepub async fn handle_close_range<G: Guest<Self>>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_flock<G: Guest<Self>>(
&self,
guest: &mut G,
call: Flock,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_read<G: Guest<Self>>(
&self,
guest: &mut G,
call: Read,
) -> Result<i64, Error>
pub async fn handle_read<G: Guest<Self>>( &self, guest: &mut G, call: Read, ) -> Result<i64, Error>
SYS_read system call (MAYHANG).
Sourcepub async fn handle_pread64<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pread64,
) -> Result<i64, Error>
pub async fn handle_pread64<G: Guest<Self>>( &self, guest: &mut G, call: Pread64, ) -> Result<i64, Error>
SYS_pread64 system call.
Sourcepub async fn handle_lseek<G: Guest<Self>>(
&self,
guest: &mut G,
call: Lseek,
) -> Result<i64, Error>
pub async fn handle_lseek<G: Guest<Self>>( &self, guest: &mut G, call: Lseek, ) -> Result<i64, Error>
SYS_lseek system call.
Sourcepub async fn handle_sendfile<G: Guest<Self>>(
&self,
guest: &mut G,
call: Sendfile,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_write<G: Guest<Self>>(
&self,
guest: &mut G,
call: Write,
) -> Result<i64, Error>
pub async fn handle_write<G: Guest<Self>>( &self, guest: &mut G, call: Write, ) -> Result<i64, Error>
SYS_write system call.
Sourcepub async fn handle_pwrite64<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pwrite64,
) -> Result<i64, Error>
pub async fn handle_pwrite64<G: Guest<Self>>( &self, guest: &mut G, call: Pwrite64, ) -> Result<i64, Error>
SYS_pwrite64 system call.
Sourcepub async fn handle_writev<G: Guest<Self>>(
&self,
guest: &mut G,
call: Writev,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_readv<G: Guest<Self>>(
&self,
guest: &mut G,
call: Readv,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_preadv<G: Guest<Self>>(
&self,
guest: &mut G,
call: Preadv,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_preadv2<G: Guest<Self>>(
&self,
guest: &mut G,
call: Preadv2,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_pwritev<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pwritev,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_pwritev2<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pwritev2,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_mmap<G: Guest<Self>>(
&self,
guest: &mut G,
call: Mmap,
) -> Result<i64, Error>
pub async fn handle_mmap<G: Guest<Self>>( &self, guest: &mut G, call: Mmap, ) -> Result<i64, Error>
SYS_mmap system call.
Sourcepub async fn handle_munmap<G: Guest<Self>>(
&self,
guest: &mut G,
call: Munmap,
) -> Result<i64, Error>
pub async fn handle_munmap<G: Guest<Self>>( &self, guest: &mut G, call: Munmap, ) -> Result<i64, Error>
SYS_munmap system call.
Sourcepub async fn handle_mremap<G: Guest<Self>>(
&self,
guest: &mut G,
call: Mremap,
) -> Result<i64, Error>
pub async fn handle_mremap<G: Guest<Self>>( &self, guest: &mut G, call: Mremap, ) -> Result<i64, Error>
SYS_mremap system call.
Sourcepub async fn handle_stat_family<G: Guest<Self>>(
&self,
guest: &mut G,
call: StatFamily,
) -> Result<i64, Error>
pub async fn handle_stat_family<G: Guest<Self>>( &self, guest: &mut G, call: StatFamily, ) -> Result<i64, Error>
Handles all stat syscalls.
Sourcepub async fn handle_statx<G: Guest<Self>>(
&self,
guest: &mut G,
call: Statx,
) -> Result<i64, Error>
pub async fn handle_statx<G: Guest<Self>>( &self, guest: &mut G, call: Statx, ) -> Result<i64, Error>
statx system call
Sourcepub async fn handle_fcntl<G: Guest<Self>>(
&self,
guest: &mut G,
call: Fcntl,
) -> Result<i64, Error>
pub async fn handle_fcntl<G: Guest<Self>>( &self, guest: &mut G, call: Fcntl, ) -> Result<i64, Error>
fcntl system call
Sourcepub async fn handle_ioctl<G: Guest<Self>>(
&self,
guest: &mut G,
call: Ioctl,
) -> Result<i64, Error>
pub async fn handle_ioctl<G: Guest<Self>>( &self, guest: &mut G, call: Ioctl, ) -> Result<i64, Error>
ioctl system call
Sourcepub async fn handle_statfs<G: Guest<Self>>(
&self,
guest: &mut G,
call: Statfs,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_fstatfs<G: Guest<Self>>(
&self,
guest: &mut G,
call: Fstatfs,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_ownership_change_noop<G: Guest<Self>>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>
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_GETFLvalidatesfchown’s descriptor and distinguishes anO_PATHdescriptor (valid forfstat, invalid forfchown);newfstatatperforms the corresponding path walk for the three pathname variants, preservingENOENT,ENOTDIR,ELOOP,ENAMETOOLONG,EFAULT, andEBADF;fchownatflags are checked explicitly before the lookup, so an unsupported flag still returnsEINVALrather 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): mode06755,04755and02755all become0755;02644keepsS_ISGIDbecause the file is not group-executable; a directory at06755keeps both bits; and ctime moves in every one of those cases, including the plain0644file 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.
Sourcepub async fn handle_dup<G: Guest<Self>>(
&self,
guest: &mut G,
call: Dup,
) -> Result<i64, Errno>
pub async fn handle_dup<G: Guest<Self>>( &self, guest: &mut G, call: Dup, ) -> Result<i64, Errno>
dup system call.
Sourcepub async fn handle_dup2<G: Guest<Self>>(
&self,
guest: &mut G,
call: Dup2,
) -> Result<i64, Errno>
pub async fn handle_dup2<G: Guest<Self>>( &self, guest: &mut G, call: Dup2, ) -> Result<i64, Errno>
dup2 system call.
Sourcepub async fn handle_dup3<G: Guest<Self>>(
&self,
guest: &mut G,
call: Dup3,
) -> Result<i64, Errno>
pub async fn handle_dup3<G: Guest<Self>>( &self, guest: &mut G, call: Dup3, ) -> Result<i64, Errno>
dup3 system call.
Sourcepub async fn handle_pipe2<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pipe2,
) -> Result<i64, Error>
pub async fn handle_pipe2<G: Guest<Self>>( &self, guest: &mut G, call: Pipe2, ) -> Result<i64, Error>
pipe2 system call.
Sourcepub async fn handle_utime<G: Guest<Self>>(
&self,
guest: &mut G,
call: Utime,
) -> Result<i64, Errno>
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
Sourcepub async fn handle_utimes<G: Guest<Self>>(
&self,
guest: &mut G,
call: Utimes,
) -> Result<i64, Errno>
pub async fn handle_utimes<G: Guest<Self>>( &self, guest: &mut G, call: Utimes, ) -> Result<i64, Errno>
utimes syscall
Sourcepub async fn handle_utimensat<G: Guest<Self>>(
&self,
guest: &mut G,
call: Utimensat,
) -> Result<i64, Errno>
pub async fn handle_utimensat<G: Guest<Self>>( &self, guest: &mut G, call: Utimensat, ) -> Result<i64, Errno>
ustimensat syscall
Sourcepub async fn handle_socket<G: Guest<Self>>(
&self,
guest: &mut G,
call: Socket,
) -> Result<i64, Error>
pub async fn handle_socket<G: Guest<Self>>( &self, guest: &mut G, call: Socket, ) -> Result<i64, Error>
socket system call.
Sourcepub async fn handle_socketpair<G: Guest<Self>>(
&self,
guest: &mut G,
call: Socketpair,
) -> Result<i64, Error>
pub async fn handle_socketpair<G: Guest<Self>>( &self, guest: &mut G, call: Socketpair, ) -> Result<i64, Error>
socketpair system call.
Sourcepub async fn handle_setsockopt<G: Guest<Self>>(
&self,
guest: &mut G,
call: Setsockopt,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_listen<G: Guest<Self>>(
&self,
guest: &mut G,
call: Listen,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getsockname<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getsockname,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getpeername<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getpeername,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getsockopt<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getsockopt,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_shutdown<G: Guest<Self>>(
&self,
guest: &mut G,
call: Shutdown,
) -> Result<i64, Error>
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).
Sourcepub async fn handle_bind<G: Guest<Self>>(
&self,
guest: &mut G,
call: Bind,
) -> Result<i64, Error>
pub async fn handle_bind<G: Guest<Self>>( &self, guest: &mut G, call: Bind, ) -> Result<i64, Error>
bind system call.
Sourcepub async fn handle_eventfd2<G: Guest<Self>>(
&self,
guest: &mut G,
call: Eventfd2,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_signalfd4<G: Guest<Self>>(
&self,
guest: &mut G,
call: Signalfd4,
) -> Result<i64, Error>
pub async fn handle_signalfd4<G: Guest<Self>>( &self, guest: &mut G, call: Signalfd4, ) -> Result<i64, Error>
signalfd4 system call.
Sourcepub async fn handle_timerfd_create<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerfdCreate,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_timerfd_settime<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerfdSettime,
) -> Result<i64, Error>
pub async fn handle_timerfd_settime<G: Guest<Self>>( &self, guest: &mut G, call: TimerfdSettime, ) -> Result<i64, Error>
timerfd_settime system call.
Sourcepub async fn handle_timerfd_gettime<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerfdGettime,
) -> Result<i64, Error>
pub async fn handle_timerfd_gettime<G: Guest<Self>>( &self, guest: &mut G, call: TimerfdGettime, ) -> Result<i64, Error>
timerfd_gettime system call.
Sourcepub async fn handle_inotify_init1<G: Guest<Self>>(
&self,
guest: &mut G,
call: InotifyInit1,
) -> Result<i64, Error>
pub async fn handle_inotify_init1<G: Guest<Self>>( &self, guest: &mut G, call: InotifyInit1, ) -> Result<i64, Error>
inotify_init1 system call.
Sourcepub async fn handle_inotify_add_watch<G: Guest<Self>>(
&self,
guest: &mut G,
call: InotifyAddWatch,
) -> Result<i64, Error>
pub async fn handle_inotify_add_watch<G: Guest<Self>>( &self, guest: &mut G, call: InotifyAddWatch, ) -> Result<i64, Error>
inotify_add_watch system call.
Sourcepub async fn handle_inotify_rm_watch<G: Guest<Self>>(
&self,
guest: &mut G,
call: InotifyRmWatch,
) -> Result<i64, Error>
pub async fn handle_inotify_rm_watch<G: Guest<Self>>( &self, guest: &mut G, call: InotifyRmWatch, ) -> Result<i64, Error>
inotify_rm_watch system call.
Sourcepub async fn handle_memfd_create<G: Guest<Self>>(
&self,
guest: &mut G,
call: MemfdCreate,
) -> Result<i64, Error>
pub async fn handle_memfd_create<G: Guest<Self>>( &self, guest: &mut G, call: MemfdCreate, ) -> Result<i64, Error>
memfd_create system call.
Sourcepub async fn handle_pidfd_open<G: Guest<Self>>(
&self,
guest: &mut G,
call: PidfdOpen,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_pidfd_send_signal<G: Guest<Self>>(
&self,
guest: &mut G,
call: Syscall,
pidfd: RawFd,
flags: u32,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_pidfd_getfd<G: Guest<Self>>(
&self,
guest: &mut G,
call: Syscall,
pidfd: RawFd,
targetfd: RawFd,
flags: u32,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_userfaultfd<G: Guest<Self>>(
&self,
guest: &mut G,
call: Userfaultfd,
) -> Result<i64, Error>
pub async fn handle_userfaultfd<G: Guest<Self>>( &self, guest: &mut G, call: Userfaultfd, ) -> Result<i64, Error>
userfaultfd system call.
Sourcepub async fn handle_accept4<G: Guest<Self>>(
&self,
guest: &mut G,
call: Accept4,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getdents<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getdents,
) -> Result<i64, Error>
pub async fn handle_getdents<G: Guest<Self>>( &self, guest: &mut G, call: Getdents, ) -> Result<i64, Error>
getdents system call.
Sourcepub async fn handle_getdents64<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getdents64,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn record_or_replay_blocking<G: Guest<Self>>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>
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.
Sourcepub async fn record_or_replay_rt_sigsuspend<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigsuspend,
) -> Result<i64, Error>
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.
Sourcepub async fn execute_nonblockable_fd_syscall<G: Guest<Self>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>>(
&self,
guest: &mut G,
call: C,
) -> Result<i64, Error>
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.
Sourcepub async fn execute_blocking_pipe_writev<G: Guest<Self>>(
&self,
guest: &mut G,
call: Writev,
expected_open_file: OpenFileId,
) -> Result<i64, Error>
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.
Sourcepub async fn execute_blocking_pipe_write<G: Guest<Self>>(
&self,
guest: &mut G,
call: Write,
expected_open_file: OpenFileId,
) -> Result<i64, Error>
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.
Sourcepub fn maybe_set_nonblocking_fd<G: Guest<Self>>(&self, guest: &G, fd: i32)
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_poll<G: Guest<Self>>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>
pub async fn handle_poll<G: Guest<Self>>( &self, guest: &mut G, call: Poll, ) -> Result<i64, Error>
poll syscall (MAYHANG)
Sourcepub async fn handle_pselect6<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pselect6,
) -> Result<i64, Error>
pub async fn handle_pselect6<G: Guest<Self>>( &self, guest: &mut G, call: Pselect6, ) -> Result<i64, Error>
pselect6 syscall (MAYHANG).
Sourcepub async fn handle_select<G: Guest<Self>>(
&self,
guest: &mut G,
call: Select,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_ppoll<G: Guest<Self>>(
&self,
guest: &mut G,
call: Ppoll,
) -> Result<i64, Error>
pub async fn handle_ppoll<G: Guest<Self>>( &self, guest: &mut G, call: Ppoll, ) -> Result<i64, Error>
ppoll syscall (MAYHANG)
Sourcepub async fn handle_internal_poll<G: Guest<Self>>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_external_poll<G: Guest<Self>>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_epoll_create1<G: Guest<Self>>(
&self,
guest: &mut G,
call: EpollCreate1,
) -> Result<i64, Error>
pub async fn handle_epoll_create1<G: Guest<Self>>( &self, guest: &mut G, call: EpollCreate1, ) -> Result<i64, Error>
epoll_create1 syscall
Sourcepub async fn handle_epoll_ctl<G: Guest<Self>>(
&self,
guest: &mut G,
call: EpollCtl,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_epoll_pwait<G: Guest<Self>>(
&self,
guest: &mut G,
call: EpollPwait,
) -> Result<i64, Error>
pub async fn handle_epoll_pwait<G: Guest<Self>>( &self, guest: &mut G, call: EpollPwait, ) -> Result<i64, Error>
epoll_pwait syscall (MAYHANG)
Sourcepub async fn handle_internal_epoll_pwait<G: Guest<Self>>(
&self,
guest: &mut G,
call: EpollPwait,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_epoll_pwait2<G: Guest<Self>>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_epoll_wait<G: Guest<Self>>(
&self,
guest: &mut G,
call: EpollWait,
) -> Result<i64, Error>
pub async fn handle_epoll_wait<G: Guest<Self>>( &self, guest: &mut G, call: EpollWait, ) -> Result<i64, Error>
epoll_wait syscall (MAYHANG)
Sourcepub async fn handle_internal_epoll_wait<G: Guest<Self>>(
&self,
guest: &mut G,
call: EpollWait,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_connect<G: Guest<Self>>(
&self,
guest: &mut G,
call: Connect,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sendrecv<G: Guest<Self>, C: SyscallInfo + NonblockableSyscall + Into<Syscall>>(
&self,
guest: &mut G,
call: C,
) -> Result<i64, Error>
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).
Sourcepub async fn handle_sendmsg<G: Guest<Self>>(
&self,
guest: &mut G,
call: Sendmsg,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sendmmsg<G: Guest<Self>>(
&self,
guest: &mut G,
call: Sendmmsg,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_recvmsg<G: Guest<Self>>(
&self,
guest: &mut G,
call: Recvmsg,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sock_diag_recvfrom<G: Guest<Self>>(
&self,
guest: &mut G,
call: Recvfrom,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sock_diag_read<G: Guest<Self>>(
&self,
guest: &mut G,
call: Read,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sock_diag_readv<G: Guest<Self>>(
&self,
guest: &mut G,
call: Readv,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sock_diag_recvmmsg<G: Guest<Self>>(
&self,
guest: &mut G,
call: Recvmmsg,
) -> Result<i64, Error>
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§impl<T: RecordOrReplay> Detcore<T>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_madvise<G: Guest<Self>>(
&self,
guest: &mut G,
call: Madvise,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_mincore<G: Guest<Self>>(
&self,
guest: &mut G,
call: Mincore,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_seccomp<G: Guest<Self>>(
&self,
_guest: &mut G,
call: Seccomp,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_arch_prctl<G: Guest<Self>>(
&self,
guest: &mut G,
call: ArchPrctl,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_prctl<G: Guest<Self>>(
&self,
guest: &mut G,
call: Prctl,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getpriority<G: Guest<Self>>(
&self,
_guest: &mut G,
call: Getpriority,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_syslog<G: Guest<Self>>(
&self,
_guest: &mut G,
call: Syslog,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_setpriority<G: Guest<Self>>(
&self,
_guest: &mut G,
call: Setpriority,
) -> Result<i64, Error>
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.
Sourcepub fn handle_process_madvise(pidfd: usize, flags: usize) -> Result<i64, Error>
pub fn handle_process_madvise(pidfd: usize, flags: usize) -> Result<i64, Error>
Reject cross-process memory advice without consulting host process state.
Sourcepub async fn handle_uname<G: Guest<Self>>(
&self,
guest: &mut G,
call: Uname,
) -> Result<i64, Error>
pub async fn handle_uname<G: Guest<Self>>( &self, guest: &mut G, call: Uname, ) -> Result<i64, Error>
uname syscall
Sourcepub async fn handle_getrandom<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getrandom,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_setsid<G: Guest<Self>>(
&self,
guest: &mut G,
call: Setsid,
) -> Result<i64, Error>
pub async fn handle_setsid<G: Guest<Self>>( &self, guest: &mut G, call: Setsid, ) -> Result<i64, Error>
setsid system call
Sourcepub async fn handle_setpgid<G: Guest<Self>>(
&self,
guest: &mut G,
call: Setpgid,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_membarrier<G: Guest<Self>>(
&self,
guest: &mut G,
call: Membarrier,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getcpu<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getcpu,
) -> Result<i64, Error>
pub async fn handle_getcpu<G: Guest<Self>>( &self, guest: &mut G, call: Getcpu, ) -> Result<i64, Error>
getcpu system call
Sourcepub async fn handle_getresuid<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getresuid,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getresgid<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getresgid,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_get_mempolicy<G: Guest<Self>>(
&self,
guest: &mut G,
call: GetMempolicy,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_move_pages<G: Guest<Self>>(
&self,
guest: &mut G,
call: MovePages,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_readlink<G: Guest<Self>>(
&self,
guest: &mut G,
call: Readlink,
) -> Result<i64, Error>
pub async fn handle_readlink<G: Guest<Self>>( &self, guest: &mut G, call: Readlink, ) -> Result<i64, Error>
Preserve Linux readlink errors and canonicalize procfs namespace identities.
Sourcepub async fn handle_readlinkat<G: Guest<Self>>(
&self,
guest: &mut G,
call: Readlinkat,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_alarm<G: Guest<Self>>(
&self,
guest: &mut G,
call: Alarm,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_setitimer<G: Guest<Self>>(
&self,
guest: &mut G,
call: Setitimer,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getitimer<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getitimer,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_pause<G: Guest<Self>>(
&self,
guest: &mut G,
call: Pause,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_rt_sigsuspend<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigsuspend,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_rt_sigaction<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigaction,
) -> Result<i64, Error>
pub async fn handle_rt_sigaction<G: Guest<Self>>( &self, guest: &mut G, call: RtSigaction, ) -> Result<i64, Error>
rt_sigaction
Sourcepub async fn handle_rt_sigprocmask<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigprocmask,
) -> Result<i64, Error>
pub async fn handle_rt_sigprocmask<G: Guest<Self>>( &self, guest: &mut G, call: RtSigprocmask, ) -> Result<i64, Error>
rt_sigprocmask
Sourcepub async fn handle_rt_sigtimedwait<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigtimedwait,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_kill<G: Guest<Self>>(
&self,
guest: &mut G,
call: Kill,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_tgkill<G: Guest<Self>>(
&self,
guest: &mut G,
call: Tgkill,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_tkill<G: Guest<Self>>(
&self,
guest: &mut G,
call: Tkill,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_rt_tgsigqueueinfo<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtTgsigqueueinfo,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_rt_sigqueueinfo<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigqueueinfo,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_rt_sigpending<G: Guest<Self>>(
&self,
guest: &mut G,
call: RtSigpending,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Source§impl<T: RecordOrReplay> Detcore<T>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_getrlimit<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getrlimit,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_setrlimit<G: Guest<Self>>(
&self,
guest: &mut G,
call: Setrlimit,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_prlimit64<G: Guest<Self>>(
&self,
guest: &mut G,
call: Prlimit64,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_getrusage<G: Guest<Self>>(
&self,
guest: &mut G,
call: Getrusage,
) -> Result<i64, Error>
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 thechildren_*fields accumulate onwait.
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.
Sourcepub async fn handle_times<G: Guest<Self>>(
&self,
guest: &mut G,
call: Times,
) -> Result<i64, Error>
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§impl<T: RecordOrReplay> Detcore<T>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn handle_clone_family<G: Guest<Self>>(
&self,
guest: &mut G,
clone_family: CloneFamily,
) -> Result<i64, Error>
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
Sourcepub async fn handle_set_tid_address<G: Guest<Self>>(
&self,
guest: &mut G,
call: SetTidAddress,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_set_robust_list<G: Guest<Self>>(
&self,
guest: &mut G,
call: SetRobustList,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_exit<G: Guest<Self>>(
&self,
guest: &mut G,
call: Exit,
) -> Result<i64, Error>
pub async fn handle_exit<G: Guest<Self>>( &self, guest: &mut G, call: Exit, ) -> Result<i64, Error>
Exit system call
Sourcepub async fn handle_exit_group<G: Guest<Self>>(
&self,
guest: &mut G,
call: ExitGroup,
) -> Result<i64, Error>
pub async fn handle_exit_group<G: Guest<Self>>( &self, guest: &mut G, call: ExitGroup, ) -> Result<i64, Error>
Exit_group system call
Sourcepub async fn handle_futex<G: Guest<Self>>(
&self,
guest: &mut G,
call: Futex,
) -> Result<i64, Error>
pub async fn handle_futex<G: Guest<Self>>( &self, guest: &mut G, call: Futex, ) -> Result<i64, Error>
Futex system call, which can block.
Sourcepub async fn handle_futex_blocking<G: Guest<Self>>(
&self,
guest: &mut G,
call: Futex,
init_val: i32,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_futex_polling<G: Guest<Self>>(
&self,
guest: &mut G,
call: Futex,
init_val: i32,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_execveat<G: Guest<Self>>(
&self,
guest: &mut G,
call: Execveat,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_yield<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedYield,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_wait4<G: Guest<Self>>(
&self,
guest: &mut G,
call: Wait4,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_waitid<G: Guest<Self>>(
&self,
guest: &mut G,
call: Waitid,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_setaffinity<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedSetaffinity,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_getaffinity<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedGetaffinity,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_getparam<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedGetparam,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_rr_get_interval<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedRrGetInterval,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_getattr<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedGetattr,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_sched_setattr<G: Guest<Self>>(
&self,
guest: &mut G,
call: SchedSetattr,
) -> Result<i64, Error>
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_NICEandRLIMIT_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.
Sourcepub async fn handle_ioprio_set<G: Guest<Self>>(
&self,
_guest: &mut G,
call: IoprioSet,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_ioprio_get<G: Guest<Self>>(
&self,
_guest: &mut G,
call: IoprioGet,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub async fn sleep_request<G: Guest<Self>>(
guest: &mut G,
ns_delta: Duration,
) -> Resources
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”.
Sourcepub async fn sleep_request_abs<G: Guest<Self>>(
guest: &mut G,
time: LogicalTime,
) -> Resources
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.
Sourcepub fn yield_request<G: Guest<Self>>(guest: &mut G) -> Resources
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).
Sourcepub fn sched_yield_request<G: Guest<Self>>(guest: &mut G) -> Resources
pub fn sched_yield_request<G: Guest<Self>>(guest: &mut G) -> Resources
Construct a request for a strong, one-turn scheduler yield.
Sourcepub fn random_priority_changepoint_request<G: Guest<Self>>(
guest: &mut G,
change_time: LogicalTime,
) -> Resources
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.
Sourcepub fn priority_changepoint_request<G: Guest<Self>>(
guest: &mut G,
change_time: LogicalTime,
new_priority: Priority,
) -> Resources
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.
Sourcepub async fn handle_gettimeofday<G: Guest<Self>>(
&self,
guest: &mut G,
call: Gettimeofday,
) -> Result<i64, Error>
pub async fn handle_gettimeofday<G: Guest<Self>>( &self, guest: &mut G, call: Gettimeofday, ) -> Result<i64, Error>
gettimeofday
Sourcepub async fn handle_time<G: Guest<Self>>(
&self,
guest: &mut G,
call: Time,
) -> Result<i64, Error>
pub async fn handle_time<G: Guest<Self>>( &self, guest: &mut G, call: Time, ) -> Result<i64, Error>
time
Sourcepub async fn handle_clock_gettime<G: Guest<Self>>(
&self,
guest: &mut G,
call: ClockGettime,
) -> Result<i64, Error>
pub async fn handle_clock_gettime<G: Guest<Self>>( &self, guest: &mut G, call: ClockGettime, ) -> Result<i64, Error>
clock_gettime
Sourcepub async fn handle_clock_getres<G: Guest<Self>>(
&self,
guest: &mut G,
call: ClockGetres,
) -> Result<i64, Error>
pub async fn handle_clock_getres<G: Guest<Self>>( &self, guest: &mut G, call: ClockGetres, ) -> Result<i64, Error>
clock_gettime
Sourcepub async fn handle_adjtimex<G: Guest<Self>>(
&self,
guest: &mut G,
call: Adjtimex,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_clock_adjtime<G: Guest<Self>>(
&self,
guest: &mut G,
call: ClockAdjtime,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_nanosleep_family<R: Guest<Self>>(
&self,
guest: &mut R,
call: NanosleepFamily,
) -> Result<i64, Error>
pub async fn handle_nanosleep_family<R: Guest<Self>>( &self, guest: &mut R, call: NanosleepFamily, ) -> Result<i64, Error>
clock_nanosleep and nanosleep
Sourcepub async fn handle_timer_create<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerCreate,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_timer_settime<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerSettime,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_timer_gettime<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerGettime,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_timer_getoverrun<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerGetoverrun,
) -> Result<i64, Error>
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.
Sourcepub async fn handle_timer_delete<G: Guest<Self>>(
&self,
guest: &mut G,
call: TimerDelete,
) -> Result<i64, Error>
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>
impl<T: RecordOrReplay> Detcore<T>
Sourcepub 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)>,
)
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>
impl<T: RecordOrReplay> AsMut<T> for Detcore<T>
Source§impl<T: RecordOrReplay> AsRef<T> for Detcore<T>
impl<T: RecordOrReplay> AsRef<T> for Detcore<T>
Source§impl<'de, T> Deserialize<'de> for Detcore<T>where
T: Deserialize<'de>,
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>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl<T: RecordOrReplay> Tool for Detcore<T>
impl<T: RecordOrReplay> Tool for Detcore<T>
Source§fn subscriptions(config: &Config) -> Subscription
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,
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
type GlobalState = GlobalState
Source§type ThreadState = ThreadState<<T as Tool>::ThreadState>
type ThreadState = ThreadState<<T as Tool>::ThreadState>
(), indicating that the tool does not have thread-level
state. Read moreSource§fn observe_signal_dequeues(config: &Config) -> bool
fn observe_signal_dequeues(config: &Config) -> bool
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,
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,
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,
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,
[eax, ebx, ecx, edx]. Read moreSource§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,
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,
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,
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,
Source§fn init_thread_state(
&self,
tid: Tid,
parent: Option<(Tid, &Self::ThreadState)>,
) -> Self::ThreadState
fn init_thread_state( &self, tid: Tid, parent: Option<(Tid, &Self::ThreadState)>, ) -> Self::ThreadState
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,
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,
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 moreSource§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,
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,
handle_syscall_event, after
injecting execve, it is not possible to run code after a successful
execve because it never returns. Read moreSource§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,
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,
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,
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,
Source§fn thread_ownership(
_cfg: &<Self::GlobalState as GlobalTool>::Config,
) -> ThreadOwnership
fn thread_ownership( _cfg: &<Self::GlobalState as GlobalTool>::Config, ) -> ThreadOwnership
CLONE_THREAD) are
owned. See ThreadOwnership. Called once per tree, like
Tool::subscriptions. Read moreSource§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,
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,
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,
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,
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
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 bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
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>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
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 rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
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 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.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
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§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
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
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
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
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.impl<T> RecordOrReplay for Twhere
T: Tool<GlobalState = GlobalState>,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.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
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.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
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.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
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.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
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.