aboutsummaryrefslogtreecommitdiffstats
path: root/src
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 /src
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 'src')
-rw-r--r--src/fs.asm128
-rw-r--r--src/kdata.asm1
-rw-r--r--src/syscall.asm16
3 files changed, 144 insertions, 1 deletions
diff --git a/src/fs.asm b/src/fs.asm
index 9aeaeb7..30dfdd6 100644
--- a/src/fs.asm
+++ b/src/fs.asm
@@ -698,7 +698,7 @@ sys_open::
.write
ld a, [wFsInode]
or a
- jr nz, .trunc
+ jr nz, .exists
; --- create ---
call alloc_inode
or a
@@ -724,6 +724,10 @@ sys_open::
or a
jr z, .fail
jr .allocof
+.exists
+ ld a, [wFsMode]
+ cp O_APPEND
+ jr z, .allocof ; append: keep the data, seek to the end below
.trunc
ld a, [wFsInode]
call file_truncate
@@ -755,6 +759,34 @@ sys_open::
xor a
ld [hl+], a ; pos lo
ld [hl], a ; pos hi
+ ld a, [wFsMode]
+ cp O_APPEND
+ jr nz, .ofdone
+ ; append: start writing at the end. With no seek syscall this is the only
+ ; way to add to a file - and without it nothing on the Game Boy can build
+ ; a multi-line file, shell scripts included.
+ push bc
+ dec hl ; HL = &pos lo
+ push hl
+ ld a, [wFsInode]
+ call inode_ptr
+ ld a, l
+ add I_SIZE
+ ld l, a
+ ld a, h
+ adc 0
+ ld h, a
+ ld a, [hl+]
+ ld c, a
+ ld a, [hl]
+ ld b, a ; BC = size
+ pop hl
+ ld a, c
+ ld [hl+], a
+ ld a, b
+ ld [hl], a ; pos = size
+ pop bc
+.ofdone
ld a, c
ret
@@ -1162,6 +1194,100 @@ sys_chdir::
ret
; =============================================================================
+; sys_stat(DE = &statreq) -> A = 0 ok / $FF no such path
+;
+; req[SR_PATH] is a pointer to the path; the type, link count, size and inode
+; come back in the same block. (The syscall trap needs HL for its dispatch
+; table, so a call with both a path and an output buffer uses a request block -
+; the shape SYS_NET already established.)
+; =============================================================================
+sys_stat::
+ ld a, e
+ ld [wFsStatReq], a
+ ld a, d
+ ld [wFsStatReq+1], a
+ ld h, d
+ ld l, e
+ ld a, [hl+] ; DE = req.path
+ ld e, a
+ ld a, [hl]
+ ld d, a
+ call copy_path
+ call resolve ; A = inode or 0 (handles "/", "." and paths)
+ or a
+ jr z, .fail
+ ld [wFsInode], a
+ call inode_ptr ; HL = &inode: type, nlink, size lo, size hi
+ ld a, [wFsStatReq]
+ add SR_TYPE
+ ld e, a
+ ld a, [wFsStatReq+1]
+ adc 0
+ ld d, a ; DE = &req[SR_TYPE]
+ ld b, 4
+.cpst
+ ld a, [hl+]
+ ld [de], a
+ inc de
+ dec b
+ jr nz, .cpst
+ ld a, [wFsInode]
+ ld [de], a ; SR_INO follows SR_SIZE
+ xor a
+ ret
+.fail
+ ld a, $FF
+ ret
+
+; =============================================================================
+; sys_fsstat(DE = buf[4]) -> buf = { blocks total, blocks free,
+; inodes total, inodes free }
+; The bitmaps are already cached in fs_bmbuf (write-through), so this is just
+; a popcount - the same one boot_fs prints at startup.
+; =============================================================================
+sys_fsstat::
+ ld a, FS_NBLOCKS
+ ld [de], a
+ inc de
+ ld hl, fs_bmbuf + BM_BLOCKS
+ ld b, FS_NBLOCKS / 8
+ call bm_free
+ ld a, c
+ ld [de], a
+ inc de
+ ld a, NINODES
+ ld [de], a
+ inc de
+ ld hl, fs_bmbuf + BM_INODES
+ ld b, NINODES / 8
+ call bm_free
+ ld a, c
+ ld [de], a
+ xor a
+ ret
+
+; bm_free - HL = bitmap, B = bytes -> C = count of CLEAR bits (= free units).
+; Preserves DE (the caller is walking its output buffer with it).
+bm_free:
+ ld c, 0
+.byte
+ ld a, [hl+]
+ cpl ; set bits = free
+ push de
+ ld e, 8
+.bit
+ rra
+ jr nc, .used
+ inc c
+.used
+ dec e
+ jr nz, .bit
+ pop de
+ dec b
+ jr nz, .byte
+ ret
+
+; =============================================================================
; sys_opendir(DE = path) -> A = 0 ok / $FF ; sets the directory ls enumerates.
; Empty path or "." means the current directory.
; =============================================================================
diff --git a/src/kdata.asm b/src/kdata.asm
index 7a6aab9..b6c2b68 100644
--- a/src/kdata.asm
+++ b/src/kdata.asm
@@ -53,6 +53,7 @@ wFsDir:: DS 1 ; directory inode for dir_* ops
wFsDirBlk:: DS 1 ; that directory's data block (for write-back)
wFsCur:: DS 1 ; current inode during path resolution
wFsPath:: DS 40 ; kernel-side copy of a path argument
+wFsStatReq:: DS 2 ; sys_stat: the caller's request block
wFsSlashPtr::DS 2 ; last '/' seen while splitting a path
wListDir:: DS 1 ; directory sys_list enumerates
wKChar:: DS 1 ; scratch for KPutc
diff --git a/src/syscall.asm b/src/syscall.asm
index 0ed9f85..9e5ca33 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -71,6 +71,9 @@ SyscallTable:
dw sys_sleep ; 34 SLEEP
dw sys_pollcon ; 35 POLLCON
dw sys_uptime ; 36 UPTIME
+ dw sys_stat ; 37 STAT
+ dw sys_fsstat ; 38 FSSTAT
+ dw sys_isatty ; 39 ISATTY
; -----------------------------------------------------------------------------
sys_nosys:
@@ -332,6 +335,19 @@ sys_sleep:
ret
; -----------------------------------------------------------------------------
+; sys_isatty() -> A = 1 if this process's stdin is the console, 0 if it is a
+; file or a pipe. The shell uses it to tell an interactive session from a
+; script (`sh < file`): no prompt, no echo, and EOF is just the end.
+; -----------------------------------------------------------------------------
+sys_isatty:
+ call CurStdin
+ cp STD_CONSOLE
+ ld a, 0
+ ret nz
+ inc a
+ ret
+
+; -----------------------------------------------------------------------------
; sys_exit(code=B) -- become a zombie awaiting reap; never returns.
; - store exit code, set PS_ZOMBIE
; - reparent any children to init (so they can still be waited on)