feat: extend shell with runProgram and splitCommandWord
This commit is contained in:
1 parent
ff86a70128
commit
28d1248711
1 file changed
+173
+173
@@ -538,6 +538,179 @@ static int shellExpandGlobs(char** argv, int argc, int maxArgc, char storage[][s
|
|||||||
result[resultCount] = storage[resultCount];
|
result[resultCount] = storage[resultCount];
|
||||||
resultCount++;
|
resultCount++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultCount == matchStart) {
|
||||||
|
if (resultCount >= maxArgc)
|
||||||
|
return -1;
|
||||||
|
result[resultCount++] = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < resultCount; i++)
|
||||||
|
argv[i] = result[i];
|
||||||
|
return resultCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback for a command that matched none of the built-ins in execute()
|
||||||
|
below: `line` is already the first word (NUL-terminated in place by
|
||||||
|
execute()'s own split) and `argument` is the rest of the original typed
|
||||||
|
line, still starting right after that NUL in the very same buffer. */
|
||||||
|
/* Returns the real exit status of whatever this line ran (0/nonzero from
|
||||||
|
the spawned process itself for a foreground command, shellExecNotFound
|
||||||
|
above for anything that never even got to spawn, or 0 immediately for a
|
||||||
|
backgrounded job -- matching real `cmd &`'s own immediate-success exit
|
||||||
|
status, never the job's own eventual, still-unknown-at-this-point exit
|
||||||
|
code) -- feeds shell.c's new `&&`/`||` chaining (execute()) the same way
|
||||||
|
listDir/catFile/makeDir/changeDir/doExport/doUnset's own int returns
|
||||||
|
already do for builtins. */
|
||||||
|
static int64 runProgram(char* line, char* argument)
|
||||||
|
{
|
||||||
|
char* argv[shellMaxArgs];
|
||||||
|
int argc = 0;
|
||||||
|
argv[argc++] = line;
|
||||||
|
argc += shellTokenize(argument, &argv[argc], shellMaxArgs - argc);
|
||||||
|
|
||||||
|
/* argv[0] (== line, the program name) is deliberately left out of
|
||||||
|
expansion: runProgram resolves the binary to spawn from `line`
|
||||||
|
itself, never from argv[0], so expanding it would let argv[0] show
|
||||||
|
the child a different name than the path actually loaded. */
|
||||||
|
char expandedArgv[shellMaxArgs][envEntryMax];
|
||||||
|
shellExpandArgv(argv + 1, argc - 1, expandedArgv + 1);
|
||||||
|
|
||||||
|
bool background = (argc > 1 && strcmp(argv[argc - 1], "&") == 0);
|
||||||
|
if (background)
|
||||||
|
argc--;
|
||||||
|
|
||||||
|
/* `< IN`/`<<< TEXT`/`> OUT`/`>> OUT`/`2> ERR`/`2>> ERR` are stripped out
|
||||||
|
of argv here, AFTER the trailing "&" check above (so "&" itself is
|
||||||
|
never mistaken for a redirection target) and BEFORE the program ever
|
||||||
|
sees argv -- exactly like a real shell, which never hands its own
|
||||||
|
redirection operators to the child as arguments. */
|
||||||
|
ShellRedirect redirect;
|
||||||
|
if (!shellExtractRedirects(argv, &argc, &redirect)) {
|
||||||
|
kprintf("%s: redirection operator with no filename\n", line);
|
||||||
|
return shellExecNotFound;
|
||||||
|
}
|
||||||
|
char redirectStorage[4][envEntryMax];
|
||||||
|
shellExpandRedirect(&redirect, redirectStorage);
|
||||||
|
|
||||||
|
/* Wildcard expansion runs LAST, on the real argv the child program will
|
||||||
|
actually receive -- after redirects/here-string/background have all
|
||||||
|
already been pulled out, so a glob character can only ever expand a
|
||||||
|
genuine program argument (see shellExpandGlobs' own doc comment for
|
||||||
|
why). argv[0] (the program name) is excluded, same reasoning as
|
||||||
|
shellExpandArgv's own exclusion just above. */
|
||||||
|
char globStorage[shellMaxArgs][shellGlobPathCapacity];
|
||||||
|
int expandedArgc = shellExpandGlobs(argv + 1, argc - 1, shellMaxArgs - 1, globStorage);
|
||||||
|
if (expandedArgc < 0) {
|
||||||
|
kprintf("%s: too many arguments after wildcard expansion\n", line);
|
||||||
|
return shellExecNotFound;
|
||||||
|
}
|
||||||
|
argc = 1 + expandedArgc;
|
||||||
|
|
||||||
|
char binPath[binPathCapacity];
|
||||||
|
snprintf(binPath, sizeof(binPath), "/bin/%s", line);
|
||||||
|
|
||||||
|
if (background) {
|
||||||
|
uint32 pid;
|
||||||
|
/* A here-string needs its own spawn path (jobctlRunBackgroundHereString
|
||||||
|
wires the child's fd 0 to a real pipe the SHELL itself writes the
|
||||||
|
text into, rather than opening a path) -- redirect.inPath is
|
||||||
|
always nullptr whenever hereString is set (shellExtractRedirects'
|
||||||
|
own last-one-wins rule between the two), so the plain Redirected
|
||||||
|
call below would otherwise just leave fd 0 at the console. */
|
||||||
|
bool spawned = redirect.hereString
|
||||||
|
? jobctlRunBackgroundHereString(binPath, (const char* const*)argv, argc,
|
||||||
|
redirect.hereString, redirect.outPath,
|
||||||
|
redirect.appendOut, redirect.errPath,
|
||||||
|
redirect.appendErr, &pid)
|
||||||
|
: jobctlRunBackgroundRedirected(binPath, (const char* const*)argv, argc,
|
||||||
|
redirect.inPath, redirect.outPath, redirect.appendOut,
|
||||||
|
redirect.errPath, redirect.appendErr, &pid);
|
||||||
|
if (!spawned) {
|
||||||
|
kprintf("unknown command or bad redirection: %s (try 'help')\n", line);
|
||||||
|
return shellExecNotFound;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < maxBackgroundJobs; i++) {
|
||||||
|
if (!backgroundJobs[i].used) {
|
||||||
|
backgroundJobs[i].used = true;
|
||||||
|
backgroundJobs[i].pid = pid;
|
||||||
|
snprintf(backgroundJobs[i].name, sizeof(backgroundJobs[i].name), "%s", line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastSpawnedPid = pid;
|
||||||
|
kprintf("[%u] %s started in background\n", pid, line);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Split into spawn-then-wait (rather than one combined call) so
|
||||||
|
lastSpawnedPid below is visible to anyone asking (shellLastSpawnedPid)
|
||||||
|
the instant the job exists, not only after jobctlWaitForeground's
|
||||||
|
blocking wait has already finished -- the same immediacy the
|
||||||
|
background branch above already gets for free from jobctlRunBackground
|
||||||
|
never blocking in the first place. Same here-string-vs-plain split as
|
||||||
|
the background branch above, same reason. */
|
||||||
|
uint32 pid = redirect.hereString
|
||||||
|
? jobctlSpawnForegroundHereString(binPath, (const char* const*)argv, argc,
|
||||||
|
redirect.hereString, redirect.outPath,
|
||||||
|
redirect.appendOut, redirect.errPath, redirect.appendErr)
|
||||||
|
: jobctlSpawnForegroundRedirected(binPath, (const char* const*)argv, argc,
|
||||||
|
redirect.inPath, redirect.outPath,
|
||||||
|
redirect.appendOut, redirect.errPath,
|
||||||
|
redirect.appendErr);
|
||||||
|
if (pid == threadInvalidId) {
|
||||||
|
kprintf("unknown command or bad redirection: %s (try 'help')\n", line);
|
||||||
|
return shellExecNotFound;
|
||||||
|
}
|
||||||
|
lastSpawnedPid = pid;
|
||||||
|
int64 exitCode = jobctlWaitForeground(pid);
|
||||||
|
kprintf("%s exited with code %ld\n", line, exitCode);
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splits each of an already-`shellSplitPipelineStages`d line's stages into
|
||||||
|
its own binary name + argv, exactly like runProgram's own split above --
|
||||||
|
each stage is independently "the first word, then the rest" in the SAME
|
||||||
|
shape execute() already hands runProgram, just done once per stage here
|
||||||
|
instead of once total. */
|
||||||
|
static void splitCommandWord(char* text, char** word, char** rest)
|
||||||
|
{
|
||||||
|
char* cursor = text;
|
||||||
|
while (*cursor && *cursor != ' ')
|
||||||
|
cursor++;
|
||||||
|
if (*cursor == ' ') {
|
||||||
|
*cursor = 0;
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
while (*cursor == ' ')
|
||||||
|
cursor++;
|
||||||
|
*word = text;
|
||||||
|
*rest = cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Real N-stage shell pipeline (2 <= count <= shellMaxPipelineStages):
|
||||||
|
stage i's stdout feeds stage i+1's stdin through a real pipe
|
||||||
|
(vfsPipeCreateHandle + jobctlSpawnPiped's fd-baking, jobctl.h) for every
|
||||||
|
adjacent pair -- stage 0 otherwise keeps the real console for its stdin,
|
||||||
|
the last stage otherwise keeps it for its stdout, exactly like a real
|
||||||
|
shell's own `cmd1 | cmd2 | ... | cmdN`. jobctlSpawnPiped already takes
|
||||||
|
independent stdin/stdout pipe handles per call, so a MIDDLE stage (both
|
||||||
|
ends piped at once) needs no new primitive here, only the loop below
|
||||||
|
wiring each stage's stdin to the previous pipe and its stdout to the
|
||||||
|
next one. No stage may itself be a builtin (echo/ls/cat/... run directly
|
||||||
|
on THIS thread via kprintf/vfs, never through a spawned process's own fd
|
||||||
|
1, so they have no real fd to redirect) -- an honestly-scoped limitation
|
||||||
|
matching jobctl.h's own doc comments elsewhere in this file. Only stage
|
||||||
|
0's stdin may additionally be a here-string (`cmd1 <<< TEXT | cmd2 | ...`,
|
||||||
|
jobctlSpawnPipedHereString below) -- every later stage's stdin is already
|
||||||
|
a pipe, exactly like the old two-stage runPipeline already required.
|
||||||
|
Returns the real exit status of the whole pipeline (the LAST stage's own
|
||||||
|
exit code, matching real `$?`, or shellExecNotFound for any error that
|
||||||
|
never even got every stage spawned) -- same "feeds shell.c's new
|
||||||
|
`&&`/`||` chaining" reasoning as runProgram's own return above. */
|
||||||
|
static int64 runPipelineStages(char* stages[], int count)
|
||||||
|
|
||||||
static void printHelp(void)
|
static void printHelp(void)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user