feat: extend shell

This commit is contained in:
dualfroz committed 2026-09-02 15:10:20 +02:00
1 parent ff4256f987
commit 265c40e48d
1 file changed
+95
+95
View File
@@ -178,4 +178,99 @@ bool shellExtractRedirects(char** argv, int* argc, ShellRedirect* out);
with '$' at all -- callers treat "false" as "not a variable reference, with '$' at all -- callers treat "false" as "not a variable reference,
leave this token exactly as typed". */ leave this token exactly as typed". */
bool shellParseVarRef(const char* token, char* nameOut, int nameCap); 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 #endif