diff options
| author | user <user@clank> | 2026-09-24 12:48:51 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-24 12:48:51 +0200 |
| commit | 65b53de14dc6918db39b6ee6dc032f805182daad (patch) | |
| tree | 6af7b52262321a3cbe11e1d472ca2938df2fc37e /usr/tail.c | |
| parent | sh: run scripts, and >> redirection (diff) | |
| download | gbos-65b53de14dc6918db39b6ee6dc032f805182daad.tar.gz gbos-65b53de14dc6918db39b6ee6dc032f805182daad.tar.xz gbos-65b53de14dc6918db39b6ee6dc032f805182daad.zip | |
usr: ls -l, df, mv, tail, tee, sleep, help, poweroff
Filling in the core utilities the box was missing:
- ls -l: type, links and size, now that stat() exists. The screen is 40
columns, so it is "d 1 256 name", not the GNU sprawl. Sizes were
previously unknowable except one file at a time with wc -c.
- df: blocks and inodes, used and free, on the 32 KiB cart disk.
- mv: no rename syscall, so it is cp + rm - and the copy has to finish before
the source goes, or a failure loses the file.
- tail [-n N]: no seek syscall, so it streams the input through a ring of N
lines rather than jumping to the end. N is capped by that ring.
- tee: the missing half of a pipeline - `cat big | tee copy | wc -l`.
- sleep: the thing every script needs; the kernel counts 1/64 s ticks.
- help: lists every command the shell knows, walked out of the kernel's own
name table. The box previously had no way to tell you what it could do.
- poweroff: the only safe way to stop a gbos that has data on it. The cart's
battery RAM is written when the emulator exits cleanly, so killing it loses
the whole filesystem - it comes back "formatted". Found while testing /rc.
Diffstat (limited to 'usr/tail.c')
| -rw-r--r-- | usr/tail.c | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/usr/tail.c b/usr/tail.c new file mode 100644 index 0000000..1fd5304 --- /dev/null +++ b/usr/tail.c @@ -0,0 +1,75 @@ +#include "gbos.h" +/* tail [-n N] [N] [file]: last N lines (default 10) of a file or stdin. + * + * There is no seek syscall, so this cannot jump to the end: it streams the + * whole input through a ring of N lines and prints the ring at EOF. The ring + * is the memory budget - N lines of up to LINEW chars each - so N is capped. */ + +#define MAXN 12 /* 12 * 42 = 504 bytes of ring, fits a task */ +#define LINEW 42 + +void main(void) { + char *argv[8]; + char ring[MAXN][LINEW]; + unsigned char len[MAXN]; + unsigned char argc = argv_parse(argv, 8); + char *nv = optval(argv, argc, 'n'); + unsigned char n = 10, i, cur = 0, filled = 0, fd = NOFD, l = 0; + 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 (n == 0) return; + if (n > MAXN) n = MAXN; + + if (fname) { + fd = open(fname, O_READ); + if (fd == EISDIR) { + puts("tail: is a directory"); + nl(); + sexit(2); + } + if (fd == NOFD) { + puts("tail: no such file"); + nl(); + sexit(2); + } + } + for (i = 0; i < MAXN; i++) len[i] = 0; + + for (;;) { + if (fd == NOFD) { + char c = readc(); + ch = (c == EOF) ? -1 : c; + } else ch = fgetc(fd); + if (ch == '\r') continue; + if (ch >= 0 && ch != '\n') { + if (len[cur] < LINEW) ring[cur][len[cur]++] = (char)ch; + continue; + } + if (ch < 0 && len[cur] == 0) break; /* EOF, nothing pending */ + cur = (unsigned char)((cur + 1) % n); /* keep only the last n */ + if (filled < n) filled++; + len[cur] = 0; + if (ch < 0) break; + } + if (fd != NOFD) close(fd); + + /* oldest kept line first: cur points at the (empty) next slot */ + i = (unsigned char)((cur + n - filled) % n); + while (filled--) { + writes(ring[i], len[i]); + nl(); + i = (unsigned char)((i + 1) % n); + } +} |
