Files
frostbyte/include/shell.h
T

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