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 "shell.h"
|
||||||
#include "console.h"
|
#include "console.h"
|
||||||
#include "keyboard.h"
|
#include "keyboard.h"
|
||||||
|
#include "ports.h"
|
||||||
#include "vga.h"
|
#include "vga.h"
|
||||||
#include "pmm.h"
|
#include "pmm.h"
|
||||||
#include "heap.h"
|
#include "heap.h"
|
||||||
@@ -9,11 +10,188 @@
|
|||||||
#include "string.h"
|
#include "string.h"
|
||||||
#include "vfs.h"
|
#include "vfs.h"
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
#include "jobctl.h"
|
||||||
|
#include "klib.h"
|
||||||
|
#include "pipe.h"
|
||||||
|
#include "rtc.h"
|
||||||
|
|
||||||
#define lineCapacity 128
|
#define lineCapacity 128
|
||||||
#define pathCapacity 256
|
#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)
|
static void printHelp(void)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user