Skip to main content

Module shell_exec

Module shell_exec 

Source
Expand description

Shell executor state for zshrs.

Not a port of Src/exec.c. zshrs replaces zsh’s tree-walking interpreter (execlist / execpline / execcmd) with a fusevm bytecode VM; the actual VM bridge lives in src/fusevm_bridge.rs. This file holds:

  • ShellExecutor — the runtime state struct that the VM and every ported builtin/utility threads through
  • VM-adjacent helpers that read/write that state
  • drift extension scaffolding still being moved out

Path-wise this file lives at the crate root (src/exec.rs) rather than in src/ported/ because nothing here corresponds 1:1 to a Src/*.c source file. crate::ported::exec is kept as a re-export alias so existing call-sites continue to compile.

Modules§

zsh_version
ZSH_VERSION / ZSH_PATCHLEVEL / ZSH_VERSION_DATE consts generated by build.rs from src/zsh/Config/version.mk. Use zsh_version::ZSH_VERSION etc. at call sites so version bumps pick up automatically.

Structs§

AutoloadFlags
Flags for autoloaded functions (autoload builtin – Src/builtin.c bin_autoload).
CompGroup
Completion group bundling multiple match candidates.
CompInitBgResult
Result from background compinit thread. Outcome of background compinit autoload. zshrs-original — Src/Modules/complete.c blocks on compinit inline. The Rust port runs it on the worker pool.
CompMatch
One completion-match candidate surfaced by the complete builtin.
CompSpec
complete builtin per-command spec (bash-style).
CompState
Per-completion state tracked across complete builtin invocations.
ForkFlags
Flags for zfork()
Intercept
An intercept registration. One AOP intercept registered against a function pattern. zshrs-original — no C counterpart.
ShellExecutor
Top-level shell executor state. Port of the file-static globals + Estate chain Src/exec.c uses — execlist() (line 1349) drives every list, with execpline() (line 1668), execpline2() (line 1991), execsimple() (line 1290), and the per-WC_* execfuncs[] table (line 268) feeding off it. The Rust port collapses everything into one ShellExecutor so we don’t need thread-local globals.
SubshellFlags
Flags for entersubsh()
SubshellSnapshot
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.
ZshrsHost
ShellHost implementation that delegates to the current ShellExecutor via the with_executor thread-local.
zstyle_entry
One zstyle entry — Rust extension that flattens what C splits across struct style (zutil.c:91, holds the style name) and struct stypat (zutil.c:97, holds pat + vals). The canonical split structs are at lines 1596 / 1608 above; this flat shape is kept while the C-style HashTable port lands.

Enums§

AdviceKind
AOP advice type — before, after, or around. Aspect-oriented advice classification. zshrs-original — no C zsh counterpart. C zsh’s closest analog is the function-wrapper hook in Src/module.c (addwrapper(), used by zsh/zprof), but per-function before/after/around AOP intercepts are unique to zshrs.
BuiltinType
Builtin command type Builtin classification. Mirrors the BINF_* flag set Src/builtin.c uses to classify special vs regular builtins.
ForkResult
Result of fork operation fork() outcome (parent / child / error). Mirrors the integer return of zfork() from Src/exec.c:349.
LoopSignal
Cross-VM loop-control signal. When break/continue is hit inside a body that runs on a sub-VM (e.g. select’s body), the inline patches mechanism can’t reach the outer loop — set this flag and the outer-loop builtin drains it after each iteration. Loop control signal from a command body. Mirrors the LF_* set Src/loop.c uses to thread break/continue/return flags up through the executor.
RedirMode
Redirection mode File-redirection mode (> / >> / < / etc.). Mirrors the REDIR_* enum from Src/zsh.h.

Constants§

BUILTIN_APPEND_ARRAY
arr+=(d e f) — append N elements to an existing indexed array. Compile emits N element pushes + name push, then CallBuiltin(295, N+1). Handler drains args (last popped = name), extends executor.arrays[name] (creates the entry if missing). Mirrors zsh’s += semantics for indexed arrays.
BUILTIN_APPEND_ASSOC
m[k]+=value — append onto an existing assoc-array value (string concat). If the key doesn’t exist, behaves like SET_ASSOC. Stack: [name, key, value].
BUILTIN_APPEND_SCALAR_OR_PUSH
name+=val (no parens) — runtime-dispatched append. If name is an indexed array → push val as element. If name is an assoc array → error (zsh requires (k v) form). Else → scalar concat (existing SET_VAR behavior).
BUILTIN_ARITH_EVAL
$((expr)) arithmetic substitution. Pops [expr_string], evaluates via the executor’s MathEval (integer-aware), returns result as Value::Str. Bypasses ArithCompiler’s float-only Op::Div path so $((10/3)) returns “3” not “3.333…”.
BUILTIN_ARRAY_ALL
${arr[@]} — splice all elements as a Value::Array. Pops one arg: name. The Array gets flattened by Op::Exec/ExecBg/CallFunction into argv.
BUILTIN_ARRAY_FLATTEN
Flatten one level of Value::Array nesting. Pops N values; for each, if it’s a Value::Array, its elements are appended directly; otherwise the value is appended as-is. Pushes a single Value::Array of the flattened result. Used by the for-loop word-list compile path: when a word like ${arr[@]} produces a nested Array, this lets for i in ${arr[@]} iterate over the inner elements rather than the outer single-element array.
BUILTIN_ARRAY_INDEX
${arr[idx]} — single-element array index. Pops two args: stack: [name, idx_str] Returns the indexed element as Value::str. Indexing semantics: zsh is 1-based by default; bash is 0-based. We follow zsh. Special idx values: @ and * return the whole array as Value::Array (which fuses correctly via the Op::Exec splice for argv splice).
BUILTIN_ARRAY_JOIN
Builtin ID for Array → String joining. Pops one value: if it’s an Array, joins its string-coerced elements with a single space; otherwise passes through. Used after Op::Glob to convert the pattern’s matched paths into the single argv-token form the bytecode word model expects (no per-word splitting yet — that’s a future phase).
BUILTIN_ARRAY_JOIN_STAR
BUILTIN_ARRAY_LENGTH
${#arr[@]} and ${#arr} (when arr is an array name) — array length. Pops one arg: name. Returns Value::str of len.
BUILTIN_BEGIN_INLINE_ENV
BUILTIN_BRACE_EXPAND
Brace expansion: {a,b,c} → 3 values, {1..5} → 5 values, {01..05} → zero-padded numerics, {a..e} → letter range. Pops one string, returns Value::Array of expansions (empty array → original string preserved).
BUILTIN_BRIDGE_BRACE_ARRAY
Bridge into subst_port::substitute_brace_array for nested forms that need to PRESERVE array shape across the expand_string boundary. Stack: [content_string]. Returns Value::Array of the per-element words. Used by the compile path for ${(@)<nested>...##pat} shapes — the standard substitute_brace returns String which collapses array→scalar; this builtin preserves the multi-word output via paramsubst’s third return (nodes vec, the C source’s aval thread).
BUILTIN_CMD_POP
Pop the top of the command-context stack. Direct port of zsh’s cmdpop(void) (Src/prompt.c:1631).
BUILTIN_CMD_PUSH
Push a CmdState token onto the command-context stack. Direct port of zsh’s cmdpush(int cmdtok) (Src/prompt.c:1623). The stack is consulted by %_ in PS4/prompt expansion to produce the cumulative control-flow-context labels (if, then, cmdand, cmdor, cmdsubst, …) that zsh -x xtrace shows in the trace prefix. Compile_zsh emits push/pop pairs around each compound command (if/while/[[…]]/((…))/$(…) etc.). Token is a CmdState as u8.
BUILTIN_CMD_SUBST_TEXT
$(cmd) command substitution. Pops [cmd_string], runs through run_command_substitution which compiles via parse_init+parse + ZshCompiler and captures stdout via an in-process pipe. Returns trimmed output as Value::Str. Avoids the sub-chunk word-emit quoting bug in the raw Op::CmdSubst path.
BUILTIN_CONCAT_DISTRIBUTE
Word-segment concat that does cartesian-product distribution over arrays. Stack: [lhs, rhs]. Used for RC_EXPAND_PARAM ${arr} and explicit-distribute forms (${^arr}, ${(@)…}).
BUILTIN_CONCAT_DISTRIBUTE_FORCED
Forced-distribute concat — like BUILTIN_CONCAT_DISTRIBUTE but always distributes cartesian regardless of the rcexpandparam option. Emitted by the segments fast-path when an is_distribute_expansion segment is present (${^arr}, ${(@)arr}, ${(s.…)arr} etc.) per zsh: the source-level distribution flag overrides the option default. Direct port of Src/subst.c:1875 case Hat: nojoin = 1 and the rcexpandparam test bypass for the explicit-distribute flags.
BUILTIN_CONCAT_SPLICE
Word-segment concat with FIRST/LAST sticking. Stack: [lhs, rhs]. Used for default unquoted splice forms (${arr[@]}, $@, $*) where prefix sticks to first element only and suffix to last only.
BUILTIN_END_INLINE_ENV
BUILTIN_ERREXIT_CHECK
BUILTIN_EXPAND_TEXT
Text-based word expansion. Pops [preserved_text]: the word with quotes preserved (Dnull→", Snull→', Bnull→\), runs expand_string (variable + cmd-sub + arith) then xpandbraces then expand_glob. Returns Value::str (single match) or Value::Array (multi-match brace/glob).
BUILTIN_FILE_MODIFIED_SINCE_ACCESS
[[ -N path ]] — file modified since last accessed (atime <= mtime).
BUILTIN_FILE_NEWER
[[ a -nt b ]] — file a newer than file b (mtime strict). Stack: [path_a, path_b]. Pushes Bool. zsh-compatible “missing” rules: if both exist, compare mtime; if only a exists → true; otherwise false.
BUILTIN_FILE_OLDER
[[ a -ot b ]] — mirror of -nt. If both exist, compare mtime; if only b exists → true; otherwise false.
BUILTIN_GET_VAR
Builtin ID for ${name} reads — routes through ShellExecutor::get_variable which knows about special params ($?, $@, $#, $1..$9), shell vars (self.variables), arrays, and env. Replaces emission of Op::GetVar for shell variable names so nested VMs (function calls) see the same storage.
BUILTIN_GLOB_EXPAND
Pop a scalar from the VM stack, run expand_glob on it, push the result as Value::Array. Used by the segment-concat compile path when var refs concatenate with glob meta literals ($D/*, ${prefix}*, etc.) — those skip the bridge’s pathname-expansion pass and would otherwise leak the glob meta to argv as a literal.
BUILTIN_GLOB_QUALIFIED
Glob qualifier filter: *(qualifier) filters glob results by predicate. Pops [pattern, qualifier_string]. Returns Value::Array of matching paths.
BUILTIN_HAS_SETGID
[[ -g path ]] — setgid bit (S_ISGID).
BUILTIN_HAS_SETUID
[[ -u path ]] — setuid bit (S_ISUID).
BUILTIN_HAS_STICKY
[[ -k path ]] — sticky bit (S_ISVTX) set on path.
BUILTIN_IS_BLOCKDEV
[[ -b path ]] — block device.
BUILTIN_IS_CHARDEV
[[ -c path ]] — character device.
BUILTIN_IS_FIFO
[[ -p path ]] — FIFO / named pipe.
BUILTIN_IS_SOCKET
[[ -S path ]] — socket.
BUILTIN_IS_TTY
[[ -t fd ]] — fd-is-a-tty check. Stack: [fd_string]. Routes through libc::isatty. Pushes Bool.
BUILTIN_OPEN_NAMED_FD
{name}>file / {name}<file / {name}>>file — named-fd allocation. Stack: [path, varid, op_byte]. Opens path per op_byte, gets the new fd (≥10 in zsh; we use libc::open with O_CLOEXEC bit cleared so the inherited fd survives Command::new spawns), stores the fd number as a string in $varid. Pushes Status (0 success, 1 error).
BUILTIN_OPTION_SET
[[ -o option ]] — shell-option-set test. Stack: [option_name]. Normalizes the name (strip underscores, lowercase) and reads exec.options. Pushes Bool.
BUILTIN_OWNED_BY_GROUP
[[ -G path ]] — owned by effective GID.
BUILTIN_OWNED_BY_USER
[[ -O path ]] — owned by effective UID.
BUILTIN_PARAM_DEFAULT_FAMILY
Phase 1 native param-modifier builtins. Each takes a fixed argv shape and returns the modified value as Value::Str. Replaces the runtime ShellWord round-trip via BUILTIN_EXPAND_WORD_RUNTIME for the common shapes.
BUILTIN_PARAM_FILTER
${var:#pattern} — array filter: remove elements matching pattern. Stack: [name, pattern]. For scalar var, returns empty if it matches the pattern, else the value. For array var, returns Array of non-matching elements.
BUILTIN_PARAM_FLAG
${(flags)name} — zsh parameter expansion flags. Stack: [name, flags]. Flags applied left-to-right. Supported subset (high-value, used by zpwr):
BUILTIN_PARAM_LENGTH
${#name} — character length of a scalar value, or element count of an indexed/assoc array. Pops [name], returns count as Value::Str.
BUILTIN_PARAM_REPLACE
${var/pat/repl} / ${var//pat/repl} / ${var/#pat/repl} / ${var/%pat/repl} — pop [name, pattern, replacement, op_byte]. op_byte: 0=first, 1=all, 2=anchor-prefix, 3=anchor-suffix.
BUILTIN_PARAM_STRIP
${var#pat} / ${var##pat} / ${var%pat} / ${var%%pat} — pop [name, pattern, op_byte]. op_byte: 0=#, 1=##, 2=%, 3=%%.
BUILTIN_PARAM_SUBSTRING
${var:offset[:length]} — pop [name, offset, length] (length=-1 means “rest of value”; negative offset counts from end).
BUILTIN_PARAM_SUBSTRING_EXPR
BUILTIN_REGEX_MATCH
Re-export the regex_match host method as a builtin so [[ s =~ pat ]] works even when fusevm’s Op::RegexMatch isn’t routed (compat fallback).
BUILTIN_REGISTER_COMPILED_FN
Register a pre-compiled fusevm chunk as a function. Stack: [name, base64-bincode-of-Chunk]. Used by compile_zsh’s compile_funcdef to register functions parsed via parse_init+parse without going through the ShellCommand JSON serialization path.
BUILTIN_RESTORE_TRY_BLOCK_STATUS
BUILTIN_RUN_BG
Builtin ID for cmd & background execution. IDs 287/288/289 are reserved for the planned array work in Phase G1 (SET_ARRAY/SET_ASSOC/ARRAY_INDEX), so this lands at 290. Pops one sub-chunk index; forks; child detaches (setsid), runs the sub-chunk on a fresh VM, exits with last_status; parent returns Status(0) immediately. Job-table registration (so jobs/fg/wait can see the pid) is deferred to Phase G6 — fire-and-forget for now.
BUILTIN_RUN_COPROC
coproc [name] { body } — bidirectional pipe to async child. Pops a name (optional, “” for default) and a sub-chunk index. Creates two pipes, forks, child redirects its fd 0/1 to the inner ends and runs the body, parent stores [write_fd, read_fd] into the named array (default COPROC). Caller closes the fds and waits when done. Job-table integration deferred to Phase G6 alongside the bg & work.
BUILTIN_RUN_PIPELINE
Builtin ID for pipeline execution. Pops N sub-chunk indices from the stack; each index points into vm.chunk.sub_chunks (compiled stage bodies). Forks N children, wires stdin/stdout between them via pipes, runs each stage’s bytecode on a fresh VM in its child, parent waits for all and pushes the last stage’s exit status. This is bytecode-native pipeline execution — no tree-walker delegation.
BUILTIN_RUN_SELECT
select var in words; do body; done — interactive numbered-menu loop. Compile emits N word pushes + var-name push + sub-chunk index push, then CallBuiltin(296, N+2). Handler prints 1) word1\n2) word2\n... to stderr, prints $PROMPT3 (default ?# ) to stderr, reads a line from stdin. On EOF returns 0. On a valid 1-based number, sets var to the chosen word, runs the sub-chunk, then redisplays the menu and loops. On invalid input redraws the menu without running the body. break from inside the body exits the loop (handled by the body’s own bytecode).
BUILTIN_SAME_FILE
[[ a -ef b ]] — same-inode test. Stack: [a, b]. Pushes Bool true iff both paths resolve to the same (dev, inode) pair (zsh + bash semantics).
BUILTIN_SET_ARRAY
Indexed-array assignment: arr=(a b c). Compile_simple emits N element pushes followed by name push, then CallBuiltin(BUILTIN_SET_ARRAY, N+1). The handler pops args (last popped = name in our pushing order) and stores Vec<String> into executor.arrays. Tree-walker callers see the same storage. Any prior scalar binding in executor.variables for name is removed so ${name} (scalar context) consistently reflects the array’s first element via get_variable.
BUILTIN_SET_ASSOC
Single-key set on an associative array: foo[key]=val. Stack (top-down): [name, key, value]. Stores value into executor.assoc_arrays[name][key], creating the outer entry if missing. compile_simple detects var[...]=... in assignments and emits this builtin.
BUILTIN_SET_BREAK
break from inside a body that runs on a sub-VM (select, future loop-via- builtin constructs). Sets executor.loop_signal = Some(LoopSignal::Break). Outer-loop builtins drain the flag after each body run and exit early.
BUILTIN_SET_CONTINUE
continue from inside a sub-VM body. Sets the signal to Continue. Outer loop builtins drain + skip-to-next-iteration.
BUILTIN_SET_LINENO
Update $LINENO to track the source line of the next statement. Stack: [n] (the line number from ZshPipe.lineno). Direct port of zsh’s lineno global tracking (Src/input.c:330) — the compiler emits one of these per top-level pipe so $LINENO reflects the source position at runtime. ID 342 picked because the previous 326 collided with BUILTIN_HAS_STICKY (the file has several other duplicate IDs — 325 has two as well — but fixing those is out of scope for this port).
BUILTIN_SET_RAW_OPT
BUILTIN_SET_SUBSCRIPT_RANGE
a[i]=(elements) / a[i,j]=(elements) / a[i]=() — subscripted-array assign with array-literal RHS. Stack: […elements, name, key]. Empty elements + single-int key a[i]=() removes that element. Comma-key a[i,j]=(...) splices.
BUILTIN_SET_TRY_BLOCK_ERROR
Capture current last_status into the TRY_BLOCK_ERROR variable. Emitted between the try block and the always block of { … } always { … } so the finally arm can read $TRY_BLOCK_ERROR.
BUILTIN_SET_VAR
Builtin ID for name=value assignments — pops [name, value] and stores into executor.variables. Replaces Op::SetVar emission for the same reason: the storage must be visible to both bytecode and tree-walker code, across nested VM boundaries.
BUILTIN_TIME_SUBLIST
time { compound; ... } — wall-clock-time the sub-chunk and print elapsed seconds. Stack: [sub_chunk_idx as Int]. Runs the sub-chunk on the current VM (so positional/local state is shared) and prints the timing summary to stderr in zsh’s format. Pushes Status.
BUILTIN_UNKNOWN_COND
[[ -X file ]] for unknown unary test op -X. Stack: [op_name]. Emits zsh’s unknown condition: -X diagnostic to stderr and pushes Bool(false). Without this, unknown conditions silently returned false matching neither zsh’s error format nor the expected status code (zsh returns 2 for parse error).
BUILTIN_VAR_EXISTS
BUILTIN_WORD_SPLIT
Word-split a string on IFS (default: whitespace). Pops one string, returns Value::Array of fields. Used in array-literal context where arr=($(cmd)) should expand cmd’s stdout into multiple elements.
BUILTIN_XTRACE_ARGS
Emit an xtrace line built from the top argc values on the VM stack, peeked WITHOUT consuming. Used to trace simple commands AFTER expansion, so echo for $i shows as echo for a / echo for b. Direct port of Src/exec.c:2055-2066.
BUILTIN_XTRACE_ASSIGN
Trace one assignment: emits name=<quoted-value> (no newline) to xtrerr if XTRACE is on. Coalesces with subsequent XTRACE_ASSIGN / XTRACE_ARGS calls onto the SAME line via the XTRACE_DONE_PS4 flag so a=1 b=2 echo $a $b produces: <PS4>a=1 b=2 echo 1 2\n matching C zsh’s execcmd_exec body (Src/exec.c:2517-2582): xtr = isset(XTRACE); if (xtr) { printprompt4(); doneps4 = 1; } while (assign) { if (xtr) fprintf(xtrerr, “%s=”, name); … eval value … if (xtr) { quotedzputs(val, xtrerr); fputc(’ ’, xtrerr); } }
BUILTIN_XTRACE_LINE
BUILTIN_XTRACE_NEWLINE
Emit a trailing \n + flush iff XTRACE is on AND PS4 was emitted by an earlier XTRACE_ASSIGN this line. Used at the end of compile_simple’s assignment-only path so the trace line gets terminated. Mirrors C’s exec.c:3397-3399 (the assign-only return path through execcmd_exec which does fputc('\n', xtrerr); fflush(xtrerr)).

Statics§

TRAP_RETURN
Port of int trap_return; from Src/exec.c:155. Carries the pending exit status from inside a trap; sentinel -2 means “running an EXIT/DEBUG-style trap at the current level” (signals.c:1166). Promoted to the user’s return N value by bin_return when POSIX-trap semantics apply (builtin.c:5852).
TRAP_STATE
Port of int trap_state; from Src/exec.c:134. Tracks whether a trap handler is currently being processed and, paired with TRAP_RETURN below, whether a return inside the trap should promote to TRAP_STATE_FORCE_RETURN to unwind the trap caller.

Functions§

convbase_underscore
Convert integer to string with underscores for readability Port of convbase_underscore(char *s, zlong v, int base, int underscore) from Src/params.c:5646. WARNING: param names don’t match C — Rust=(val, base, underscore) vs C=(s, v, base, underscore)
format_int_in_base
Format an integer in the given base (2-36) using zsh’s BASE#DIGITS form. Port of convbase(char *s, zlong v, int base) from Src/utils.c (also called from Src/math.c:1089). Bases 2-9 are unsigned-style; uppercase A-Z are used for digits >= 10. A negative value is output as -BASE#DIGITS. WARNING: param names don’t match C — Rust=(n, base) vs C=(buf, l, 10)
getfpfunc
Port of getfpfunc(char *s, int *ksh, char **fdir, char **alt_path, int test_only) from Src/exec.c:5260. Walks $fpath (or the supplied spec_path slice) for a file named name and writes the resolved directory through *dir_path_out (matching the C char **dir_path). Returns Some(file_contents_path) on success, None when not found.
getoutput
Free-function wrapper for getoutput() from Src/exec.c:4712. Runs a command-substitution body in the active executor and returns its captured stdout. The C signature is LinkList getoutput(char *cmd, int qt) but every caller in subst.rs joins the list back into a string, so the Rust port collapses the intermediate.
glob_match_static
Static glob match — same logic as glob_match but callable without &self, needed for Rayon parallel iterators that can’t capture &self.
loadautofn
Direct port of Shfunc loadautofn(Shfunc shf, int ks, int test_only, int ignore_loaddir) from Src/exec.c:5050. Walks $fpath for a file named shf->node.nam, reads it, installs the text body on the corresponding shfunctab entry, and clears PM_UNDEFINED.
scan_magic_assoc_keys