aboutsummaryrefslogtreecommitdiffstats
path: root/usr/xxd.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-24 14:15:46 +0200
committeruser <user@clank>2026-09-24 14:15:46 +0200
commit50a56b48f22cd31868403f1fde10cf8367344e2e (patch)
tree57c606182378dde67ce63d115ea07f98cc96b709 /usr/xxd.c
parentusr: xxd - hex dump a file or stdin (diff)
downloadgbos-50a56b48f22cd31868403f1fde10cf8367344e2e.tar.gz
gbos-50a56b48f22cd31868403f1fde10cf8367344e2e.tar.xz
gbos-50a56b48f22cd31868403f1fde10cf8367344e2e.zip
fs: mount the builtins at /bin
The commands only existed in the kernel's program table, so the only way to find out what the machine could do was to already know (or read the source). They are now a directory in the tree: /# ls / /# xxd -l 16 /bin/uname bin 0000: cd 6b 43 06 00 0e 00 f7 .kC..... readme 0008: 18 fe 47 0e 03 f7 c9 21 ..G....! /# ls -l /bin | head -2 - 1 16384 worker /bin is synthetic, not disk: dir_find resolves "bin" in the root to BIN_INO and names inside it through the NameTable, so `ls /bin` lists exactly what the shell can run - one table, one truth, no second list to keep in sync. The root listing carries "bin" as a synthetic first entry so it is discoverable by walking the tree. A program image is inode BIN_FILE|progid: it stats as a 16 KiB file (a program IS its ROM bank) and reads through rom_getb, which maps that bank, takes the byte, and maps the CALLER'S text bank back before returning - the caller is itself executing from $4000, so leaving the wrong bank mapped would return into another program's code. Verified byte-for-byte against the host's view of the blob. The tree is read-only: create/remove/write inside /bin all fail. xxd grows -l (stop after N bytes), because a program image is 2048 dump lines - more than a pipe's temp file can hold, let alone the screen. Two traps worth remembering, both found the hard way here: `and BIN_FILE` destroys A, so the inode must be reloaded before inode_ptr (missing it once made ls -l stat inode 0 and call every file an empty non-directory); and ls now copies its directory argument to the stack first, because stat()'s request block is a static living at $A000 - on top of the command line it was reading.
Diffstat (limited to 'usr/xxd.c')
-rw-r--r--usr/xxd.c22
1 files changed, 19 insertions, 3 deletions
diff --git a/usr/xxd.c b/usr/xxd.c
index 8e255ae..421c9c6 100644
--- a/usr/xxd.c
+++ b/usr/xxd.c
@@ -6,10 +6,22 @@
* line and make the dump unreadable. -c overrides it anyway.
*
* There is no seek syscall, so this streams from the start - which is also
- * why there is no -s (skip). */
+ * why there is no -s (skip). -l stops after N bytes, which is how you look at
+ * a program image: /bin/<prog> is a whole 16 KiB ROM bank, and dumping all of
+ * it is 2048 lines (and more than a pipe's temp file can hold). */
#define MAXC 16
+/* atou() is 8-bit; -l wants up to 16 KiB */
+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;
+}
+
static char hexd(unsigned char v) { return (char)(v < 10 ? '0' + v : 'a' + (v - 10)); }
static void hex2(unsigned char v) {
putc(hexd((unsigned char)(v >> 4)));
@@ -25,6 +37,8 @@ void main(void) {
unsigned char buf[MAXC];
unsigned char argc = argv_parse(argv, 8);
char *cv = optval(argv, argc, 'c');
+ char *lv = optval(argv, argc, 'l');
+ unsigned int limit = 0; /* 0 = no limit */
unsigned char cols = 8, fd = NOFD, n = 0, i;
unsigned int off = 0;
char *fname = 0;
@@ -34,9 +48,10 @@ void main(void) {
cols = atou(cv);
if (cols == 0 || cols > MAXC) cols = 8;
}
+ if (lv) limit = atou16(lv);
for (i = 0; i < argc; i++) {
if (argv[i][0] == '-') continue;
- if (cv == argv[i]) continue;
+ if (cv == argv[i] || lv == argv[i]) continue;
fname = argv[i];
break;
}
@@ -55,7 +70,8 @@ void main(void) {
}
for (;;) {
- if (fd == NOFD) {
+ if (limit && off + n >= limit) ch = -1; /* -l: stop after N bytes */
+ else if (fd == NOFD) {
char c = readc();
ch = (c == EOF) ? -1 : c;
} else ch = fgetc(fd);