feat: extend shell with threadStateName

This commit is contained in:
dualfroz committed 2026-09-08 12:23:41 +02:00
1 parent 28d1248711
commit ff36cc0275
1 file changed
+194 -2
+194 -2
View File
@@ -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 never even got every stage spawned) -- same "feeds shell.c's new
`&&`/`||` chaining" reasoning as runProgram's own return above. */ `&&`/`||` chaining" reasoning as runProgram's own return above. */
static int64 runPipelineStages(char* stages[], int count) 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) static void printHelp(void)
{ {
@@ -718,15 +875,36 @@ static void printHelp(void)
kprintf(" help show this list\n"); kprintf(" help show this list\n");
kprintf(" echo TEXT print the given text\n"); kprintf(" echo TEXT print the given text\n");
kprintf(" echo TEXT >F write the text to file F\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(" cat FILE print a file\n");
kprintf(" mkdir PATH create a directory (writable mounts only)\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(" cd PATH change the working directory\n");
kprintf(" pwd print 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(" mem physical memory and heap usage\n");
kprintf(" uptime ticks and seconds since boot\n"); kprintf(" uptime ticks and seconds since boot\n");
kprintf(" ps list kernel threads\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(" 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) static void printMem(void)
@@ -751,12 +929,26 @@ static void printUptime(void)
ticks, ticks / frequency, frequency); 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) static void printThreads(void)
{ {
kprintf("id state name\n"); kprintf("id state name\n");
for (uint32 i = 0; i < threadCount(); i++) { for (uint32 i = 0; i < threadCount(); i++) {
Thread* thread = threadAt(i); Thread* thread = threadAt(i);
const char* state = thread->state == threadRunning ? "running" : "runnable"; const char* state = threadStateName(thread->state);
char marker = thread->id == currentThreadId() ? '*' : ' '; char marker = thread->id == currentThreadId() ? '*' : ' ';
kprintf("%c%u %s %s\n", marker, thread->id, state, thread->name); kprintf("%c%u %s %s\n", marker, thread->id, state, thread->name);
} }