aboutsummaryrefslogtreecommitdiffstats
path: root/usr/sh.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/sh.c')
-rw-r--r--usr/sh.c200
1 files changed, 200 insertions, 0 deletions
diff --git a/usr/sh.c b/usr/sh.c
new file mode 100644
index 0000000..3178894
--- /dev/null
+++ b/usr/sh.c
@@ -0,0 +1,200 @@
+#include "gbos.h"
+/* gbos shell: prompt -> read a line -> parse -> run, with I/O redirection
+ ( > file, < file ) and pipes ( cmd1 | cmd2, via a temp file ).
+ Operators must be space-separated (e.g. cat readme > out ). */
+
+#define ARGV0 ((char *)0xA000) /* where a child reads its command line */
+
+/* split s into space-separated tokens (NUL-terminated in place). */
+static unsigned char tokenize(char *s, char **tok, unsigned char max) {
+ unsigned char n = 0;
+ for (;;) {
+ while (*s == ' ') *s++ = 0;
+ if (*s == 0) break;
+ if (n < max) tok[n++] = s;
+ while (*s && *s != ' ') s++;
+ }
+ return n;
+}
+
+static unsigned char isop(char *t, char c) { return t[0] == c && t[1] == 0; }
+static unsigned char iscmd(char *t, char a, char b) {
+ return t[0] == a && t[1] == b && t[2] == 0;
+}
+
+/* keep the prompt's path string roughly in sync with chdir(). */
+static void set_cwd(char *cwd, char *arg) {
+ char *d, *s, *last;
+ if (arg[0] == '/') { /* absolute */
+ d = cwd; s = arg;
+ while (*s) *d++ = *s++;
+ *d = 0;
+ } else if (arg[0] == '.' && arg[1] == '.' && arg[2] == 0) {
+ last = cwd; d = cwd;
+ while (*d) { if (*d == '/') last = d; d++; }
+ if (last == cwd) { cwd[0] = '/'; cwd[1] = 0; } else *last = 0;
+ } else if (arg[0] == '.' && arg[1] == 0) {
+ /* stay */
+ } else { /* relative: append */
+ d = cwd; while (*d) d++;
+ if (!(cwd[0] == '/' && cwd[1] == 0)) *d++ = '/';
+ s = arg; while (*s) *d++ = *s++;
+ *d = 0;
+ }
+}
+
+/* build the child command line "name\0arg1 arg2\0" at 0xA000 */
+static void set_cmdline(char **tok, unsigned char start, unsigned char end) {
+ char *p = ARGV0, *t;
+ unsigned char i;
+ t = tok[start];
+ while (*t) *p++ = *t++;
+ *p++ = 0; /* NUL after the command word */
+ for (i = start + 1; i < end; i++) {
+ if (i > start + 1) *p++ = ' ';
+ t = tok[i];
+ while (*t) *p++ = *t++;
+ }
+ *p = 0;
+}
+
+/* run tok[start..end): parse its own >/< redirects, honor forced in/out.
+ If bg, launch in the background (don't wait; leave fds to the child). */
+static void run(char **tok, unsigned char start, unsigned char end,
+ unsigned char infd, unsigned char outfd, unsigned char bg) {
+ unsigned char i, cmdend = end, myin = NOFD, myout = NOFD, id, pid;
+ for (i = start; i < end; i++) {
+ if (isop(tok[i], '>')) {
+ if (i + 1 < end) myout = open(tok[i + 1], O_WRITE);
+ if (i < cmdend) cmdend = i;
+ } else if (isop(tok[i], '<')) {
+ if (i + 1 < end) myin = open(tok[i + 1], O_READ);
+ if (i < cmdend) cmdend = i;
+ }
+ }
+ id = lookup(tok[start]);
+ if (id == NOFD) { puts(tok[start]); puts(": not found"); nl(); goto done; }
+ set_cmdline(tok, start, cmdend);
+ pid = fork();
+ if (pid == 0) {
+ if (myin != NOFD) setin(myin);
+ else if (infd != NOFD) setin(infd);
+ if (myout != NOFD) setout(myout);
+ else if (outfd != NOFD) setout(outfd);
+ exec(id);
+ }
+ if (bg) { putc('['); putu(pid); puts("]"); nl(); return; } /* background */
+ /* wait for THIS child; report any background jobs that finish meanwhile */
+ for (;;) {
+ unsigned char r = wait();
+ if (r == pid || r == NOFD) break;
+ putc('['); putu(r); puts(" done]"); nl();
+ }
+done:
+ if (myin != NOFD) close(myin);
+ if (myout != NOFD) close(myout);
+}
+
+/* run a multi-stage pipeline (a | b | c): wire stages together with real
+ kernel pipes, launch them ALL concurrently, then wait for them. */
+static void run_pipeline(char **tok, unsigned char nt) {
+ unsigned char pids[8], npid = 0;
+ unsigned char cfd[8], ncfd = 0; /* file-redirect fds; closed after all exit */
+ unsigned char start = 0, prev_r = NOFD;
+ for (;;) {
+ unsigned char end = start, last, i, cmdend, fin = NOFD, fout = NOFD;
+ unsigned char my_out = NOFD, next_r = NOFD, id, pid;
+ while (end < nt && !isop(tok[end], '|')) end++;
+ last = (end >= nt);
+ if (!last) { /* pipe from this stage to the next */
+ unsigned char pr = pipe();
+ if (pr != NOFD) { next_r = pr; my_out = pr + 1; }
+ }
+ cmdend = end; /* this stage's own >/< redirects */
+ for (i = start; i < end; i++) {
+ if (isop(tok[i], '>')) { if (i + 1 < end) fout = open(tok[i+1], O_WRITE); if (i < cmdend) cmdend = i; }
+ else if (isop(tok[i], '<')) { if (i + 1 < end) fin = open(tok[i+1], O_READ); if (i < cmdend) cmdend = i; }
+ }
+ id = lookup(tok[start]);
+ if (id == NOFD) {
+ puts(tok[start]); puts(": not found"); nl();
+ if (prev_r != NOFD) close(prev_r); /* orphaned pipe ends -> EOF/EPIPE */
+ if (my_out != NOFD) close(my_out);
+ if (fin != NOFD) close(fin);
+ if (fout != NOFD) close(fout);
+ } else {
+ set_cmdline(tok, start, cmdend);
+ pid = fork();
+ if (pid == 0) {
+ if (fin != NOFD) setin(fin); else if (prev_r != NOFD) setin(prev_r);
+ if (fout != NOFD) setout(fout); else if (my_out != NOFD) setout(my_out);
+ exec(id);
+ }
+ if (npid < 8) pids[npid++] = pid;
+ if (fin != NOFD && ncfd < 8) cfd[ncfd++] = fin;
+ if (fout != NOFD && ncfd < 8) cfd[ncfd++] = fout;
+ }
+ prev_r = next_r;
+ if (last) break;
+ start = end + 1;
+ }
+ { unsigned char got = 0; /* wait for all stages */
+ while (got < npid) {
+ unsigned char r = wait(), j, mine = 0;
+ if (r == NOFD) break;
+ for (j = 0; j < npid; j++) if (pids[j] == r) { mine = 1; break; }
+ if (mine) got++;
+ else { putc('['); putu(r); puts(" done]"); nl(); }
+ }
+ }
+ { unsigned char k; for (k = 0; k < ncfd; k++) close(cfd[k]); }
+}
+
+void main(void) {
+ char line[80];
+ char *tok[16];
+ char cwd[40];
+ cwd[0] = '/'; cwd[1] = 0;
+ /* bring up networking first: run the DHCP client and wait for it to finish
+ (got a lease, or gave up). Only then drop to the prompt. */
+ {
+ unsigned char d = lookup("dhcp");
+ if (d != NOFD) { if (fork() == 0) exec(d); else wait(); }
+ }
+ for (;;) {
+ unsigned char nt, i, pipepos, c, bg;
+ /* reap finished background jobs */
+ { unsigned char p; while ((p = reap())) { putc('['); putu(p); puts(" done]"); nl(); } }
+ puts(cwd); puts("# ");
+ /* read a line (echoing), stop at newline; exit on EOF */
+ i = 0;
+ for (;;) {
+ c = readc();
+ if (c == EOF) poweroff();
+ if (c == '\r' || c == '\n') { nl(); break; }
+ if (c == 8 || c == 127) { /* backspace: drop last char */
+ if (i > 0) { i--; putc(8); }
+ continue;
+ }
+ if (i < 79) { line[i++] = c; putc(c); }
+ }
+ line[i] = 0;
+ nt = tokenize(line, tok, 16);
+ if (nt == 0) continue;
+ if (iscmd(tok[0], 'c', 'd')) { /* cd builtin */
+ char *arg = (nt >= 2) ? tok[1] : "/";
+ if (chdir(arg) == 0) set_cwd(cwd, arg);
+ else { puts("cd: no such directory"); nl(); }
+ continue;
+ }
+ if ((tok[0][0] == 'e' && tok[0][1] == 'x') ||
+ (tok[0][0] == 'q' && tok[0][1] == 'u')) poweroff();
+ bg = 0;
+ if (isop(tok[nt - 1], '&')) { bg = 1; nt--; if (nt == 0) continue; }
+ pipepos = 0;
+ for (i = 1; i < nt; i++)
+ if (isop(tok[i], '|')) { pipepos = i; break; }
+ if (pipepos) run_pipeline(tok, nt);
+ else run(tok, 0, nt, NOFD, NOFD, bg);
+ }
+}