diff options
| author | user <user@clank> | 2026-09-22 16:02:55 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-22 16:02:55 +0200 |
| commit | 43e43b6dcec29e2b008d20135f5d02523b62fbcf (patch) | |
| tree | d647acd07596e290c70a9884c1dc5d8c20a31067 /usr/grep.c | |
| parent | net: three TCP receive bugs - caller corruption, data loss, early EOF (diff) | |
| download | gbos-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 '')
| -rw-r--r-- | usr/grep.c | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/usr/grep.c b/usr/grep.c new file mode 100644 index 0000000..3d2b325 --- /dev/null +++ b/usr/grep.c @@ -0,0 +1,99 @@ +#include "gbos.h" +/* grep [-v][-i][-n] PAT [file]: print the lines of a file (or stdin) that + * contain the substring PAT. Plain substring matching, no regex - '.' and '*' + * are literal characters. -v inverts the match, -i folds case, -n prefixes + * each hit with its line number. + * + * NB: no statics here. A program's _DATA area starts at $A000, which is also + * where the shell leaves the command line (usr/sh.c ARGV0), so statics would + * eat argv. Locals live on the task stack below $BFFF and are always safe. */ + +#define MAXLINE 80 /* two terminal rows; longer lines are clipped */ + +static char fold(char c, unsigned char ic) { + if (ic && c >= 'A' && c <= 'Z') return (char)(c + 32); + return c; +} + +/* is pat[] present anywhere in line[0..len)? */ +static unsigned char contains(char *line, unsigned char len, char *pat, unsigned char ic) { + unsigned char i, j; + if (!pat[0]) return 1; /* empty pattern matches all */ + for (i = 0; i < len; i++) { + for (j = 0;; j++) { + if (!pat[j]) return 1; /* ran out of pattern: a hit */ + if (i + j >= len) break; /* ran out of line: no hit here */ + if (fold(line[i + j], ic) != fold(pat[j], ic)) break; + } + } + return 0; +} + +void main(void) { + char *argv[8]; + char line[MAXLINE]; + unsigned char argc = argv_parse(argv, 8); + unsigned char inv = hasflag(argv, argc, 'v') ? 1 : 0; + unsigned char ic = hasflag(argv, argc, 'i') ? 1 : 0; + unsigned char num = hasflag(argv, argc, 'n') ? 1 : 0; + unsigned char i, len = 0, hits = 0, fd = NOFD; + unsigned int lineno = 0; + char *pat = 0, *fname = 0; + int ch; + + /* first non-flag word is the pattern, second (if any) is the file */ + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-') continue; + if (!pat) pat = argv[i]; + else { + fname = argv[i]; + break; + } + } + if (!pat) { + puts("usage: grep [-vin] PAT [file]"); + nl(); + sexit(2); + } + + if (fname) { + fd = open(fname, O_READ); + if (fd == EISDIR) { + puts("grep: is a directory"); + nl(); + sexit(2); + } + if (fd == NOFD) { + puts("grep: no such file"); + nl(); + sexit(2); + } + } + + for (;;) { + if (fd == NOFD) { + char c = readc(); + ch = (c == EOF) ? -1 : c; + } else ch = fgetc(fd); + if (ch == '\r') continue; /* CRLF: the LF ends the line */ + if (ch >= 0 && ch != '\n') { /* ordinary character */ + if (len < MAXLINE) line[len++] = (char)ch; + continue; + } + if (ch < 0 && len == 0) break; /* EOF, nothing pending */ + lineno++; + if (contains(line, len, pat, ic) != inv) { + if (num) { + putu(lineno); + putc(':'); + } + writes(line, len); /* one batched render */ + nl(); + hits = 1; + } + len = 0; + if (ch < 0) break; /* EOF after a final partial line */ + } + if (fd != NOFD) close(fd); + sexit(hits ? 0 : 1); /* grep(1) exit convention */ +} |
