diff --git a/src/shell.c b/src/shell.c index 0089257..2ea3e38 100644 --- a/src/shell.c +++ b/src/shell.c @@ -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/ 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) {