pub struct SubshellSnapshot {Show 30 fields
pub paramtab: hashtable_nodes<Param>,
pub paramtab_hashed_storage: CowHashMap<String, IndexMap<String, String>>,
pub positional_params: Vec<String>,
pub special_globals: Vec<(String, String)>,
pub flock_fds: Vec<i32>,
pub loop_flags: (i32, i32, i32, i32),
pub cmdoutval: i32,
pub stopmsg: i32,
pub traps: HashMap<String, String>,
pub opts: HashMap<String, bool>,
pub tables: SubshForkCopy,
pub shfuncs: Arc<shfunc_table>,
pub functions_compiled: CowHashMap<String, Chunk>,
pub function_source: CowHashMap<String, String>,
pub thingytab: HashMap<String, Thingy>,
pub keymapnamtab: hashtable_nodes<KeymapName>,
pub lastpid: i32,
pub jobtab: Vec<job>,
pub curjob: i32,
pub prevjob: i32,
pub maxjob: usize,
pub thisjob: i32,
pub oldjobtab: Vec<job>,
pub oldmaxjob: usize,
pub fd_frame: SubshFdFrame,
pub redir_depth: usize,
pub slots_mark: usize,
pub sigtrapped: Vec<i32>,
pub subsh: i32,
pub locale: Option<CString>,
}Expand description
Snapshot of subshell-isolated state. Captured at ( entry, restored at
) exit. zsh subshell semantics: assignments inside (…) don’t leak to
the outer scope — and that includes export. zsh forks a child for the
subshell so the child’s env::set_var dies with the child; without a fork
(zshrs runs subshells in-process for perf), we snapshot+restore the OS
env table around the subshell. Otherwise (export y=v) would leak y
to the parent shell, breaking every script that uses a subshell to
scope an env override.
Snapshot of mutable executor state across a subshell
boundary.
Port of the entersubsh() save/restore Src/exec.c does at
line 1084 — captures everything that must be replaced when a
(...) group fires.
Fields§
§paramtab: hashtable_nodes<Param>Snapshot of paramtab (the C-canonical parameter store) at
subshell entry. Step 1 of the unification mirrors writes to
paramtab, so subshell-scoped assignments now show up there
too — without this snapshot, restoring only variables /
arrays / assoc_arrays leaks the subshell’s writes to the
parent via paramtab (e.g. x=outer; (x=inner); echo $x returned
inner because paramsubst reads through paramtab).
Same node storage as the live table (Src/params.c:854
newparamtable(151, "paramtab")) so restoring a snapshot restores
C’s bucket-walk order too, not just the name→value mapping.
paramtab_hashed_storage: CowHashMap<String, IndexMap<String, String>>paramtab_hashed_storage field.
positional_params: Vec<String>positional_params field.
special_globals: Vec<(String, String)>Values of the special parameters whose backing store is a process
GLOBAL rather than the parameter table — Src/params.c’s char *ifs
(IFS), wordchars, home, histsiz, … — each reached through a GSU
getfn/setfn pair (the dispatch list at params.rs:12548).
The paramtab snapshot above restores the param NODE, but the node only
carries the GSU pair; the value itself lives in the global, which a
paramtab restore doesn’t touch. C forks for (...), so a child’s writes
to those globals die with it. zshrs runs subshells in-process, so
(IFS=,; :) left the PARENT’s IFS as , — and every later word-split
in the parent silently used it. Same fork-copy reasoning as opts
and tables.
flock_fds: Vec<i32>Flock fds (Src/utils.c:2111 addlockfd) live at subshell
entry. zsystem flock FILE keeps the fd open for the life of
the shell; under C’s forked (...) the child’s fd — and hence
the lock — dies when the subshell exits. zshrs runs subshells
in-process, so the lock outlived the subshell and every later
zsystem flock on that file (from a real forked background job)
blocked forever. Recorded here so subshell_end can close the
fds the subshell itself opened.
loop_flags: (i32, i32, i32, i32)loops / breaks / contflag at subshell entry
(c:Src/loop.c, c:Src/builtin.c bin_break). C forks for (...),
so a break executed inside dies with the child and the parent’s
loop runs on: for i in 1 2; do (break); print after; done prints
after twice. zshrs runs subshells in-process, so the three
counters have to be restored by hand at the boundary.
Fourth slot: ancestor_loops (c:Src/loop.c:36), which entry moves
loops into (c:Src/exec.c:1263-1264, 55061).
cmdoutval: i32cmdoutval (c:Src/exec.c:225) at subshell entry. A process global
the forked child owns a private copy of: x=$(exit 3); (y=1); exec 3>f still reports 3 in zsh although the subshell’s assignment reset
its own copy (c:2796). Restored by hand like loop_flags.
stopmsg: i32stopmsg (c:Src/builtin.c) at subshell entry. Another process
global the child owns a copy of: a nofork ${ exit N } in a
subshell sets it (c:Src/subst.c:2100) and the child then exits, so
in C it never reaches the parent. Leaked here it made every later
exit in a function or nofork skip its deferral (c:5884).
traps: HashMap<String, String>Parent’s traps at subshell entry. zsh’s (trap "echo X" EXIT; true) runs the trap when the subshell exits — BEFORE the parent
continues. Without this snapshot, the trap inherited from parent
would fire, OR a trap set inside the subshell would leak to the
parent’s process exit. Restored on subshell_end after the
subshell’s own EXIT trap (if any) has fired. Stores a snapshot
of crate::ported::builtin::traps_table() (canonical).
opts: HashMap<String, bool>Parent’s shell options at subshell entry. (set -e) /
(setopt extendedglob) mustn’t leak; zsh forks the subshell
so child options die with the child. We run in-process, so we
must restore the option store on subshell_end.
tables: SubshForkCopyThe fork-copied hash tables — aliastab, sufaliastab, … — at
subshell entry (see crate::ported::exec::SubshForkCopy). zsh forks
for (...) so (alias x=y) inside a subshell dies with the child
and doesn’t leak to the parent. Bug #209 in docs/BUGS.md. The
tables are restored whole — node flags (ALIAS_GLOBAL / DISABLED)
and bucket order included — which the (name, text, flags) list
this replaced got only partly right, and it never covered
sufaliastab at all: (alias -s x=y) leaked.
shfuncs: Arc<shfunc_table>Parent’s shell-function table at subshell entry. C zsh’s
entersubsh (Src/exec.c) forks before running the
subshell body so (f() { ... }) defining a function dies
with the child and never leaks to the parent. zshrs runs
subshells in-process, so we must clone shfunctab on entry
and restore on exit. Bug #208 in docs/BUGS.md. Stored as a
clone of the whole shfunc_table — the bucket layout is part
of the state, because ${(k)functions} / compadd -k functions
emit C’s bucket-walk order verbatim
(Src/Modules/parameter.c:480-481); rebuilding the table from
an unordered map on restore would reshuffle that order after
every ( … ) / $( … ).
functions_compiled: CowHashMap<String, Chunk>Parent’s compiled-function chunks at subshell entry. Companion
to shfuncs above — ShellExecutor.functions_compiled is the
runtime dispatch table that Op::CallFunction reads through;
without restoring it, a subshell (g() { override; }) leaves
the override bytecode chunk in place so the parent’s
g call still runs the override after subshell_end
restored shfunctab. Bug #208 in docs/BUGS.md.
Copy-on-write (crate::cow_map): taking this snapshot is a
refcount bump, and the deep copy happens only if the subshell
body actually compiles or redefines a function.
function_source: CowHashMap<String, String>Parent’s function source map at subshell entry. Companion to
functions_compiled so typeset -f / whence show the
parent’s source after subshell exit, not the subshell’s
overridden body. Bug #208 in docs/BUGS.md.
Copy-on-write, for the same reason as functions_compiled.
thingytab: HashMap<String, Thingy>Parent’s THINGYTAB (ZLE widget registry) at subshell entry.
zsh forks for (...) so zle -N w f / zle -D w inside the
subshell flip widget bindings only in the child; when the
child exits the parent’s widget table is untouched. zshrs runs
subshells in-process so a subshell’s zle -D w would
otherwise unbind the parent’s widget. Bug #453 in docs/BUGS.md.
keymapnamtab: hashtable_nodes<KeymapName>Parent’s KEYMAPNAMTAB (named keymap registry) at subshell
entry. Same fork-copy semantics as THINGYTAB — a subshell’s
bindkey -N km / bindkey -D km mutates only the child’s
keymap registry in C zsh. Bug #454 in docs/BUGS.md.
lastpid: i32Parent’s $! (clone::lastpid) at subshell entry. C zsh forks
for (...), so a background job started INSIDE the subshell
sets the child’s lastpid only — ( : & ); echo $! prints 0
in zsh. zshrs runs subshells in-process, so restore on end.
jobtab: Vec<job>Job-control state at subshell entry: (JOBTAB clone, CURJOB,
PREVJOB, MAXJOB, THISJOB). C zsh forks for (...) so any
disown / wait / new & job inside the subshell mutates the
CHILD’s copy of jobtab and dies with it (Src/exec.c::entersubsh
fork semantics); the parent’s table is untouched. zshrs’s
in-process subshell must snapshot/restore to match — without
this, sleep 1 & (disown); jobs shows an empty table where
zsh still lists the job. Bug #462.
curjob: i32curjob at subshell entry (Src/jobs.c:75 global).
prevjob: i32prevjob at subshell entry (Src/jobs.c:80 global).
maxjob: usizemaxjob at subshell entry (Src/jobs.c:71 global).
thisjob: i32thisjob at subshell entry (Src/jobs.c:77 global).
oldjobtab: Vec<job>oldjobtab / oldmaxjob at subshell entry (Src/jobs.c:101/104).
The subshell’s clearjobtab (c:1808-1825) saves the parent’s jobs
there; in C that copy lives in the forked child only, so the
parent’s own oldjobtab must come back unchanged or its jobs,
$jobstates and disown would read the dead subshell’s snapshot.
oldmaxjob: usize§fd_frame: SubshFdFrameThe descriptors 0-9 a bare exec redirection in the body moves
are saved here on first touch and put back when this is dropped
(see crate::ported::exec::SubshFdFrame). C zsh forks for (...)
so the child’s exec >file / exec 3<&- dies with it; without
this, (exec >t.log; ...); cat t.log left the PARENT’s fd 1
pointing at t.log and cat looped forever copying the file into
itself.
redir_depth: usizeredirect_scope_stack depth at entry. A body aborted by an error
skips its WithRedirectsEnd; C’s forked child takes those fds with
it, so subshell_end unwinds to here.
slots_mark: usizePipeline job slots held at entry (exec_jobs::execpline_slots_mark).
Slots the body takes come from the cleared child table, so
subshell_end drops their records along with that table.
sigtrapped: Vec<i32>sigtrapped[] at subshell entry (Src/signals.c:39). C’s
entersubsh clears per-signal trap STATE via unsettrap(sig)
(c:Src/exec.c:1088-1092), which zeroes both the body and the
sigtrapped flags. zshrs cleared only the body table, so the flags
desynced: a subshell that dropped a trap body still reported the
signal as trapped. Snapshot the whole vector so subshell_end can
restore the parent’s exact state (including an inherited
ZSIG_IGNORED on SIGQUIT).
subsh: i32subsh at subshell entry (Src/exec.c:160 global). C’s
entersubsh sets subsh = 1 for a real (non-ESUB_FAKE)
subshell at c:Src/exec.c:1192-1193, and the forked child
carries it for the whole body. PRINT_EXIT_VALUE reads it
(c:4309 && !subsh), which is why zsh prints nothing for
setopt printexitvalue; (false) while still reporting a
bare false. zshrs runs ( … ) in-process, so the flag has
to be set on entry and restored by hand on End.
locale: Option<CString>The libc locale on entry, as setlocale(LC_ALL, NULL) reports it.
LC_ALL=C / LANG=… / LC_CTYPE=… inside the body reach libc via
setlang / lcsetfn (c:Src/params.c:4811, c:4877), which in C
changes only the forked child. The paramtab restore puts the
parameters back without re-running their setfns, so the parent kept
the child’s locale: () { (LC_ALL=C; true) } left ${(q+)…} quoting
multibyte text as raw bytes for the rest of the script.
Auto Trait Implementations§
impl !Freeze for SubshellSnapshot
impl RefUnwindSafe for SubshellSnapshot
impl Send for SubshellSnapshot
impl Sync for SubshellSnapshot
impl Unpin for SubshellSnapshot
impl UnsafeUnpin for SubshellSnapshot
impl UnwindSafe for SubshellSnapshot
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