277 lines
17 KiB
C
277 lines
17 KiB
C
#ifndef FROSTBYTE_SHELL_H
|
|
#define FROSTBYTE_SHELL_H
|
|
|
|
#include "types.h"
|
|
|
|
void shellRun(void);
|
|
|
|
/* Mirrors elf.h's elfMaxArgs (kept as shell.c's own named copy rather than
|
|
including elf.h just for one constant, the same "each caller of this
|
|
pattern owns its own named copy" choice jobctl.c/kmain.c/syscall.c
|
|
already made for their own shared "one slot's worth of stack" constant)
|
|
-- an argv built with more tokens than this could never be handed to
|
|
elfBuildInitialStack anyway. */
|
|
#define shellMaxArgs 16
|
|
|
|
/* Upper bound on how many `|`-separated stages shellSplitPipelineStages
|
|
below (and shell.c's runPipelineStages) will ever build at once -- a
|
|
generous cap for anything a human could plausibly type on one
|
|
lineCapacity-bounded (128 bytes, shell.c) command line, not a claim that
|
|
real shells have such a limit. Named analogously to shellMaxArgs (a
|
|
per-command cap) but for the orthogonal "how many commands are chained
|
|
together" axis. */
|
|
#define shellMaxPipelineStages 4
|
|
|
|
/* Splits `text` in place (each run of spaces becomes a NUL terminator,
|
|
like strtok but reentrant -- no hidden static state) into at most
|
|
maxTokens pointers into that same buffer, returning how many it found.
|
|
Takes an explicit capacity rather than hardcoding shellMaxArgs
|
|
internally so a caller that already consumed some of its own argv[]
|
|
array (runProgram, shell.c: argv[0] is always the program name itself,
|
|
not something shellTokenize ever produces) can correctly pass only the
|
|
REMAINING room, not the full array size -- passing the full array size
|
|
there would let this function write one past the end of it. Exposed
|
|
here (rather than kept static in shell.c) so shellTest.c (ktest.h) can
|
|
exercise its edge cases -- leading/trailing/repeated spaces, empty
|
|
input, hitting the capacity -- as pure string manipulation with no VFS
|
|
or scheduler involved, safe to run from KTEST's pre-scheduler
|
|
context. */
|
|
int shellTokenize(char* text, char* tokens[], int maxTokens);
|
|
|
|
/* Splits `line` in place at every top-level '|' into up to `maxStages`
|
|
NUL-terminated stages, written into `stages[0..result-1]` -- shell.c's
|
|
`cmd1 [args] | cmd2 [args] | ... | cmdN [args]` pipeline syntax, now a
|
|
REAL N-stage chain rather than only ever the first two commands (jobctl.h's
|
|
jobctlSpawnPiped already took independent stdin/stdout pipe handles per
|
|
spawn from the day it was written, so a middle stage with BOTH ends piped
|
|
at once needed no new kernel primitive -- only this function and
|
|
shell.c's runPipelineStages were ever the limiting factor).
|
|
Whitespace directly touching each '|' is trimmed from both sides of it
|
|
(so "a | b | c" and "a|b|c" produce the same stages) -- shellTokenize's
|
|
own token splitter only ever strips LEADING spaces from whatever it is
|
|
handed, never trailing ones, so every stage but the last has its trailing
|
|
spaces trimmed here first.
|
|
Returns 0 (leaving `line` and `stages` completely untouched) if there is
|
|
no '|' anywhere in the line at all, so the caller (execute()) knows to
|
|
fall back to running a single command exactly as before. Returns -1
|
|
(also leaving everything untouched) if the line contains MORE than
|
|
`maxStages` stages -- a real shell has no such limit, but this kernel's
|
|
per-pipeline arrays (shell.c's runPipelineStages, jobctl.c's
|
|
jobctlWaitPipelineStages) are fixed-size, so a caller must be able to
|
|
tell "malformed count" apart from "no pipe found" to report the right
|
|
error instead of silently truncating a longer chain. Otherwise returns
|
|
the real stage count (2..maxStages). Exposed here (rather than kept
|
|
static in shell.c) for the same pre-scheduler, no-VFS KTEST coverage
|
|
shellTokenize already gets (shellTest.c). */
|
|
int shellSplitPipelineStages(char* line, char* stages[], int maxStages);
|
|
|
|
/* Upper bound on how many `&&`/`||`-separated segments shellSplitAndOr
|
|
below (and shell.c's execute()) will ever build at once -- the exact
|
|
same "generous cap for one lineCapacity-bounded command line, not a
|
|
claim real shells have such a limit" reasoning shellMaxPipelineStages
|
|
above already gives, on the orthogonal "how many conditionally-run
|
|
commands are chained together" axis. */
|
|
#define shellMaxAndOrSegments 4
|
|
|
|
/* The operator immediately BEFORE segments[i+1] in shellSplitAndOr's own
|
|
output (there is no operator before segments[0], so andOrOps[] below
|
|
only ever has count-1 meaningful entries) -- real shell `&&`
|
|
(short-circuit: run the next command only if the previous one
|
|
succeeded, exit status 0) vs `||` (run the next command only if the
|
|
previous one FAILED, exit status != 0). */
|
|
typedef enum {
|
|
shellAndOrAnd = 1,
|
|
shellAndOrOr = 2
|
|
} ShellAndOrOp;
|
|
|
|
/* Splits `line` in place at every top-level `&&`/`||` into up to
|
|
`maxSegments` NUL-terminated segments, written into
|
|
`segments[0..result-1]`, with `andOrOps[0..result-2]` recording which
|
|
operator sat between each adjacent pair -- shell.c's new
|
|
`cmd1 && cmd2 || cmd3 && ...` conditional-chaining syntax, run BEFORE
|
|
pipeline splitting (`|` binds tighter than `&&`/`||`, exactly like a
|
|
real shell: `a | b && c` means "(a | b) && c", so each of THIS
|
|
function's own segments is independently handed to
|
|
shellSplitPipelineStages afterward, never the other way around).
|
|
Whitespace directly touching each operator is trimmed from both sides
|
|
of it, the same "a&&b and a && b produce the same segments" convention
|
|
shellSplitPipelineStages already established for `|`. A lone `&`
|
|
(shell.c's own trailing background marker, checked much later after
|
|
this function has already run) is never mistaken for half of `&&`,
|
|
since this function only ever matches the exact two-character sequence
|
|
-- a single `&` anywhere else in the line is simply left inside
|
|
whichever segment it falls in, untouched.
|
|
Returns 0 (leaving `line` and `segments` completely untouched) if there
|
|
is no top-level `&&`/`||` anywhere in the line, so the caller knows to
|
|
run it as a single command-or-pipeline exactly as before. Returns -1
|
|
(also leaving everything untouched) if the line contains MORE than
|
|
`maxSegments` segments, the same "malformed count, do not silently
|
|
truncate" shape shellSplitPipelineStages already uses for its own
|
|
analogous limit. Otherwise returns the real segment count
|
|
(2..maxSegments). Exposed here (rather than kept static in shell.c) for
|
|
the same pre-scheduler, no-VFS KTEST coverage shellSplitPipelineStages
|
|
already gets (shellTest.c). */
|
|
int shellSplitAndOr(char* line, char* segments[], ShellAndOrOp andOrOps[], int maxSegments);
|
|
|
|
/* Result of shellExtractRedirects below -- nullptr inPath/outPath/errPath/
|
|
hereString means "no such redirection was found, leave that fd at its
|
|
normal default". */
|
|
typedef struct ShellRedirect {
|
|
const char* inPath;
|
|
const char* outPath;
|
|
bool appendOut;
|
|
const char* errPath;
|
|
bool appendErr;
|
|
/* `<<<` TEXT -- shell.c's new here-string syntax. Unlike inPath (a file
|
|
to open), this is the literal next token's own text, fed to the
|
|
child's fd 0 through a real anonymous pipe the SHELL itself writes
|
|
into (jobctl.h's jobctlSpawnForegroundHereString/
|
|
jobctlRunBackgroundHereString), never opened as a path. Mutually
|
|
exclusive with inPath at the SAME fd 0 -- see shellExtractRedirects'
|
|
own doc comment below for the last-one-wins rule between the two. */
|
|
const char* hereString;
|
|
} ShellRedirect;
|
|
|
|
/* Scans an already-tokenized argv (shellTokenize's own output shape) for
|
|
`<`, `<<<`, `>`, `>>`, `2>`, and `2>>` tokens -- shell.c's new
|
|
`cmd [ARGS] [< IN | <<< TEXT] [> OUT | >> OUT] [2> ERR | 2>> ERR]`
|
|
redirection syntax -- and removes each one together with the token
|
|
immediately after it (a filename for every operator except `<<<`, whose
|
|
own following token is the literal here-string text instead), compacting
|
|
the remaining tokens down to a real argv with no gaps (exactly what
|
|
jobctl.h's spawn functions need to hand a program, which must never see
|
|
its own redirection operators as if they were its own arguments).
|
|
Writes the compacted count back through `argc` and fills `out` with
|
|
whichever paths/text were found (nullptr for whichever operator never
|
|
appeared). Returns false, leaving `argv`/`*argc` completely untouched, if
|
|
`<`, `<<<`, `>`, `>>`, `2>`, or `2>>` appears as the LAST token with no
|
|
filename/text after it -- the same "malformed input, caller falls back to
|
|
reporting an error" shape shellSplitPipelineStages uses for "no pipe
|
|
found" (though this one look more like an error since every OTHER token
|
|
still gets scanned first, unlike shellSplitPipelineStages's single
|
|
up-front counting pass). A later `>`/`>>` overwrites an earlier one (and vice versa)
|
|
rather than erroring, the same "last one wins" a real shell gives
|
|
`cmd > a > b` -- only ever ONE is meaningful since a single fd 1 can only
|
|
be redirected once; `2>`/`2>>` follow the exact same last-one-wins rule
|
|
independently for fd 2, since the two operator families target different
|
|
fds and never conflict with each other. `<` and `<<<` follow the SAME
|
|
last-one-wins rule for fd 0 across the two of them together (whichever
|
|
appears later wins, clearing the other field) -- a real shell gives fd 0
|
|
to whichever of `< file`/`<<< text` was written last too, since both
|
|
target the exact same single fd. Exposed here (rather than kept static in
|
|
shell.c) for the same pre-scheduler, no-VFS KTEST coverage
|
|
shellTokenize/shellSplitPipelineStages already get (shellTest.c). */
|
|
bool shellExtractRedirects(char** argv, int* argc, ShellRedirect* out);
|
|
|
|
/* Recognizes shell.c's `$NAME` unquoted-parameter-expansion syntax (the
|
|
glue that actually resolves it against a real environment, shell.c's
|
|
new shellExpandArgv, is not pure -- it reads sched.h's Thread.env via
|
|
currentThreadGetEnv, off limits for KTEST's pre-scheduler context -- so
|
|
only the SYNTAX half lives here, the same "pure string logic here, the
|
|
thread/VFS-touching glue stays static in shell.c" split shellTokenize/
|
|
shellSplitPipelineStages/shellExtractRedirects above already use).
|
|
`token` must be exactly '$' followed by one or more characters drawn
|
|
from [A-Za-z0-9_] (real shell variable-name syntax) and nothing else;
|
|
on a match, copies just the name (without the leading '$') into
|
|
`nameOut` and returns true. Returns false, `nameOut` untouched, for a
|
|
bare "$", a name that does not fit `nameCap`, or anything not starting
|
|
with '$' at all -- callers treat "false" as "not a variable reference,
|
|
leave this token exactly as typed". */
|
|
bool shellParseVarRef(const char* token, char* nameOut, int nameCap);
|
|
|
|
/* Recognizes shell.c's `$?` special parameter -- real POSIX shells' own
|
|
most-recently-completed command status, distinct from any `$NAME`
|
|
environment variable (shellParseVarRef above deliberately rejects it:
|
|
'?' is not in its own [A-Za-z0-9_] name-character set, so the two
|
|
token classes can never collide). `token` must be EXACTLY the two
|
|
characters "$?" and nothing else -- same "whole token or nothing"
|
|
contract shellParseVarRef already gives `$NAME`, so `$?x` or `x$?` are
|
|
both rejected, left for the caller to treat as an ordinary literal
|
|
token. The glue that resolves a match into an actual decimal status
|
|
string (shell.c's shellExpandToken, which reads shell.c's own
|
|
file-scope `lastStatus`) is not pure, so only this syntax check lives
|
|
here -- the same "pure logic here, thread/state-touching glue stays
|
|
static in shell.c" split shellParseVarRef already established. */
|
|
bool shellIsStatusVarToken(const char* token);
|
|
|
|
/* Classic shell wildcard matcher: '*' matches any run of characters
|
|
(including none), '?' matches exactly one character, anything else must
|
|
match `name` literally. Pure string logic (no VFS/scheduler involved),
|
|
iterative rather than recursive (a single backtrack point remembering
|
|
the most recent '*' and where in `name` it started matching from) --
|
|
the same shape a real libc fnmatch(3) uses, and the same "pure logic
|
|
here, thread/VFS-touching glue stays static in shell.c" split
|
|
shellTokenize/shellSplitPipelineStages/shellExtractRedirects/
|
|
shellParseVarRef above already established. Exposed here for
|
|
shellTest.c's KTEST coverage. */
|
|
bool shellGlobMatch(const char* pattern, const char* name);
|
|
|
|
/* True if `token` contains at least one glob metacharacter ('*' or '?') --
|
|
the same "is this token special" gate shellParseVarRef gives '$', used by
|
|
shell.c's shellExpandGlobs to decide whether a token needs real directory
|
|
listing + matching at all, or can pass through completely untouched (the
|
|
common case for most argv words, which have no wildcard in them at
|
|
all). */
|
|
bool shellTokenHasGlob(const char* token);
|
|
|
|
/* Splits `token` at its LAST '/' into `dirOut` (everything up to and
|
|
including that '/', or an empty string if `token` has no '/' at all --
|
|
meaning "the current directory", exactly like a bare `*.txt` in a real
|
|
shell) and `patternOut` (everything after it, the actual glob pattern to
|
|
match each directory entry's own name against via shellGlobMatch above).
|
|
Only this FINAL path component may itself contain glob syntax -- `dirOut`
|
|
is never re-scanned for `*`/`?` by anything in shell.c, an
|
|
honestly-scoped limitation (e.g. `/tmp/gd/ *.txt` is supported,
|
|
`/tmp/ * /file.txt` is not) matching this file's existing pattern of
|
|
documenting what its syntax does NOT attempt (see runPipelineStages' own
|
|
"no stage may be a builtin" and shellExtractRedirects' own redirect-target
|
|
scope limits). Truncates silently (like every other fixed-capacity buffer
|
|
in this shell) if either half does not fit its own capacity. Exposed here
|
|
for the same pre-scheduler, no-VFS KTEST coverage every other pure
|
|
function in this file already gets (shellTest.c). */
|
|
void shellSplitGlobToken(const char* token, char* dirOut, int dirCap, char* patternOut, int patternCap);
|
|
|
|
/* Recognizes shell.c's `$(cmd [args])` command-substitution syntax --
|
|
locates the FIRST `$(` anywhere in `line` and its matching `)` (no
|
|
nesting: the very next `)` after the opening `$(`, an honestly-scoped
|
|
limitation matching shellSplitGlobToken's own "only the final path
|
|
component" scope note), and copies the text between them (the command
|
|
to run, still completely unparsed) into `cmdOut`, truncated to `cmdCap`
|
|
like every other fixed-capacity buffer in this file.
|
|
Returns true with `*startOut` set to -1 (cmdOut/`*endOut` left
|
|
untouched) if `line` contains no `$(` at all -- the caller's signal to
|
|
stop looking, exactly like shellSplitPipelineStages' own "0 means
|
|
nothing found" convention but spelled with a sentinel index instead of a
|
|
sentinel count, since a real match also needs to report WHERE in the
|
|
line it sits. On a real match, `*startOut`/`*endOut` are the byte
|
|
offsets of the leading `$` and one-past the matching `)` respectively,
|
|
so a caller can splice `line[0..*startOut)` + (whatever `cmdOut`
|
|
evaluates to) + `line[*endOut..]` into a new line the same way
|
|
shellExtractRedirects' caller already reassembles argv around removed
|
|
tokens. Returns false, leaving everything untouched, if a `$(` is found
|
|
with no matching `)` anywhere after it -- the same "malformed input,
|
|
caller reports an error" shape shellExtractRedirects already uses for an
|
|
operator with no filename. Exposed here (rather than kept static in
|
|
shell.c) for the same pre-scheduler, no-VFS KTEST coverage every other
|
|
pure syntax function in this file already gets (shellTest.c) -- the glue
|
|
that actually RUNS `cmdOut` and splices its captured output back in
|
|
(shell.c's shellExpandCommandSubst) is not pure, so only the syntax half
|
|
lives here, the same split shellParseVarRef/shellIsStatusVarToken above
|
|
already established for `$NAME`/`$?`. */
|
|
bool shellFindCommandSubst(const char* line, int* startOut, int* endOut, char* cmdOut, int cmdCap);
|
|
|
|
/* The pid most recently spawned by the shell's own `NAME [ARGS] [&]`
|
|
fallback (shell.c's runProgram) -- foreground or background alike, set
|
|
the instant jobctlSpawnForeground/jobctlRunBackground (jobctl.h) hand
|
|
one back, never only after a foreground job has already exited -- or
|
|
threadInvalidId if the shell has never run anything yet. Lets a
|
|
boot-time live demo (kmain.c) that drives the real interactive shell
|
|
through injected keystrokes (there being no automated way to drive a
|
|
real PS/2 keyboard under headless QEMU) find out what pid it just asked
|
|
the shell to run, so it can act on that specific job (e.g. proving a
|
|
backgrounded one stays alive across an injected Ctrl-C the same way
|
|
user/pgroupbg.c's boot-time-spawned counterpart already does). */
|
|
uint32 shellLastSpawnedPid(void);
|
|
|
|
#endif
|