182 lines
11 KiB
C
182 lines
11 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);
|
|
#endif
|