feat: extend shell with threadStateName
This commit is contained in:
1 parent
28d1248711
commit
ff36cc0275
1 file changed
+194
-2
+194
-2
@@ -711,6 +711,163 @@ static void splitCommandWord(char* text, char** word, char** rest)
|
||||
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)
|
||||
{
|
||||
char* word[shellMaxPipelineStages];
|
||||
char* argv[shellMaxPipelineStages][shellMaxArgs];
|
||||
int argc[shellMaxPipelineStages];
|
||||
ShellRedirect redirect[shellMaxPipelineStages];
|
||||
/* Per-stage expansion scratch for shellExpandArgv, same "argv[0] is
|
||||
never expanded" reasoning as runProgram's own expandedArgv -- each
|
||||
stage's actual binary comes from word[i], never from argv[i][0]. */
|
||||
char expanded[shellMaxPipelineStages][shellMaxArgs][envEntryMax];
|
||||
/* Per-stage wildcard expansion scratch (shellExpandGlobs) -- a
|
||||
dedicated, smaller shellGlobPathCapacity buffer rather than reusing
|
||||
pathCapacity, since this array is already multiplied by
|
||||
shellMaxPipelineStages on top of shellMaxArgs and shares a single
|
||||
16KiB kernel-thread stack (sched.c's threadStackSize) with every
|
||||
other local array in this function -- see shellExpandGlobs' own doc
|
||||
comment in shell.c for the full sizing reasoning. */
|
||||
char globStorage[shellMaxPipelineStages][shellMaxArgs][shellGlobPathCapacity];
|
||||
/* Per-stage redirect-target expansion scratch (shellExpandRedirect) --
|
||||
same reasoning as runProgram's own redirectStorage, one dedicated
|
||||
4-slot array per stage since a stage's own inPath/outPath/errPath/
|
||||
hereString could all need expanding on the same line at once. */
|
||||
char redirectStorage[shellMaxPipelineStages][4][envEntryMax];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
char* rest;
|
||||
splitCommandWord(stages[i], &word[i], &rest);
|
||||
argc[i] = 0;
|
||||
argv[i][argc[i]++] = word[i];
|
||||
argc[i] += shellTokenize(rest, &argv[i][argc[i]], shellMaxArgs - argc[i]);
|
||||
shellExpandArgv(&argv[i][1], argc[i] - 1, &expanded[i][1]);
|
||||
|
||||
if (!shellExtractRedirects(argv[i], &argc[i], &redirect[i])) {
|
||||
kprintf("%s: redirection operator with no filename\n", word[i]);
|
||||
return shellExecNotFound;
|
||||
}
|
||||
shellExpandRedirect(&redirect[i], redirectStorage[i]);
|
||||
|
||||
/* Same "expand real arguments last, after redirects" ordering as
|
||||
runProgram's own single-command path above. */
|
||||
int expandedArgc = shellExpandGlobs(&argv[i][1], argc[i] - 1, shellMaxArgs - 1, globStorage[i]);
|
||||
if (expandedArgc < 0) {
|
||||
kprintf("%s: too many arguments after wildcard expansion\n", word[i]);
|
||||
return shellExecNotFound;
|
||||
}
|
||||
argc[i] = 1 + expandedArgc;
|
||||
}
|
||||
|
||||
/* Only the two OPEN ends of the pipeline may be file/here-string
|
||||
redirected for stdin/stdout -- stage 0's stdin and the LAST stage's
|
||||
stdout -- exactly like this function's own header comment already
|
||||
scopes "no stage may be a builtin" for a similar reason. Every
|
||||
interior fd is already wired to a real pipe on both sides, so those
|
||||
combinations are rejected outright below rather than silently
|
||||
guessing which of "the pipe" or "the file" should win. fd 2
|
||||
(stderr) is never part of any pipe, so `2>`/`2>>` are always allowed
|
||||
on every stage independently, with no such restriction. */
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i > 0 && redirect[i].inPath) {
|
||||
kprintf("pipeline: '%s' already reads from the pipe, input redirection is only "
|
||||
"supported on the first stage\n", word[i]);
|
||||
return shellExecNotFound;
|
||||
}
|
||||
if (i > 0 && redirect[i].hereString) {
|
||||
kprintf("pipeline: '%s' already reads from the pipe, here-string redirection is "
|
||||
"only supported on the first stage\n", word[i]);
|
||||
return shellExecNotFound;
|
||||
}
|
||||
if (i < count - 1 && redirect[i].outPath) {
|
||||
kprintf("pipeline: '%s' already writes to the pipe, output redirection is only "
|
||||
"supported on the last stage\n", word[i]);
|
||||
return shellExecNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 pids[shellMaxPipelineStages];
|
||||
/* pipeHandle[i] connects stage i's stdout to stage i+1's stdin -- only
|
||||
count-1 links exist for count stages. */
|
||||
int pipeHandle[shellMaxPipelineStages - 1];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
int stdinHandle = (i == 0) ? -1 : pipeHandle[i - 1];
|
||||
int stdoutHandle = -1;
|
||||
if (i < count - 1) {
|
||||
pipeHandle[i] = vfsPipeCreateHandle();
|
||||
if (pipeHandle[i] < 0) {
|
||||
kprintf("pipeline: no pipe slots free\n");
|
||||
for (int k = 0; k < i; k++) {
|
||||
threadKill(pids[k]);
|
||||
threadJoin(pids[k]);
|
||||
}
|
||||
return shellExecNotFound;
|
||||
}
|
||||
stdoutHandle = pipeHandle[i];
|
||||
}
|
||||
|
||||
/* joinPgid: every stage after the first joins stage 0's own group
|
||||
(jobctlSpawnPiped's own doc comment) so a single Ctrl-C, or this
|
||||
function's own eventual keyboardSetForegroundPgid via
|
||||
jobctlWaitPipelineStages, reaches every stage at once. */
|
||||
uint32 joinPgid = (i == 0) ? threadInvalidId : pids[0];
|
||||
|
||||
char binPath[binPathCapacity];
|
||||
snprintf(binPath, sizeof(binPath), "/bin/%s", word[i]);
|
||||
|
||||
/* A here-string is only ever possible on stage 0 (validated above)
|
||||
-- jobctlSpawnPipedHereString wires fd 0 to a real pipe the
|
||||
SHELL itself writes the text into, rather than leaving it at the
|
||||
console, then passes stdoutHandle/joinPgid straight through
|
||||
exactly like the plain jobctlSpawnPiped every other stage
|
||||
uses. */
|
||||
uint32 pid = (i == 0 && redirect[i].hereString)
|
||||
? jobctlSpawnPipedHereString(binPath, (const char* const*)argv[i], argc[i],
|
||||
redirect[i].hereString, stdoutHandle, joinPgid)
|
||||
: jobctlSpawnPiped(binPath, (const char* const*)argv[i], argc[i],
|
||||
stdinHandle, stdoutHandle, joinPgid);
|
||||
|
||||
if (pid == threadInvalidId) {
|
||||
kprintf("unknown command: %s (try 'help')\n", word[i]);
|
||||
if (i < count - 1)
|
||||
pipeDestroyIfUnattached(pipeHandle[i]);
|
||||
for (int k = 0; k < i; k++) {
|
||||
threadKill(pids[k]);
|
||||
threadJoin(pids[k]);
|
||||
}
|
||||
return shellExecNotFound;
|
||||
}
|
||||
pids[i] = pid;
|
||||
|
||||
const char* inPath = (i == 0) ? redirect[i].inPath : nullptr;
|
||||
const char* outPath = (i == count - 1) ? redirect[i].outPath : nullptr;
|
||||
bool appendOut = (i == count - 1) ? redirect[i].appendOut : false;
|
||||
if ((inPath || outPath || redirect[i].errPath) &&
|
||||
!jobctlApplyRedirects(pid, inPath, outPath, appendOut,
|
||||
redirect[i].errPath, redirect[i].appendErr)) {
|
||||
kprintf("%s: cannot open %s\n", word[i],
|
||||
inPath ? inPath : (outPath ? outPath : redirect[i].errPath));
|
||||
for (int k = 0; k <= i; k++) {
|
||||
threadKill(pids[k]);
|
||||
threadJoin(pids[k]);
|
||||
}
|
||||
return shellExecNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
lastSpawnedPid = pids[count - 1];
|
||||
int64 exitCode = jobctlWaitPipelineStages(pids, count);
|
||||
|
||||
char summary[lineCapacity];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i > 0)
|
||||
pos += snprintf(summary + pos, sizeof(summary) - pos, " | ");
|
||||
pos += snprintf(summary + pos, sizeof(summary) - pos, "%s", word[i]);
|
||||
}
|
||||
kprintf("%s exited with code %ld\n", summary, exitCode);
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
static void printHelp(void)
|
||||
{
|
||||
@@ -718,15 +875,36 @@ static void printHelp(void)
|
||||
kprintf(" help show this list\n");
|
||||
kprintf(" echo TEXT print the given text\n");
|
||||
kprintf(" echo TEXT >F write the text to file F\n");
|
||||
kprintf(" ls [PATH] list a directory\n");
|
||||
kprintf(" ls [-l] [PATH] list a directory (tmpfs or /mnt), -l adds type/size/mtime\n");
|
||||
kprintf(" cat FILE print a file\n");
|
||||
kprintf(" mkdir PATH create a directory (writable mounts only)\n");
|
||||
kprintf(" rm PATH remove a file (refuses a directory)\n");
|
||||
kprintf(" rmdir PATH remove an empty directory\n");
|
||||
kprintf(" chmod MODE PATH set owner/group/other rwx bits (octal, e.g. 644 or 750)\n");
|
||||
kprintf(" chgrp GID PATH set group (decimal); owner may only set its own gid\n");
|
||||
kprintf(" cd PATH change the working directory\n");
|
||||
kprintf(" pwd print the working directory\n");
|
||||
kprintf(" whoami print this shell's own uid (0 = root)\n");
|
||||
kprintf(" mem physical memory and heap usage\n");
|
||||
kprintf(" uptime ticks and seconds since boot\n");
|
||||
kprintf(" ps list kernel threads\n");
|
||||
kprintf(" jobs list running background jobs\n");
|
||||
kprintf(" export N=V set environment variable N to V (inherited by children)\n");
|
||||
kprintf(" unset N remove environment variable N\n");
|
||||
kprintf(" env list this shell's environment variables\n");
|
||||
kprintf(" clear clear the screen\n");
|
||||
kprintf(" $NAME (in a spawned program's arguments) expands to that variable\n");
|
||||
kprintf(" $? (same place) expands to the last command's exit status\n");
|
||||
kprintf(" NAME dir/*.ext (glob) expands to every real matching filename in dir\n");
|
||||
kprintf(" NAME [ARGS] run /bin/NAME in the foreground, waits for it to exit\n");
|
||||
kprintf(" NAME [ARGS] & run /bin/NAME in the background, prints its pid\n");
|
||||
kprintf(" NAME < IN redirect NAME's stdin from file IN\n");
|
||||
kprintf(" NAME <<< TEXT feed TEXT (plus a newline) to NAME's stdin\n");
|
||||
kprintf(" NAME > OUT redirect NAME's stdout to file OUT (create/truncate)\n");
|
||||
kprintf(" NAME >> OUT redirect NAME's stdout to file OUT (create/append)\n");
|
||||
kprintf(" NAME 2> ERR redirect NAME's stderr to file ERR (create/truncate)\n");
|
||||
kprintf(" NAME 2>> ERR redirect NAME's stderr to file ERR (create/append)\n");
|
||||
kprintf(" A [ARGS] | B [ARGS] pipe A's stdout into B's stdin, waits for both\n");
|
||||
}
|
||||
|
||||
static void printMem(void)
|
||||
@@ -751,12 +929,26 @@ static void printUptime(void)
|
||||
ticks, ticks / frequency, frequency);
|
||||
}
|
||||
|
||||
static const char* threadStateName(ThreadState state)
|
||||
{
|
||||
switch (state) {
|
||||
case threadUnused: return "unused";
|
||||
case threadRunnable: return "runnable";
|
||||
case threadRunning: return "running";
|
||||
case threadSleeping: return "sleeping";
|
||||
case threadDead: return "dead";
|
||||
case threadBlocked: return "blocked";
|
||||
case threadStopped: return "stopped";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static void printThreads(void)
|
||||
{
|
||||
kprintf("id state name\n");
|
||||
for (uint32 i = 0; i < threadCount(); i++) {
|
||||
Thread* thread = threadAt(i);
|
||||
const char* state = thread->state == threadRunning ? "running" : "runnable";
|
||||
const char* state = threadStateName(thread->state);
|
||||
char marker = thread->id == currentThreadId() ? '*' : ' ';
|
||||
kprintf("%c%u %s %s\n", marker, thread->id, state, thread->name);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user