pub struct SubshStateGuard { /* private fields */ }Expand description
RAII guard applying the fork-safe subset of
entersubsh(ESUB_PGRP|ESUB_NOMONITOR, NULL) (Src/exec.c:1083) around a
command substitution that zshrs runs in-process, restoring every
touched global on drop.
C’s getoutput() forks, and only the child runs entersubsh:
/* pid == 0 */
child_unblock();
zclose(pipes[0]);
redup(pipes[1], 1);
entersubsh(ESUB_PGRP|ESUB_NOMONITOR, NULL); /* c:4781 */
cmdpush(CS_CMDSUBST);
execode(prog, 0, 1, "cmdsubst");so every state delta dies with the child and the parent is untouched.
zshrs collapses that fork (getoutput below → run_command_substitution),
which means the deltas were previously never applied at all: inside
$( ) the shell kept the caller’s zleactive / opts[USEZLE], so every
reader of them took the “we are inside the line editor” branch. Observed
failure: lt=( ${(f)"$(fc -l -200)"} ) bound to a ZLE widget died with
no interactive history within ZLE — bin_fc’s guard
(builtin.rs:2757, a correct port of Src/builtin.c:1523-1527) firing on
a stale zleactive. Same class of bug for every other reader:
jobs.rs:1114 (time-report suppression), utils.rs:1785 (screen
ownership), signals.rs:2050 (refresh-on-signal).
APPLIED (pure restorable scalar state; correct without a fork):
- c:1097 / c:1207 —
opts[MONITOR] = 0. ESUB_NOMONITOR is set andjob_control_okis 0 (no ESUB_JOB_CONTROL), so both C branches agree. A substitution body must not do job control. - c:1164 —
shout = NULL. Its readers already handle the NULL case the way the C child does (putshoutfalls back to stderr,checkrmallreturns 1 atutils.rs:3565per c:2871,mailcheckstays silent per c:1670). - c:1208 —
opts[USEZLE] = 0. - c:1209 —
zleactive = 0.
DELIBERATELY SKIPPED — each would be wrong, or is already handled elsewhere on the in-process path:
- c:1127-1131
unsettrap(sig)— APPLIED, but not here: the trap table is not scalar state (trap bodies andsigactiondispositions), sorun_command_substitutionresets it with the same helper( … )uses (fusevm_bridge::entersubsh_reset_traps) and puts the parent’s flags and dispositions back on exit. - c:1095
exit_val = 0— ALREADY DONE on this path:vm_helper.rs:3893swapsEXIT_VALto 0 before the nested VM runs and restores it atvm_helper.rs:4004. - c:1110-1151
setpgrp/attachtty/gleader bookkeeping — process-group and tty surgery. With no fork this moves the interactive shell’s own process group and hands the terminal away. - c:1154
subsh = 1—init.rs:2289doesif subsh != 0 { realexit() }. With no fork, anexitinside$( )would then hard-kill the interactive shell instead of ending the substitution. - c:1161
zsh_subshell++— ALREADY DONE on this path byCmdSubstSubshellBump(fusevm_bridge.rs:232, entered atvm_helper.rs:3734). Bumping again would make$ZSH_SUBSHELLread 2 inside$( )instead of 1. - c:1165-1193
signal_ignore/signal_default— per-process signal dispositions. Defaulting SIGINT/SIGQUIT/SIGTSTP in the live interactive shell would let ^C kill and ^Z stop the shell itself. - c:1202-1205
signal_unblock— the process signal mask is owned by the trap dispatcher’s block/unblock pairing; unblocking here breaks that pairing. - c:1214-1217 close FDT_SAVED_MASK fds — fd surgery. These are the parent’s saved descriptors for redirection restore; closing them is unrecoverable.
- c:1219
clearjobtab(monitor)— would destroy the parent’s job table. - c:1220
get_usage()— overwrites the parent’stimesbaseline with no fork to discard it, and nothing inside the substitution reads it. - c:1221
forklevel = locallevel— FORKLEVEL gates local-scope unwinding; the in-process body pushes and pops its own locals normally, so moving the fork boundary would strand them.
Nesting is safe: each guard saves what it observed on entry and restores
exactly that on drop, so LIFO nesting ($( … $( … ) … )) unwinds
correctly.
Implementations§
Trait Implementations§
Source§impl Drop for SubshStateGuard
impl Drop for SubshStateGuard
Auto Trait Implementations§
impl Freeze for SubshStateGuard
impl RefUnwindSafe for SubshStateGuard
impl Send for SubshStateGuard
impl Sync for SubshStateGuard
impl Unpin for SubshStateGuard
impl UnsafeUnpin for SubshStateGuard
impl UnwindSafe for SubshStateGuard
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<F, W, T, D> Deserialize<With<T, W>, D> for F
impl<F, W, T, D> Deserialize<With<T, W>, D> for F
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more