Documentation
¶
Overview ¶
Package interp builds and configures the mvdan.cc/sh/v3/interp runner with the gobash customization hooks. It is the bridge between the public gobash.Bash surface and mvdan/sh's tree-walking interpreter:
- VFS-backed Open/Stat/ReadDir handlers route every file operation through the FileSystem on the caller's *Bash instead of the host disk.
- An ExecHandlers middleware dispatches every non-mvdan-builtin command through the gobash command Registry. The middleware NEVER falls through to mvdan/sh's DefaultExecHandler; unregistered commands trigger a `command not found` stderr write plus ExitStatus(127), closing the os/exec gap that existed in Phases 5–7. The Phase 8 acceptance test `TestPhase8UnknownCommandIsNotFound` locks this in.
- Env, Cwd, and the three stdio streams are passed in via Config; the caller is responsible for wrapping stdout/stderr in the MaxOutputSize ringbuf writer before BuildRunner sees them.
Import cycle avoidance ¶
Phase 5's kickoff suggested a signature of `BuildRunner(ctx, *Bash, ExecOptions)`. Because gobash imports this package and *gobash.Bash lives in the root package, taking *Bash here would create a cycle. Instead we accept a flat Config struct that the caller (gobash.(*Bash).Exec) assembles from the *Bash + ExecOptions inputs. The semantic intent (wire env / cwd / streams / FS / callHandler into the runner) is identical.
Phase 3 mvdan/sh quirks (still load-bearing) ¶
- interp.Dir(path) runs an os.Stat(path) on the host disk at runner-init time. We never use it; we set runner.Dir = cwd directly after interp.New returns.
- interp.HandlerCtx(ctx) panics when ctx has no HandlerContext. mvdan/sh's Runner.stat / Runner.lstat path call our handlers with the bare ctx during early init. HandlerDir below recovers and falls back to empty, which gbfs.Resolve handles correctly for absolute inputs.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildRunner ¶
BuildRunner constructs a fully-configured *mvinterp.Runner.
ctx is reserved for future use (mvdan/sh's constructor takes no ctx today) and is intentionally part of the public signature so the eventual richer hookpoint — e.g. cancelling pending I/O during runner-init — does not require a breaking change.
func HandlerDir ¶
HandlerDir returns the current Dir from the HandlerContext stored in ctx, or empty string if mvdan/sh has not injected one yet.
mvdan/sh's Runner.stat / Runner.lstat call our StatHandler with the bare ctx (no HandlerContext attached), and interp.HandlerCtx panics in that case. The defer/recover absorbs the panic and falls back to empty — which gbfs.Resolve treats correctly for absolute paths.
Exported so tests in the gobash root package can re-use the exact same guard.
Types ¶
type Config ¶
type Config struct {
// Env is the initial environment as a slice of KEY=VAL pairs,
// matching expand.ListEnviron's input format. Nil is treated as
// an empty environment.
Env []string
// Cwd is the script's working directory. Resolved through the VFS
// (NOT the host disk) — see the package-level Phase 3 quirk note.
// Empty string leaves the runner's default Dir unchanged.
Cwd string
// Stdin / Stdout / Stderr are the three streams handed to
// interp.StdIO. Nil Stdin is treated as an EOF reader so scripts
// that try to read still terminate cleanly. Nil Stdout / Stderr
// are illegal — the caller must supply at least a discard sink
// (e.g. the MaxOutputSize ringbuf writer wrapping io.Discard).
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// FS is the virtual filesystem backing the Open / Stat / ReadDir
// handlers. Required: nil triggers an explicit error so a missing
// wire-up surfaces immediately instead of silently shelling out to
// the host disk.
FS gbfs.FileSystem
// CallHandler is the per-command interception point used today by
// the limit accounting (MaxLoopIterations / MaxCommandCount /
// MaxCallDepth). Nil disables call-handler wiring; mvdan/sh's
// default behavior takes over.
CallHandler mvinterp.CallHandlerFunc
// Registry is the command dispatch registry. Nil is treated as an
// empty registry — every non-mvdan-builtin command will resolve
// to `command not found` (ExitStatus 127). The runtime supplies a
// fully-populated *command.Registry; tests in this package omit
// the field deliberately so the not-found path is exercised in
// isolation.
Registry *command.Registry
// Fetch is the resolved network Doer. When nil, command.Context.Fetch
// is also nil and network-touching commands must error cleanly.
// The spec: the runtime decides whether to supply a SecureFetch or
// honor a host-provided override; the dispatch bridge just passes
// through whatever the host gave it.
Fetch network.Doer
// Sleep is the host-supplied sleep hook surfaced to commands via
// command.Context.Sleep (Phase 10 Wave A's `sleep` is the first
// consumer). Nil leaves command.Context.Sleep nil; the builtin
// falls back to time.Sleep.
Sleep command.SleepFunc
// Trace is the host-supplied trace hook surfaced to commands via
// command.Context.Trace. Wave A does not emit events; the field
// is plumbed today so later waves can without a follow-up
// Context surface bump.
Trace command.TraceFunc
// Limits is the per-Exec resolved limit set surfaced to commands
// via command.Context.Limits. Wave A built-ins ignore it; Wave D
// and the optional runtimes are the first real consumers.
Limits command.Limits
// ExportedEnv is the per-dispatch exported-env view surfaced to
// commands via command.Context.ExportedEnv. Wave A does not
// consume it; landing zone for env/printenv/export (Wave G).
ExportedEnv map[string]string
// Aliases is the per-Bash alias table surfaced to commands via
// command.Context.Aliases (Phase 10 Wave G `alias` / `unalias`).
// The runtime always supplies a live table; a nil interface is
// not safe to call through.
Aliases command.AliasTable
// History is the per-Bash command-history ring surfaced to
// commands via command.Context.History (Phase 10 Wave G
// `history`).
History command.HistoryRing
// Exec is the sub-shell invocation hook surfaced to commands via
// command.Context.Exec (Phase 10 Wave G `bash` / `sh` /
// `timeout`). The spec reserves this for Phase 11; Wave G needs
// it earlier.
Exec command.SubExecFunc
// SourceDepth is the depth this dispatch starts at. Used by the
// Phase 11 source / eval / . / bash / sh / timeout builtins to
// enforce Limits.MaxSourceDepth across nested invocations.
SourceDepth int
// Shopt is the per-Bash shopt table surfaced to commands via
// command.Context.Shopt (Phase 11). Concrete implementation in
// internal/runtimestate.
Shopt command.ShoptTable
// ReadDirHook, if non-nil, is invoked before every VFS ReadDir.
// Returning a non-nil error fails the ReadDir without consulting
// the underlying FS — this is the Phase 6 wire-up point for the
// MaxGlobOperations limit (every ReadDir contributes one glob op).
// The spec routes pathname expansion through this hook because
// mvdan/sh does not expose a glob-specific counter.
ReadDirHook func(ctx context.Context, path string) error
}
Config carries everything BuildRunner needs to construct a runner. The caller — typically gobash.(*Bash).Exec — assembles this from the *Bash + ExecOptions inputs. All fields except CallHandler are required; the zero value of Config is not usable.