aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md23
-rw-r--r--c/gbos.h4
-rw-r--r--c/libc.s25
-rw-r--r--c/ls.c5
-rw-r--r--c/mkdir.c8
-rw-r--r--c/sh.c38
-rw-r--r--include/gbos.inc11
-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
13 files changed, 463 insertions, 49 deletions
diff --git a/Makefile b/Makefile
index 87a7d9d..58a6bd1 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ $(BUILD)/%.o: src/%.asm | $(BUILD)
# C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm
CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \
c/pid.bin c/cat.bin c/wc.bin c/head.bin c/args.bin \
- c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin
+ c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin c/mkdir.bin
$(BUILD)/programs.o: $(CBLOBS)
# C programs also depend on the shared libc sources
diff --git a/README.md b/README.md
index 74f3608..a968e4a 100644
--- a/README.md
+++ b/README.md
@@ -304,9 +304,12 @@ blocks 2-3 inode table (32 inodes x 16 B: type, size, 8 direct block ptrs)
blocks 4.. data blocks
```
-- **Inodes + a root directory.** A directory is a file of 16-byte entries
- (inode# + name). Files are up to **2 KiB** (8 direct blocks); up to **16**
- entries in the root, **32** inodes.
+- **Inodes + nested directories.** A directory is a file of 16-byte entries
+ (inode# + name), each carrying `.`/`..`. Files are up to **2 KiB** (8 direct
+ blocks); **32** inodes, 16 entries per directory.
+- **Paths + per-process cwd.** `open`/`mkdir`/`chdir`/`remove`/`ls` take paths
+ (absolute `/a/b` or relative `a/b`); each process has a cwd inode (`PROC_CWD`,
+ inherited on fork). Shell builtins: `cd`, and a cwd-aware prompt.
- **Bounce buffer.** Only `read_block`/`write_block` touch cart-RAM banking;
everything else works on WRAM buffers. The bitmap + inode table are cached in
WRAMX; data/dir blocks stream through a one-block cache.
@@ -320,8 +323,15 @@ $ save notes
gbos has a real filesystem now
$ cat notes
gbos has a real filesystem now
-$ ls
-notes
+$ mkdir docs
+$ cd docs
+/docs$ save note
+hi
+/docs$ cd /
+$ cat docs/note
+hi
+$ ls docs
+note
$ wc notes # survives a reboot
1 5 31
```
@@ -347,7 +357,8 @@ $ wc notes # survives a reboot
- [x] **stage 1**: block device (`blk.asm`) via a WRAM bounce buffer
- [x] **stage 2**: inodes + root directory; variable-size files (<=2 KiB),
bitmap allocators; 16 files / 32 inodes; persists across reboots
- - [ ] stage 3: nested directories, path resolution, per-process cwd, `cd`/`mkdir`
+ - [x] **stage 3**: nested directories, path resolution, per-process cwd,
+ `cd`/`mkdir`/`ls <dir>` — the whole tree persists across reboots
- [ ] Preemptive scheduling: real context save in `TimerISR` → `hSwitchTo`
- [ ] Use the `$D000-$DFFF` u-area (SVBK) for per-process kernel state / kstack
- [ ] `brk`/heap allocator inside the task bank (heap up, stack down, collision = ENOMEM)
diff --git a/c/gbos.h b/c/gbos.h
index 6badd16..c93414f 100644
--- a/c/gbos.h
+++ b/c/gbos.h
@@ -27,6 +27,10 @@ int fgetc(unsigned char fd); /* a byte, or -1 at EOF */
void fputc(unsigned char fd, char ch);
unsigned char flist(unsigned char slot, char *namebuf); /* 1 if slot used */
void fremove(const char *name);
+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 NOFD 0xFF
diff --git a/c/libc.s b/c/libc.s
index c245bbd..d499521 100644
--- a/c/libc.s
+++ b/c/libc.s
@@ -221,3 +221,28 @@ _bpeek2:
rst #0x30
ret
+
+ .globl _mkdir, _chdir
+ ;; unsigned char mkdir(const char *path /*DE*/) -> A (0 ok / $FF)
+_mkdir:
+ ld c, #25 ; SYS_MKDIR
+ rst #0x30
+ ret
+ ;; unsigned char chdir(const char *path /*DE*/) -> A (0 ok / $FF)
+_chdir:
+ ld c, #26 ; SYS_CHDIR
+ rst #0x30
+ ret
+
+ .globl _opendir
+ ;; unsigned char opendir(const char *path /*DE*/) -> A (0 ok / $FF)
+_opendir:
+ ld c, #27 ; SYS_OPENDIR
+ rst #0x30
+ ret
+
+ .globl _poweroff
+ ;; void poweroff(void) - execute the emulator's clean-exit opcode ($ED)
+_poweroff:
+ .db 0xED
+1$: jr 1$
diff --git a/c/ls.c b/c/ls.c
index 5d5ce43..0e2e01f 100644
--- a/c/ls.c
+++ b/c/ls.c
@@ -1,7 +1,10 @@
#include "gbos.h"
-/* ls: list files in the filesystem. */
+/* ls [dir]: list a directory (default: current). */
void main(void) {
+ char *argv[4];
char name[16];
+ unsigned char argc = argv_parse(argv, 4);
unsigned char i;
+ if (opendir(argc > 0 ? argv[0] : ".")) { puts("ls: no such directory"); nl(); return; }
for (i = 0; flist(i, name); i++) { name[15] = 0; puts(name); nl(); }
}
diff --git a/c/mkdir.c b/c/mkdir.c
new file mode 100644
index 0000000..c907518
--- /dev/null
+++ b/c/mkdir.c
@@ -0,0 +1,8 @@
+#include "gbos.h"
+/* mkdir <path>: create a directory. */
+void main(void) {
+ char *argv[4];
+ unsigned char argc = argv_parse(argv, 4);
+ if (argc < 1) { puts("usage: mkdir <name>"); nl(); return; }
+ if (mkdir(argv[0])) { puts("mkdir: failed"); nl(); }
+}
diff --git a/c/sh.c b/c/sh.c
index 8aa619e..b1bf630 100644
--- a/c/sh.c
+++ b/c/sh.c
@@ -18,6 +18,30 @@ static unsigned char tokenize(char *s, char **tok, unsigned char max) {
}
static unsigned char isop(char *t, char c) { return t[0] == c && t[1] == 0; }
+static unsigned char iscmd(char *t, char a, char b) {
+ return t[0] == a && t[1] == b && t[2] == 0;
+}
+
+/* keep the prompt's path string roughly in sync with chdir(). */
+static void set_cwd(char *cwd, char *arg) {
+ char *d, *s, *last;
+ if (arg[0] == '/') { /* absolute */
+ d = cwd; s = arg;
+ while (*s) *d++ = *s++;
+ *d = 0;
+ } else if (arg[0] == '.' && arg[1] == '.' && arg[2] == 0) {
+ last = cwd; d = cwd;
+ while (*d) { if (*d == '/') last = d; d++; }
+ if (last == cwd) { cwd[0] = '/'; cwd[1] = 0; } else *last = 0;
+ } else if (arg[0] == '.' && arg[1] == 0) {
+ /* stay */
+ } else { /* relative: append */
+ d = cwd; while (*d) d++;
+ if (!(cwd[0] == '/' && cwd[1] == 0)) *d++ = '/';
+ s = arg; while (*s) *d++ = *s++;
+ *d = 0;
+ }
+}
/* build the child command line "name\0arg1 arg2\0" at 0xA000 */
static void set_cmdline(char **tok, unsigned char start, unsigned char end) {
@@ -74,22 +98,32 @@ done:
void main(void) {
char line[80];
char *tok[16];
+ char cwd[40];
+ cwd[0] = '/'; cwd[1] = 0;
for (;;) {
unsigned char nt, i, pipepos, c, bg;
/* reap finished background jobs */
{ unsigned char p; while ((p = reap())) { putc('['); putu(p); puts(" done]"); nl(); } }
- puts("$ ");
+ puts(cwd); puts("$ ");
/* read a line (echoing), stop at newline; exit on EOF */
i = 0;
for (;;) {
c = readc();
- if (c == EOF) return;
+ if (c == EOF) poweroff();
if (c == '\r' || c == '\n') { nl(); break; }
if (i < 79) { line[i++] = c; putc(c); }
}
line[i] = 0;
nt = tokenize(line, tok, 16);
if (nt == 0) continue;
+ if (iscmd(tok[0], 'c', 'd')) { /* cd builtin */
+ char *arg = (nt >= 2) ? tok[1] : "/";
+ if (chdir(arg) == 0) set_cwd(cwd, arg);
+ else { puts("cd: no such directory"); nl(); }
+ continue;
+ }
+ if ((tok[0][0] == 'e' && tok[0][1] == 'x') ||
+ (tok[0][0] == 'q' && tok[0][1] == 'u')) poweroff();
bg = 0;
if (isop(tok[nt - 1], '&')) { bg = 1; nt--; if (nt == 0) continue; }
pipepos = 0;
diff --git a/include/gbos.inc b/include/gbos.inc
index 770bd55..c4804c4 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -85,6 +85,7 @@ DEF PROC_EXIT RB 1 ; exit code (valid when PS_ZOMBIE)
DEF PROC_STDIN RB 1 ; $FF = console, else an open-file fd
DEF PROC_STDOUT RB 1 ; $FF = console, else an open-file fd
DEF PROC_PROG RB 1 ; program id currently running ($FF = none), for ps
+DEF PROC_CWD RB 1 ; current working directory (inode #)
DEF PROC_SIZE RB 0
DEF KILL_CODE EQU 137 ; exit status of a killed process (128 + SIGKILL)
@@ -118,8 +119,11 @@ DEF SYS_PS EQU 20 ; process table entry (B=slot, DE=buf3 -> A=
DEF SYS_PROGNAME EQU 21 ; program id -> name (B=id, DE=namebuf)
DEF SYS_REAP EQU 22 ; reap a zombie child, nohang (-> A=pid or 0)
DEF SYS_BFILL EQU 23 ; [debug] fill a disk block (B=block, E=byte)
-DEF SYS_BPEEK EQU 24 ; [debug] read a disk block[0] (B=block -> A)
-DEF SYS_MAX EQU 25
+DEF SYS_BPEEK EQU 24 ; [debug] read a disk block (B=block, E=off -> A)
+DEF SYS_MKDIR EQU 25 ; make a directory (DE=path -> A=0/$FF)
+DEF SYS_CHDIR EQU 26 ; change directory (DE=path -> A=0/$FF)
+DEF SYS_OPENDIR EQU 27 ; point ls at a directory (DE=path -> A=0/$FF)
+DEF SYS_MAX EQU 28
; SYS_KILL (9) is now implemented (B=pid -> A=0/$FF; pid 1 is immortal)
; (SYS_OPEN=4 / SYS_CLOSE=5 are now implemented by the filesystem)
@@ -136,7 +140,7 @@ DEF BLK_SUPER EQU 0
DEF BLK_BITMAP EQU 1
DEF BLK_INODE0 EQU 2 ; inode table = blocks 2,3
DEF BLK_DATA0 EQU 4 ; first data block
-DEF FS_VERSION EQU 2
+DEF FS_VERSION EQU 3 ; bumped: dirs now carry "."/".." entries
DEF NINODES EQU 32
DEF ROOT_INO EQU 1
DEF NDIRECT EQU 8 ; direct block pointers per inode -> files <= 2 KiB
@@ -196,5 +200,6 @@ DEF PROG_PS EQU 16
DEF PROG_KILL EQU 17
DEF PROG_SPIN EQU 18
DEF PROG_BLK EQU 19
+DEF PROG_MKDIR EQU 20
ENDC
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: