aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-16 12:49:19 +0200
committeruser <user@clank>2026-07-16 12:49:19 +0200
commit5fefd233769a56caefdb2ffecaa9fb8b9ec6db86 (patch)
tree99f5b285a45c7b3792bb64e79fe58f70a6d01cab /src
parentfs rewrite stage 2: inodes + root directory (block-based, persistent) (diff)
downloadgbos-5fefd233769a56caefdb2ffecaa9fb8b9ec6db86.tar.gz
gbos-5fefd233769a56caefdb2ffecaa9fb8b9ec6db86.tar.xz
gbos-5fefd233769a56caefdb2ffecaa9fb8b9ec6db86.zip
fs rewrite stage 3: nested directories, paths, cwd (cd/mkdir/ls)
- directories generalized: dir_find/dir_add/dir_remove take any dir inode; every dir carries '.'/'..'. Path resolution (resolve / resolve_parent) walks absolute or cwd-relative paths; open/mkdir/chdir/remove/opendir take paths. - per-process cwd: PROC_CWD in the PCB (root by default, inherited on fork); sys_chdir sets it, sys_opendir points ls at any directory. - shell: 'cd' builtin + cwd-aware prompt; 'mkdir'/'ls <dir>' tools; 'exit'/'quit' and EOF call poweroff() (clean shutdown via the $ED opcode -> reliable save). - three HL/buffer-clobber bugs fixed along the way: resolve didn't preserve the path cursor across dir_find; cur_cwd clobbered HL; resolve_parent set the final name before resolve() overwrote wFsNameBuf (now copied after). - verified: nested mkdir/cd/save, multi-level paths (cat docs/sub/b), and the whole tree persists across a reboot. - README: document directories + the poweroff opcode.
Diffstat (limited to '')
-rw-r--r--src/fs.asm369
-rw-r--r--src/kdata.asm8
-rw-r--r--src/proc.asm9
-rw-r--r--src/programs.asm7
-rw-r--r--src/syscall.asm3
5 files changed, 360 insertions, 36 deletions
diff --git a/src/fs.asm b/src/fs.asm
index 7e59f3e..d543b0f 100644
--- a/src/fs.asm
+++ b/src/fs.asm
@@ -221,23 +221,28 @@ entry_ptr:
ld l, a
ret ; (fs_dirbuf is page-aligned, no carry)
-; load_rootdir - read the root directory's data block into fs_dirbuf
-load_rootdir:
- ld a, [fs_inobuf + 16 + I_BLOCKS] ; root inode (1) blocks[0]
+; dir_load - read directory wFsDir's block into fs_dirbuf (single-block dirs).
+dir_load:
+ ld a, [wFsDir]
+ call inode_ptr
+ ld a, l
+ add I_BLOCKS
+ ld l, a
+ ld a, [hl] ; blocks[0]
+ ld [wFsDirBlk], a
ld hl, fs_dirbuf
jp read_block
-; write_rootdir - write fs_dirbuf back to the root directory's data block
-write_rootdir:
- ld a, [fs_inobuf + 16 + I_BLOCKS]
+dir_write:
+ ld a, [wFsDirBlk]
ld hl, fs_dirbuf
jp write_block
; -----------------------------------------------------------------------------
-; dir_find - name in wFsNameBuf -> A = inode (0 if not found)
+; dir_find - dir inode in wFsDir, name in wFsNameBuf -> A = inode (0 if none)
; -----------------------------------------------------------------------------
dir_find::
- call load_rootdir
+ call dir_load
ld c, 0
.l
call entry_ptr ; HL = &entry
@@ -255,16 +260,15 @@ dir_find::
xor a
ret
.found
- dec hl ; back to entry base
+ dec hl
ld a, [hl]
ret
; -----------------------------------------------------------------------------
-; dir_add - A = inode to link (name in wFsNameBuf) -> A = 1 ok / 0 dir full
+; dir_add - dir in wFsDir, child in wFsInode, name in wFsNameBuf -> A = 1/0
; -----------------------------------------------------------------------------
dir_add::
- ld [wFsInode], a
- call load_rootdir
+ call dir_load
ld c, 0
.l
call entry_ptr
@@ -280,7 +284,7 @@ dir_add::
.free
ld a, [wFsInode]
ld [hl+], a ; entry.inode
- ld de, wFsNameBuf ; copy the name
+ ld de, wFsNameBuf
ld b, 15
.cp
ld a, [de]
@@ -288,15 +292,15 @@ dir_add::
inc de
dec b
jr nz, .cp
- call write_rootdir
+ call dir_write
ld a, 1
ret
; -----------------------------------------------------------------------------
-; dir_remove - name in wFsNameBuf -> A = inode removed (0 if not found)
+; dir_remove - dir in wFsDir, name in wFsNameBuf -> A = inode removed (0 none)
; -----------------------------------------------------------------------------
dir_remove::
- call load_rootdir
+ call dir_load
ld c, 0
.l
call entry_ptr
@@ -314,16 +318,173 @@ dir_remove::
xor a
ret
.found
- dec hl ; entry base
+ dec hl
ld a, [hl]
ld [wFsInode], a
xor a
- ld [hl], a ; clear entry.inode
- call write_rootdir
+ ld [hl], a
+ call dir_write
ld a, [wFsInode]
ret
; -----------------------------------------------------------------------------
+; cur_cwd -> A = current process's cwd inode
+; -----------------------------------------------------------------------------
+cur_cwd:
+ push hl ; callers (resolve) keep a path cursor in HL
+ ld a, [wCurProc]
+ add PROC_CWD
+ ld l, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld h, a
+ ld a, [hl]
+ pop hl
+ ret
+
+; -----------------------------------------------------------------------------
+; resolve - path in wFsPath -> A = inode (0 if any component missing)
+; -----------------------------------------------------------------------------
+resolve:
+ ld hl, wFsPath
+ ld a, [hl]
+ cp $2F ; '/'
+ jr nz, .rel
+ inc hl
+ ld a, ROOT_INO
+ jr .setcur
+.rel
+ call cur_cwd
+.setcur
+ ld [wFsCur], a
+.loop
+ ld a, [hl] ; skip '/' separators
+ cp $2F
+ jr nz, .comp
+ inc hl
+ jr .loop
+.comp
+ ld a, [hl]
+ or a
+ jr z, .done ; end of path
+ call extract_comp ; wFsNameBuf = component, HL past it
+ push hl ; dir_find clobbers HL (the path cursor)
+ ld a, [wFsCur]
+ ld [wFsDir], a
+ call dir_find ; A = child inode
+ pop hl
+ or a
+ ret z ; missing component
+ ld [wFsCur], a
+ jr .loop
+.done
+ ld a, [wFsCur]
+ ret
+
+; extract_comp - copy the path component at [HL] into wFsNameBuf, advancing HL
+; to the next '/' or NUL. (<=15 chars)
+extract_comp:
+ ld de, wFsNameBuf
+ ld b, 15
+.l
+ ld a, [hl]
+ or a
+ jr z, .end
+ cp $2F
+ jr z, .end
+ ld [de], a
+ inc de
+ inc hl
+ dec b
+ jr nz, .l
+.end
+ xor a
+ ld [de], a
+ ret
+
+; -----------------------------------------------------------------------------
+; resolve_parent - path in wFsPath -> A = parent dir inode (0 if a dir in the
+; path is missing), wFsNameBuf = the final component.
+; -----------------------------------------------------------------------------
+resolve_parent:
+ ; find the last '/'
+ ld hl, wFsPath
+ xor a
+ ld [wFsSlashPtr], a
+ ld [wFsSlashPtr+1], a
+.scan
+ ld a, [hl]
+ or a
+ jr z, .scandone
+ cp $2F
+ jr nz, .noslash
+ ld a, l
+ ld [wFsSlashPtr], a
+ ld a, h
+ ld [wFsSlashPtr+1], a
+.noslash
+ inc hl
+ jr .scan
+.scandone
+ ld a, [wFsSlashPtr]
+ or a
+ jr nz, .haveslash
+ ld a, [wFsSlashPtr+1]
+ or a
+ jr nz, .haveslash
+ ; no '/': parent = cwd, name = whole path
+ ld hl, wFsPath
+ call comp_to_namebuf
+ call cur_cwd
+ ret
+.haveslash
+ ld a, [wFsSlashPtr]
+ ld l, a
+ ld a, [wFsSlashPtr+1]
+ ld h, a ; HL = &lastslash
+ ; if the slash is the very first char, parent = root
+ ld a, l
+ cp LOW(wFsPath)
+ jr nz, .split
+ ld a, h
+ cp HIGH(wFsPath)
+ jr nz, .split
+ inc hl ; name = after the '/'
+ call comp_to_namebuf
+ ld a, ROOT_INO
+ ret
+.split
+ push hl ; &lastslash
+ xor a
+ ld [hl], a ; terminate the dir part at the slash
+ call resolve ; resolve the dir part -> A = parent inode
+ pop hl ; &lastslash
+ or a
+ ret z ; a directory in the path is missing
+ ld [wFsCur], a
+ inc hl ; name = after the '/'
+ call comp_to_namebuf ; set the final name AFTER resolve (it clobbers it)
+ ld a, [wFsCur]
+ ret
+
+; comp_to_namebuf - copy the NUL-terminated string at HL into wFsNameBuf (<=15)
+comp_to_namebuf:
+ ld de, wFsNameBuf
+ ld b, 15
+.l
+ ld a, [hl]
+ ld [de], a
+ or a
+ ret z
+ inc hl
+ inc de
+ dec b
+ jr nz, .l
+ xor a
+ ld [de], a
+ ret
+
+; -----------------------------------------------------------------------------
; file_truncate - A = inode ; free all data blocks, size = 0 (cached inode)
; -----------------------------------------------------------------------------
file_truncate::
@@ -411,9 +572,18 @@ fs_format::
ld a, BLK_INODE0 + 1
ld hl, fs_inobuf + 256
call write_block
- ; root dir data block (block 4): empty
+ ; root dir data block (block 4): "." and ".." both -> root (inode 1)
ld hl, fs_dirbuf
call zero256
+ ld a, ROOT_INO
+ ld [fs_dirbuf + 0], a ; entry 0 inode
+ ld a, $2E ; '.'
+ ld [fs_dirbuf + 1], a
+ ld a, ROOT_INO
+ ld [fs_dirbuf + 16], a ; entry 1 inode
+ ld a, $2E
+ ld [fs_dirbuf + 17], a ; '.'
+ ld [fs_dirbuf + 18], a ; '.'
ld a, BLK_DATA0
ld hl, fs_dirbuf
jp write_block
@@ -458,6 +628,8 @@ fs_init::
call read_block
ld a, $FF
ld [wFsCurBlk], a
+ ld a, ROOT_INO
+ ld [wListDir], a
ret
; -----------------------------------------------------------------------------
@@ -477,10 +649,10 @@ OFPtr::
pop de
ret
-; copy the NUL-terminated name at DE (process memory) into wFsNameBuf.
-copy_name:
- ld hl, wFsNameBuf
- ld b, 15
+; copy the NUL-terminated path at DE (process memory) into wFsPath (<=39).
+copy_path:
+ ld hl, wFsPath
+ ld b, 39
.c
ld a, [de]
ld [hl+], a
@@ -494,13 +666,17 @@ copy_name:
ret
; =============================================================================
-; sys_open(DE = name, B = mode 0=read/1=write) -> A = fd or $FF
+; sys_open(DE = path, B = mode 0=read/1=write) -> A = fd or $FF
; =============================================================================
sys_open::
ld a, b
ld [wFsMode], a
- call copy_name ; name -> wFsNameBuf (before any block I/O)
- call dir_find ; A = inode or 0
+ call copy_path ; path -> wFsPath
+ call resolve_parent ; A = parent dir inode, wFsNameBuf = final name
+ or a
+ jr z, .fail
+ ld [wFsDir], a
+ call dir_find ; A = target inode or 0
ld [wFsInode], a
ld a, [wFsMode]
or a
@@ -535,8 +711,7 @@ sys_open::
ld [hl], a ; nlink
ld a, [wFsInode]
call write_inode
- ld a, [wFsInode]
- call dir_add
+ call dir_add ; wFsDir=parent, wFsInode=child, wFsNameBuf=name
or a
jr z, .fail
jr .allocof
@@ -776,7 +951,7 @@ sys_putb::
; =============================================================================
; sys_list(B = index, DE = namebuf) -> A = 1 (name copied) / 0 (no more)
-; index selects the index-th *used* entry in the root directory.
+; index selects the index-th non-hidden entry in the *current* directory.
; =============================================================================
sys_list::
ld a, e
@@ -785,7 +960,9 @@ sys_list::
ld [wFsOFPtr+1], a
ld a, b
ld [wFsSlot], a ; wanted index
- call load_rootdir
+ ld a, [wListDir]
+ ld [wFsDir], a
+ call dir_load
ld c, 0 ; entry cursor
ld b, 0 ; used counter
.l
@@ -793,6 +970,11 @@ sys_list::
ld a, [hl]
or a
jr z, .next
+ inc hl
+ ld a, [hl] ; name[0]
+ dec hl
+ cp $2E ; '.' -> hidden (skip ".", "..")
+ jr z, .next
ld a, [wFsSlot]
cp b
jr z, .found
@@ -821,10 +1003,14 @@ sys_list::
ret
; =============================================================================
-; sys_remove(DE = name) -> A = 0 ok / $FF not found
+; sys_remove(DE = path) -> A = 0 ok / $FF not found
; =============================================================================
sys_remove::
- call copy_name
+ call copy_path
+ call resolve_parent ; A = parent inode, wFsNameBuf = name
+ or a
+ jr z, .no
+ ld [wFsDir], a
call dir_remove ; A = inode or 0
or a
jr z, .no
@@ -843,3 +1029,120 @@ sys_remove::
.no
ld a, $FF
ret
+
+; =============================================================================
+; sys_mkdir(DE = path) -> A = 0 ok / $FF
+; =============================================================================
+sys_mkdir::
+ call copy_path
+ call resolve_parent ; A = parent inode, wFsNameBuf = name
+ or a
+ jr z, .fail
+ ld [wFsDir], a ; parent dir
+ call dir_find ; already exists?
+ or a
+ jr nz, .fail
+ call alloc_inode
+ or a
+ jr z, .fail
+ ld [wFsInode], a ; new directory inode
+ call alloc_block
+ or a
+ jr z, .fail
+ ld [wFsSlot], a ; new directory's data block
+ ; init the inode (type=dir, nlink=1, size=32, blocks[0]=data block)
+ ld a, [wFsInode]
+ call inode_ptr
+ push hl
+ ld b, 16
+ xor a
+.zi
+ ld [hl+], a
+ dec b
+ jr nz, .zi
+ pop hl
+ ld a, IT_DIR
+ ld [hl+], a
+ ld a, 1
+ ld [hl+], a
+ ld a, 32
+ ld [hl+], a
+ xor a
+ ld [hl+], a
+ ld a, [wFsSlot]
+ ld [hl], a
+ ld a, [wFsInode]
+ call write_inode
+ ; the new dir's data block: "." -> self, ".." -> parent
+ ld hl, fs_dirbuf
+ call zero256
+ ld a, [wFsInode]
+ ld [fs_dirbuf + 0], a
+ ld a, $2E
+ ld [fs_dirbuf + 1], a
+ ld a, [wFsDir]
+ ld [fs_dirbuf + 16], a
+ ld a, $2E
+ ld [fs_dirbuf + 17], a
+ ld [fs_dirbuf + 18], a
+ ld a, [wFsSlot]
+ ld hl, fs_dirbuf
+ call write_block
+ ; link the new dir into the parent (wFsDir/wFsInode/wFsNameBuf all set)
+ call dir_add
+ or a
+ jr z, .fail
+ xor a
+ ret
+.fail
+ ld a, $FF
+ ret
+
+; =============================================================================
+; sys_chdir(DE = path) -> A = 0 ok / $FF (not found or not a directory)
+; =============================================================================
+sys_chdir::
+ call copy_path
+ call resolve ; A = inode or 0
+ or a
+ jr z, .fail
+ ld [wFsInode], a
+ call inode_ptr
+ ld a, [hl] ; I_TYPE
+ cp IT_DIR
+ jr nz, .fail
+ ld a, [wCurProc]
+ add PROC_CWD
+ ld l, a
+ ld a, [wCurProc+1]
+ adc 0
+ ld h, a
+ ld a, [wFsInode]
+ ld [hl], a ; PROC_CWD = inode
+ xor a
+ ret
+.fail
+ ld a, $FF
+ ret
+
+; =============================================================================
+; sys_opendir(DE = path) -> A = 0 ok / $FF ; sets the directory ls enumerates.
+; Empty path or "." means the current directory.
+; =============================================================================
+sys_opendir::
+ call copy_path
+ call resolve
+ or a
+ jr z, .fail
+ ld [wFsInode], a
+ call inode_ptr
+ ld a, [hl]
+ cp IT_DIR
+ jr nz, .fail
+ ld a, [wFsInode]
+ ld [wListDir], a
+ xor a
+ ret
+.fail
+ ld a, $FF
+ ret
diff --git a/src/kdata.asm b/src/kdata.asm
index b239caa..6c520dd 100644
--- a/src/kdata.asm
+++ b/src/kdata.asm
@@ -48,7 +48,13 @@ wFsOFPtr:: DS 2
wFsCurBlk:: DS 1 ; data block currently in fs_datbuf ($FF = none)
wFsDirty:: DS 1 ; fs_datbuf needs write-back
wFsInode:: DS 1 ; working inode number
-wFsNameBuf:: DS 16 ; kernel-side copy of a name argument
+wFsNameBuf:: DS 16 ; kernel-side copy of a name/component
+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
+wFsSlashPtr::DS 2 ; last '/' seen while splitting a path
+wListDir:: DS 1 ; directory sys_list enumerates
wKChar:: DS 1 ; scratch for KPutc
wKillParent::DS 1 ; scratch for sys_kill
wPsBuf:: DS 2 ; scratch for sys_ps
diff --git a/src/proc.asm b/src/proc.asm
index cd79160..c22d40d 100644
--- a/src/proc.asm
+++ b/src/proc.asm
@@ -226,7 +226,9 @@ ProcCreate::
ld [hl+], a ; PROC_STDIN = console
ld [hl+], a ; PROC_STDOUT = console
ld a, $FF
- ld [hl], a ; PROC_PROG = none
+ ld [hl+], a ; PROC_PROG = none
+ ld a, ROOT_INO
+ ld [hl], a ; PROC_CWD = root
; build the initial stack frame in the task's RAM bank
ld a, [wTmpEntry]
@@ -458,7 +460,10 @@ sys_fork::
ld [hl+], a ; child STDOUT = parent STDOUT
inc de
ld a, [de]
- ld [hl], a ; child PROC_PROG = parent PROC_PROG
+ ld [hl+], a ; child PROC_PROG = parent PROC_PROG
+ inc de
+ ld a, [de]
+ ld [hl], a ; child PROC_CWD = parent PROC_CWD
; ---- return to the parent with child pid ----
ld a, [wForkUserSP]
diff --git a/src/programs.asm b/src/programs.asm
index 53142ec..64efc4d 100644
--- a/src/programs.asm
+++ b/src/programs.asm
@@ -100,6 +100,9 @@ ProgSpin:
SECTION "prog_blk", ROMX[$4000], BANK[21]
ProgBlk:
INCBIN "c/blk.bin"
+SECTION "prog_mkdir", ROMX[$4000], BANK[22]
+ProgMkdir:
+ INCBIN "c/mkdir.bin"
; -----------------------------------------------------------------------------
; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and
@@ -182,6 +185,8 @@ ProgramTable::
dw ProgSpin
db LOW(BANK(ProgBlk)), HIGH(BANK(ProgBlk))
dw ProgBlk
+ db LOW(BANK(ProgMkdir)), HIGH(BANK(ProgMkdir))
+ dw ProgMkdir
; -----------------------------------------------------------------------------
; NameTable (ROM0) - command name -> program id, searched by sys_lookup.
@@ -228,4 +233,6 @@ NameTable::
db PROG_SPIN
db "blk", 0
db PROG_BLK
+ db "mkdir", 0
+ db PROG_MKDIR
db 0
diff --git a/src/syscall.asm b/src/syscall.asm
index 29dc2b0..5557b5f 100644
--- a/src/syscall.asm
+++ b/src/syscall.asm
@@ -59,6 +59,9 @@ SyscallTable:
dw sys_reap ; 22 REAP
dw sys_bfill ; 23 BFILL (debug)
dw sys_bpeek ; 24 BPEEK (debug)
+ dw sys_mkdir ; 25 MKDIR
+ dw sys_chdir ; 26 CHDIR
+ dw sys_opendir ; 27 OPENDIR
; -----------------------------------------------------------------------------
sys_nosys: