aboutsummaryrefslogtreecommitdiffstats
path: root/usr/nc.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 16:02:55 +0200
committeruser <user@clank>2026-09-22 16:02:55 +0200
commit43e43b6dcec29e2b008d20135f5d02523b62fbcf (patch)
treed647acd07596e290c70a9884c1dc5d8c20a31067 /usr/nc.c
parentnet: three TCP receive bugs - caller corruption, data loss, early EOF (diff)
downloadgbos-43e43b6dcec29e2b008d20135f5d02523b62fbcf.tar.gz
gbos-43e43b6dcec29e2b008d20135f5d02523b62fbcf.tar.xz
gbos-43e43b6dcec29e2b008d20135f5d02523b62fbcf.zip
usr: grep, more, cp, nc - and make pollcon pump the link
The shell has had pipes for a while with nothing to pipe *through*, and an 18-row screen with no way to stop output scrolling off it. - grep [-vin] PAT [file]: substring match (no regex), stdin or a file, grep(1)'s exit convention. The other end of the pipe, at last. - more [file]: pages 17 lines at a time, --More-- bar in white-on-blue, any key pages, q quits. Counts *screen* rows, so wrapped lines pay their real cost. Keys come from pollin/pollcon, not stdin, so `cat big | more` still has a keyboard. - cp SRC DST: the fs has rm/mkdir but no way to duplicate a file. mv is this plus rm (there is no rename). Rejects SRC == DST, which would otherwise truncate the source via O_WRITE before reading a byte. - nc [-s] HOST PORT: raw TCP. Interactive (START sends the line with a CRLF, /q quits) or -s to pump stdin and print the reply with its own wall-clock idle timeout, because the kernel's blocking recv counts pumps, not seconds. Generalizes what chat/irc hardcode: `nc towel.blinkenlights.nl 23` works. sys_pollcon now pumps the link before draining the console ring. The ring is filled *by* net_pump, so a program that never touches the network polled a ring nothing would ever fill: host-injected keys (gbtype/gbdemo) hung `more` forever, and the Game Boy stopped answering pings for as long as it ran. KGetc already pumps for this reason. New tools take banks 35-38. Locals over statics in all four: a program's _DATA starts at $A000, where the shell leaves the command line.
Diffstat (limited to 'usr/nc.c')
-rw-r--r--usr/nc.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/usr/nc.c b/usr/nc.c
new file mode 100644
index 0000000..023bedb
--- /dev/null
+++ b/usr/nc.c
@@ -0,0 +1,197 @@
+#include "resolve.h"
+/* nc [-s] HOST PORT - raw TCP, the way netcat does it. The kernel owns
+ * SLIP/IP/TCP; this is just a pump between the socket and the terminal.
+ *
+ * nc towel.blinkenlights.nl 23 telnet-ish: ASCIImation on a Game Boy
+ * nc 10.0.0.1 6667 talk to anything line-oriented
+ * echo GET / HTTP/1.0 > r (blank line needed by most servers)
+ * nc -s example.com 80 < r -s: send stdin, print the reply, exit
+ *
+ * Interactive mode: SELECT opens the on-screen keyboard, typing echoes
+ * locally, START sends the line with a CRLF. The line "/q" quits without
+ * being sent (there is no ^C here); a remote EOF quits too.
+ *
+ * Everything lives on the stack on purpose: resolve.h's DNS buffer is a
+ * static, so it sits at $A000 - right on top of the command line the shell
+ * left for us. Copy argv before the first DNS call, exactly like ping.c. */
+
+#define BUFSZ 208 /* one full segment: the most the kernel buffers */
+#define LINESZ 64
+
+static unsigned char split(char *s, char **tok, unsigned char max) {
+ unsigned char n = 0;
+ for (;;) {
+ while (*s == ' ') *s++ = 0;
+ if (!*s) break;
+ if (n < max) tok[n++] = s;
+ while (*s && *s != ' ') s++;
+ }
+ return n;
+}
+
+/* atou() is 8-bit; ports are not */
+static unsigned int atou16(const char *s) {
+ unsigned int n = 0;
+ while (*s >= '0' && *s <= '9') {
+ n = n * 10 + (unsigned char)(*s - '0');
+ s++;
+ }
+ return n;
+}
+
+void main(void) {
+ char args[LINESZ]; /* stack copy of the command line */
+ char line[LINESZ];
+ unsigned char buf[BUFSZ];
+ unsigned char dst[4];
+ char *tok[4];
+ char *host = 0, *portstr = 0;
+ unsigned int port;
+ unsigned char ntok, i, n, c, sock, sflag = 0, llen = 0, idle = 0;
+
+ { /* copy args off $A000 before resolve()'s statics land on them */
+ char *a = getargs();
+ for (i = 0; a[i] && i < LINESZ - 1; i++) args[i] = a[i];
+ args[i] = 0;
+ }
+
+ ntok = split(args, tok, 4);
+ for (i = 0; i < ntok; i++) {
+ if (tok[i][0] == '-') {
+ if (tok[i][1] == 's') sflag = 1;
+ continue;
+ }
+ if (!host) host = tok[i];
+ else if (!portstr) portstr = tok[i];
+ }
+ if (!host || !portstr) {
+ puts("usage: nc [-s] HOST PORT");
+ nl();
+ puts(" -s: send stdin, print reply, exit");
+ nl();
+ sexit(2);
+ }
+ port = atou16(portstr);
+ if (port == 0) {
+ puts("nc: bad port");
+ nl();
+ sexit(2);
+ }
+
+ if (!resolve(host, dst)) {
+ puts("nc: cannot resolve ");
+ puts(host);
+ nl();
+ sexit(1);
+ }
+
+ sock = net_socket(SOCK_TCP);
+ if (sock == 0xFF) {
+ puts("nc: no socket");
+ nl();
+ sexit(1);
+ }
+ /* vary the local port per run (DIV noise + pid): a fixed one can collide
+ with a half-dead connection still open at the server (see irc.c) */
+ net_bind(sock, 42000 + ((unsigned int)*(volatile unsigned char *)0xFF04 << 2) + getpid());
+
+ puts("-!- connecting ");
+ for (i = 0; i < 4; i++) {
+ putu(dst[i]);
+ if (i < 3) putc('.');
+ }
+ putc(':');
+ putu(port);
+ nl();
+ if (net_connect(sock, dst, port) == 0xFF) {
+ puts("nc: connect failed");
+ nl();
+ net_close(sock);
+ sexit(1);
+ }
+
+ if (sflag) { /* batch mode: stdin -> socket -> term */
+ n = 0;
+ for (;;) {
+ char ch = readc(); /* blocks forever on a console stdin! */
+ if (ch == EOF) break;
+ buf[n++] = (unsigned char)ch;
+ if (n == BUFSZ) {
+ net_send(sock, buf, n);
+ n = 0;
+ }
+ }
+ if (n) net_send(sock, buf, n);
+ /* Own the timeout out here (like ping): the kernel's blocking recv
+ counts pumps, not seconds, so a peer that just goes quiet would
+ wedge us for minutes instead of ending the transfer. */
+ idle = 0;
+ for (;;) {
+ n = net_recv_nb(sock, buf, BUFSZ);
+ if (n == 0) {
+ nl();
+ puts("[eof]");
+ nl();
+ break;
+ }
+ if (n == 0xFE || n == 0xFF) {
+ if (++idle >= 80) {
+ nl();
+ puts("[timeout]");
+ nl();
+ break;
+ }
+ msleep(50); /* ~4s of silence ends it */
+ continue;
+ }
+ writes((char *)buf, n);
+ idle = 0;
+ }
+ net_close(sock);
+ sexit(0);
+ }
+
+ puts("-!- connected (SELECT = keyboard, /q quits)");
+ nl();
+ for (;;) {
+ n = net_recv_nb(sock, buf, BUFSZ);
+ if (n == 0) {
+ puts("-!- closed by peer");
+ nl();
+ break;
+ }
+ if (n != 0xFE && n != 0xFF) writes((char *)buf, n); /* batched render */
+
+ /* poll input every pass, even mid-flood, or a chatty peer starves the
+ keyboard and dropped keystrokes look like a broken OSK (irc.c) */
+ c = pollin();
+ if (!c) c = pollcon();
+ if (c == '\n') {
+ nl();
+ if (llen == 2 && line[0] == '/' && line[1] == 'q') break;
+ line[llen] = '\r';
+ line[llen + 1] = '\n';
+ net_send(sock, line, (unsigned char)(llen + 2));
+ llen = 0;
+ } else if (c == 8) {
+ if (llen) {
+ llen--;
+ putc(8);
+ }
+ } else if (c) {
+ if (llen < LINESZ - 2) {
+ line[llen++] = (char)c;
+ putc((char)c);
+ }
+ }
+
+ if (n == 0xFE || n == 0xFF) { /* nothing arrived: back off a little */
+ if (!c && ++idle >= 4) {
+ msleep(20);
+ idle = 4;
+ }
+ } else idle = 0;
+ }
+ net_close(sock);
+ sexit(0);
+}