diff options
Diffstat (limited to 'usr/sh.c')
| -rw-r--r-- | usr/sh.c | 200 |
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); + } +} |
