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];
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user