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