feat: extend shell with execute
This commit is contained in:
1 parent
ebdc94d031
commit
c885f33d76
2 files changed
+178
-17
No files matched your search
+137
-17
@@ -1545,7 +1545,29 @@ static bool shellExpandCommandSubst(const char* srcLine, char* outLine, int outC
|
|||||||
static int64 executeSingle(char* line)
|
static int64 executeSingle(char* line)
|
||||||
{
|
{
|
||||||
if (line[0] == 0)
|
if (line[0] == 0)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
|
char* pipeStages[shellMaxPipelineStages];
|
||||||
|
int pipeStageCount = shellSplitPipelineStages(line, pipeStages, shellMaxPipelineStages);
|
||||||
|
if (pipeStageCount == -1) {
|
||||||
|
kprintf("pipeline: too many '|' stages (max %d)\n", shellMaxPipelineStages);
|
||||||
|
return shellExecNotFound;
|
||||||
|
}
|
||||||
|
if (pipeStageCount > 0)
|
||||||
|
return runPipelineStages(pipeStages, pipeStageCount);
|
||||||
|
|
||||||
|
/* `$(cmd)` command substitution (shellExpandCommandSubst above) only
|
||||||
|
ever runs for a single, non-piped command -- an honestly-scoped
|
||||||
|
limitation matching this file's own history of landing a feature for
|
||||||
|
the plain case first and generalizing to pipeline stages in a later
|
||||||
|
fala (`<<<` here-strings took the same path: jobctlSpawnForegroundHereString
|
||||||
|
landed before jobctlSpawnPipedHereString ever fed a pipeline's first
|
||||||
|
stage). `|` has already been ruled out above (pipeStageCount == 0),
|
||||||
|
so nothing here can still be misread as a pipe. */
|
||||||
|
char expandedLine[lineCapacity];
|
||||||
|
if (!shellExpandCommandSubst(line, expandedLine, sizeof(expandedLine)))
|
||||||
|
return shellExecNotFound;
|
||||||
|
line = expandedLine;
|
||||||
|
|
||||||
char* argument = line;
|
char* argument = line;
|
||||||
while (*argument && *argument != ' ')
|
while (*argument && *argument != ' ')
|
||||||
@@ -1557,30 +1579,111 @@ static int64 executeSingle(char* line)
|
|||||||
while (*argument == ' ')
|
while (*argument == ' ')
|
||||||
argument++;
|
argument++;
|
||||||
|
|
||||||
if (strcmp(line, "help") == 0)
|
if (strcmp(line, "help") == 0) {
|
||||||
printHelp();
|
printHelp();
|
||||||
else if (strcmp(line, "echo") == 0)
|
return 0;
|
||||||
|
} else if (strcmp(line, "echo") == 0) {
|
||||||
doEcho(argument);
|
doEcho(argument);
|
||||||
else if (strcmp(line, "ls") == 0)
|
return 0;
|
||||||
listDir(argument);
|
} else if (strcmp(line, "ls") == 0)
|
||||||
|
return listDir(argument);
|
||||||
else if (strcmp(line, "cat") == 0)
|
else if (strcmp(line, "cat") == 0)
|
||||||
catFile(argument);
|
return catFile(argument);
|
||||||
else if (strcmp(line, "mkdir") == 0)
|
else if (strcmp(line, "mkdir") == 0)
|
||||||
makeDir(argument);
|
return makeDir(argument);
|
||||||
|
else if (strcmp(line, "rm") == 0)
|
||||||
|
return removeFile(argument);
|
||||||
|
else if (strcmp(line, "rmdir") == 0)
|
||||||
|
return removeDir(argument);
|
||||||
|
else if (strcmp(line, "chmod") == 0)
|
||||||
|
return doChmod(argument);
|
||||||
|
else if (strcmp(line, "chown") == 0)
|
||||||
|
return doChown(argument);
|
||||||
|
else if (strcmp(line, "chgrp") == 0)
|
||||||
|
return doChgrp(argument);
|
||||||
else if (strcmp(line, "cd") == 0)
|
else if (strcmp(line, "cd") == 0)
|
||||||
changeDir(argument);
|
return changeDir(argument);
|
||||||
else if (strcmp(line, "pwd") == 0)
|
else if (strcmp(line, "pwd") == 0) {
|
||||||
kprintf("%s\n", cwd);
|
char path[pathCapacity];
|
||||||
else if (strcmp(line, "mem") == 0)
|
vfsGetcwd(path, sizeof(path));
|
||||||
|
kprintf("%s\n", path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (strcmp(line, "whoami") == 0) {
|
||||||
|
kprintf("%u\n", currentThreadUid());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (strcmp(line, "mem") == 0) {
|
||||||
printMem();
|
printMem();
|
||||||
else if (strcmp(line, "uptime") == 0)
|
return 0;
|
||||||
|
} else if (strcmp(line, "uptime") == 0) {
|
||||||
printUptime();
|
printUptime();
|
||||||
else if (strcmp(line, "ps") == 0)
|
return 0;
|
||||||
|
} else if (strcmp(line, "ps") == 0) {
|
||||||
printThreads();
|
printThreads();
|
||||||
else if (strcmp(line, "clear") == 0)
|
return 0;
|
||||||
|
} else if (strcmp(line, "jobs") == 0) {
|
||||||
|
listBackgroundJobs();
|
||||||
|
return 0;
|
||||||
|
} else if (strcmp(line, "export") == 0)
|
||||||
|
return doExport(argument);
|
||||||
|
else if (strcmp(line, "env") == 0) {
|
||||||
|
printEnv();
|
||||||
|
return 0;
|
||||||
|
} else if (strcmp(line, "unset") == 0)
|
||||||
|
return doUnset(argument);
|
||||||
|
else if (strcmp(line, "clear") == 0) {
|
||||||
vgaClear();
|
vgaClear();
|
||||||
else
|
return 0;
|
||||||
kprintf("unknown command: %s (try 'help')\n", line);
|
} else
|
||||||
|
return runProgram(line, argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splits `line` at every top-level `&&`/`||` (shellSplitAndOr, shell.h)
|
||||||
|
and runs each segment left to right through executeSingle above,
|
||||||
|
short-circuiting exactly like a real shell: a segment preceded by `&&`
|
||||||
|
only runs if the PREVIOUS segment that actually ran (not necessarily the
|
||||||
|
lexically previous one -- a skipped segment leaves `status` completely
|
||||||
|
unchanged) exited 0, one preceded by `||` only runs if that same status
|
||||||
|
was nonzero. This is real conditional chaining WITHIN a single typed
|
||||||
|
line (`cmd1 && cmd2 || cmd3`, the classic shell if-then-else idiom).
|
||||||
|
`lastStatus` (this file's own file-scope static above) is updated right
|
||||||
|
after every executeSingle() call below, chained or not -- a real shell's
|
||||||
|
own `$?` updates after EVERY simple command, including mid-chain (so
|
||||||
|
`false || echo $?` sees `false`'s own status while evaluating `echo`'s
|
||||||
|
argv, not some later value), and survives PAST this function returning,
|
||||||
|
so a later, completely separate typed line can still read it via
|
||||||
|
shellExpandToken's own `$?` handling -- closing the "no persistent `$?`
|
||||||
|
across separately typed lines" gap this file used to document here as a
|
||||||
|
deliberate limitation. A SKIPPED segment leaves `lastStatus` untouched
|
||||||
|
exactly like it already leaves the local chaining `status` untouched. */
|
||||||
|
static void execute(char* line)
|
||||||
|
{
|
||||||
|
if (line[0] == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char* andOrSegments[shellMaxAndOrSegments];
|
||||||
|
ShellAndOrOp andOrOps[shellMaxAndOrSegments - 1];
|
||||||
|
int andOrCount = shellSplitAndOr(line, andOrSegments, andOrOps, shellMaxAndOrSegments);
|
||||||
|
if (andOrCount == -1) {
|
||||||
|
kprintf("shell: too many '&&'/'||' segments (max %d)\n", shellMaxAndOrSegments);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (andOrCount == 0) {
|
||||||
|
lastStatus = executeSingle(line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 status = 0;
|
||||||
|
for (int i = 0; i < andOrCount; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
bool shouldRun = (andOrOps[i - 1] == shellAndOrAnd) ? (status == 0) : (status != 0);
|
||||||
|
if (!shouldRun)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
status = executeSingle(andOrSegments[i]);
|
||||||
|
lastStatus = status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shellRun(void)
|
void shellRun(void)
|
||||||
@@ -1588,17 +1691,34 @@ void shellRun(void)
|
|||||||
char line[lineCapacity];
|
char line[lineCapacity];
|
||||||
uint32 length = 0;
|
uint32 length = 0;
|
||||||
|
|
||||||
|
/* The shell is its own independent console reader (keyboard.h), same as
|
||||||
|
every ring-3 process's fd 0 -- a keystroke keyboardHandleScancode
|
||||||
|
decodes is broadcast to every open reader, this one included, so the
|
||||||
|
shell keeps working unchanged even while other processes are also
|
||||||
|
blocked reading fd 0 (see the consoleReaderA/consoleReaderB live boot
|
||||||
|
demo in kmain.c). Plain kernel-thread context (not a syscall
|
||||||
|
handler), so the blocking helper's real halt() loop is safe here,
|
||||||
|
exactly like pipeReadBlocking's callers. */
|
||||||
|
int reader = keyboardOpenReader();
|
||||||
|
if (reader == consoleReaderInvalid) {
|
||||||
|
kprintf("[shell] console reader table full, shell has no input\n");
|
||||||
|
for (;;)
|
||||||
|
halt();
|
||||||
|
}
|
||||||
|
|
||||||
kprintf("\nfrostbyte shell ready. type 'help'.\n");
|
kprintf("\nfrostbyte shell ready. type 'help'.\n");
|
||||||
kprintf("frostbyte> ");
|
kprintf("frostbyte> ");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char c = keyboardReadChar();
|
char c;
|
||||||
|
keyboardReaderReadBlocking(reader, &c, 1);
|
||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
kputchar('\n');
|
kputchar('\n');
|
||||||
line[length] = 0;
|
line[length] = 0;
|
||||||
execute(line);
|
execute(line);
|
||||||
length = 0;
|
length = 0;
|
||||||
|
reapFinishedBackgroundJobs();
|
||||||
kprintf("frostbyte> ");
|
kprintf("frostbyte> ");
|
||||||
} else if (c == '\b') {
|
} else if (c == '\b') {
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#include "syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shared signal-delivery trampoline (see signal.h/signal.c). This is NOT
|
||||||
|
* entered like a normal function call from C -- signal.c's
|
||||||
|
* sigConsumePending rewrites a thread's own saved trapframe so that
|
||||||
|
* iretq lands ring 3 execution directly at sigTrampolineStart, with %rax
|
||||||
|
* already holding the handler's address and %rdi already holding the
|
||||||
|
* signal number (the handler's sole SysV argument), exactly as if the
|
||||||
|
* kernel itself had performed `call *%rax` with that argument set up.
|
||||||
|
*
|
||||||
|
* Position independent by construction: every instruction here is either
|
||||||
|
* PC-relative (the final backward jmp) or references no address at all
|
||||||
|
* (call through a register, an immediate syscall number, int $0x80) -- so
|
||||||
|
* sigInstallTrampoline can copy these bytes to any physical frame and map
|
||||||
|
* that frame at any virtual address (sigTrampolineVirtualAddr) and it
|
||||||
|
* still runs correctly, unlike this same code linked and executed in
|
||||||
|
* place inside the kernel's own (very different) virtual address range.
|
||||||
|
*/
|
||||||
|
.section .text
|
||||||
|
.code64
|
||||||
|
|
||||||
|
.global sigTrampolineStart
|
||||||
|
.global sigTrampolineEnd
|
||||||
|
|
||||||
|
sigTrampolineStart:
|
||||||
|
call *%rax
|
||||||
|
movl $sysSigReturn, %eax
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
/* Only reached if sysSigReturn somehow returned instead of restoring the
|
||||||
|
pre-signal context and resuming it (a kernel bug, not a state a correct
|
||||||
|
sysOnSigReturn can produce) -- park here rather than run off into
|
||||||
|
whatever garbage happens to follow this page. */
|
||||||
|
sigTrampolineHang:
|
||||||
|
hlt
|
||||||
|
jmp sigTrampolineHang
|
||||||
|
|
||||||
|
sigTrampolineEnd:
|
||||||
|
|
||||||
|
.section .note.GNU-stack, "", @progbits
|
||||||
Reference in new issue
Block a user