pub struct Detcore<T = NoopTool> { /* private fields */ }Expand description
The detcore tool and its per-process state.
Implementations§
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_openat<G>(
&self,
guest: &mut G,
call: Openat,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_openat<G>(
&self,
guest: &mut G,
call: Openat,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Openat system call.
Sourcepub async fn handle_close<G>(
&self,
guest: &mut G,
call: Close,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_close<G>(
&self,
guest: &mut G,
call: Close,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_close system call.
Sourcepub async fn handle_close_range<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_close_range<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Close a contiguous descriptor range and mirror successful closes in Detcore.
The pinned Reverie revision exposes close_range as Syscall::Other. The
common flags=0 operation cannot block and is deterministic for the
process-local descriptor table. CLOSE_RANGE_UNSHARE and
CLOSE_RANGE_CLOEXEC need separate shared-table modeling, so return ENOSYS
for nonzero flags rather than letting strict execution silently diverge.
Sourcepub async fn handle_flock<G>(
&self,
guest: &mut G,
call: Flock,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_flock<G>(
&self,
guest: &mut G,
call: Flock,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Advisory whole-file locks, forwarded to the kernel.
This was previously an unconditional no-op success, justified by the
claim that “an advisory whole-file lock is never contended within the
serialized container”. That is false: serializing guest threads stops
them EXECUTING simultaneously, it does not stop their lock HOLD
INTERVALS from overlapping. A holder that is descheduled – because it
blocked, forked, or simply used up its timeslice – keeps holding while
another process runs and observes the lock. Measured before this change,
on both ptrace and DBI, two processes held the same LOCK_EX
simultaneously while native correctly returned EWOULDBLOCK.
A no-op is the wrong failure direction for a determinism tool. It is deterministically wrong, so double-run verification cannot see it, and it silently removes mutual exclusion from every guest that uses a lockfile.
Forwarding is what fcntl already does for POSIX record locks, which is
why those work. The guest’s descriptor is a real host descriptor, so the
kernel supplies the whole contract for free and consistently with itself:
shared vs exclusive, LOCK_NB, upgrade/downgrade (which Linux performs
non-atomically – see below, this handler compensates), release on
LOCK_UN, release when the last descriptor for the open file
description is closed, and release on process exit.
Determinism, scoped to what is actually true. When every contender is inside the container the outcome is a function of which guest holds the lock, and that is fixed by Detcore’s deterministic schedule, so a given program and seed produce the same acquisition outcome every run.
The scope is not decoration. Because this forwards to the kernel, a
process OUTSIDE the container holding a lock on a guest-visible file
does change the guest’s result – measured: with a host flock -x
holder, a guest LOCK_EX|LOCK_NB returns EWOULDBLOCK, and acquires
without one. That is a host-state leak, it is faithful to Linux, and it
is the same leak fcntl record locks have always had here. Hermit
already declines to make a mutating external filesystem deterministic,
and lock state on a shared file is part of that state. Do not restate
this as “no host state enters the decision”: it does, and the previous
bug in this very function came from writing down a determinism argument
that was broader than the truth.
Note that the no-op this replaced was not host-independent in any useful sense either – it was host-independent by being wrong in all cases.
§Why a blocking request is probed non-blockingly, and what that costs
A guest thread parked inside a kernel flock is not visible to the
deterministic scheduler as blocked, so nothing runs to release the lock
and the whole container wedges – measured: a four-way contention guest
that completes natively hung indefinitely under a plain forwarding
implementation. So a blocking operation is rewritten to LOCK_NB and,
if it turns out to be contended, refused rather than hung.
That rewrite is not free, and the cost is a lock the guest already
owns. Linux converts an flock lock in place and the conversion is not
atomic: flock_lock_inode deletes this open file description’s existing
lock before it scans for a conflict, so a contended LOCK_SH ->
LOCK_EX conversion leaves the caller holding nothing and then reports
EWOULDBLOCK. Natively the guest never observes that intermediate
state, because a blocking request would sleep and eventually acquire.
Under the rewrite it would: the guest asked to wait, got told “no”, and
silently lost the shared lock it was already relying on.
So this handler restores the prior mode before refusing a blocking
conversion, making the refusal side-effect-free. It deliberately does
not restore when the guest itself passed LOCK_NB: there the drop is
exactly what Linux does, and re-acquiring would be a divergence in the
other direction. DetFd::flock_mode is what makes the two cases
distinguishable – it records the mode Detcore last saw the kernel grant
for this open file description, so a first acquisition (nothing to lose)
is not confused with a conversion (something to lose).
That cache covers locks Detcore granted while it had sole knowledge of
the open file description. State becomes permanently unknown when the
descriptor is inherited across a process fork, discovered after tracing
begins, or received through SCM_RIGHTS (recorded unobserved), because another process can
change that shared kernel lock without updating this cache. A blocking
conversion in unknown state is refused before the nonblocking probe, so
the refusal cannot destroy a lock Detcore cannot restore.
Sourcepub async fn handle_read<G>(
&self,
guest: &mut G,
call: Read,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_read<G>(
&self,
guest: &mut G,
call: Read,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_read system call (MAYHANG).
Sourcepub async fn handle_pread64<G>(
&self,
guest: &mut G,
call: Pread64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pread64<G>(
&self,
guest: &mut G,
call: Pread64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_pread64 system call.
Sourcepub async fn handle_lseek<G>(
&self,
guest: &mut G,
call: Lseek,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_lseek<G>(
&self,
guest: &mut G,
call: Lseek,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_lseek system call.
Sourcepub async fn handle_sendfile<G>(
&self,
guest: &mut G,
call: Sendfile,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sendfile<G>(
&self,
guest: &mut G,
call: Sendfile,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Copy data between tracked regular files or memfds.
The kernel advances the input offset (or the explicit offset pointer) and destination offset atomically with the copy. Detcore serializes destination writes while the strict scheduler orders the stable input read, and routes the syscall through record/replay so that the result and offset update stay ordered with other file operations. Socket and pipe destinations can block and need the nonblocking scheduler path; return ENOSYS for those endpoint types so libc/application fallbacks use Detcore’s existing read/write handlers instead.
Sourcepub async fn handle_write<G>(
&self,
guest: &mut G,
call: Write,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_write<G>(
&self,
guest: &mut G,
call: Write,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_write system call.
Sourcepub async fn handle_pwrite64<G>(
&self,
guest: &mut G,
call: Pwrite64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pwrite64<G>(
&self,
guest: &mut G,
call: Pwrite64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_pwrite64 system call.
Sourcepub async fn handle_writev<G>(
&self,
guest: &mut G,
call: Writev,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_writev<G>(
&self,
guest: &mut G,
call: Writev,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_writev system call.
Preserve the initial writev as one kernel operation so its iovec order remains intact. Detcore adds open-file resource ordering and nonblocking scheduler integration; a blocking pipe short write is completed by the helper because Hermit injected O_NONBLOCK.
Sourcepub async fn handle_readv<G>(
&self,
guest: &mut G,
call: Readv,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_readv<G>(
&self,
guest: &mut G,
call: Readv,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_readv system call: the vectored form of read.
Mirrors Self::handle_writev for the read direction. Detcore adds
open-file resource ordering and, for physically nonblocking pipe/socket
fds, the nonblocking scheduler integration. Random devices use the shared
canonical cursor; other descriptors retain their recorded kernel operation.
Sourcepub async fn handle_preadv<G>(
&self,
guest: &mut G,
call: Preadv,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_preadv<G>(
&self,
guest: &mut G,
call: Preadv,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_preadv system call: the vectored form of pread64.
RNG reads use the canonical stream at the explicit offset without advancing its shared cursor. Other files retain their kernel operation.
Sourcepub async fn handle_preadv2<G>(
&self,
guest: &mut G,
call: Preadv2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_preadv2<G>(
&self,
guest: &mut G,
call: Preadv2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_preadv2 system call: positioned vectors, or the shared stream when offset is -1. RNG flag validation precedes output copying.
Sourcepub async fn handle_pwritev<G>(
&self,
guest: &mut G,
call: Pwritev,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pwritev<G>(
&self,
guest: &mut G,
call: Pwritev,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_pwritev system call: the vectored form of pwrite64.
Positioned writes target seekable files and do not block, so this mirrors
Self::handle_pwrite64’s ordering, records/replays the single kernel
operation, and bumps the virtual mtime on a successful write.
Sourcepub async fn handle_pwritev2<G>(
&self,
guest: &mut G,
call: Pwritev2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pwritev2<G>(
&self,
guest: &mut G,
call: Pwritev2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_pwritev2 system call: pwritev with a trailing per-call flags
argument, which record/replay forwards unchanged.
Sourcepub async fn handle_mmap<G>(
&self,
guest: &mut G,
call: Mmap,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_mmap<G>(
&self,
guest: &mut G,
call: Mmap,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_mmap system call.
Sourcepub async fn handle_munmap<G>(
&self,
guest: &mut G,
call: Munmap,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_munmap<G>(
&self,
guest: &mut G,
call: Munmap,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_munmap system call.
Sourcepub async fn handle_mremap<G>(
&self,
guest: &mut G,
call: Mremap,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_mremap<G>(
&self,
guest: &mut G,
call: Mremap,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
SYS_mremap system call.
Sourcepub async fn handle_stat_family<G>(
&self,
guest: &mut G,
call: StatFamily,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_stat_family<G>(
&self,
guest: &mut G,
call: StatFamily,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Handles all stat syscalls.
Sourcepub async fn handle_statx<G>(
&self,
guest: &mut G,
call: Statx,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_statx<G>(
&self,
guest: &mut G,
call: Statx,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
statx system call
Sourcepub async fn handle_fcntl<G>(
&self,
guest: &mut G,
call: Fcntl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_fcntl<G>(
&self,
guest: &mut G,
call: Fcntl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
fcntl system call
Sourcepub async fn handle_ioctl<G>(
&self,
guest: &mut G,
call: Ioctl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_ioctl<G>(
&self,
guest: &mut G,
call: Ioctl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
ioctl system call
Sourcepub async fn handle_statfs<G>(
&self,
guest: &mut G,
call: Statfs,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_statfs<G>(
&self,
guest: &mut G,
call: Statfs,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
statfs: report deterministic filesystem statistics.
The kernel’s statfs reflects live host state: the free-block counts
(f_bfree, f_bavail), the free-inode count (f_ffree) and the device
id (f_fsid) all vary between runs as the underlying host filesystem
fills and drains, which makes a bare passthrough diverge under --verify
(e.g. tar calls statfs on its target filesystem). The static geometry
of the mount (f_type, f_bsize, f_blocks, f_namelen, …) is
reproducible, so we run the real syscall and then canonicalize only the
volatile fields.
Sourcepub async fn handle_fstatfs<G>(
&self,
guest: &mut G,
call: Fstatfs,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_fstatfs<G>(
&self,
guest: &mut G,
call: Fstatfs,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
fstatfs: same determinization as Self::handle_statfs, keyed on an fd.
Sourcepub async fn handle_ownership_change_noop<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_ownership_change_noop<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
The chown family (chown, fchown, fchownat, lchown).
Detcore presents a fixed virtual-root identity, so the permission
answer must be the one a real root gets: success, for any uid. But root
privilege affects only the ownership permission check — it does not
waive pathname, descriptor, or flag errors. A real root’s
chown("/does/not/exist", 0, 0) still fails with ENOENT.
So this does not fabricate a bare Ok(0). It translates the mutation
into a side-effect-free metadata lookup with the same target-selection
arguments, and reports success only if that lookup succeeds:
F_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>(
&self,
guest: &mut G,
call: Dup,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
pub async fn handle_dup<G>(
&self,
guest: &mut G,
call: Dup,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
dup system call.
Sourcepub async fn handle_dup2<G>(
&self,
guest: &mut G,
call: Dup2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_dup2<G>(
&self,
guest: &mut G,
call: Dup2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
dup2 system call.
Sourcepub async fn handle_dup3<G>(
&self,
guest: &mut G,
call: Dup3,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_dup3<G>(
&self,
guest: &mut G,
call: Dup3,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
dup3 system call.
Sourcepub async fn handle_pipe2<G>(
&self,
guest: &mut G,
call: Pipe2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pipe2<G>(
&self,
guest: &mut G,
call: Pipe2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pipe2 system call.
Sourcepub async fn handle_utime<G>(
&self,
guest: &mut G,
call: Utime,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
pub async fn handle_utime<G>(
&self,
guest: &mut G,
call: Utime,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
utime syscall: update access/modification time on a file
Sourcepub async fn handle_utimes<G>(
&self,
guest: &mut G,
call: Utimes,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
pub async fn handle_utimes<G>(
&self,
guest: &mut G,
call: Utimes,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
utimes syscall
Sourcepub async fn handle_utimensat<G>(
&self,
guest: &mut G,
call: Utimensat,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
pub async fn handle_utimensat<G>(
&self,
guest: &mut G,
call: Utimensat,
) -> Result<i64, Errno>where
G: Guest<Detcore<T>>,
ustimensat syscall
Sourcepub async fn handle_socket<G>(
&self,
guest: &mut G,
call: Socket,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_socket<G>(
&self,
guest: &mut G,
call: Socket,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
socket system call.
Sourcepub async fn handle_socketpair<G>(
&self,
guest: &mut G,
call: Socketpair,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_socketpair<G>(
&self,
guest: &mut G,
call: Socketpair,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
socketpair system call.
Sourcepub async fn handle_setsockopt<G>(
&self,
guest: &mut G,
call: Setsockopt,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_setsockopt<G>(
&self,
guest: &mut G,
call: Setsockopt,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Apply a socket option to an already tracked socket. Record mode captures the result; replay re-applies a successful option before later socket I/O, which remains mediated by Detcore’s nonblocking scheduler paths.
Under record and replay a successful SO_RCVTIMEO also sets the
socket’s modeled receive timeout (DetFd::receive_timeout), which a
blocking accept in turn waits by. The value is read before the call,
from the same guest memory in both phases.
Sourcepub async fn handle_listen<G>(
&self,
guest: &mut G,
call: Listen,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_listen<G>(
&self,
guest: &mut G,
call: Listen,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Transition an already tracked socket into listening state.
Sourcepub async fn handle_getsockname<G>(
&self,
guest: &mut G,
call: Getsockname,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getsockname<G>(
&self,
guest: &mut G,
call: Getsockname,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return the local address of a tracked socket.
Sourcepub async fn handle_getpeername<G>(
&self,
guest: &mut G,
call: Getpeername,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getpeername<G>(
&self,
guest: &mut G,
call: Getpeername,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return the peer address of a tracked socket.
Sourcepub async fn handle_getsockopt<G>(
&self,
guest: &mut G,
call: Getsockopt,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getsockopt<G>(
&self,
guest: &mut G,
call: Getsockopt,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return an option value from a tracked socket. Hermit only promises normal run determinism for isolated guest networking; record/replay captures the result when external socket state is part of the recording boundary.
Sourcepub async fn handle_shutdown<G>(
&self,
guest: &mut G,
call: Shutdown,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_shutdown<G>(
&self,
guest: &mut G,
call: Shutdown,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Half-close the read and/or write direction of an already tracked socket. shutdown never blocks and returns no data; its effect is deterministic given the container’s socket state, so it forwards via record_or_replay exactly like the rest of the socket family (KVM ratchet round 12).
Shutting the read side marks the socket when Linux shuts it: on success,
and on an unconnected socket, whose read side Linux shuts even as it
reports ENOTCONN (inet_shutdown). A polled receive asks the kernel
before it relies on the mark (finish_after_read_shutdown), since a TCP
disconnect opens the read side again (tcp_disconnect).
Sourcepub async fn handle_bind<G>(
&self,
guest: &mut G,
call: Bind,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_bind<G>(
&self,
guest: &mut G,
call: Bind,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
bind system call.
Sourcepub async fn handle_eventfd2<G>(
&self,
guest: &mut G,
call: Eventfd2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_eventfd2<G>(
&self,
guest: &mut G,
call: Eventfd2,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Create and register an event notification counter.
Determinism: strict execution serializes creation, so the initial counter, guest-visible flags, and descriptor number depend only on syscall arguments and the reconstructed file table. Any internally added nonblocking flag remains hidden from the guest.
Sourcepub async fn handle_signalfd4<G>(
&self,
guest: &mut G,
call: Signalfd4,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_signalfd4<G>(
&self,
guest: &mut G,
call: Signalfd4,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
signalfd4 system call.
Sourcepub async fn handle_timerfd_create<G>(
&self,
guest: &mut G,
call: TimerfdCreate,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timerfd_create<G>(
&self,
guest: &mut G,
call: TimerfdCreate,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Create and register a timer notification descriptor.
Determinism: strict execution serializes creation, which exposes only kernel validation, guest-visible flags, and a descriptor number; this operation does not read the clock.
Sourcepub async fn handle_timerfd_settime<G>(
&self,
guest: &mut G,
call: TimerfdSettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timerfd_settime<G>(
&self,
guest: &mut G,
call: TimerfdSettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
timerfd_settime system call. Under virtual time the setting is kept on
Detcore’s clock and the host timerfd stays disarmed
(crate::syscalls::timerfd).
Sourcepub async fn handle_timerfd_gettime<G>(
&self,
guest: &mut G,
call: TimerfdGettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timerfd_gettime<G>(
&self,
guest: &mut G,
call: TimerfdGettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
timerfd_gettime system call, answered from the virtual clock for a timerfd Detcore keeps there.
Sourcepub async fn handle_inotify_init1<G>(
&self,
guest: &mut G,
call: InotifyInit1,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_inotify_init1<G>(
&self,
guest: &mut G,
call: InotifyInit1,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
inotify_init1 system call (and inotify_init, which is inotify_init1 with no flags).
As for an eventfd, the instance is made physically nonblocking
whenever Detcore sequentializes threads outside record and replay, and
the guest’s blocking mode is the logical one Detcore keeps on the open
file description, which F_GETFL, F_SETFL and FIONBIO show and
change (keeps_physically_nonblocking). A blocking read with nothing
queued then waits in Detcore’s turns (IOAction::NonblockizeRetry)
instead of in the kernel, where nothing orders the other guest tasks
whose filesystem operations would end it
(https://github.com/rrnewton/hermit/issues/4768).
Sourcepub async fn handle_inotify_add_watch<G>(
&self,
guest: &mut G,
call: InotifyAddWatch,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_inotify_add_watch<G>(
&self,
guest: &mut G,
call: InotifyAddWatch,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
inotify_add_watch system call.
Sourcepub async fn handle_inotify_rm_watch<G>(
&self,
guest: &mut G,
call: InotifyRmWatch,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_inotify_rm_watch<G>(
&self,
guest: &mut G,
call: InotifyRmWatch,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
inotify_rm_watch system call.
Sourcepub async fn handle_memfd_create<G>(
&self,
guest: &mut G,
call: MemfdCreate,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_memfd_create<G>(
&self,
guest: &mut G,
call: MemfdCreate,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
memfd_create system call.
Sourcepub async fn handle_pidfd_open<G>(
&self,
guest: &mut G,
call: PidfdOpen,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pidfd_open<G>(
&self,
guest: &mut G,
call: PidfdOpen,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Create a pidfd through record/replay and synchronize the descriptor with Detcore’s metadata before fcntl, poll, close, or waitid can observe it.
Sourcepub async fn handle_pidfd_send_signal<G>(
&self,
guest: &mut G,
call: Syscall,
pidfd: i32,
signal: i32,
flags: u32,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pidfd_send_signal<G>(
&self,
guest: &mut G,
call: Syscall,
pidfd: i32,
signal: i32,
flags: u32,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Deliver a signal to the process referred to by a pidfd.
pidfd_send_signal(pidfd, sig, info, flags) names its target by an open
kernel descriptor rather than a numeric PID. Unlike kill(2), there is
therefore no host-PID/virtual-PID ambiguity for Detcore to resolve: the
pidfd was bound to one specific process at pidfd_open time. Signal
generation runs inside this thread’s serialized scheduler turn, as for
tgkill/tkill/rt_tgsigqueueinfo, which forward through record/replay
and then tell the scheduler that the signal is pending for its receiver
(notify_cross_task_signal), so that a receiver parked in a blocking call
is woken. This handler does the same for a process with one live thread
(https://github.com/rrnewton/hermit/issues/4099).
The flags and the descriptor are decided as Linux decides them, ahead of the forward (https://github.com/rrnewton/hermit/issues/4108):
- an unknown flag bit, or more than one of the three scope flags, is
EINVAL;
PIDFD_SIGNAL_THREAD_GROUPis the scope a process pidfd has anyway, andPIDFD_SIGNAL_THREADsends to the thread whose id is the pid, astgkill(pid, pid)does;PIDFD_SIGNAL_PROCESS_GROUPis refused by name, even with signal 0, whose answer would come from the process group the guest inherits from Hermit; - a descriptor names a process when it is a pidfd from
pidfd_openor a/proc/<pid>directory of the guest’s procfs (process_directory_descriptor); any other file is EBADF, and a descriptor Detcore cannot classify is refused by name.
A process-directed signal to a process with several live threads is
refused by name, as kill refuses it (Detcore::process_signal_target).
call is the raw Syscall::Other; pidfd/flags are pre-extracted from
its arguments by the dispatcher.
Sourcepub async fn handle_pidfd_getfd<G>(
&self,
guest: &mut G,
call: Syscall,
pidfd: i32,
targetfd: i32,
flags: u32,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pidfd_getfd<G>(
&self,
guest: &mut G,
call: Syscall,
pidfd: i32,
targetfd: i32,
flags: u32,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Duplicate a descriptor from the process referred to by a pidfd.
pidfd_getfd(pidfd, targetfd, flags) returns a fresh descriptor in the
caller that aliases targetfd in the target process. The source pidfd
names one specific process fixed at pidfd_open time, the returned
descriptor number is chosen through record/replay (so it is stable across
runs), and a successful modeled operation executes inside this thread’s
serialized turn, so the result is deterministic. For zero flags, Detcore
fails closed with EBADF unless it models the descriptor as a pidfd.
Linux checks the kernel-reserved flags first, however, so a nonzero
value takes the raw record/replay path and preserves the kernel’s exact
EINVAL across valid and invalid descriptor combinations.
The modeled path is narrower than “same process”: the caller must be the
thread-group leader named by the pidfd. CLONE_THREAD does not imply
CLONE_FILES, so a nonleader can share the target’s TGID while using a
different descriptor table. Requiring target == getpid() == gettid()
proves that targetfd is resolved in the caller’s exact table. The
returned descriptor is then modeled as a real alias of the source open
file description. Broader support needs a cross-task OFD channel that
Detcore does not have today, so every other target is refused with
EOPNOTSUPP. Failed calls leave descriptor state unchanged.
Sourcepub async fn handle_userfaultfd<G>(
&self,
guest: &mut G,
call: Userfaultfd,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_userfaultfd<G>(
&self,
guest: &mut G,
call: Userfaultfd,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
userfaultfd system call. A backend that does not implement it declines
it with a typed refusal, which is named rather than handed to the
guest as its errno (Detcore::report_backend_refusal).
Sourcepub async fn handle_accept4<G>(
&self,
guest: &mut G,
call: Accept4,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_accept4<G>(
&self,
guest: &mut G,
call: Accept4,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
accept4 system call (MAYHANG).
§Category: External OR Internal IO
When do we know? We only know if an accept4 did an extra-container IO AFTER it returns.
I.e. we could accept a connection from another endpoint in the container, or from the outside,
and we don’t know which at the point where accept4 is called.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn record_or_replay_blocking<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn record_or_replay_blocking<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Record or replay a BLOCKING syscall without stalling the current thread (and thus deadlocking). This uses a protocol of an extra resource request before/after the syscall to inform the scheduler that the thread is leaving/rejoining the runnable threads pool.
This is only valid to use (1) in hermit record/replay modes, or (2) when we’re in “hermit run”, but we’re NOT sequentializing threads, because in that case it’s ok to use the blocking versions of system calls.
Sourcepub async fn record_or_replay_blocking_with_mask<G>(
&self,
guest: &mut G,
call: Syscall,
blocked_signal_mask: Option<u64>,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn record_or_replay_blocking_with_mask<G>(
&self,
guest: &mut G,
call: Syscall,
blocked_signal_mask: Option<u64>,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
record_or_replay_blocking for a call that sleeps under its own
temporary signal mask (ppoll, pselect6, or an rt_sigsuspend that
finds a signal already pending): blocked_signal_mask is the mask the
kernel installs for the call, read in this thread’s turn
(Resources::blocked_signal_mask). None means the call sleeps under
the thread’s own mask.
Sourcepub async fn record_or_replay_blocking_with_unknown_mask<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn record_or_replay_blocking_with_unknown_mask<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
record_or_replay_blocking for a call that sleeps under a temporary
mask its turn cannot vouch for (Resources::blocked_signal_mask_unknown):
the scheduler records no sleeping mask for it.
Sourcepub async fn record_or_replay_blocking_with_unknown_mask_then<G>(
&self,
guest: &mut G,
call: Syscall,
after_grant: impl FnOnce(&mut G),
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn record_or_replay_blocking_with_unknown_mask_then<G>(
&self,
guest: &mut G,
call: Syscall,
after_grant: impl FnOnce(&mut G),
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
record_or_replay_blocking_with_unknown_mask, running after_grant once
the scheduler has granted the call and before it runs: after any
sibling that ran while the request waited. In replay nothing else runs
until the served call finishes; in recording a sibling turn may still
run before the call enters the kernel.
Sourcepub async fn record_or_replay_rt_sigsuspend<G>(
&self,
guest: &mut G,
call: RtSigsuspend,
temporary_mask: u64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn record_or_replay_rt_sigsuspend<G>(
&self,
guest: &mut G,
call: RtSigsuspend,
temporary_mask: u64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Execute the real rt_sigsuspend outside the runnable set while preserving
its signal-only completion condition for the scheduler. temporary_mask
is the mask the call sleeps under, as Resources::blocked_signal_mask
describes.
Sourcepub async fn execute_nonblockable_fd_syscall<G, C>(
&self,
guest: &mut G,
call: C,
) -> Result<i64, Error>
pub async fn execute_nonblockable_fd_syscall<G, C>( &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>(
&self,
guest: &mut G,
call: Writev,
expected_open_file: OpenFileId,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn execute_blocking_pipe_writev<G>(
&self,
guest: &mut G,
call: Writev,
expected_open_file: OpenFileId,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Complete a logically blocking pipe writev after Hermit has made the pipe physically nonblocking. A positive short write is an implementation artifact here: without O_NONBLOCK, Linux blocks until the full vector is written unless a signal or error interrupts it. Atomic vectors retain a private iovec snapshot for every retry; larger vectors advance a positive short-write remainder through scalar writes.
Sourcepub async fn execute_blocking_pipe_write<G>(
&self,
guest: &mut G,
call: Write,
expected_open_file: OpenFileId,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn execute_blocking_pipe_write<G>(
&self,
guest: &mut G,
call: Write,
expected_open_file: OpenFileId,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Complete a logically blocking scalar pipe write after Hermit has made the pipe physically nonblocking.
Linux may return a positive short write for a request larger than PIPE_BUF, then
continue blocking for the remainder. Hermit’s physical O_NONBLOCK is an internal
scheduler mechanism, so exposing that first short write changes guest behavior. Retry
the unconsumed suffix until the logical write completes, a signal arrives, or a real
error occurs. A signal or error after progress returns the partial byte count, matching
Linux.
A concurrent close/dup2 can replace the numeric fd while this helper is yielded. Linux keeps the original open-file description alive inside a blocking syscall, but Reverie does not yet expose a backend-neutral retained-fd handle. Detect replacement before a retry and fail closed rather than writing the suffix into an unrelated object.
Sourcepub fn maybe_set_nonblocking_fd<G>(&self, guest: &G, fd: i32)where
G: Guest<Detcore<T>>,
pub fn maybe_set_nonblocking_fd<G>(&self, guest: &G, fd: i32)where
G: Guest<Detcore<T>>,
Override physically_nonblocking to true for the file descriptor, if appropriate.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_poll<G>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_poll<G>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
poll syscall (MAYHANG)
Sourcepub async fn handle_pselect6<G>(
&self,
guest: &mut G,
call: Pselect6,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pselect6<G>(
&self,
guest: &mut G,
call: Pselect6,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pselect6 syscall (MAYHANG).
Sourcepub async fn handle_select<G>(
&self,
guest: &mut G,
call: Select,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_select<G>(
&self,
guest: &mut G,
call: Select,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
select syscall (MAYHANG).
select is the classic timeval sibling of pselect6 (which is already
Determinized). It reuses the pselect6 fd-set scratch machinery, but takes
a struct timeval timeout (which Linux updates in place with the time not
slept) and carries no signal mask.
Sourcepub async fn handle_ppoll<G>(
&self,
guest: &mut G,
call: Ppoll,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_ppoll<G>(
&self,
guest: &mut G,
call: Ppoll,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
ppoll syscall (MAYHANG)
Sourcepub async fn handle_internal_poll<G>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_internal_poll<G>(
&self,
guest: &mut G,
call: Poll,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Handle a guest-internal poll call that can be fully determinized.
Sourcepub async fn handle_epoll_create1<G>(
&self,
guest: &mut G,
call: EpollCreate1,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_epoll_create1<G>(
&self,
guest: &mut G,
call: EpollCreate1,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
epoll_create1 syscall
Sourcepub async fn handle_epoll_ctl<G>(
&self,
guest: &mut G,
call: EpollCtl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_epoll_ctl<G>(
&self,
guest: &mut G,
call: EpollCtl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Apply an ADD, MOD, or DEL mutation to an epoll interest list.
Determinism: strict execution serializes this mutation, so its result depends only on the operation, event payload, and deterministically reconstructed epoll/file-descriptor state. Record/replay records each answer and replays it; replay reapplies only the recorded successes, so its epoll sets hold what the recording’s held (https://github.com/rrnewton/hermit/issues/31).
Sourcepub async fn handle_epoll_pwait<G>(
&self,
guest: &mut G,
call: EpollPwait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_epoll_pwait<G>(
&self,
guest: &mut G,
call: EpollPwait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
epoll_pwait syscall (MAYHANG)
Sourcepub async fn handle_internal_epoll_pwait<G>(
&self,
guest: &mut G,
call: EpollPwait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_internal_epoll_pwait<G>(
&self,
guest: &mut G,
call: EpollPwait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Handle a guest-internal epoll_pwait (NULL sigmask) that can be fully
determinized. Mirrors handle_internal_epoll_wait.
Sourcepub async fn handle_epoll_pwait2<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_epoll_pwait2<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
epoll_pwait2 syscall (MAYHANG).
epoll_pwait2 is epoll_pwait with a struct timespec * timeout instead of
an int-milliseconds timeout; recent glibc implements epoll_wait/
epoll_pwait via epoll_pwait2 when the kernel supports it. The pinned
Reverie revision has no typed variant, so it arrives as a raw
Syscall::Other and is dispatched here by Sysno. Detcore treats it
like epoll_pwait: under record and replay a call that may wait waits
outside the turn; under sequentialized threads it polls
(handle_internal_epoll_pwait2); otherwise, and for a zero timeout, a
scheduler yield point is followed by the raw call.
Sourcepub async fn handle_epoll_wait<G>(
&self,
guest: &mut G,
call: EpollWait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_epoll_wait<G>(
&self,
guest: &mut G,
call: EpollWait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
epoll_wait syscall (MAYHANG)
Sourcepub async fn handle_internal_epoll_wait<G>(
&self,
guest: &mut G,
call: EpollWait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_internal_epoll_wait<G>(
&self,
guest: &mut G,
call: EpollWait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Handle a guest-internal epoll_wait call that can be fully determinized.
Sourcepub async fn handle_connect<G>(
&self,
guest: &mut G,
call: Connect,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_connect<G>(
&self,
guest: &mut G,
call: Connect,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Connect system call (MAYHANG) Note that connect waits until a TCP handshake but does not wait for accept() on the other end. Nevertheless, it can block for a long time while waiting for connection, unless the socket is already nonblocking.
Sourcepub async fn handle_sendrecv<G, C>(
&self,
guest: &mut G,
call: C,
) -> Result<i64, Error>
pub async fn handle_sendrecv<G, C>( &self, guest: &mut G, call: C, ) -> Result<i64, Error>
Handles sendto, sendmsg, and sendmmsg syscalls (MAYHANG).
Sourcepub async fn handle_sendmsg<G>(
&self,
guest: &mut G,
call: Sendmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sendmsg<G>(
&self,
guest: &mut G,
call: Sendmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Sends one message and invalidates process-wide flock knowledge after success.
The guest can mutate shared message and control memory while this helper deschedules. Parsing before the syscall would not prove which descriptors the kernel later transferred, so a successful unbound send conservatively makes every cached open-file-description lock mode unknown.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_sendmmsg<G>(
&self,
guest: &mut G,
call: Sendmmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sendmmsg<G>(
&self,
guest: &mut G,
call: Sendmmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Sends a message batch and invalidates process-wide flock knowledge when the kernel reports at least one message sent. This intentionally includes descriptors named only by an unsent tail message: the mutable guest array is not stable across a possible deschedule, so narrower attribution is unsafe.
Sourcepub async fn handle_recvmsg<G>(
&self,
guest: &mut G,
call: Recvmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_recvmsg<G>(
&self,
guest: &mut G,
call: Recvmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Receive one message, then record the descriptors it received over
SCM_RIGHTS (Detcore::record_received_descriptors) and read their
locks (Detcore::refresh_description_locks). Both read only the
control bytes Linux reports writing, at the control buffer the
message named before the call (ReceiveGeometry).
Sourcepub async fn handle_sock_diag_recvfrom<G>(
&self,
guest: &mut G,
call: Recvfrom,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sock_diag_recvfrom<G>(
&self,
guest: &mut G,
call: Recvfrom,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
recvfrom on a socket-diag descriptor: one destination buffer.
recv(2) has no syscall of its own on x86_64 — glibc lowers it to
recvfrom with a null address — so this covers recv as well, which is
what Python’s socket.recv() reaches.
Sourcepub async fn handle_sock_diag_read<G>(
&self,
guest: &mut G,
call: Read,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sock_diag_read<G>(
&self,
guest: &mut G,
call: Read,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
read on a socket-diag descriptor: one destination buffer.
Sourcepub async fn handle_sock_diag_readv<G>(
&self,
guest: &mut G,
call: Readv,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sock_diag_readv<G>(
&self,
guest: &mut G,
call: Readv,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
readv on a socket-diag descriptor: an iovec array, no message header.
Sourcepub async fn handle_sock_diag_recvmmsg<G>(
&self,
guest: &mut G,
call: Recvmmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sock_diag_recvmmsg<G>(
&self,
guest: &mut G,
call: Recvmmsg,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
recvmmsg on a socket-diag descriptor.
Each delivered mmsghdr is a separate datagram with its own byte count
in msg_len, so each is gathered and sanitized independently; treating
the batch as one buffer would let one message’s length run into the
next message’s memory.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_madvise<G>(
&self,
guest: &mut G,
call: Madvise,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_madvise<G>(
&self,
guest: &mut G,
call: Madvise,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Apply a deterministic policy to madvise(2).
Hints and advice with guest-visible semantics are forwarded to the
backend, on every backend. A backend that does not implement an advice
answers ENOSYS with the typed refusal MemoryAdvice, which is logged as a
warning naming the advice; the guest sees the ENOSYS.
Record/replay accepts pure hints as no-ops and passes guest-semantic advice
to the recorder and replayer, which record and restore the effects that
differ because replay replaces file mappings with anonymous mappings.
Reclaim and asynchronous VM-policy advice receives fixed success without exposing host memory pressure. Resource-
dependent, backing-store, and hardware-failure operations receive fixed errors.
Sourcepub async fn handle_mincore<G>(
&self,
guest: &mut G,
call: Mincore,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_mincore<G>(
&self,
guest: &mut G,
call: Mincore,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Deterministic mincore(2).
Real page residency reflects host memory pressure and is therefore
nondeterministic, which is why mincore was previously classified as an
unsupported syscall. GNU grep (and other glibc consumers) invoke mincore
under the KVM backend on a code path the ptrace backend does not take, so
leaving it unsupported aborts the guest under --strict.
Inject the call first so the backend preserves Linux pointer and mapping validation, then report every mapped page as resident. The residency vector is only an advisory hint, so replacing those nondeterministic bits with a constant answer remains bitwise-identical across runs.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_seccomp<G>(
&self,
_guest: &mut G,
call: Seccomp,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_seccomp<G>(
&self,
_guest: &mut G,
call: Seccomp,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Validates seccomp capability probes without installing guest filters.
Sourcepub async fn handle_arch_prctl<G>(
&self,
guest: &mut G,
call: ArchPrctl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_arch_prctl<G>(
&self,
guest: &mut G,
call: ArchPrctl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Preserve thread-local bases while hiding host CPU feature controls.
Sourcepub async fn handle_prctl<G>(
&self,
guest: &mut G,
call: Prctl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_prctl<G>(
&self,
guest: &mut G,
call: Prctl,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Preserve deterministic Ruby thread controls, report the container’s fixed capability bounding set, and reject options that expose unmodeled process or host state.
Sourcepub async fn handle_getpriority<G>(
&self,
_guest: &mut G,
call: Getpriority,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getpriority<G>(
&self,
_guest: &mut G,
call: Getpriority,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Report the deterministic default nice value for any scheduling target.
Under Hermit the Linux nice value is inert: the scheduler is virtualized
and guest threads are serialized onto one virtual CPU, so a process’s,
group’s, or user’s scheduling priority never affects guest-visible
computation. Report the deterministic default nice (0) for every valid
target regardless of who — real tools such as renice -p <pid> always
pass an explicit pid, and the raw getpriority(2) never checks
permissions on a read, so it must never return EPERM. An unknown
which still faults with EINVAL, matching Linux.
Sourcepub async fn handle_syslog<G>(
&self,
_guest: &mut G,
call: Syslog,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_syslog<G>(
&self,
_guest: &mut G,
call: Syslog,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Present an empty kernel ring buffer. Reads and size queries return zero, controls are inert, and invalid actions preserve Linux’s EINVAL boundary.
Sourcepub async fn handle_setpriority<G>(
&self,
_guest: &mut G,
call: Setpriority,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_setpriority<G>(
&self,
_guest: &mut G,
call: Setpriority,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Accept any priority change as a deterministic no-op.
Nice values are inert under Hermit’s virtualized, serialized scheduler,
so accept the request without touching host scheduling. The guest runs as
a single uid-0 container principal, so a real setpriority(2) from the
caller would succeed anyway; never fabricate EPERM for tools such as
nice -n 5 <cmd>, renice -p <pid>, or Python’s os.nice. An unknown
which still faults with EINVAL, matching Linux.
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>(
&self,
guest: &mut G,
call: Uname,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_uname<G>(
&self,
guest: &mut G,
call: Uname,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
uname syscall
Sourcepub async fn handle_getrandom<G>(
&self,
guest: &mut G,
call: Getrandom,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getrandom<G>(
&self,
guest: &mut G,
call: Getrandom,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Fill getrandom(2) requests from the current thread’s seeded deterministic PRNG.
Supported blocking/source-selection flags share that always-ready stream; invalid Linux
flag combinations are rejected before guest memory is touched.
Sourcepub async fn handle_setsid<G>(
&self,
guest: &mut G,
call: Setsid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_setsid<G>(
&self,
guest: &mut G,
call: Setsid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
setsid system call, answered as Linux answers it from Detcore’s model
of the guest’s sessions and process groups (ThreadTree), the same on
every backend (slice B3a-4c of
https://github.com/rrnewton/hermit/issues/4266). The call then goes to
the backend too, so that a kernel that keeps process groups (ptrace’s)
stays in step; a backend that keeps none answers ENOSYS (KVM’s
executor), and the model is the guest’s answer. Any other disagreement
is refused by name, never answered with the backend’s errno. A caller
in a nested PID namespace, whose ids are not the model’s, is answered
by the kernel and mirrored, as before.
Sourcepub async fn handle_group_or_session_getter<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_group_or_session_getter<G>(
&self,
guest: &mut G,
call: Syscall,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
getpgrp, getpgid and getsid, answered from Detcore’s model of the
guest’s process groups and sessions (ThreadTree), which setpgid and
setsid keep, the same on every backend. The root process’s group and
session come from outside the container’s PID namespace and read as 0,
as on Linux (scheduler::OUTSIDE_NAMESPACE), and so do the namespace
init’s, pid 1. A pid that names no task Linux still has (none
registered, an exited thread, a process already reaped, automatically
or by the namespace’s init) is ESRCH; a negative one too, as Linux
finds no task for it (Scheduler::group_or_session_of). A caller in a
nested PID namespace, whose ids are not the model’s, is answered by the
kernel as before.
Sourcepub async fn handle_setpgid<G>(
&self,
guest: &mut G,
call: Setpgid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_setpgid<G>(
&self,
guest: &mut G,
call: Setpgid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
setpgid system call, answered as Linux answers it from Detcore’s model
(ThreadTree::setpgid_decision), and mirrored to the backend, as
handle_setsid. A caller in a nested PID namespace is answered by the
kernel and the model mirrors it, as before: the kernel validates, and
group-selecting waits read the model rather than host /proc state.
Sourcepub async fn handle_membarrier<G>(
&self,
guest: &mut G,
call: Membarrier,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_membarrier<G>(
&self,
guest: &mut G,
call: Membarrier,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
membarrier (system call).
membarrier(2) issues process-wide memory barriers so that userspace can
use asymmetric fences (e.g. CPython’s QSBR, RCU-style reclamation).
Detcore serializes all guest threads onto a single logical CPU with a
total memory order, so any requested barrier is already satisfied and
every command is a deterministic no-op. For MEMBARRIER_CMD_QUERY we
report the set of commands we emulate so the guest stays on this
controlled path instead of a host-dependent fallback; every other command
returns success without doing anything.
Sourcepub async fn handle_getcpu<G>(
&self,
guest: &mut G,
call: Getcpu,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getcpu<G>(
&self,
guest: &mut G,
call: Getcpu,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
getcpu system call: CPU 0 of Hermit’s historical single CPU, or under
--virtual-cpus the thread’s virtual CPU (Detcore::virtual_cpu).
The vDSO’s getcpu reaches here too: Reverie turns it into this
system call.
Sourcepub async fn handle_getresuid<G>(
&self,
guest: &mut G,
call: Getresuid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getresuid<G>(
&self,
guest: &mut G,
call: Getresuid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
getresuid under Hermit. Detcore presents a fixed virtual-root identity, so the real, effective, and saved user IDs are all the constant 0. Under the ptrace backend the guest runs inside a CLONE_NEWUSER namespace that already maps the host uid to 0; emulating the same constant here makes in-process backends (DBT) agree with that golden reference instead of leaking the host uid, and the fully emulated result is bitwise-identical across –verify and record/replay.
Sourcepub async fn handle_getresgid<G>(
&self,
guest: &mut G,
call: Getresgid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getresgid<G>(
&self,
guest: &mut G,
call: Getresgid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
getresgid under Hermit. The group-ID counterpart of handle_getresuid:
the real, effective, and saved group IDs are all the fixed virtual-root
constant 0, matching the ptrace CLONE_NEWUSER identity and deterministic
across –verify and record/replay.
Sourcepub async fn handle_get_mempolicy<G>(
&self,
guest: &mut G,
call: GetMempolicy,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_get_mempolicy<G>(
&self,
guest: &mut G,
call: GetMempolicy,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
get_mempolicy under Hermit. The container exposes a single virtual NUMA node, so the effective policy is always the default and every address resolves to node 0. The result is fully emulated (never injected), so it is bitwise-identical across the two –verify runs and under record/replay, removing the host-NUMA-topology dependence a passthrough would introduce.
It follows Linux’s kernel_get_mempolicy and do_get_mempolicy
(mm/mempolicy.c) for a kernel with one node (nr_node_ids 1):
- a node mask with
maxnodebelow 1 is EINVAL; - an unknown flag (any bit beyond
MPOL_F_NODE,MPOL_F_ADDRandMPOL_F_MEMS_ALLOWED, including those above bit 31 of the raw argument, which Reverie’s typed call drops),MPOL_F_MEMS_ALLOWEDwithMPOL_F_NODEorMPOL_F_ADDR, and an address withoutMPOL_F_ADDRare EINVAL; *policyis 0:MPOL_DEFAULT, or node 0 forMPOL_F_NODE;- the node mask is written as
copy_nodes_to_userwrites it: node 0 alone forMPOL_F_MEMS_ALLOWED, as/proc/<pid>/statusreportsMems_allowed_list(https://github.com/rrnewton/hermit/issues/4214), and no node for the default policy, inALIGN(maxnode - 1, 64) / 8bytes (none for amaxnodeof 1), EINVAL beyond a page; - both are written with the guest’s own access rights, so a read-only or
unmapped buffer is EFAULT, as from
put_userandcopy_to_user.
Limits: set_mempolicy and mbind succeed without recording a policy,
so this always reports the default policy, where Linux reports, say,
MPOL_INTERLEAVE after set_mempolicy(MPOL_INTERLEAVE). For the same
reason MPOL_F_NODE without MPOL_F_ADDR answers node 0, as Linux does
for an interleave policy (libnuma’s numa_get_interleave_node), where
it refuses it under the default policy. MPOL_F_ADDR does not check
that the address is mapped, where Linux answers EFAULT for an unmapped
one.
Sourcepub async fn handle_move_pages<G>(
&self,
guest: &mut G,
call: MovePages,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_move_pages<G>(
&self,
guest: &mut G,
call: MovePages,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
move_pages under Hermit. On a single virtual NUMA node nothing can be relocated, so report every page as residing on node 0 and succeed. The answer is a fixed constant, so it is deterministic across –verify and record/replay.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_alarm<G>(
&self,
guest: &mut G,
call: Alarm,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_alarm<G>(
&self,
guest: &mut G,
call: Alarm,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
We send the alarms to the global scheduler to handle.
Sourcepub async fn handle_setitimer<G>(
&self,
guest: &mut G,
call: Setitimer,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_setitimer<G>(
&self,
guest: &mut G,
call: Setitimer,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Schedule a one-shot or periodic real-time interval timer on Detcore logical time.
Sourcepub async fn handle_getitimer<G>(
&self,
guest: &mut G,
call: Getitimer,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getitimer<G>(
&self,
guest: &mut G,
call: Getitimer,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return interval-timer state from Detcore’s logical scheduler.
Sourcepub async fn handle_pause<G>(
&self,
guest: &mut G,
call: Pause,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_pause<G>(
&self,
guest: &mut G,
call: Pause,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
A pause is really just an unbounded sleep.
Sourcepub async fn handle_rt_sigsuspend<G>(
&self,
guest: &mut G,
call: RtSigsuspend,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_rt_sigsuspend<G>(
&self,
guest: &mut G,
call: RtSigsuspend,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Run rt_sigsuspend, without holding the deterministic scheduler turn where the call sleeps.
The kernel must perform the temporary mask swap atomically and restore the original mask after signal delivery, so execute the real blocking syscall while marking this thread as blocked outside the runnable set.
On a backend whose rt_sigsuspend never sleeps
(rt_sigsuspend_never_sleeps: KVM), the call instead runs in the
caller’s own turn, and a refused wait yields the turn
(https://github.com/rrnewton/hermit/issues/4360).
Sourcepub async fn handle_rt_sigaction<G>(
&self,
guest: &mut G,
call: RtSigaction,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_rt_sigaction<G>(
&self,
guest: &mut G,
call: RtSigaction,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
rt_sigaction
Sourcepub async fn handle_rt_sigprocmask<G>(
&self,
guest: &mut G,
call: RtSigprocmask,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_rt_sigprocmask<G>(
&self,
guest: &mut G,
call: RtSigprocmask,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
rt_sigprocmask
Sourcepub async fn handle_rt_sigtimedwait<G>(
&self,
guest: &mut G,
call: RtSigtimedwait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_rt_sigtimedwait<G>(
&self,
guest: &mut G,
call: RtSigtimedwait,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
rt_sigtimedwait system call
This is handled by the scheduler and not passed to the record/replay layer, because currently signals are not recorded.
Sourcepub async fn handle_kill<G>(
&self,
guest: &mut G,
call: Kill,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_kill<G>(
&self,
guest: &mut G,
call: Kill,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Resolve signal-zero existence checks in the fixed PID namespace, then route an unambiguous positive-PID process signal through the backend. Backends that can execute with guest PIDs preserve process-directed delivery; DBT translates it to the sole live thread because its native process uses a host PID. An unmaskable SIGKILL to a specific process group is also safe to preserve on backends whose guests use real namespace PIDs; other process-group and broadcast delivery remains refused until Detcore models eligible signal masks.
Sourcepub async fn handle_tgkill<G>(
&self,
guest: &mut G,
call: Tgkill,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_tgkill<G>(
&self,
guest: &mut G,
call: Tgkill,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Send a thread-directed signal through the kernel. Guest PID/TID values are stable in the fresh PID namespace and delivery is scheduler-serialized.
Sourcepub async fn handle_tkill<G>(
&self,
guest: &mut G,
call: Tkill,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_tkill<G>(
&self,
guest: &mut G,
call: Tkill,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Send a thread-directed signal through the older two-argument tkill.
Like its tgkill sibling, the target thread is addressed by a guest TID
that is stable in the fresh PID namespace and delivery is
scheduler-serialized, so forwarding the kernel call is deterministic.
Sourcepub async fn handle_rt_tgsigqueueinfo<G>(
&self,
guest: &mut G,
call: RtTgsigqueueinfo,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_rt_tgsigqueueinfo<G>(
&self,
guest: &mut G,
call: RtTgsigqueueinfo,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Queue a thread-directed signal with an accompanying siginfo_t. Like
tgkill, the target is a specific thread named by stable guest TGID/TID
and delivery is scheduler-serialized; the guest-supplied siginfo is
deterministic input, so forwarding the kernel call is deterministic.
Sourcepub async fn handle_rt_sigqueueinfo<G>(
&self,
guest: &mut G,
call: RtSigqueueinfo,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_rt_sigqueueinfo<G>(
&self,
guest: &mut G,
call: RtSigqueueinfo,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Queue a process-directed signal with an accompanying siginfo_t. Mirrors handle_kill:
preserve process-directed delivery when the backend accepts guest PIDs, otherwise route an
unambiguous positive-PID target to its sole live thread via rt_tgsigqueueinfo. Ambiguous
multithreaded process-directed delivery is refused until Detcore models eligible masks.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_getrlimit<G>(
&self,
guest: &mut G,
call: Getrlimit,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getrlimit<G>(
&self,
guest: &mut G,
call: Getrlimit,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return one deterministic process resource limit through the legacy ABI.
Sourcepub async fn handle_setrlimit<G>(
&self,
guest: &mut G,
call: Setrlimit,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_setrlimit<G>(
&self,
guest: &mut G,
call: Setrlimit,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Update one virtual process resource limit through the legacy ABI.
Sourcepub async fn handle_prlimit64<G>(
&self,
guest: &mut G,
call: Prlimit64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_prlimit64<G>(
&self,
guest: &mut G,
call: Prlimit64,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Virtualize prlimit64(2) for the current guest process.
Queries return process-local deterministic values. Exact no-op updates succeed for every valid resource, as on Linux. Changes are kept virtual and restricted to limits that do not grant access to host resources or affect host scheduling. Accepted changes update only guest-observable compatibility state; they are not a sandbox boundary and do not ask the host kernel to enforce the virtual limit.
Sourcepub async fn handle_getrusage<G>(
&self,
guest: &mut G,
call: Getrusage,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_getrusage<G>(
&self,
guest: &mut G,
call: Getrusage,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return a deterministic resource-usage snapshot.
ru_utime/ru_stime come from the SAME logical CPU accounting that backs times(2)
(see Self::handle_times), not from host scheduler counters. Reporting them as zero,
as this did previously, was both a fidelity bug and an internal contradiction: a guest
that called times(2) saw advancing CPU time while getrusage(2) insisted the same
process had consumed none. Deriving both from ProcessCpuSnapshot makes the two
syscalls agree by construction rather than by coincidence.
The who values report different aggregates, matching Linux:
RUSAGE_SELF— this process, summed across its threads.RUSAGE_THREAD— the calling thread alone. This reads the thread’s own logical CPU counters rather than the process totals; substituting the process aggregate would over-report for every multithreaded guest.RUSAGE_CHILDREN— reaped children only, which is exactly what 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>(
&self,
guest: &mut G,
call: Times,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_times<G>(
&self,
guest: &mut G,
call: Times,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Return deterministic elapsed ticks and process CPU accounting for times(2).
Linux’s host boot epoch and scheduler CPU counters are nondeterministic. Detcore instead derives the return value from its global logical clock. Per-process logical CPU accounting aggregates user instruction and syscall-system time across threads; forked processes start fresh counters and contribute their totals to the parent’s child counters when reaped.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn handle_clone_family<G>(
&self,
guest: &mut G,
clone_family: CloneFamily,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_clone_family<G>(
&self,
guest: &mut G,
clone_family: CloneFamily,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Clone, clone3, fork, vfork system calls
Sourcepub async fn handle_set_tid_address<G>(
&self,
guest: &mut G,
call: SetTidAddress,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_set_tid_address<G>(
&self,
guest: &mut G,
call: SetTidAddress,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
set_tid_address system call.
Linux owns the guest-visible registration and return value. Detcore mirrors the accepted address into the scheduler so its modeled CHILD_CLEARTID wake targets the same word as the backend.
Sourcepub async fn handle_set_robust_list<G>(
&self,
guest: &mut G,
call: SetRobustList,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_set_robust_list<G>(
&self,
guest: &mut G,
call: SetRobustList,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
set_robust_list system call.
Still a pass-through: Linux owns the registration and supplies the
result, and Detcore only records the head address so it can replay
exit_robust_list() when the thread dies (see
Self::run_robust_list_owner_death). Recording happens only after the
kernel accepts the call, so a rejected length or address never becomes
Detcore state.
The call goes through record_or_replay, like every other pass-through.
Using Guest::inject directly would keep the classification but drop the
behavior it implies: the syscall would vanish from a hermit record
trace, and a log recorded by a build that did record it would no longer
replay.
Sourcepub async fn exec_robust_list_wakes<G>(
&self,
guest: &mut G,
) -> Vec<(DetPid, FutexID)>where
G: Guest<Detcore<T>>,
pub async fn exec_robust_list_wakes<G>(
&self,
guest: &mut G,
) -> Vec<(DetPid, FutexID)>where
G: Guest<Detcore<T>>,
The owner-death wakes a successful execve makes Linux deliver, in delivery order, for a backend that runs the native execve outside Detcore’s handler and so cannot stage them in this thread’s state (DBT, whose new image starts a fresh runtime). Read while the old image is mapped, before the native exec; none where Detcore does not model owner death (https://github.com/rrnewton/hermit/issues/4268).
Sourcepub async fn handle_exit<G>(
&self,
guest: &mut G,
call: Exit,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_exit<G>(
&self,
guest: &mut G,
call: Exit,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Exit system call
Sourcepub async fn handle_exit_group<G>(
&self,
guest: &mut G,
call: ExitGroup,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_exit_group<G>(
&self,
guest: &mut G,
call: ExitGroup,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Exit_group system call
Sourcepub async fn handle_futex<G>(
&self,
guest: &mut G,
call: Futex,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_futex<G>(
&self,
guest: &mut G,
call: Futex,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Futex system call, which can block.
Sourcepub fn exec_image_at<G>(
&self,
guest: &G,
dirfd: i32,
path: &Path,
) -> Option<ExecRecord>where
G: Guest<Detcore<T>>,
pub fn exec_image_at<G>(
&self,
guest: &G,
dirfd: i32,
path: &Path,
) -> Option<ExecRecord>where
G: Guest<Detcore<T>>,
Self::exec_image for a path the caller already read from the
guest: public for a backend that runs the native execve outside
Detcore’s handler, prepares the exec itself (crate::prepare_exec)
and reads guest memory its own way.
Sourcepub async fn handle_execveat<G>(
&self,
guest: &mut G,
call: Execveat,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_execveat<G>(
&self,
guest: &mut G,
call: Execveat,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Execveat system call. Doesn’t return if successful.
Sourcepub async fn handle_sched_yield<G>(
&self,
guest: &mut G,
call: SchedYield,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_yield<G>(
&self,
guest: &mut G,
call: SchedYield,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
End the current logical timeslice for a sequentialized sched_yield.
Sourcepub async fn handle_wait4<G>(
&self,
guest: &mut G,
call: Wait4,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_wait4<G>(
&self,
guest: &mut G,
call: Wait4,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
wait4 system call This is handled by the scheduler and not passed to the record/replay layer.
Sourcepub async fn handle_waitid<G>(
&self,
guest: &mut G,
call: Waitid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_waitid<G>(
&self,
guest: &mut G,
call: Waitid,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
waitid system call This is handled by the scheduler and not passed to the record/replay layer.
Sourcepub async fn handle_sched_setaffinity<G>(
&self,
guest: &mut G,
call: SchedSetaffinity,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_setaffinity<G>(
&self,
guest: &mut G,
call: SchedSetaffinity,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Accept valid affinity masks without changing the host scheduler.
Under --virtual-cpus the mask is checked and kept as Linux does
(virtual_sched_setaffinity); Hermit’s historical single CPU accepts
any readable mask and stays CPU 0.
Sourcepub async fn handle_sched_getaffinity<G>(
&self,
guest: &mut G,
call: SchedGetaffinity,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_getaffinity<G>(
&self,
guest: &mut G,
call: SchedGetaffinity,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Report that we are on cpu 0, irrespective of what physical CPU we are on.
Linux’s sched_getaffinity accepts any length whose bits cover
nr_cpu_ids (one, for Hermit’s single virtual CPU) and that is a
multiple of sizeof(long), copies min(len, cpumask_size()) bytes and
returns that count. Detcore’s mask is VIRTUAL_CPUSET_BYTES long
(kernel_max 127), so an 8-byte set, which CPU_ALLOC gives for the
one CPU that sysconf(_SC_NPROCESSORS_CONF) reports, receives its
first 8 bytes (https://github.com/rrnewton/hermit/issues/4160).
Under --virtual-cpus it is the named task’s mask over the machine’s
CPUs (virtual_sched_getaffinity).
Sourcepub async fn handle_sched_getparam<G>(
&self,
guest: &mut G,
call: SchedGetparam,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_getparam<G>(
&self,
guest: &mut G,
call: SchedGetparam,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
sched_getparam under Hermit. Detcore replaces the Linux scheduler with its own deterministic one, so a thread’s Linux scheduling parameters are inert. Report a fixed SCHED_OTHER priority of 0. The value is emulated (never injected), so it is identical across –verify runs and record/replay.
Sourcepub async fn handle_sched_rr_get_interval<G>(
&self,
guest: &mut G,
call: SchedRrGetInterval,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_rr_get_interval<G>(
&self,
guest: &mut G,
call: SchedRrGetInterval,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
sched_rr_get_interval under Hermit. The round-robin quantum is a property of the Linux scheduler, which Detcore does not use, so report a fixed zero interval. Being a constant, it is deterministic across –verify and record/replay.
Sourcepub async fn handle_sched_getattr<G>(
&self,
guest: &mut G,
call: SchedGetattr,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_getattr<G>(
&self,
guest: &mut G,
call: SchedGetattr,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
sched_getattr under Hermit. Detcore replaces the Linux scheduler with its
own deterministic one, so a thread’s Linux scheduling attributes are inert.
Report a fixed SCHED_OTHER policy with zeroed nice/priority/flags. The
value is emulated (never injected), so it is identical across –verify runs
and record/replay. Re-enables chrt under –strict.
Sourcepub async fn handle_sched_setattr<G>(
&self,
guest: &mut G,
call: SchedSetattr,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_sched_setattr<G>(
&self,
guest: &mut G,
call: SchedSetattr,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Linux scheduler attributes cannot affect Detcore’s replacement scheduler, so a well-formed request is accepted as a deterministic no-op, matching the existing sched_setscheduler and sched_setparam policy.
Suppressing the effect is not the same as accepting arguments Linux refuses, nor as refusing arguments Linux accepts. Both directions are guest-visible: a probe that expects EINVAL and sees success takes the wrong branch, and so does one that expects success and sees E2BIG.
The order below is the kernel’s, and it is guest-visible when two
arguments are wrong at once, because the kernel returns the first
applicable error. sched_setattr() screens uattr, pid and flags
together; sched_copy_attr() then handles the size, the trailing bytes
and the util-clamp size rule; the signed-policy test follows; the target
pid is resolved there, in the middle; and only then does
__sched_setscheduler() judge the policy, the flags, the priority and
the deadline parameters. Putting any of that last group before the pid
lookup makes a request against a nonexistent pid report EINVAL where
Linux reports ESRCH.
§Determinism
Every check is a pure function of the guest’s own arguments. The one
piece of state consulted is the pid lookup, which asks the scheduler’s
own task table via thread_is_live – Detcore state, replayed
identically – rather than the host’s process table, which would leak
unrelated host processes into a guest-visible answer.
Specifically NOT tool_global::resolve_kill_targets, which
models kill(2) and so recognises only thread-group leaders; asking it
this question reports ESRCH for a live non-leader thread.
§Deliberately not emulated
Three behaviours are excluded, under one rule: an answer that depends on the host’s kernel configuration or the caller’s privileges is not reproduced, because a deterministic sandbox must not vary with the machine underneath it. Each was measured natively and would differ on a differently-built or differently-privileged host:
- EPERM for a real-time priority, a SCHED_DEADLINE admission, or a
negative nice, which depends on
CAP_SYS_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>(
&self,
_guest: &mut G,
call: IoprioSet,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_ioprio_set<G>(
&self,
_guest: &mut G,
call: IoprioSet,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
ioprio_set under Hermit. Detcore serializes guest threads onto one virtual
CPU, so the block-layer I/O scheduling class and priority cannot change
guest-visible computation. Accept and suppress the request as a
deterministic no-op success, mirroring how sched_setaffinity is handled.
Re-enables ionice under –strict.
Sourcepub async fn handle_ioprio_get<G>(
&self,
_guest: &mut G,
call: IoprioGet,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_ioprio_get<G>(
&self,
_guest: &mut G,
call: IoprioGet,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
ioprio_get under Hermit. I/O priority is inert under Detcore’s serialized scheduler, so process queries observe the fixed raw IOPRIO_CLASS_NONE value while group/user queries observe the effective SCHED_OTHER default of IOPRIO_CLASS_BE/4, without consulting host block-scheduler state.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn sleep_request<G>(guest: &mut G, ns_delta: Duration) -> Resourceswhere
G: Guest<Detcore<T>>,
pub async fn sleep_request<G>(guest: &mut G, ns_delta: Duration) -> Resourceswhere
G: Guest<Detcore<T>>,
Convenience function for constructing a sleep request with a nanosecond offset from “now”.
A zero offset marks the request a zero-length sleep (Resources::zero_length_sleep).
Sourcepub async fn sleep_request_abs<G>(guest: &mut G, time: LogicalTime) -> Resourceswhere
G: Guest<Detcore<T>>,
pub async fn sleep_request_abs<G>(guest: &mut G, time: LogicalTime) -> Resourceswhere
G: Guest<Detcore<T>>,
Convenience function for constructing a sleep request with a absolute nanosecond value from the realtime clock.
Sourcepub fn yield_request<G>(guest: &mut G) -> Resourceswhere
G: Guest<Detcore<T>>,
pub fn yield_request<G>(guest: &mut G) -> Resourceswhere
G: Guest<Detcore<T>>,
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: &mut G) -> Resourceswhere
G: Guest<Detcore<T>>,
pub fn sched_yield_request<G>(guest: &mut G) -> Resourceswhere
G: Guest<Detcore<T>>,
Construct a request for a strong, one-turn scheduler yield.
Sourcepub fn random_priority_changepoint_request<G>(
guest: &mut G,
change_time: LogicalTime,
) -> Resourceswhere
G: Guest<Detcore<T>>,
pub fn random_priority_changepoint_request<G>(
guest: &mut G,
change_time: LogicalTime,
) -> Resourceswhere
G: Guest<Detcore<T>>,
Construct a random PriorityChangePoint request using the local PRNG.
Sourcepub fn priority_changepoint_request<G>(
guest: &mut G,
change_time: LogicalTime,
new_priority: u64,
) -> Resourceswhere
G: Guest<Detcore<T>>,
pub fn priority_changepoint_request<G>(
guest: &mut G,
change_time: LogicalTime,
new_priority: u64,
) -> Resourceswhere
G: Guest<Detcore<T>>,
Construct a PriorityChangePoint request using the supplied time and priority.
Sourcepub async fn handle_gettimeofday<G>(
&self,
guest: &mut G,
call: Gettimeofday,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_gettimeofday<G>(
&self,
guest: &mut G,
call: Gettimeofday,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
gettimeofday
Sourcepub async fn handle_time<G>(
&self,
guest: &mut G,
call: Time,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_time<G>(
&self,
guest: &mut G,
call: Time,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
time
Sourcepub async fn handle_clock_gettime<G>(
&self,
guest: &mut G,
call: ClockGettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_clock_gettime<G>(
&self,
guest: &mut G,
call: ClockGettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
clock_gettime. A CPU clock reads Detcore’s logical CPU accounting
(crate::cpu_clock); every other clock reads the guest clock. Every
accepted id first makes the guest-clock observation, a global request,
as every clock read always has, so a CPU-clock read keeps its place in
the schedule and only the value it returns is the CPU clock’s.
Sourcepub async fn handle_clock_getres<G>(
&self,
guest: &mut G,
call: ClockGetres,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_clock_getres<G>(
&self,
guest: &mut G,
call: ClockGetres,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
clock_getres
Sourcepub async fn handle_adjtimex<G>(
&self,
guest: &mut G,
call: Adjtimex,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_adjtimex<G>(
&self,
guest: &mut G,
call: Adjtimex,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Report Hermit’s virtual clock with a fixed unsynchronized discipline. Adjustment modes are capability-gated host mutations and receive EPERM.
Sourcepub async fn handle_clock_adjtime<G>(
&self,
guest: &mut G,
call: ClockAdjtime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_clock_adjtime<G>(
&self,
guest: &mut G,
call: ClockAdjtime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
Apply the adjtimex policy to CLOCK_REALTIME. Linux does not permit NTP adjustment of the other fixed clock IDs, so reject them with EOPNOTSUPP.
Sourcepub async fn handle_nanosleep_family<R>(
&self,
guest: &mut R,
call: NanosleepFamily,
) -> Result<i64, Error>where
R: Guest<Detcore<T>>,
pub async fn handle_nanosleep_family<R>(
&self,
guest: &mut R,
call: NanosleepFamily,
) -> Result<i64, Error>where
R: Guest<Detcore<T>>,
clock_nanosleep and nanosleep
Sourcepub async fn handle_timer_create<G>(
&self,
guest: &mut G,
call: TimerCreate,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timer_create<G>(
&self,
guest: &mut G,
call: TimerCreate,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
timer_create: allocate a per-process POSIX timer and hand back a
deterministic id, retaining any scheduler-deliverable signal and, for
SIGEV_THREAD_ID, the thread it notifies. The notification is checked
as Linux’s good_sigevent does: SIGEV_SIGNAL and SIGEV_THREAD (which
the kernel treats alike; libc implements the thread itself) need a
signal from 1 to 64; SIGEV_THREAD_ID needs, first, a thread of the
caller’s own process (https://github.com/rrnewton/hermit/issues/4164);
any other value is EINVAL.
Sourcepub async fn handle_timer_settime<G>(
&self,
guest: &mut G,
call: TimerSettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timer_settime<G>(
&self,
guest: &mut G,
call: TimerSettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
timer_settime: arm or disarm a timer against the deterministic virtual
clock. The old arming is reported through old_value when requested.
Sourcepub async fn handle_timer_gettime<G>(
&self,
guest: &mut G,
call: TimerGettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timer_gettime<G>(
&self,
guest: &mut G,
call: TimerGettime,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
timer_gettime: report the time remaining until the next expiration and the reload interval, both computed from the virtual clock.
Sourcepub async fn handle_timer_getoverrun<G>(
&self,
guest: &mut G,
call: TimerGetoverrun,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
pub async fn handle_timer_getoverrun<G>(
&self,
guest: &mut G,
call: TimerGetoverrun,
) -> Result<i64, Error>where
G: Guest<Detcore<T>>,
timer_getoverrun: the expirations the timer’s last delivered signal
missed (it_overrun_last), where the scheduler coalesces timers
(https://github.com/rrnewton/hermit/issues/4262); 0 elsewhere.
Source§impl<T> Detcore<T>where
T: RecordOrReplay,
impl<T> Detcore<T>where
T: RecordOrReplay,
Sourcepub async fn register_external_child<G>(
&self,
guest: &mut G,
child_tid: Pid,
child_tid_addr: usize,
flags: CloneFlags,
exit_signal: i32,
physical_ids: Option<(i32, i32)>,
)where
G: Guest<Detcore<T>>,
pub async fn register_external_child<G>(
&self,
guest: &mut G,
child_tid: Pid,
child_tid_addr: usize,
flags: CloneFlags,
exit_signal: i32,
physical_ids: Option<(i32, i32)>,
)where
G: Guest<Detcore<T>>,
Registers a child whose native backend executed the clone syscall.
The caller must initialize the child’s local thread state from the same
parent state and clone flags before the child enters its start hook.
No backend that reaches this keeps a signal-state copy; one that does
must call signal_shadow::before_clone before its native clone, as
handle_clone_family does.
Trait Implementations§
Source§impl<T> AsMut<T> for Detcore<T>where
T: RecordOrReplay,
impl<T> AsMut<T> for Detcore<T>where
T: RecordOrReplay,
Source§impl<T> AsRef<T> for Detcore<T>where
T: RecordOrReplay,
impl<T> AsRef<T> for Detcore<T>where
T: RecordOrReplay,
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<Detcore<T>, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<Detcore<T>, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Source§impl<T> Serialize for Detcore<T>where
T: Serialize,
impl<T> Serialize for Detcore<T>where
T: Serialize,
Source§fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
Source§impl<T> Tool for Detcore<T>where
T: RecordOrReplay,
impl<T> Tool for Detcore<T>where
T: RecordOrReplay,
Source§fn may_observe_signal_dequeues(config: &Config) -> bool
fn may_observe_signal_dequeues(config: &Config) -> bool
Only a sequentialized run on a backend with process signal control
can install that control. Whether this run observes dequeues is
decided after installation by GlobalState’s
GlobalTool::observe_signal_dequeues.
Source§fn requires_signal_control(config: &Config) -> bool
fn requires_signal_control(config: &Config) -> bool
The same runs require the control: real-timer signals on such a run travel only through acknowledged shared dequeues. Reverie refuses it before any guest code, with ENOSYS on a runner that offers no control, as Detcore’s own installation did before this check moved into Reverie, and with EINVAL on a runner whose offer was not taken. So Detcore’s scheduler needs no refusal of its own for a capable backend without a control.
Source§fn enforces_serialized_execution(config: &Config) -> bool
fn enforces_serialized_execution(config: &Config) -> bool
A sequentialized run lets one guest thread run guest code at a time,
and Detcore refuses every way to install a seccomp filter: seccomp(2)
(handle_seccomp, EOPNOTSUPP or an earlier argument error) and
prctl(PR_SET_SECCOMP) (not a supported option, ENOSYS). Both
refusals need the call intercepted, so the declaration also requires
both in the subscriptions. The ptrace backend then keeps the guest’s
SIGTRAP action and mask across its own single-steps
(https://github.com/rrnewton/hermit/issues/4409).
One exception to “one at a time”: a call the scheduler runs in the
background (BlockingExternalIO, BlockingRtSigsuspend) is injected
with a returning inject, so its thread runs only that syscall, in the
kernel, and stops before any guest instruction. It can install no
filter. Its injection is single-stepped, though, and that step’s trap,
at a host-timed moment, can reset the process’s shared SIGTRAP action
while another thread’s injection is kept. An ignored, or caught and
blocked, SIGTRAP can then end at SIG_DFL, where an undeclared run
always leaves it; the blocked mask is per thread and is not affected.
Source§fn observes_sigreturns(config: &Config) -> bool
fn observes_sigreturns(config: &Config) -> bool
The signal-state copy follows handler returns from the backend’s
reports where it has them (syscalls::signal_shadow).
Source§fn in_guest_signal_delivery(config: &Config) -> bool
fn in_guest_signal_delivery(config: &Config) -> bool
The in-guest delivery gate exactly as the host prepared it
(Config::in_guest_signal_delivery): Hermit sets it only where the
whole protocol holds, and clears it in every other run.
Source§fn note_unreported_signal(
&self,
thread_state: &mut <Detcore<T> as Tool>::ThreadState,
signal: &UnreportedSignal,
kept: bool,
)
fn note_unreported_signal( &self, thread_state: &mut <Detcore<T> as Tool>::ThreadState, signal: &UnreportedSignal, kept: bool, )
Counts each signal the backend passes on without reporting it, which could reach a guest handler, so the delivery counters show what no delivery check saw (A5 step 3.2b).
Source§fn subscriptions(config: &Config) -> Subscription
fn subscriptions(config: &Config) -> Subscription
NOTE: these subscriptions are used ONLY for hermit run mode. Hermit record has its own subscriptions specified in recorder/mod.rs.
Source§fn handle_raw_signal_event<'life0, 'life1, 'async_trait, G>(
&'life0 self,
guest: &'life1 mut G,
signal: i32,
) -> Pin<Box<dyn Future<Output = Result<Option<i32>, Errno>> + Send + 'async_trait>>
fn handle_raw_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, signal: i32, ) -> Pin<Box<dyn Future<Output = Result<Option<i32>, Errno>> + Send + 'async_trait>>
A realtime signal (32 to 64), which Signal cannot name: handled as
any other (handle_inbound_signal), so it takes its turn in the
schedule (https://github.com/rrnewton/reverie/issues/207).
Source§fn announce_signal_event<'life0, 'life1, 'async_trait, G>(
&'life0 self,
guest: &'life1 mut G,
signal: i32,
) -> Pin<Box<dyn Future<Output = Result<Option<SignalAnnouncement>, Errno>> + Send + 'async_trait>>
fn announce_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, signal: i32, ) -> Pin<Box<dyn Future<Output = Result<Option<SignalAnnouncement>, Errno>> + Send + 'async_trait>>
The first half of a delivery a backend announces before the kernel
dequeues it (an in-guest backend’s prediction at a return to guest
code): the delivery’s turn alone (inbound_turn with
TurnScope::Announcement), kept under the announcement’s token
(ThreadState::signal_announcements). Detcore never suppresses here:
its drop decisions (a duplicate child-exit SIGCHLD, a stale POSIX
timer entry) exist only in configurations it refuses. A signal outside
1 to 64 is EINVAL. A configuration in which the commit could not
keep the protocol, or a thread holding a claimed SIGCHLD, is refused
here, before any turn, with EPROTONOSUPPORT (two_phase_refusal):
Detcore has opted in, and EOPNOTSUPP is left to mean that a Tool has
not (Reverie’s contract, step 4).
Source§fn commit_signal_event<'life0, 'life1, 'async_trait, G>(
&'life0 self,
guest: &'life1 mut G,
announcement: SignalAnnouncement,
) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
fn commit_signal_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, announcement: SignalAnnouncement, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
The second half of an announced delivery, once the backend’s final
read confirmed it, in ptrace’s order: what the dequeue settles
(inbound_dequeued), what the announcement’s turn left
(inbound_turn_settled, which also gives the delivery its ordinal),
what the granted delivery settles (inbound_delivered), the delivery
check (check_delivery_to_record, whose Guest::deliver_signal is
the backend’s check of its prediction and whose siginfo read gets
none), and, if the check approves, the record
(record_checked_delivery). An announcement this thread does not
hold, signal and token both, is EINVAL; a drop decision, which only
refused configurations make, is EPROTO.
It must not wait for a turn. The announcement refused every
configuration in which one of its parts files a scheduler request; if
the thread was granted a turn all the same, the backend’s final read
no longer holds, so the commit fails closed with EDEADLK, before the
delivery is recorded. It checks twice: after inbound_delivered’s
effects, before the delivery check, and after the delivery check
(whose Guest::deliver_signal must not wait for a turn), before the
record. Any Err ends the run.
Source§fn withdraw_signal_event(
&self,
thread_state: &mut <Detcore<T> as Tool>::ThreadState,
announcement: SignalAnnouncement,
)
fn withdraw_signal_event( &self, thread_state: &mut <Detcore<T> as Tool>::ThreadState, announcement: SignalAnnouncement, )
An announced delivery that does not happen at the backend’s return: its turn stays taken (one more turn than ptrace, which never reports a delivery that does not happen), and nothing else was done for it, so the signal, still pending, is announced again at a later return.
Source§fn exit_for_predicted_death<'life0, 'life1, 'async_trait, G>(
&'life0 self,
guest: &'life1 mut G,
status: ExitStatus,
) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
fn exit_for_predicted_death<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, status: ExitStatus, ) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>
A death the backend’s final read predicted, after its commits (A5
design 16.30; Reverie’s contract, step 3): the thread keeps its turn
for its group’s exit (Scheduler::hold_turn_for_group_exit), so no
other thread takes a turn between the decision and the grant, then
files the group Exit, which that hold grants at the very next turn
and which starts the physical-exit hold. This is what DBT’s fatal
default action does (end_thread_group_before_fatal_delivery), here
at the in-guest runtime’s return, where the kernel then ends the
group.
Robust-futex owner-death wakes are not claimed: the group’s robust lists are not walked here, which stays the named gap of https://github.com/rrnewton/hermit/issues/4268 (D24, IR-11).
Refused with EPROTONOSUPPORT before anything is filed when the run
does not take in-guest delivery (Config::in_guest_signal_delivery,
the gate this protocol needs), and when the thread does not hold the
turn, which leaves the decision unprotected: Detcore has opted in, so
EOPNOTSUPP stays Reverie’s “not opted in”. A status that is not a
death by a signal is EINVAL.
Source§fn filter_unreported_signal<'life0, 'life1, 'async_trait, R>(
&'life0 self,
rpc: &'life1 R,
unreported: UnreportedSignal,
) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>>
fn filter_unreported_signal<'life0, 'life1, 'async_trait, R>( &'life0 self, rpc: &'life1 R, unreported: UnreportedSignal, ) -> Pin<Box<dyn Future<Output = bool> + Send + 'async_trait>>
A SIGCHLD the backend passes on without reporting it is dropped when
it is the kernel’s copy of a child-exit notification the scheduler
sends itself (scheduler::child_exit_sigchld). A copy the scheduler
sent is kept, with the siginfo the tracer gave it: this hook cannot
replace it.
Either way, the scheduler’s record of copies Linux discarded when they
were sent learns what became of the copy (consumed_signal_control,
https://github.com/rrnewton/hermit/issues/4293).
Source§fn handle_timer_event<'life0, 'life1, 'async_trait, G>(
&'life0 self,
guest: &'life1 mut G,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
fn handle_timer_event<'life0, 'life1, 'async_trait, G>( &'life0 self, guest: &'life1 mut G, ) -> Pin<Box<dyn Future<Output = ()> + Send + '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 stream_role() -> ToolStreamRole
fn stream_role() -> ToolStreamRole
Source§fn observes_native_syscalls(config: &Config) -> bool
fn observes_native_syscalls(config: &Config) -> bool
Source§fn observe_native_syscall(
&self,
_state: &<Detcore<T> as Tool>::ThreadState,
facts: &dyn NativeSyscallFacts,
call: &NativeSyscallCall,
) -> Result<Option<Arc<dyn NativeSyscallObserver>>, Error>
fn observe_native_syscall( &self, _state: &<Detcore<T> as Tool>::ThreadState, facts: &dyn NativeSyscallFacts, call: &NativeSyscallCall, ) -> Result<Option<Arc<dyn NativeSyscallObserver>>, Error>
Source§fn facility_admission(config: &Config) -> Result<(), Error>
fn facility_admission(config: &Config) -> Result<(), Error>
Source§fn reserved_input_requirement(
config: &Config,
) -> Option<ReservedInputRequirement>
fn reserved_input_requirement( config: &Config, ) -> Option<ReservedInputRequirement>
Source§fn native_mm_ownership(config: &Config) -> NativeMmOwnership
fn native_mm_ownership(config: &Config) -> NativeMmOwnership
NativeMmOwnership] for the complete obligation. Read moreSource§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>>
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>>
Source§fn handle_sigreturn(
&self,
thread_state: &mut <Detcore<T> as Tool>::ThreadState,
report: SigreturnReport,
)
fn handle_sigreturn( &self, thread_state: &mut <Detcore<T> as Tool>::ThreadState, report: SigreturnReport, )
rt_sigreturn before it runs, for a Tool for which
[Tool::observes_sigreturns] holds, on a backend that reports
sigreturns. The backend then carries the call out exactly as it would
without the hook. Read moreSource§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>>
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>>
[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>>
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>>
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>>
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>>
Source§fn init_thread_state(
&self,
tid: Pid,
parent: Option<(Pid, &<Detcore<T> as Tool>::ThreadState)>,
) -> <Detcore<T> as Tool>::ThreadState
fn init_thread_state( &self, tid: Pid, parent: Option<(Pid, &<Detcore<T> as Tool>::ThreadState)>, ) -> <Detcore<T> as Tool>::ThreadState
Source§fn on_initial_image_attached(
&self,
tid: Pid,
state: &mut <Detcore<T> as Tool>::ThreadState,
) -> Result<(), Error>
fn on_initial_image_attached( &self, tid: Pid, state: &mut <Detcore<T> as Tool>::ThreadState, ) -> Result<(), Error>
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>>
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>>
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>>
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>>
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>>
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>>
Source§fn on_exit_thread<'life0, 'life1, 'async_trait, G>(
&'life0 self,
tid: Pid,
global_state: &'life1 G,
thread_state: <Detcore<T> as Tool>::ThreadState,
exit_status: ExitStatus,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
fn on_exit_thread<'life0, 'life1, 'async_trait, G>( &'life0 self, tid: Pid, global_state: &'life1 G, thread_state: <Detcore<T> as Tool>::ThreadState, exit_status: ExitStatus, ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'async_trait>>
§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 more§fn on_thread_state_ready(
&self,
_tid: Pid,
_global_state: &Self::GlobalState,
_thread_state: &Self::ThreadState,
) -> Result<(), Error>
fn on_thread_state_ready( &self, _tid: Pid, _global_state: &Self::GlobalState, _thread_state: &Self::ThreadState, ) -> Result<(), Error>
handle_thread_start or handle_post_exec, before ordinary guest
execution resumes. The ptrace backend supplies this synchronous hook;
other backends must implement an equivalent owned lifecycle boundary
before relying on it. The default performs no work. Read more§fn handle_exec_commit<'life0, 'life1, 'async_trait, T>(
&'life0 self,
_guest: &'life1 mut T,
) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
T: 'async_trait + Guest<Self>,
Self: 'async_trait,
fn handle_exec_commit<'life0, 'life1, 'async_trait, T>(
&'life0 self,
_guest: &'life1 mut T,
) -> Pin<Box<dyn Future<Output = Result<(), Errno>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
T: 'async_trait + Guest<Self>,
Self: 'async_trait,
execve or execveat this Tool injected can no longer
fail, on a backend that carries the exec out itself rather than through
a host kernel exec (today the KVM backend). The backend has finished
every step that could fail: it has read the path, the argument strings
and the environment strings out of guest memory and validated the new
image. The old image’s memory is still this guest’s, and the image is
replaced only after this returns; handle_post_exec then follows in
the new image, and the injection does not return. This is the point
Linux’s begin_new_exec reaches past point_of_no_return, where
exec_mmap runs exec_mm_release on the old address space
(fs/exec.c), after do_execveat_common has copied the strings. A
Tool that models what Linux does to the old image there, such as the
robust-futex walk futex_exec_release makes, does it here, where the
strings the new image receives can no longer change. Read more§fn handle_structured_signal_event<'life0, 'life1, 'async_trait, T>(
&'life0 self,
guest: &'life1 mut T,
event: SignalEvent,
) -> Pin<Box<dyn Future<Output = Result<Option<SignalEvent>, Errno>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
T: 'async_trait + Guest<Self>,
Self: 'async_trait,
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 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> Conv for T
impl<T> Conv for T
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
§impl<T> FmtForward for T
impl<T> FmtForward for T
§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.§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.§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.§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.§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.§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.§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.§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.§fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
§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();§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
§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();§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
§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();§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
§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();§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.
§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);§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
§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 more§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 more§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
§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
§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.§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.§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>,
§impl<T> Tap for T
impl<T> Tap for T
§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 more§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 more§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 more§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 more§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 more§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 more§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.§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.§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.§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.§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.§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.§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.