aboutsummaryrefslogtreecommitdiffstats
path: root/usr/tail.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-24 12:48:51 +0200
committeruser <user@clank>2026-09-24 12:48:51 +0200
commit65b53de14dc6918db39b6ee6dc032f805182daad (patch)
tree6af7b52262321a3cbe11e1d472ca2938df2fc37e /usr/tail.c
parentsh: run scripts, and >> redirection (diff)
downloadgbos-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.c75
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);
+ }
+}