diff options
Diffstat (limited to 'usr')
| -rw-r--r-- | usr/args.c | 12 | ||||
| -rw-r--r-- | usr/blk.c | 13 | ||||
| -rwxr-xr-x | usr/build.sh | 21 | ||||
| -rw-r--r-- | usr/cat.c | 17 | ||||
| -rw-r--r-- | usr/chat.c | 57 | ||||
| -rw-r--r-- | usr/chello.c | 9 | ||||
| -rw-r--r-- | usr/count.c | 16 | ||||
| -rw-r--r-- | usr/crt0.s | 11 | ||||
| -rw-r--r-- | usr/dhcp.c | 72 | ||||
| -rw-r--r-- | usr/echo.c | 5 | ||||
| -rw-r--r-- | usr/false.c | 2 | ||||
| -rw-r--r-- | usr/gbos.h | 64 | ||||
| -rw-r--r-- | usr/head.c | 28 | ||||
| -rw-r--r-- | usr/irc.c | 422 | ||||
| -rw-r--r-- | usr/kill.c | 10 | ||||
| -rw-r--r-- | usr/libc.c | 67 | ||||
| -rw-r--r-- | usr/libc.s | 304 | ||||
| -rw-r--r-- | usr/ls.c | 10 | ||||
| -rw-r--r-- | usr/mkdir.c | 8 | ||||
| -rw-r--r-- | usr/necho.c | 13 | ||||
| -rw-r--r-- | usr/netd.c | 12 | ||||
| -rw-r--r-- | usr/netlib.h | 39 | ||||
| -rw-r--r-- | usr/nslookup.c | 23 | ||||
| -rw-r--r-- | usr/pid.c | 2 | ||||
| -rw-r--r-- | usr/ping.c | 61 | ||||
| -rw-r--r-- | usr/ps.c | 14 | ||||
| -rw-r--r-- | usr/ptest.c | 19 | ||||
| -rw-r--r-- | usr/resolve.h | 81 | ||||
| -rw-r--r-- | usr/rm.c | 7 | ||||
| -rw-r--r-- | usr/save.c | 19 | ||||
| -rw-r--r-- | usr/sh.c | 200 | ||||
| -rw-r--r-- | usr/sock.h | 71 | ||||
| -rw-r--r-- | usr/spin.c | 6 | ||||
| -rw-r--r-- | usr/true.c | 2 | ||||
| -rw-r--r-- | usr/uname.c | 2 | ||||
| -rw-r--r-- | usr/wc.c | 32 | ||||
| -rw-r--r-- | usr/wget.c | 43 |
37 files changed, 1794 insertions, 0 deletions
diff --git a/usr/args.c b/usr/args.c new file mode 100644 index 0000000..c6f4db3 --- /dev/null +++ b/usr/args.c @@ -0,0 +1,12 @@ +#include "gbos.h" +/* args: an argc/argv demo. Splits its argument string into argv[] and prints + argc plus each argument. Try: `args one two three` */ +void main(void) { + char *argv[8]; + unsigned char argc = argv_parse(argv, 8); + unsigned char i; + puts("argc="); putu(argc); nl(); + for (i = 0; i < argc; i++) { + puts("argv["); putu(i); puts("]="); puts(argv[i]); nl(); + } +} diff --git a/usr/blk.c b/usr/blk.c new file mode 100644 index 0000000..36839c0 --- /dev/null +++ b/usr/blk.c @@ -0,0 +1,13 @@ +#include "gbos.h" +/* blk: exercise the block device. blk fill <n> <v> | blk peek <n> */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + if (argc >= 3 && argv[0][0] == 'f') { + bfill(atou(argv[1]), atou(argv[2])); + } else if (argc >= 2 && argv[0][0] == 'p') { + putu(bpeek2(atou(argv[1]), argc>=3?atou(argv[2]):0)); nl(); + } else { + puts("usage: blk fill <n> <v> | blk peek <n>"); nl(); + } +} diff --git a/usr/build.sh b/usr/build.sh new file mode 100755 index 0000000..93815d2 --- /dev/null +++ b/usr/build.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Build a gbos C program into a self-contained 16 KiB ROM-bank image. +# SDCC native toolchain; crt0 linked FIRST so _start is the $4000 entry. +# usage: usr/build.sh <prog.c> <out.bin> (run from the repo root) +set -e +SRC="$1"; OUT="$2"; B=build/usr/obj +LIBDIR=/usr/share/sdcc/lib/sm83 +mkdir -p "$B" "$(dirname "$OUT")" +sdasgb -o "$B/crt0.rel" usr/crt0.s +sdasgb -o "$B/libc.rel" usr/libc.s +sdcc -msm83 -c -Iusr usr/libc.c -o "$B/libc_c.rel" +sdcc -msm83 -c -Iusr "$SRC" -o "$B/prog.rel" +# link: crt0 first (=> _start at 0x4000), then libc, program, and sm83 lib +sdldgb -n -m -w -j -i "$B/prog.ihx" \ + -b _CODE=0x4000 -b _DATA=0xa000 \ + -k "$LIBDIR" -l sm83 \ + "$B/crt0.rel" "$B/libc.rel" "$B/libc_c.rel" "$B/prog.rel" +makebin -Z -p "$B/prog.ihx" "$B/prog.bin" +dd if="$B/prog.bin" of="$OUT" bs=1024 skip=16 count=16 2>/dev/null +echo "wrote $OUT ($(wc -c < "$OUT") bytes); entry:" +grep -iE ' _start| _main' "$B/prog.map" | head -2 diff --git a/usr/cat.c b/usr/cat.c new file mode 100644 index 0000000..790cb88 --- /dev/null +++ b/usr/cat.c @@ -0,0 +1,17 @@ +#include "gbos.h" +/* cat [file]: print a file, or copy stdin to stdout until EOF. */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + if (argc > 0) { + unsigned char fd = open(argv[0], O_READ); + int ch; + if (fd == EISDIR) { puts("cat: is a directory"); nl(); return; } + if (fd == NOFD) { puts("cat: no such file"); nl(); return; } + while ((ch = fgetc(fd)) >= 0) putc((char)ch); + close(fd); + } else { + char c; + for (;;) { c = readc(); if (c == EOF) break; putc(c); } + } +} diff --git a/usr/chat.c b/usr/chat.c new file mode 100644 index 0000000..987b336 --- /dev/null +++ b/usr/chat.c @@ -0,0 +1,57 @@ +#include "netlib.h" +/* chat: a link-port chat client. Incoming SLIP frames from the host gateway + print as messages; SELECT brings up the on-screen keyboard, type a line and + press START to send it. A poll loop interleaves receiving and typing so + messages arrive while you're not mid-line. */ + +static char rxbuf[96]; +static unsigned char rxlen, rxin, rxesc; + +/* feed one non-blocking byte to the SLIP receiver; returns a frame length when + a whole frame has arrived (copied into out), else 0. */ +static unsigned char slip_poll(char *out) { + int b = srecv_nb(); + unsigned char c, i, n; + if (b < 0) return 0; + c = (unsigned char)b; + if (c == SLIP_END) { + if (rxin && rxlen) { + n = rxlen; + for (i = 0; i < n; i++) out[i] = rxbuf[i]; + rxlen = 0; rxin = 0; rxesc = 0; + return n; + } + rxin = 1; rxlen = 0; rxesc = 0; + return 0; + } + if (!rxin) return 0; + if (rxesc) { + if (c == SLIP_ESC_END) c = SLIP_END; + else if (c == SLIP_ESC_ESC) c = SLIP_ESC; + rxesc = 0; + } else if (c == SLIP_ESC) { rxesc = 1; return 0; } + if (rxlen < 96) rxbuf[rxlen++] = (char)c; + return 0; +} + +void main(void) { + char line[64], frame[96]; + unsigned char linelen = 0, n, i, c; + puts("chat: SELECT=keyboard START=send"); nl(); + for (;;) { + n = slip_poll(frame); /* incoming message? */ + if (n) { + for (i = 0; i < n; i++) putc(frame[i]); + nl(); + } + c = pollin(); /* typed a key? */ + if (c == '\n') { + if (linelen) { slip_send(line, linelen); linelen = 0; } + } else if (c == 8) { + if (linelen) { linelen--; putc(8); } + } else if (c) { + if (linelen < 63) { line[linelen++] = (char)c; putc((char)c); } + } + yield(); + } +} diff --git a/usr/chello.c b/usr/chello.c new file mode 100644 index 0000000..f570b61 --- /dev/null +++ b/usr/chello.c @@ -0,0 +1,9 @@ +#include "gbos.h" +void main(void) { + writes("hello from C on gbos!", 21); + nl(); + writes("my pid is ", 10); + /* print pid as a digit */ + { unsigned char c = getpid() + '0'; char d = c; writes(&d, 1); } + nl(); +} diff --git a/usr/count.c b/usr/count.c new file mode 100644 index 0000000..37cdcce --- /dev/null +++ b/usr/count.c @@ -0,0 +1,16 @@ +#include "gbos.h" +/* count [n]: print n numbered lines (default 25) to observe terminal scrolling + and the OSK scroll-region. Each line is tagged so you can tell which lines + are visible after scrolling. */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + unsigned char n = 25, i; + if (argc >= 1) n = atou(argv[0]); + for (i = 1; i <= n; i++) { + puts("line "); + putu(i); + puts(" ------------------"); + nl(); + } +} diff --git a/usr/crt0.s b/usr/crt0.s new file mode 100644 index 0000000..0d8371f --- /dev/null +++ b/usr/crt0.s @@ -0,0 +1,11 @@ + .module crt0 + .globl _main + ;; gbos C entry. exec() maps our ROM bank and jumps here ($4000). + ;; Call main(), then exit(0). (Args are fetched on demand via getargs().) + .area _CODE +_start:: + call _main + ld b, #0 + ld c, #0 ; SYS_EXIT + rst #0x30 +1$: jr 1$ diff --git a/usr/dhcp.c b/usr/dhcp.c new file mode 100644 index 0000000..e4186f4 --- /dev/null +++ b/usr/dhcp.c @@ -0,0 +1,72 @@ +#include "sock.h" +/* dhcp - a minimal DHCP client. DISCOVER -> OFFER -> REQUEST -> ACK over UDP + (kernel owns UDP; this owns BOOTP/DHCP), then hands the leased address to the + kernel with net_setip(). Run at boot before the shell. Times out gracefully + if there's no server (the shell then comes up unconfigured). */ + +static unsigned char pkt[288]; /* BOOTP is ~240B + options (frames > 255) */ +static unsigned char ip[4]; +static unsigned char bcast[4]; + +/* fill the fixed BOOTP request header + magic cookie; options start at 240 */ +static void bootp(void) { + unsigned int i; + for (i = 0; i < 240; i++) pkt[i] = 0; + pkt[0] = 1; pkt[1] = 1; pkt[2] = 6; /* op=request htype=eth hlen=6 */ + pkt[4] = 0x12; pkt[5] = 0x34; pkt[6] = 0x56; pkt[7] = 0x78; /* xid */ + pkt[10] = 0x80; /* flags: broadcast reply */ + pkt[28] = 0x02; pkt[29] = 0x47; pkt[30] = 0x42; pkt[33] = 0x01; /* client MAC */ + pkt[236] = 0x63; pkt[237] = 0x82; pkt[238] = 0x53; pkt[239] = 0x63; /* magic */ +} + +/* DHCP message type from the received packet (option 53), or 0 */ +static unsigned char mtype(void) { + unsigned int p = 240; + while (p < 280 && pkt[p] != 255) { + if (pkt[p] == 0) { p++; continue; } /* pad */ + if (pkt[p] == 53) return pkt[p + 2]; + p += 2 + pkt[p + 1]; + } + return 0; +} + +void main(void) { + unsigned char s, i, n; + unsigned int p; + bcast[0] = 255; bcast[1] = 255; bcast[2] = 255; bcast[3] = 255; + + puts("dhcp: discovering"); nl(); + s = net_socket(SOCK_UDP); + if (s == 0xFF) { puts("dhcp: no socket"); nl(); return; } + net_bind(s, 68); + net_connect(s, bcast, 67); + + /* DISCOVER -> OFFER (retry a few times for startup timing) */ + bootp(); + p = 240; + pkt[p++] = 53; pkt[p++] = 1; pkt[p++] = 1; /* DHCPDISCOVER */ + pkt[p++] = 255; + n = 0xFF; + for (i = 0; i < 3 && n == 0xFF; i++) { + net_send(s, pkt, (unsigned char)p); + n = net_recv(s, pkt, 255); + } + if (n == 0xFF || mtype() != 2) { puts("dhcp: no offer"); nl(); net_close(s); return; } + ip[0] = pkt[16]; ip[1] = pkt[17]; ip[2] = pkt[18]; ip[3] = pkt[19]; /* yiaddr */ + + /* REQUEST -> ACK */ + bootp(); + p = 240; + pkt[p++] = 53; pkt[p++] = 1; pkt[p++] = 3; /* DHCPREQUEST */ + pkt[p++] = 50; pkt[p++] = 4; /* requested IP */ + pkt[p++] = ip[0]; pkt[p++] = ip[1]; pkt[p++] = ip[2]; pkt[p++] = ip[3]; + pkt[p++] = 255; + net_send(s, pkt, (unsigned char)p); + n = net_recv(s, pkt, 255); + net_close(s); + if (n == 0xFF || mtype() != 5) { puts("dhcp: request failed"); nl(); return; } + ip[0] = pkt[16]; ip[1] = pkt[17]; ip[2] = pkt[18]; ip[3] = pkt[19]; + + net_setip(ip); + puts("dhcp: leased "); for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); } nl(); +} diff --git a/usr/echo.c b/usr/echo.c new file mode 100644 index 0000000..f8d5125 --- /dev/null +++ b/usr/echo.c @@ -0,0 +1,5 @@ +#include "gbos.h" +void main(void) { + puts(getargs()); + nl(); +} diff --git a/usr/false.c b/usr/false.c new file mode 100644 index 0000000..a124951 --- /dev/null +++ b/usr/false.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { sexit(1); } diff --git a/usr/gbos.h b/usr/gbos.h new file mode 100644 index 0000000..2350665 --- /dev/null +++ b/usr/gbos.h @@ -0,0 +1,64 @@ +/* gbos userland C API (thin wrappers over rst $30 syscalls; see c/libc.s) */ +#ifndef GBOS_H +#define GBOS_H +#define EOF 4 /* readc() returns this at end of input */ + +void writes(const char *buf, unsigned char len); +void putc(char c); +void puts(const char *s); /* write a NUL-terminated string */ +void nl(void); /* newline (CRLF) */ +unsigned char strlen(const char *s); +char *getargs(void); /* this program's argument string */ +char readc(void); /* one input byte, EOF at end */ +void sexit(unsigned char code); +unsigned char getpid(void); +unsigned char pipe(void); /* -> read fd; write end is read+1 */ +void ssend(unsigned char b); /* link-port: transmit one byte */ +unsigned char srecv(void); /* link-port: receive one byte (blocks) */ +int srecv_nb(void); /* link-port: receive, or -1 if none */ +unsigned char pollin(void); /* poll OSK for a typed char, or 0 */ +unsigned char pollcon(void); /* poll console ring (link-injected + input, filled by net pumps), or 0 */ +unsigned char sys_net(void *req); /* socket layer trap (see sock.h) */ +void gsleep(unsigned char units); /* delay units*(1/64)s (raw trap) */ +void msleep(unsigned int ms); /* delay ~ms milliseconds */ + +/* helpers (c/libc.c) */ +void putu(unsigned int n); /* print decimal */ +unsigned char atou(const char *s); /* parse decimal */ +unsigned char argv_parse(char **argv, unsigned char maxv); /* argc/argv */ +unsigned char hasflag(char **argv, unsigned char argc, char f); /* -x flag */ +char *optval(char **argv, unsigned char argc, char opt); /* -x VALUE */ + +/* filesystem (c/libc.s -> kernel syscalls) */ +unsigned char open(const char *name, unsigned char mode); /* 0=read 1=write */ +void close(unsigned char fd); +int fgetc(unsigned char fd); /* a byte, or -1 at EOF */ +void fputc(unsigned char fd, char ch); +unsigned char flist(unsigned char slot, char *namebuf); /* 1 if slot used */ +void fremove(const char *name); +unsigned char mkdir(const char *path); +unsigned char chdir(const char *path); +unsigned char opendir(const char *path); +void poweroff(void); /* clean emulator shutdown (saves) */ +#define O_READ 0 +#define O_WRITE 1 +#define NOFD 0xFF +#define EISDIR 0xFE /* open() failed: path is a directory */ + +/* process control (c/libc.s) */ +unsigned char fork(void); +void exec(unsigned char id); /* replace image; never returns */ +unsigned char wait(void); +void setin(unsigned char fd); /* redirect stdin ($FF=console) */ +void setout(unsigned char fd); /* redirect stdout ($FF=console) */ +unsigned char lookup(const char *name); /* command name -> program id */ +unsigned char kill(unsigned char pid); /* terminate a pid (1 is immortal)*/ +unsigned char psget(unsigned char slot, unsigned char *buf); /* {pid,state,prog}*/ +void progname(unsigned char id, char *buf); /* id -> name */ +void yield(void); +unsigned char reap(void); /* reap a finished bg job, or 0 */ +void bfill(unsigned char block, unsigned char byte); /* [debug] disk */ +unsigned char bpeek(unsigned char block); /* [debug] disk */ +unsigned char bpeek2(unsigned char block, unsigned char off); +#endif diff --git a/usr/head.c b/usr/head.c new file mode 100644 index 0000000..3298e54 --- /dev/null +++ b/usr/head.c @@ -0,0 +1,28 @@ +#include "gbos.h" +/* head [-n N] [N] [file]: first N lines of a file or stdin (default 10). */ +void main(void) { + char *argv[8]; + unsigned char argc = argv_parse(argv, 8); + char *nv = optval(argv, argc, 'n'); + unsigned char n = 10, lines = 0, i, fd = NOFD; + char *fname = 0; + int ch; + if (nv) n = atou(nv); + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-') continue; + if (nv == argv[i]) continue; + if (!nv && argv[i][0] >= '0' && argv[i][0] <= '9') { n = atou(argv[i]); continue; } + fname = argv[i]; break; + } + if (fname) { + fd = open(fname, O_READ); + if (fd > 3) { puts("head: no such file"); nl(); return; } + } + for (;;) { + if (fd > 3) { char c = readc(); if (c == EOF) break; ch = c; } + else { ch = fgetc(fd); if (ch < 0) break; } + if (lines < n) putc((char)ch); + if (ch == '\n') lines++; + } + if (fd != NOFD) close(fd); +} diff --git a/usr/irc.c b/usr/irc.c new file mode 100644 index 0000000..250738a --- /dev/null +++ b/usr/irc.c @@ -0,0 +1,422 @@ +#include "resolve.h" +/* irc HOST [NICK] - a bitchx/irssi-flavored IRC client on 40x18 columns. + The kernel owns TCP; this owns the protocol and the screen. + + UI: messages scroll above; the bottom line is an irssi-style input line + "[#chan] text_" redrawn in place with \r (the terminal has no cursor + addressing - \r + overprint is the entire trick). Long input scrolls + horizontally like irssi. Keys come from the OSK (SELECT toggles it) and + from the console ring (pollcon), so a hub can type into the client too. + + Formats, straight from the elders: + <nick> text channel message * nick text CTCP ACTION + <nick:#c> text msg to a non-current channel + *nick* text private msg >target< text outbound /msg + -nick- text notice -!- text server/status */ + +/* seg first + large: the arg string lives at 0xA000 and the first static + lands on it; args are copied out in main() before seg is ever written. */ +static unsigned char seg[220]; /* one TCP segment from the kernel */ +static char line[240]; /* server line accumulator */ +static unsigned char llen; +static char input[140]; /* the input line being typed */ +static unsigned char inlen; +static unsigned char drawn; /* chars currently drawn on input row */ +static char nick[16]; +static char target[32]; /* current channel/query, "" = none */ +static char sbuf[200]; /* outgoing IRC line builder */ +static unsigned char slen; +static unsigned char namebuf[64]; +static unsigned char dst[4]; +static unsigned char sock; +static unsigned char quitting; +/* A WRITABLE empty string for parse defaults. handle() writes terminators + into pfx (bang) and txt (strip_fmt); a read-only "" literal lives in ROM + ($4000-$5FFF), where a write is an MBC5 *RAM-bank-select* - it would swap + the whole $A000 data bank out from under every static (instant garbage). + A prefix-less or empty-trailing server line is enough to trigger it. */ +static char empty[1]; + +/* ---- tiny string helpers (SDCC libc is us) ------------------------------- */ +static unsigned char sceq(const char *a, const char *b) { /* case-insens. */ + unsigned char ca, cb; + for (;;) { + ca = (unsigned char)*a++; cb = (unsigned char)*b++; + if (ca >= 'a' && ca <= 'z') ca -= 32; + if (cb >= 'a' && cb <= 'z') cb -= 32; + if (ca != cb) return 0; + if (!ca) return 1; + } +} +static void scpy(char *d, const char *s, unsigned char max) { + unsigned char i; + for (i = 0; s[i] && i < (unsigned char)(max - 1); i++) d[i] = s[i]; + d[i] = 0; +} + +/* ---- outgoing IRC lines --------------------------------------------------- */ +static void sc(const char *s) { /* append to sbuf */ + while (*s && slen < 197) sbuf[slen++] = *s++; +} +static void sfin(void) { /* CRLF + send */ + sbuf[slen++] = '\r'; sbuf[slen++] = '\n'; + net_send(sock, sbuf, slen); + slen = 0; +} + +/* ---- the input line ------------------------------------------------------- */ +/* erase what's drawn on the bottom row, park at col 0 */ +static void row_clear(void) { + unsigned char i; + putc('\r'); + for (i = 0; i < drawn; i++) putc(' '); + putc('\r'); + drawn = 0; +} +/* redraw "[target] tail-of-input_"; keep total < 40 so it never wraps */ +static void row_draw(void) { + unsigned char pl, vis, i; + const char *t = target[0] ? target : "(status)"; + row_clear(); + putc('['); + for (pl = 0; t[pl] && pl < 12; pl++) putc(t[pl]); + putc(']'); putc(' '); + pl += 3; /* prompt width */ + vis = 39 - pl - 1; /* room for text (cursor gets 1) */ + i = (inlen > vis) ? (unsigned char)(inlen - vis) : 0; + for (; i < inlen; i++) putc(input[i]); + drawn = pl + ((inlen > vis) ? vis : inlen); +} +/* print a message line above the input line */ +static void say(const char *s) { + row_clear(); + puts(s); nl(); + row_draw(); +} +static void say2(const char *a, const char *b) { + row_clear(); + puts(a); puts(b); nl(); + row_draw(); +} +static void say3(const char *a, const char *b, const char *c) { + row_clear(); + puts(a); puts(b); puts(c); nl(); + row_draw(); +} + +/* ---- server line parsing -------------------------------------------------- */ +/* split "pfx!user@host" -> pfx nick only (in place) */ +static void bang(char *p) { + while (*p && *p != '!') p++; + *p = 0; +} +static unsigned char isnum(const char *c) { + return c[0] >= '0' && c[0] <= '9'; +} +static unsigned char ishex(char c) { + return (c>='0'&&c<='9')||(c>='a'&&c<='f')||(c>='A'&&c<='F'); +} + +/* Strip mIRC/IRC formatting in place - crucially their *arguments*, or a + color logo (a real MOTD!) leaves its \x03 color numbers on screen as digit + soup. \x03 = color (up to 2 digits[,up to 2 digits]); \x04 = hex color + (up to 6 hex[,6 hex]); \x02 bold \x0f reset \x11 mono \x16 reverse + \x1d italic \x1e strike \x1f underline are lone toggles. Leaves \x01 + (CTCP) intact for the PRIVMSG path. */ +static void strip_fmt(char *s) { + char *w = s, *r = s; + unsigned char c, d; + while (*r) { + c = (unsigned char)*r; + if (c == 3) { + r++; d = 0; + while (*r>='0'&&*r<='9'&&d<2) { r++; d++; } + if (*r==','&&r[1]>='0'&&r[1]<='9') { r++; d=0; while(*r>='0'&&*r<='9'&&d<2){r++;d++;} } + continue; + } + if (c == 4) { + r++; d = 0; + while (ishex(*r)&&d<6) { r++; d++; } + if (*r==','&&ishex(r[1])) { r++; d=0; while(ishex(*r)&&d<6){r++;d++;} } + continue; + } + if (c==2||c==15||c==17||c==22||c==29||c==30||c==31) { r++; continue; } + *w++ = *r++; + } + *w = 0; +} + +static void handle(char *l) { + char *pfx = empty, *cmd, *arg, *txt = empty; + char *p = l; + + if (*p == ':') { /* :prefix */ + pfx = ++p; + while (*p && *p != ' ') p++; + if (*p) *p++ = 0; + } + cmd = p; /* command */ + while (*p && *p != ' ') p++; + if (*p) *p++ = 0; + arg = p; /* params, then :trailing */ + while (*p) { + if (*p == ':' && (p == arg || p[-1] == 0)) { txt = p + 1; p[-1] = 0; break; } + if (*p == ' ') *p = 0; + p++; + } + /* arg = first param (NUL-joined list), txt = trailing */ + strip_fmt(txt); /* kill color/format codes + args */ + + if (sceq(cmd, "PING")) { + slen = 0; sc("PONG :"); sc(txt[0] ? txt : arg); sfin(); + say("-!- Ping? Pong!"); + return; + } + bang(pfx); + if (sceq(cmd, "PRIVMSG")) { + char *dest = arg; + unsigned char act = 0; + if (txt[0] == 1) { /* CTCP */ + char *e = txt + 1; + while (*e && *e != 1) e++; + *e = 0; txt++; + if (txt[0]=='A'&&txt[1]=='C'&&txt[2]=='T'&&txt[3]=='I'&&txt[4]=='O'&&txt[5]=='N'&&txt[6]==' ') { + act = 1; txt += 7; + } else if (txt[0]=='V') { /* VERSION */ + slen = 0; sc("NOTICE "); sc(pfx); sc(" :\001VERSION gbos:sl0pboy:GameBoyColor\001"); sfin(); + return; + } else return; + } + row_clear(); + if (act) { puts("* "); puts(pfx); putc(' '); } + else if (sceq(dest, nick)) { putc('*'); puts(pfx); puts("* "); } + else if (sceq(dest, target)) { putc('<'); puts(pfx); puts("> "); } + else { putc('<'); puts(pfx); putc(':'); puts(dest); puts("> "); } + puts(txt); nl(); + row_draw(); + return; + } + if (sceq(cmd, "NOTICE")) { + row_clear(); + putc('-'); puts(pfx[0] ? pfx : "server"); puts("- "); puts(txt); nl(); + row_draw(); + return; + } + if (sceq(cmd, "JOIN")) { + char *ch = txt[0] ? txt : arg; + if (sceq(pfx, nick)) { + scpy(target, ch, sizeof(target)); + say2("-!- now talking in ", target); + } else say3("-!- ", pfx, " has joined"); + return; + } + if (sceq(cmd, "PART")) { + if (sceq(pfx, nick)) { target[0] = 0; say("-!- you left"); } + else say3("-!- ", pfx, " has left"); + return; + } + if (sceq(cmd, "QUIT")) { say3("-!- ", pfx, " has quit"); return; } + if (sceq(cmd, "KICK")) { + char *ch = arg, *who = arg; + while (*who) who++; + who++; /* param 2: the victim */ + if (sceq(who, nick)) { target[0] = 0; say2("-!- you were kicked from ", ch); } + else say3("-!- ", who, " was kicked"); + return; + } + if (sceq(cmd, "NICK")) { + char *nn = txt[0] ? txt : arg; + if (sceq(pfx, nick)) scpy(nick, nn, sizeof(nick)); + row_clear(); + puts("-!- "); puts(pfx); puts(" is now known as "); puts(nn); nl(); + row_draw(); + return; + } + if (isnum(cmd)) { + if (cmd[0]=='4'&&cmd[1]=='3'&&cmd[2]=='3') { /* nick in use */ + unsigned char n = strlen(nick); + if (n < 14) { nick[n] = '_'; nick[n+1] = 0; } + slen = 0; sc("NICK "); sc(nick); sfin(); + say2("-!- nick in use, trying ", nick); + return; + } + if (cmd[0]=='3'&&cmd[1]=='3'&&cmd[2]=='2') { /* topic */ + say2("-!- topic: ", txt); + return; + } + if (cmd[0]=='3'&&cmd[1]=='5'&&cmd[2]=='3') { /* names */ + say2("-!- users: ", txt); + return; + } + if (cmd[1]=='3'&&cmd[0]=='3') return; /* 333 topic-by: skip */ + if (cmd[0]=='0'&&cmd[1]=='0'&&(cmd[2]=='4'||cmd[2]=='5')) return; /* MYINFO/ISUPPORT noise */ + if (txt[0]) say2("-!- ", txt); /* motd & friends */ + return; + } + /* anything else: show it raw-ish, bitchx would too */ + if (txt[0]) say3(cmd, " ", txt); +} + +/* accumulate TCP bytes into lines */ +static void feed(unsigned char *b, unsigned char n) { + unsigned char i; + for (i = 0; i < n; i++) { + char c = (char)b[i]; + if (c == '\r') continue; + if (c == '\n') { line[llen] = 0; if (llen) handle(line); llen = 0; } + else if (llen < 239) line[llen++] = c; + } +} + +/* ---- typed input ---------------------------------------------------------- */ +static void run_cmd(char *t) { + char *a = t; + while (*a && *a != ' ') a++; + if (*a) *a++ = 0; /* t = /cmd, a = rest */ + + if (sceq(t, "/join") || sceq(t, "/j")) { + if (!*a) { say("-!- join what?"); return; } + slen = 0; sc("JOIN "); sc(a); sfin(); + return; + } + if (sceq(t, "/part") || sceq(t, "/p")) { + slen = 0; sc("PART "); sc(*a ? a : target); sfin(); + return; + } + if (sceq(t, "/msg")) { + char *m = a; + while (*m && *m != ' ') m++; + if (*m) *m++ = 0; + if (!*a || !*m) { say("-!- /msg nick text"); return; } + slen = 0; sc("PRIVMSG "); sc(a); sc(" :"); sc(m); sfin(); + row_clear(); + putc('>'); puts(a); puts("< "); puts(m); nl(); + row_draw(); + return; + } + if (sceq(t, "/me")) { + if (!target[0]) { say("-!- no channel"); return; } + slen = 0; sc("PRIVMSG "); sc(target); sc(" :\001ACTION "); sc(a); sc("\001"); sfin(); + row_clear(); + puts("* "); puts(nick); putc(' '); puts(a); nl(); + row_draw(); + return; + } + if (sceq(t, "/nick")) { + if (!*a) { say("-!- /nick newnick"); return; } + slen = 0; sc("NICK "); sc(a); sfin(); + return; + } + if (sceq(t, "/quit")) { + slen = 0; sc("QUIT :"); sc(*a ? a : "sl0pboy out"); sfin(); + quitting = 1; + return; + } + if (sceq(t, "/raw") || sceq(t, "/quote")) { + slen = 0; sc(a); sfin(); + return; + } + say("-!- commands: /join /part /msg /me /nick /quit /raw"); +} + +static void submit(void) { + input[inlen] = 0; + if (!inlen) return; + if (input[0] == '/') { + inlen = 0; + run_cmd(input); + row_draw(); + return; + } + if (!target[0]) { inlen = 0; say("-!- join a channel first (/join #gb)"); return; } + slen = 0; sc("PRIVMSG "); sc(target); sc(" :"); sc(input); sfin(); + row_clear(); + putc('<'); puts(nick); puts("> "); puts(input); nl(); + inlen = 0; + row_draw(); +} + +static void key(unsigned char c) { + if (c == '\n' || c == '\r') { submit(); return; } + if (c == 8 || c == 127) { + if (inlen) { inlen--; row_draw(); } + return; + } + if (c < 32) return; + if (inlen < 139) { input[inlen++] = (char)c; row_draw(); } +} + +/* ---- main ----------------------------------------------------------------- */ +void main(void) { + char *argp; + unsigned char i, n, idle; + + /* gbos does not zero C statics (crt0 zeroes nothing; fork inherits the + parent's RAM bank) - initialize every piece of state explicitly */ + llen = inlen = drawn = slen = quitting = 0; + target[0] = 0; + empty[0] = 0; /* gbos doesn't zero BSS */ + + argp = getargs(); /* copy args before seg is used */ + for (i = 0; argp[i] && i < 63; i++) namebuf[i] = (unsigned char)argp[i]; + namebuf[i] = 0; + + /* split "HOST [NICK]" */ + scpy(nick, "sl0pboy", sizeof(nick)); + for (i = 0; namebuf[i]; i++) + if (namebuf[i] == ' ') { + namebuf[i] = 0; + if (namebuf[i+1]) scpy(nick, (char *)namebuf + i + 1, sizeof(nick)); + break; + } + if (!namebuf[0]) { + puts("usage: irc HOST [NICK] (port 6667)"); nl(); + puts(" then: /join #chan, SELECT = keyboard"); nl(); + sexit(1); + } + + if (!resolve((char *)namebuf, dst)) { + puts("irc: cannot resolve "); puts((char *)namebuf); nl(); sexit(1); + } + + sock = net_socket(SOCK_TCP); + if (sock == 0xFF) { puts("irc: no socket"); nl(); sexit(1); } + /* vary the local port per run (DIV timer noise): a fixed port can hit a + stale half-dead connection at the server after an unclean exit */ + net_bind(sock, 41000 + ((unsigned int)*(volatile unsigned char *)0xFF04 << 2) + getpid()); + puts("-!- connecting to "); puts((char *)namebuf); puts(":6667"); nl(); + if (net_connect(sock, dst, 6667) == 0xFF) { + puts("irc: connect failed"); nl(); net_close(sock); sexit(1); + } + + slen = 0; sc("NICK "); sc(nick); sfin(); + slen = 0; sc("USER gbos 0 * :sl0pboy on gbos"); sfin(); + puts("-!- registering as "); puts(nick); + puts(" (SELECT = keyboard)"); nl(); + row_draw(); + + idle = 0; + for (;;) { + n = net_recv_nb(sock, seg, (unsigned char)sizeof(seg)); + if (n == 0) { say("-!- disconnected"); break; } + if (n != 0xFE && n != 0xFF) feed(seg, n); /* render a segment if any */ + if (quitting) { say("-!- quit"); break; } + + /* Poll input EVERY pass, even mid-flood: otherwise a big MOTD (or a + * busy channel) starves the keyboard for its whole duration - the OSK + * won't even open, and keystrokes are dropped. That's what made + * '/join #sl0p right after connect' look broken. */ + i = pollin(); /* OSK */ + if (!i) i = pollcon(); /* hub-injected console bytes */ + if (i) key(i); + + if (n == 0xFE || n == 0xFF) { /* nothing received this pass */ + if (!i && ++idle >= 4) { msleep(20); idle = 4; } + } else { + idle = 0; /* actively receiving; don't sleep */ + } + } + net_close(sock); + row_clear(); + sexit(0); +} diff --git a/usr/kill.c b/usr/kill.c new file mode 100644 index 0000000..e524627 --- /dev/null +++ b/usr/kill.c @@ -0,0 +1,10 @@ +#include "gbos.h" +/* kill <pid>: terminate a process (pid 1 is immortal). */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + unsigned char pid; + if (argc < 1) { puts("usage: kill <pid>"); nl(); return; } + pid = atou(argv[0]); + if (kill(pid)) { puts("kill: no such pid (or immortal)"); nl(); } +} diff --git a/usr/libc.c b/usr/libc.c new file mode 100644 index 0000000..a48a64c --- /dev/null +++ b/usr/libc.c @@ -0,0 +1,67 @@ +/* gbos libc C helpers (compiled once, linked into every program). */ +#include "gbos.h" + +/* sleep for approximately 'ms' milliseconds (cooperative; other procs run). + The kernel counts in 1/64-second units (~15.6 ms), so we round ms to that. */ +void msleep(unsigned int ms) { + unsigned int u = ms >> 4; /* ~ms/15.6, close enough for a delay */ + if (u > 255) u = 255; + if (u == 0 && ms) u = 1; + gsleep((unsigned char)u); +} + +/* print an unsigned int in decimal */ +void putu(unsigned int n) { + char buf[5]; + unsigned char i = 0; + if (n == 0) { putc('0'); return; } + while (n) { buf[i++] = '0' + (n % 10); n = n / 10; } + while (i) putc(buf[--i]); +} + +/* parse a decimal number (stops at the first non-digit) */ +unsigned char atou(const char *s) { + unsigned char n = 0; + while (*s >= '0' && *s <= '9') { n = n * 10 + (*s - '0'); s++; } + return n; +} + +/* tokenize the argument string in place into argv[]; returns argc. + (replaces the spaces in the arg string with NULs) */ +unsigned char argv_parse(char **argv, unsigned char maxv) { + char *p = getargs(); + unsigned char argc = 0; + for (;;) { + while (*p == ' ') p++; + if (*p == 0) break; + if (argc < maxv) argv[argc] = p; + argc++; + while (*p != 0 && *p != ' ') p++; + if (*p == ' ') *p++ = 0; + } + return argc; +} + +/* return nonzero if single-char flag `f` appears (as -f or within -abc) */ +unsigned char hasflag(char **argv, unsigned char argc, char f) { + unsigned char i, j; + for (i = 0; i < argc; i++) { + if (argv[i][0] != '-') continue; + for (j = 1; argv[i][j]; j++) + if (argv[i][j] == f) return 1; + } + return 0; +} + +/* return the value for option -<opt> ("-n 5" or "-n5"), or 0 if absent */ +char *optval(char **argv, unsigned char argc, char opt) { + unsigned char i; + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] == opt) { + if (argv[i][2]) return &argv[i][2]; + if (i + 1 < argc) return argv[i+1]; + return 0; + } + } + return 0; +} diff --git a/usr/libc.s b/usr/libc.s new file mode 100644 index 0000000..5b176d9 --- /dev/null +++ b/usr/libc.s @@ -0,0 +1,304 @@ + .module libc + .globl _writes, _putc, _puts, _nl, _strlen, _readc, _sexit, _getpid, _getargs + .globl _open, _close, _fgetc, _fputc, _flist, _fremove, _pipe, _ssend, _srecv, _srecv_nb, _pollin, _pollcon + .globl _sys_net, _gsleep + ;; SDCC sm83 sdcccall(1): 1st arg -> A (byte) or DE (pointer); ret A / BC. + ;; rst $30 (the trap) clobbers BC/DE/HL; SDCC treats them caller-saved, so + ;; wrappers need not preserve them - but we compute cleanly regardless. + .area _CODE + + ;; void writes(const char *buf /*DE*/, unsigned char len /*A*/) +_writes: + ld b, a + ld c, #3 ; SYS_WRITE + rst #0x30 + ret + + ;; char *getargs(void) -> BC : the arg string (command line past word 1). + ;; The shell leaves "cmd\0args\0" at 0xA000, inherited by the child via fork. +_getargs: + ld hl, #0xa000 +1$: ld a, (hl) + inc hl + or a + jr nz, 1$ + ld b, h + ld c, l + ret + + ;; void putc(char c /*A*/) -> stdout via SYS_PUTC +_putc: + ld e, a + ld c, #16 ; SYS_PUTC + rst #0x30 + ret + + ;; void puts(const char *s /*DE*/) - write until NUL (no newline) +_puts: +1$: ld a, (de) + or a + ret z + push de + ld e, a + ld c, #16 ; SYS_PUTC + rst #0x30 + pop de + inc de + jr 1$ + + ;; void nl(void) - CRLF via stdout +_nl: + ld e, #0x0d + ld c, #16 ; SYS_PUTC + rst #0x30 + ld e, #0x0a + ld c, #16 + rst #0x30 + ret + + ;; unsigned char strlen(const char *s /*DE*/) -> A +_strlen: + ld c, #0 +1$: ld a, (de) + or a + jr z, 2$ + inc c + inc de + jr 1$ +2$: ld a, c + ret + + ;; char readc(void) -> A (4 = EOF) +_readc: + ld c, #2 ; SYS_READ + rst #0x30 + ret + + ;; void sexit(unsigned char code /*A*/) - never returns +_sexit: + ld b, a + ld c, #0 ; SYS_EXIT + rst #0x30 + ret + + ;; unsigned char getpid(void) -> A +_getpid: + ld c, #8 ; SYS_GETPID + rst #0x30 + ret + + ;; unsigned char pipe(void) -> A (read fd; write end is read+1) +_pipe: + ld c, #28 ; SYS_PIPE + rst #0x30 + ret + + ;; void ssend(unsigned char b /*A*/) -- link-port transmit +_ssend: + ld e, a + ld c, #29 ; SYS_SSEND + rst #0x30 + ret + + ;; unsigned char srecv(void) -> A -- link-port receive (blocks) +_srecv: + ld c, #30 ; SYS_SRECV + rst #0x30 + ret + + ;; int srecv_nb(void) -> byte, or -1 if none (non-blocking) +_srecv_nb: + ld c, #31 ; SYS_SRECVNB + rst #0x30 + jr c, 1$ + ld c, a + ld b, #0 + ret +1$: ld bc, #0xffff + ret + + ;; unsigned char sys_net(void *req /*DE*/) -> A -- socket layer trap +_sys_net: + ld c, #33 ; SYS_NET (DE already = &netreq) + rst #0x30 + ret + + ;; void gsleep(unsigned char units /*A*/) - delay units*(1/64)s +_gsleep: + ld b, a + ld c, #34 ; SYS_SLEEP + rst #0x30 + ret + + ;; unsigned char pollin(void) -> A (typed OSK char, or 0) +_pollin: + ld c, #32 ; SYS_POLLIN + rst #0x30 + ret + + ;; unsigned char pollcon(void) -> A (queued console byte, or 0) +_pollcon: + ld c, #35 ; SYS_POLLCON + rst #0x30 + ret + + ;; unsigned char open(const char *name /*DE*/, unsigned char mode /*A*/) -> A(fd) +_open: + ld b, a ; B = mode + ld c, #4 ; SYS_OPEN + rst #0x30 + ret + + ;; void close(unsigned char fd /*A*/) +_close: + ld b, a + ld c, #5 ; SYS_CLOSE + rst #0x30 + ret + + ;; int fgetc(unsigned char fd /*A*/) -> BC (byte, or -1 at EOF) +_fgetc: + ld b, a + ld c, #12 ; SYS_GETB + rst #0x30 + jr c, 1$ + ld c, a + ld b, #0 + ret +1$: ld bc, #0xffff + ret + + ;; void fputc(unsigned char fd /*A*/, char ch /*E*/) + ;; byte goes to the kernel in E (the trap clobbers A during dispatch) +_fputc: + ld b, a ; B = fd (ch stays in E) + ld c, #13 ; SYS_PUTB + rst #0x30 + ret + + ;; unsigned char flist(unsigned char slot /*A*/, char *namebuf /*DE*/) -> A +_flist: + ld b, a + ld c, #14 ; SYS_LIST + rst #0x30 + ret + + ;; void fremove(const char *name /*DE*/) +_fremove: + ld c, #15 ; SYS_REMOVE + rst #0x30 + ret + + .globl _fork, _exec, _wait, _setin, _setout, _lookup + ;; unsigned char fork(void) -> A (child pid in parent, 0 in child) +_fork: + ld c, #1 ; SYS_FORK + rst #0x30 + ret + ;; void exec(unsigned char id /*A*/) - never returns +_exec: + ld b, a + ld c, #6 ; SYS_EXEC + rst #0x30 + ret + ;; unsigned char wait(void) -> A (reaped pid) +_wait: + ld c, #7 ; SYS_WAIT + rst #0x30 + ret + ;; void setin(unsigned char fd /*A*/) +_setin: + ld b, a + ld c, #17 ; SYS_SETIN + rst #0x30 + ret + ;; void setout(unsigned char fd /*A*/) +_setout: + ld b, a + ld c, #18 ; SYS_SETOUT + rst #0x30 + ret + ;; unsigned char lookup(const char *name /*DE*/) -> A (prog id or $FF) +_lookup: + ld c, #19 ; SYS_LOOKUP + rst #0x30 + ret + + .globl _kill, _psget, _progname + ;; unsigned char kill(unsigned char pid /*A*/) -> A (0 ok, $FF fail) +_kill: + ld b, a + ld c, #9 ; SYS_KILL + rst #0x30 + ret + ;; unsigned char psget(unsigned char slot /*A*/, unsigned char *buf /*DE*/) -> A +_psget: + ld b, a + ld c, #20 ; SYS_PS + rst #0x30 + ret + ;; void progname(unsigned char id /*A*/, char *buf /*DE*/) +_progname: + ld b, a + ld c, #21 ; SYS_PROGNAME + rst #0x30 + ret + + .globl _yield, _reap + ;; void yield(void) +_yield: + ld c, #11 ; SYS_YIELD + rst #0x30 + ret + ;; unsigned char reap(void) -> A (reaped pid, or 0) +_reap: + ld c, #22 ; SYS_REAP + rst #0x30 + ret + + .globl _bfill, _bpeek, _bpeek2 + ;; void bfill(unsigned char block /*A*/, unsigned char byte /*E*/) +_bfill: + ld b, a + ld c, #23 ; SYS_BFILL + rst #0x30 + ret + ;; unsigned char bpeek(unsigned char block /*A*/) -> A +_bpeek: + ld b, a + ld e, #0 + ld c, #24 ; SYS_BPEEK + rst #0x30 + ret + ;; unsigned char bpeek2(unsigned char block /*A*/, unsigned char off /*E*/) -> A +_bpeek2: + ld b, a + ld c, #24 ; SYS_BPEEK (off already in E) + rst #0x30 + ret + + + .globl _mkdir, _chdir + ;; unsigned char mkdir(const char *path /*DE*/) -> A (0 ok / $FF) +_mkdir: + ld c, #25 ; SYS_MKDIR + rst #0x30 + ret + ;; unsigned char chdir(const char *path /*DE*/) -> A (0 ok / $FF) +_chdir: + ld c, #26 ; SYS_CHDIR + rst #0x30 + ret + + .globl _opendir + ;; unsigned char opendir(const char *path /*DE*/) -> A (0 ok / $FF) +_opendir: + ld c, #27 ; SYS_OPENDIR + rst #0x30 + ret + + .globl _poweroff + ;; void poweroff(void) - execute the emulator's clean-exit opcode ($ED) +_poweroff: + .db 0xED +1$: jr 1$ diff --git a/usr/ls.c b/usr/ls.c new file mode 100644 index 0000000..0e2e01f --- /dev/null +++ b/usr/ls.c @@ -0,0 +1,10 @@ +#include "gbos.h" +/* ls [dir]: list a directory (default: current). */ +void main(void) { + char *argv[4]; + char name[16]; + unsigned char argc = argv_parse(argv, 4); + unsigned char i; + if (opendir(argc > 0 ? argv[0] : ".")) { puts("ls: no such directory"); nl(); return; } + for (i = 0; flist(i, name); i++) { name[15] = 0; puts(name); nl(); } +} diff --git a/usr/mkdir.c b/usr/mkdir.c new file mode 100644 index 0000000..c907518 --- /dev/null +++ b/usr/mkdir.c @@ -0,0 +1,8 @@ +#include "gbos.h" +/* mkdir <path>: create a directory. */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + if (argc < 1) { puts("usage: mkdir <name>"); nl(); return; } + if (mkdir(argv[0])) { puts("mkdir: failed"); nl(); } +} diff --git a/usr/necho.c b/usr/necho.c new file mode 100644 index 0000000..99551d4 --- /dev/null +++ b/usr/necho.c @@ -0,0 +1,13 @@ +#include "netlib.h" +/* necho: send a SLIP-framed packet over the link port and print the reply. + Talks to a host gateway (tools/gateway.py) that echoes frames back. */ + +void main(void) { + char buf[64]; + unsigned char len; + puts("net: send 'hello from gbos'"); nl(); + slip_send("hello from gbos", 15); + len = slip_recv(buf, 63); + buf[len] = 0; + puts("net: recv '"); puts(buf); puts("'"); nl(); +} diff --git a/usr/netd.c b/usr/netd.c new file mode 100644 index 0000000..b07abd2 --- /dev/null +++ b/usr/netd.c @@ -0,0 +1,12 @@ +#include "sock.h" +/* netd - the network daemon. It just pumps the kernel RX path; the kernel's + socket layer owns SLIP/IP and auto-answers inbound ICMP echo requests, so + running netd makes the Game Boy respond to pings. No SLIP/IP here anymore. */ + +void main(void) { + puts("netd: up (10.0.0.2)"); nl(); + for (;;) { + net_poll(); /* drain RX, answer pings */ + yield(); + } +} diff --git a/usr/netlib.h b/usr/netlib.h new file mode 100644 index 0000000..ec98eac --- /dev/null +++ b/usr/netlib.h @@ -0,0 +1,39 @@ +#ifndef NETLIB_H +#define NETLIB_H +/* SLIP (RFC 1055) framing over the link port. Header-only so each program gets + its own copy (the C build links one translation unit per program). */ +#include "gbos.h" + +#define SLIP_END 0xC0 +#define SLIP_ESC 0xDB +#define SLIP_ESC_END 0xDC +#define SLIP_ESC_ESC 0xDD + +/* send a framed packet (len up to 255) */ +static void slip_send(const char *buf, unsigned char len) { + unsigned char i, c; + ssend(SLIP_END); + for (i = 0; i < len; i++) { + c = (unsigned char)buf[i]; + if (c == SLIP_END) { ssend(SLIP_ESC); ssend(SLIP_ESC_END); } + else if (c == SLIP_ESC) { ssend(SLIP_ESC); ssend(SLIP_ESC_ESC); } + else ssend(c); + } + ssend(SLIP_END); +} + +/* receive one framed packet into buf (max), returns its length */ +static unsigned char slip_recv(char *buf, unsigned char max) { + unsigned char len = 0, c; + for (;;) { + c = srecv(); + if (c == SLIP_END) { if (len) return len; continue; } + if (c == SLIP_ESC) { + c = srecv(); + if (c == SLIP_ESC_END) c = SLIP_END; + else if (c == SLIP_ESC_ESC) c = SLIP_ESC; + } + if (len < max) buf[len++] = (char)c; + } +} +#endif diff --git a/usr/nslookup.c b/usr/nslookup.c new file mode 100644 index 0000000..6a531b8 --- /dev/null +++ b/usr/nslookup.c @@ -0,0 +1,23 @@ +#include "resolve.h" +/* nslookup HOST - resolve a hostname to an IPv4 address via DNS over UDP. + All the DNS logic lives in resolve.h (shared with wget). */ + +static unsigned char namebuf[64]; /* first so it lands past the arg at 0xA000 */ +static unsigned char ip[4]; + +void main(void) { + char *host; + unsigned char i; + host = getargs(); + if (!host || !*host) { puts("usage: nslookup HOST"); nl(); sexit(1); } + for (i = 0; host[i] && i < 63; i++) namebuf[i] = (unsigned char)host[i]; + namebuf[i] = 0; + + if (resolve((char *)namebuf, ip)) { + puts((char *)namebuf); puts(" -> "); + for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); } + nl(); + } else { + puts("nslookup: cannot resolve "); puts((char *)namebuf); nl(); + } +} diff --git a/usr/pid.c b/usr/pid.c new file mode 100644 index 0000000..9e4e792 --- /dev/null +++ b/usr/pid.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { putu(getpid()); nl(); } diff --git a/usr/ping.c b/usr/ping.c new file mode 100644 index 0000000..8671318 --- /dev/null +++ b/usr/ping.c @@ -0,0 +1,61 @@ +#include "resolve.h" +/* ping HOST|IP - originate ICMP echo requests via the kernel socket layer. + Resolves a hostname via DNS first (resolve.h); default target 10.0.0.1. No + SLIP/IP/checksums here - the kernel owns all of that. */ + +/* rbuf is first + large so namebuf/dst land past the arg string at 0xA000 */ +static unsigned char rbuf[96]; +static unsigned char namebuf[64]; +static unsigned char dst[4]; +static unsigned char payload[4]; + +static void put_ip(unsigned char *ip) { + unsigned char i; + for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); } +} + +/* wait up to ~2s for an echo reply without blocking in the kernel: a lost + reply (some hosts rate-limit ICMP) must cost one "timeout" line, not + minutes wedged inside net_recv's pump-counted wait. */ +static unsigned char wait_reply(unsigned char s) { + unsigned char t, r; + for (t = 0; t < 40; t++) { + r = net_recv_nb(s, rbuf, (unsigned char)sizeof(rbuf)); + if (r != 0xFE) return r; /* reply (or error) */ + msleep(50); + } + return 0xFF; /* ~2s with no reply */ +} + +void main(void) { + char *arg; + unsigned char s, seq, got = 0, i; + arg = getargs(); + if (!arg || !*arg) { dst[0] = 10; dst[1] = 0; dst[2] = 0; dst[3] = 1; } + else { + for (i = 0; arg[i] && i < 63; i++) namebuf[i] = (unsigned char)arg[i]; + namebuf[i] = 0; + if (!resolve((char *)namebuf, dst)) { + puts("ping: cannot resolve "); puts((char *)namebuf); nl(); sexit(1); + } + } + + payload[0] = 'g'; payload[1] = 'b'; payload[2] = 'o'; payload[3] = 's'; + s = net_socket(SOCK_ICMP); + if (s == 0xFF) { puts("ping: no socket"); nl(); sexit(1); } + net_connect(s, dst, 0); + + puts("PING "); put_ip(dst); nl(); + for (seq = 1; seq <= 4; seq++) { + net_send(s, payload, 4); + if (wait_reply(s) != 0xFF) { + got++; + puts(" reply from "); put_ip(dst); puts(" seq="); putu(seq); nl(); + } else { + puts(" seq="); putu(seq); puts(" timeout"); nl(); + } + if (seq < 4) msleep(800); /* pace like real ping */ + } + puts("-- "); putu(got); puts("/4 received"); nl(); + net_close(s); +} diff --git a/usr/ps.c b/usr/ps.c new file mode 100644 index 0000000..7bfc0de --- /dev/null +++ b/usr/ps.c @@ -0,0 +1,14 @@ +#include "gbos.h" +/* ps: list live processes (pid, state, command). */ +void main(void) { + unsigned char i, buf[3]; + char name[9]; + for (i = 0; i < 8; i++) { + if (psget(i, buf)) { /* buf = {pid, state, prog} */ + putu(buf[0]); putc(' '); + putc("-RrBZ"[buf[1]]); putc(' '); + progname(buf[2], name); + puts(name); nl(); + } + } +} diff --git a/usr/ptest.c b/usr/ptest.c new file mode 100644 index 0000000..88b7d30 --- /dev/null +++ b/usr/ptest.c @@ -0,0 +1,19 @@ +#include "gbos.h" +/* ptest: exercise the kernel pipe in one process - write some bytes, read them + back, then close the write end and confirm the reader sees EOF. */ +void main(void) { + unsigned char pr = pipe(); + unsigned char pw = pr + 1; + int c; + if (pr == NOFD) { puts("pipe: none free"); nl(); return; } + fputc(pw, 'H'); fputc(pw, 'i'); fputc(pw, '!'); + c = fgetc(pr); putc(c); + c = fgetc(pr); putc(c); + c = fgetc(pr); putc(c); + nl(); + close(pw); /* last writer gone */ + c = fgetc(pr); + if (c < 0) puts("EOF"); else { puts("got "); putc(c); } + nl(); + close(pr); +} diff --git a/usr/resolve.h b/usr/resolve.h new file mode 100644 index 0000000..9aaccc1 --- /dev/null +++ b/usr/resolve.h @@ -0,0 +1,81 @@ +#ifndef RESOLVE_H +#define RESOLVE_H +/* resolve.h - turn a dotted-quad OR a hostname into an IPv4 address. Hostname + lookups go out as a DNS A query over a UDP socket to 1.1.1.1:53; the kernel + owns UDP, this owns DNS. Header-only (one copy per program). */ +#include "sock.h" + +static unsigned char _dnsbuf[200]; +static unsigned char _dnssrv[4]; + +/* parse "A.B.C.D" into out[4]; return 1 on success */ +static unsigned char parse_ip(char *s, unsigned char *out) { + unsigned char i, v; + for (i = 0; i < 4; i++) { + if (*s < '0' || *s > '9') return 0; + v = 0; + while (*s >= '0' && *s <= '9') { v = (unsigned char)(v * 10 + (*s - '0')); s++; } + out[i] = v; + if (i < 3) { if (*s != '.') return 0; s++; } + } + return 1; +} + +static unsigned char _dns_query(char *host) { + unsigned char i = 0, p = 12, lblpos, len; + _dnsbuf[0] = 0x12; _dnsbuf[1] = 0x34; _dnsbuf[2] = 0x01; _dnsbuf[3] = 0x00; + _dnsbuf[4] = 0; _dnsbuf[5] = 1; + _dnsbuf[6] = 0; _dnsbuf[7] = 0; _dnsbuf[8] = 0; _dnsbuf[9] = 0; _dnsbuf[10] = 0; _dnsbuf[11] = 0; + while (host[i]) { + lblpos = p++; len = 0; + while (host[i] && host[i] != '.') { _dnsbuf[p++] = host[i]; i++; len++; } + _dnsbuf[lblpos] = len; + if (host[i] == '.') i++; + } + _dnsbuf[p++] = 0; _dnsbuf[p++] = 0; _dnsbuf[p++] = 1; _dnsbuf[p++] = 0; _dnsbuf[p++] = 1; + return p; +} +static unsigned int _dns_skip(unsigned int p) { + for (;;) { + if (_dnsbuf[p] == 0) return p + 1; + if ((_dnsbuf[p] & 0xC0) == 0xC0) return p + 2; + p += _dnsbuf[p] + 1; + } +} +static unsigned char _dns_answer(unsigned char *ip) { + unsigned int p, a, an, type, rdlen; + an = ((unsigned int)_dnsbuf[6] << 8) | _dnsbuf[7]; + if (an == 0) return 0; + p = _dns_skip(12) + 4; + for (a = 0; a < an; a++) { + p = _dns_skip(p); + type = ((unsigned int)_dnsbuf[p] << 8) | _dnsbuf[p + 1]; + p += 8; + rdlen = ((unsigned int)_dnsbuf[p] << 8) | _dnsbuf[p + 1]; + p += 2; + if (type == 1 && rdlen == 4) { + ip[0] = _dnsbuf[p]; ip[1] = _dnsbuf[p + 1]; ip[2] = _dnsbuf[p + 2]; ip[3] = _dnsbuf[p + 3]; + return 1; + } + p += rdlen; + } + return 0; +} + +/* resolve a dotted-quad or hostname into ip[4]; return 1 on success */ +static unsigned char resolve(char *host, unsigned char *ip) { + unsigned char s, n; + if (parse_ip(host, ip)) return 1; /* already an address */ + _dnssrv[0] = 1; _dnssrv[1] = 1; _dnssrv[2] = 1; _dnssrv[3] = 1; + s = net_socket(SOCK_UDP); + if (s == 0xFF) return 0; + net_bind(s, 40000); + net_connect(s, _dnssrv, 53); + n = _dns_query(host); + net_send(s, _dnsbuf, n); + n = net_recv(s, _dnsbuf, 200); + net_close(s); + if (n == 0xFF) return 0; + return _dns_answer(ip); +} +#endif diff --git a/usr/rm.c b/usr/rm.c new file mode 100644 index 0000000..25f3004 --- /dev/null +++ b/usr/rm.c @@ -0,0 +1,7 @@ +#include "gbos.h" +/* rm <name>: delete a file. */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + if (argc > 0) fremove(argv[0]); +} diff --git a/usr/save.c b/usr/save.c new file mode 100644 index 0000000..1755fea --- /dev/null +++ b/usr/save.c @@ -0,0 +1,19 @@ +#include "gbos.h" +/* save <name>: read one line from stdin and store it as a file. */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + unsigned char fd; + char c; + if (argc < 1) { puts("usage: save <name>"); nl(); return; } + fd = open(argv[0], O_WRITE); + if (fd > 3) { puts("save: cannot create"); nl(); return; } + for (;;) { + c = readc(); + if (c == EOF) break; + putc(c); /* echo */ + fputc(fd, c); + if (c == '\n') break; + } + close(fd); +} 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); + } +} diff --git a/usr/sock.h b/usr/sock.h new file mode 100644 index 0000000..876e85c --- /dev/null +++ b/usr/sock.h @@ -0,0 +1,71 @@ +#ifndef SOCK_H +#define SOCK_H +/* gbos socket API - the kernel owns SLIP/IP/ICMP/UDP/TCP; programs just do + socket()/connect()/send()/recv()/close(). Header-only: one copy per program. + The netreq layout must match include/gbos.inc (NR_* / NET_* / SOCK_*). */ +#include "gbos.h" + +#define SOCK_ICMP 1 +#define SOCK_UDP 2 +#define SOCK_TCP 3 + +struct netreq { + unsigned char op; + unsigned char sock; + unsigned char *buf; + unsigned char len; + unsigned char ip[4]; + unsigned char port_hi, port_lo; +}; +static struct netreq _nr; + +/* -> socket id, or 0xFF if the table is full */ +static unsigned char net_socket(unsigned char type) { + _nr.op = 0; _nr.sock = type; + return sys_net(&_nr); +} +/* set the remote address/port for send()/recv() */ +static unsigned char net_connect(unsigned char s, const unsigned char *ip, unsigned int port) { + unsigned char i; + _nr.op = 1; _nr.sock = s; + for (i = 0; i < 4; i++) _nr.ip[i] = ip[i]; + _nr.port_hi = (unsigned char)(port >> 8); _nr.port_lo = (unsigned char)port; + return sys_net(&_nr); +} +/* send 'len' payload bytes to the connected peer -> 0 ok / 0xFF error */ +static unsigned char net_send(unsigned char s, void *buf, unsigned char len) { + _nr.op = 2; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = len; + return sys_net(&_nr); +} +/* receive up to 'max' payload bytes -> length, or 0xFF on timeout */ +static unsigned char net_recv(unsigned char s, void *buf, unsigned char max) { + _nr.op = 3; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max; + return sys_net(&_nr); +} +/* non-blocking recv: one RX pump -> length, 0xFE if nothing buffered yet, + 0 on TCP EOF. Loop it with msleep() to own your timeout (see ping). */ +static unsigned char net_recv_nb(unsigned char s, void *buf, unsigned char max) { + _nr.op = 8; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max; + return sys_net(&_nr); +} +/* set the local (source) port - needed so UDP replies demux back to us */ +static unsigned char net_bind(unsigned char s, unsigned int port) { + _nr.op = 5; _nr.sock = s; + _nr.port_hi = (unsigned char)(port >> 8); _nr.port_lo = (unsigned char)port; + return sys_net(&_nr); +} +static void net_close(unsigned char s) { + _nr.op = 4; _nr.sock = s; sys_net(&_nr); +} +/* pump the RX path once (answers inbound pings) without blocking */ +static void net_poll(void) { + _nr.op = 6; sys_net(&_nr); +} +/* set our IPv4 address (the DHCP client calls this once it has a lease) */ +static void net_setip(const unsigned char *ip) { + unsigned char i; + _nr.op = 7; + for (i = 0; i < 4; i++) _nr.ip[i] = ip[i]; + sys_net(&_nr); +} +#endif diff --git a/usr/spin.c b/usr/spin.c new file mode 100644 index 0000000..2a3d4e8 --- /dev/null +++ b/usr/spin.c @@ -0,0 +1,6 @@ +#include "gbos.h" +/* spin: a harmless long-running process that just yields forever. + Useful as a target for `ps` and `kill`. */ +void main(void) { + for (;;) yield(); +} diff --git a/usr/true.c b/usr/true.c new file mode 100644 index 0000000..4cf437e --- /dev/null +++ b/usr/true.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { sexit(0); } diff --git a/usr/uname.c b/usr/uname.c new file mode 100644 index 0000000..511501d --- /dev/null +++ b/usr/uname.c @@ -0,0 +1,2 @@ +#include "gbos.h" +void main(void) { puts("gbos sm83 (Game Boy Color)"); nl(); } diff --git a/usr/wc.c b/usr/wc.c new file mode 100644 index 0000000..4fa48ee --- /dev/null +++ b/usr/wc.c @@ -0,0 +1,32 @@ +#include "gbos.h" +/* wc [-l][-w][-c] [file]: count lines/words/chars of a file or stdin. */ +void main(void) { + char *argv[8]; + unsigned char argc = argv_parse(argv, 8); + unsigned char wl = hasflag(argv, argc, 'l'); + unsigned char ww = hasflag(argv, argc, 'w'); + unsigned char wch = hasflag(argv, argc, 'c'); + unsigned int lines = 0, words = 0, chars = 0; + unsigned char inword = 0, first = 1, i, fd = NOFD; + char *fname = 0; + int ch; + if (!wl && !ww && !wch) { wl = ww = wch = 1; } + for (i = 0; i < argc; i++) if (argv[i][0] != '-') { fname = argv[i]; break; } + if (fname) { + fd = open(fname, O_READ); + if (fd > 3) { puts("wc: no such file"); nl(); return; } + } + for (;;) { + if (fd > 3) { char c = readc(); if (c == EOF) break; ch = c; } + else { ch = fgetc(fd); if (ch < 0) break; } + chars++; + if (ch == '\n') lines++; + if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') inword = 0; + else if (!inword) { inword = 1; words++; } + } + if (fd != NOFD) close(fd); + if (wl) { putu(lines); first = 0; } + if (ww) { if (!first) putc(' '); putu(words); first = 0; } + if (wch) { if (!first) putc(' '); putu(chars); } + nl(); +} diff --git a/usr/wget.c b/usr/wget.c new file mode 100644 index 0000000..aafbae0 --- /dev/null +++ b/usr/wget.c @@ -0,0 +1,43 @@ +#include "resolve.h" +/* wget HOST|IP - fetch http://HOST/ over the kernel TCP socket layer and print + it. Resolves a hostname via DNS first (resolve.h), then connect()/send()/ + recv() over TCP - all the SLIP/IP/UDP/TCP lives in the kernel. */ + +static unsigned char buf[220]; /* first + large so namebuf/dst land arg-safe */ +static unsigned char namebuf[64]; +static unsigned char dst[4]; + +void main(void) { + char *arg, *r; + unsigned char s, n, i; + arg = getargs(); + if (!arg || !*arg) { puts("usage: wget HOST|IP"); nl(); sexit(1); } + for (i = 0; arg[i] && i < 63; i++) namebuf[i] = (unsigned char)arg[i]; + namebuf[i] = 0; + + if (!resolve((char *)namebuf, dst)) { + puts("wget: cannot resolve "); puts((char *)namebuf); nl(); sexit(1); + } + puts("connecting "); for (i = 0; i < 4; i++) { putu(dst[i]); if (i < 3) putc('.'); } nl(); + + s = net_socket(SOCK_TCP); + if (s == 0xFF) { puts("wget: no socket"); nl(); sexit(1); } + net_bind(s, 40001); + if (net_connect(s, dst, 80) == 0xFF) { puts("wget: connect failed"); nl(); net_close(s); sexit(1); } + + /* GET / HTTP/1.0 with the requested host as the Host header */ + r = "GET / HTTP/1.0\r\nHost: "; + n = 0; + while (r[n]) { buf[n] = (unsigned char)r[n]; n++; } + for (i = 0; namebuf[i]; i++) buf[n++] = namebuf[i]; + buf[n++] = '\r'; buf[n++] = '\n'; buf[n++] = '\r'; buf[n++] = '\n'; + net_send(s, buf, n); + + for (;;) { + n = net_recv(s, buf, (unsigned char)sizeof(buf)); + if (n == 0xFF) { nl(); puts("[timeout]"); nl(); break; } + if (n == 0) { nl(); puts("[eof]"); nl(); break; } + for (i = 0; i < n; i++) putc((char)buf[i]); + } + net_close(s); +} |
