aboutsummaryrefslogtreecommitdiffstats
path: root/usr
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-24 12:48:37 +0200
committeruser <user@clank>2026-09-24 12:48:37 +0200
commitdc9dffb9069e32a1a0cf14883176544b234fc3e5 (patch)
tree4877daa5f01e98ac5d7e64d98c4144c94fa94a91 /usr
parentterm: write the palette attribute for every re-rendered tile, not just the cu... (diff)
downloadgbos-dc9dffb9069e32a1a0cf14883176544b234fc3e5.tar.gz
gbos-dc9dffb9069e32a1a0cf14883176544b234fc3e5.tar.xz
gbos-dc9dffb9069e32a1a0cf14883176544b234fc3e5.zip
kernel: stat/fsstat/isatty syscalls, and O_APPEND
Four things userland had no way to ask for: - SYS_STAT: type, link count, size and inode for a path. The trap needs HL for its dispatch table, so a call taking both a path and an output buffer passes a request block - the shape SYS_NET already established. usr/stat.h wraps it, header-only like sock.h, because the block must live in the caller's RAM. - SYS_FSSTAT: blocks/inodes, total and free. The bitmaps are already cached write-through in fs_bmbuf, so this is the popcount boot_fs already prints. - SYS_ISATTY: is this process's stdin the console? The shell needs it to tell an interactive session from a script. - O_APPEND: open a file positioned at EOF. There is no seek syscall, so this is the only way to add to a file - without it nothing running ON the Game Boy can build a multi-line file, shell scripts included. Together these back ls -l, df, and `>>`.
Diffstat (limited to 'usr')
-rw-r--r--usr/gbos.h8
-rw-r--r--usr/libc.s20
-rw-r--r--usr/stat.h36
3 files changed, 61 insertions, 3 deletions
diff --git a/usr/gbos.h b/usr/gbos.h
index 5fd897a..014aea4 100644
--- a/usr/gbos.h
+++ b/usr/gbos.h
@@ -20,6 +20,9 @@ unsigned char pollin(void); /* poll OSK for a typed char, or 0 */
unsigned char pollcon(void); /* poll console ring (link-injected
input, filled by net pumps), or 0 */
unsigned char sys_net(void *req); /* socket layer trap (see sock.h) */
+unsigned char sys_stat(void *req); /* stat trap (see stat.h) */
+unsigned char sys_fsstat(void *buf); /* free blocks/inodes (see stat.h) */
+unsigned char isatty(void); /* 1 if stdin is the console */
void gsleep(unsigned char units); /* delay units*(1/64)s (raw trap) */
void gticks(unsigned char buf[4]); /* 64 Hz uptime ticks, 32-bit LE */
void msleep(unsigned int ms); /* delay ~ms milliseconds */
@@ -42,8 +45,9 @@ unsigned char mkdir(const char *path);
unsigned char chdir(const char *path);
unsigned char opendir(const char *path);
void poweroff(void); /* clean emulator shutdown (saves) */
-#define O_READ 0
-#define O_WRITE 1
+#define O_READ 0
+#define O_WRITE 1
+#define O_APPEND 2 /* write at the end, keeping the existing contents */
#define NOFD 0xFF
#define EISDIR 0xFE /* open() failed: path is a directory */
diff --git a/usr/libc.s b/usr/libc.s
index fbca836..de087b6 100644
--- a/usr/libc.s
+++ b/usr/libc.s
@@ -1,7 +1,7 @@
.module libc
.globl _writes, _putc, _puts, _nl, _strlen, _readc, _sexit, _getpid, _getargs
.globl _open, _close, _fgetc, _fputc, _flist, _fremove, _pipe, _ssend, _srecv, _srecv_nb, _pollin, _pollcon
- .globl _sys_net, _gsleep, _gticks
+ .globl _sys_net, _gsleep, _gticks, _sys_stat, _sys_fsstat, _isatty
;; SDCC sm83 sdcccall(1): 1st arg -> A (byte) or DE (pointer); ret A / BC.
;; rst $30 (the trap) clobbers BC/DE/HL; SDCC treats them caller-saved, so
;; wrappers need not preserve them - but we compute cleanly regardless.
@@ -318,3 +318,21 @@ _opendir:
_poweroff:
.db 0xED
1$: jr 1$
+
+ ;; unsigned char sys_stat(void *req /*DE*/) -> A (see fs.asm sys_stat)
+_sys_stat:
+ ld c, #37 ; SYS_STAT
+ rst #0x30
+ ret
+
+ ;; unsigned char sys_fsstat(void *buf /*DE*/) -> A
+_sys_fsstat:
+ ld c, #38 ; SYS_FSSTAT
+ rst #0x30
+ ret
+
+ ;; unsigned char isatty(void) -> A (1 = stdin is the console)
+_isatty:
+ ld c, #39 ; SYS_ISATTY
+ rst #0x30
+ ret
diff --git a/usr/stat.h b/usr/stat.h
new file mode 100644
index 0000000..10acc54
--- /dev/null
+++ b/usr/stat.h
@@ -0,0 +1,36 @@
+#ifndef STAT_H
+#define STAT_H
+/* stat.h - file metadata and filesystem totals. Header-only (one copy per
+ program), like sock.h: the request block has to live in the caller's RAM.
+ The kernel trap needs HL for its dispatch table, so a call with both a path
+ and an output buffer passes a request block rather than two pointers. */
+#include "gbos.h"
+
+#define ST_FILE 1
+#define ST_DIR 2
+
+struct gstat {
+ unsigned char type; /* ST_FILE / ST_DIR */
+ unsigned char nlink;
+ unsigned int size; /* bytes */
+ unsigned char ino;
+};
+
+static unsigned char _sreq[7]; /* path ptr, then type/nlink/size/ino */
+
+/* -> 1 on success, 0 if the path does not exist */
+static unsigned char gstat(const char *path, struct gstat *st) {
+ _sreq[0] = (unsigned char)(unsigned int)path;
+ _sreq[1] = (unsigned char)(((unsigned int)path) >> 8);
+ if (sys_stat(_sreq) != 0) return 0;
+ st->type = _sreq[2];
+ st->nlink = _sreq[3];
+ st->size = (unsigned int)_sreq[4] | ((unsigned int)_sreq[5] << 8);
+ st->ino = _sreq[6];
+ return 1;
+}
+
+/* out[] = { blocks total, blocks free, inodes total, inodes free } */
+static void gfsstat(unsigned char *out) { sys_fsstat(out); }
+
+#endif