feat: extend shell with BackgroundJob and shellLastSpawnedPid
This commit is contained in:
1 parent
e03c0d5f7e
commit
96148ff0e7
1 file changed
+179
-1
+179
-1
@@ -1,6 +1,7 @@
|
||||
#include "shell.h"
|
||||
#include "console.h"
|
||||
#include "keyboard.h"
|
||||
#include "ports.h"
|
||||
#include "vga.h"
|
||||
#include "pmm.h"
|
||||
#include "heap.h"
|
||||
@@ -9,11 +10,188 @@
|
||||
#include "string.h"
|
||||
#include "vfs.h"
|
||||
#include "fs.h"
|
||||
#include "jobctl.h"
|
||||
#include "klib.h"
|
||||
#include "pipe.h"
|
||||
#include "rtc.h"
|
||||
|
||||
#define lineCapacity 128
|
||||
#define pathCapacity 256
|
||||
#define binPathCapacity 272
|
||||
|
||||
static char cwd[pathCapacity] = "/";
|
||||
/* Real shell "command could not even be started" exit status (bad
|
||||
redirection, wildcard overflow, unknown binary, ...) -- the same 127
|
||||
real shells report for "command not found", reused here for every
|
||||
pre-spawn failure in runProgram/runPipelineStages so shell.c's new
|
||||
`&&`/`||` chaining (execute()) always sees a nonzero, "this failed"
|
||||
status for them, exactly like a real spawned process's own nonzero
|
||||
exit code would. */
|
||||
#define shellExecNotFound 127
|
||||
|
||||
/* The shell used to own the only "current directory" concept in this whole
|
||||
kernel, a single static local shared by nothing else -- sched.h's new
|
||||
Thread.cwd (per-thread, real chdir(2)/getcwd(2) semantics, vfs.h's
|
||||
vfsChdir/vfsGetcwd/vfsResolvePath) replaces it: the shell is a plain
|
||||
kernel thread like any other (see shellRun's own comment below), so it
|
||||
simply becomes one more caller of the same real subsystem instead of
|
||||
keeping a second, disconnected notion of "cwd" in parallel. This also
|
||||
means a job this shell spawns now genuinely inherits whatever directory
|
||||
the shell itself is currently in (initialCwd, sched.c) -- true before
|
||||
this fala only by accident of both defaulting to "/", never actually
|
||||
wired together. */
|
||||
|
||||
/*
|
||||
* Real job control for the interactive shell (jobctl.h does the actual
|
||||
* spawning/terminal-ownership work): any command line that is not one of
|
||||
* the built-ins above is tried as /bin/<word> instead of just being
|
||||
* rejected outright, with the rest of the line tokenized into a real argv
|
||||
* exactly like a normal shell does. A trailing lone "&" token backgrounds
|
||||
* it (jobctlRunBackground, no terminal, returns immediately) instead of
|
||||
* running it in the foreground (jobctlSpawnForeground + jobctlWaitForeground,
|
||||
* which blocks until it exits with the terminal genuinely handed to it --
|
||||
* see jobctl.c).
|
||||
*/
|
||||
#define maxBackgroundJobs 8
|
||||
|
||||
typedef struct BackgroundJob {
|
||||
uint32 pid;
|
||||
char name[32];
|
||||
bool used;
|
||||
} BackgroundJob;
|
||||
|
||||
static BackgroundJob backgroundJobs[maxBackgroundJobs];
|
||||
static uint32 lastSpawnedPid = threadInvalidId;
|
||||
|
||||
/* Real shell `$?` -- the exit status of the most recently completed
|
||||
command-or-pipeline-or-builtin, persisting across SEPARATELY typed
|
||||
lines (unlike the `&&`/`||` chaining `status` local in execute() below,
|
||||
which only ever lives for the duration of one typed line's own
|
||||
segments). Initialized to 0, matching a real shell's own `$?` before
|
||||
anything has ever run. Updated in exactly one place (execute(), right
|
||||
after every executeSingle() call, chained or not) and read in exactly
|
||||
one place (shellExpandToken, via shellIsStatusVarToken) -- the same
|
||||
"shell is a plain kernel thread, needs no syscall to reach its own
|
||||
state" shape doExport/doUnset/changeDir already use for their own
|
||||
thread-local state, just kept in a file-scope static here since $? is
|
||||
a shell-language special parameter, not a real environment variable
|
||||
(real shells never let you `export $?` either). */
|
||||
static int64 lastStatus = 0;
|
||||
|
||||
uint32 shellLastSpawnedPid(void)
|
||||
{
|
||||
return lastSpawnedPid;
|
||||
}
|
||||
|
||||
/* Opportunistically reaps any of THIS shell's own children that have
|
||||
already exited (threadJoinAnyChild, sched.h -- the same non-blocking
|
||||
waitpid(-1)-shaped scan user/waitanytest.c already proves) and prints
|
||||
what finished. Only ever finds background jobs here: a foreground job is
|
||||
already fully joined-and-reaped by jobctlWaitForeground itself before
|
||||
that call ever returns, so by the time control comes back to this
|
||||
function there is nothing left over from it to double-reap. */
|
||||
static void reapFinishedBackgroundJobs(void)
|
||||
{
|
||||
uint32 deadId;
|
||||
int64 code;
|
||||
while (threadJoinAnyChild(currentThreadId(), &deadId, &code) == waitScanFoundDead) {
|
||||
for (int i = 0; i < maxBackgroundJobs; i++) {
|
||||
if (backgroundJobs[i].used && backgroundJobs[i].pid == deadId) {
|
||||
kprintf("[%u] %s done, exit code %ld\n", deadId, backgroundJobs[i].name, code);
|
||||
backgroundJobs[i].used = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void listBackgroundJobs(void)
|
||||
{
|
||||
bool any = false;
|
||||
for (int i = 0; i < maxBackgroundJobs; i++) {
|
||||
if (backgroundJobs[i].used) {
|
||||
kprintf("[%u] running %s\n", backgroundJobs[i].pid, backgroundJobs[i].name);
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if (!any)
|
||||
kprintf("no background jobs\n");
|
||||
}
|
||||
|
||||
int shellTokenize(char* text, char* tokens[], int maxTokens)
|
||||
{
|
||||
int count = 0;
|
||||
char* cursor = text;
|
||||
while (*cursor && count < maxTokens) {
|
||||
while (*cursor == ' ')
|
||||
cursor++;
|
||||
if (*cursor == 0)
|
||||
break;
|
||||
tokens[count++] = cursor;
|
||||
while (*cursor && *cursor != ' ')
|
||||
cursor++;
|
||||
if (*cursor == ' ') {
|
||||
*cursor = 0;
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int shellSplitPipelineStages(char* line, char* stages[], int maxStages)
|
||||
{
|
||||
/* First pass: count top-level '|' occurrences without mutating
|
||||
anything, so a too-many-stages line can be rejected with `line`
|
||||
completely untouched -- the same "validate before mutating" shape
|
||||
shellExtractRedirects already uses for its own dangling-operator
|
||||
check. */
|
||||
int pipeCount = 0;
|
||||
for (const char* cursor = line; *cursor; cursor++)
|
||||
if (*cursor == '|')
|
||||
pipeCount++;
|
||||
|
||||
if (pipeCount == 0)
|
||||
return 0;
|
||||
if (pipeCount + 1 > maxStages)
|
||||
return -1;
|
||||
|
||||
int count = 0;
|
||||
char* stageStart = line;
|
||||
char* cursor = line;
|
||||
while (true) {
|
||||
bool atPipe = (*cursor == '|');
|
||||
bool atEnd = (*cursor == 0);
|
||||
if (atPipe || atEnd) {
|
||||
char* stageEnd = cursor;
|
||||
while (stageEnd > stageStart && stageEnd[-1] == ' ')
|
||||
stageEnd--;
|
||||
*stageEnd = 0;
|
||||
|
||||
stages[count++] = stageStart;
|
||||
|
||||
if (atEnd)
|
||||
break;
|
||||
|
||||
*cursor = 0;
|
||||
stageStart = cursor + 1;
|
||||
while (*stageStart == ' ')
|
||||
stageStart++;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int shellSplitAndOr(char* line, char* segments[], ShellAndOrOp andOrOps[], int maxSegments)
|
||||
{
|
||||
/* Same "count first, mutate nothing, reject up front" shape
|
||||
shellSplitPipelineStages uses for its own analogous limit --
|
||||
matching two-character operators one at a time (never overlapping:
|
||||
stepping by 2 on a match means "&&&" is one "&&" followed by a lone
|
||||
'&' left inside a segment, not two overlapping matches). */
|
||||
int opCount = 0;
|
||||
for (const char* cursor = line; *cursor; ) {
|
||||
if ((cursor[0] == '&' && cursor[1] == '&') || (cursor[0] == '|' && cursor[1] == '|')) {
|
||||
|
||||
static void printHelp(void)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user