diff options
| author | user <user@clank> | 2026-09-24 23:08:35 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-24 23:08:35 +0200 |
| commit | 1430515ad419f2fddeb0265337a0da043901411b (patch) | |
| tree | e94c53cb7d59ca4826242b81afeb3de3c655d02e /usr | |
| parent | usr: touch, tr, rev, cut, uniq (diff) | |
| download | gbos-1430515ad419f2fddeb0265337a0da043901411b.tar.gz gbos-1430515ad419f2fddeb0265337a0da043901411b.tar.xz gbos-1430515ad419f2fddeb0265337a0da043901411b.zip | |
fs: hard links - ln(1), and rm that removes a NAME
gbfs has carried I_NLINK since the beginning and nothing ever incremented it,
so a file could only have one name. SYS_LINK adds a second directory entry
pointing at the same inode: the data is stored once, and both names read it.
That makes rm's job different. It now decrements the link count and only frees
the inode and its blocks when the LAST name goes - removing one of two links
used to free blocks the other still pointed at.
Refused, with the reasons that matter here: linking a directory (it would make
a cycle the tree walkers cannot survive), an existing name, and anything under
/bin, which is ROM.
The syscall takes a request block because the trap needs HL for its dispatch
table - and ln copies both paths to the stack first, since that block is a
static living at $A000, on top of the command line it is reading.
Diffstat (limited to 'usr')
| -rw-r--r-- | usr/gbos.h | 3 | ||||
| -rw-r--r-- | usr/libc.s | 20 | ||||
| -rw-r--r-- | usr/ln.c | 43 |
3 files changed, 65 insertions, 1 deletions
@@ -23,6 +23,9 @@ 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 */ +unsigned char sys_link(void *req); /* hard link trap (see usr/ln.c) */ +void settime(unsigned char buf[4]); /* wall clock: unix seconds, LE */ +unsigned char gettime(unsigned char buf[4]); /* -> 1 if the clock is set */ 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 */ @@ -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, _sys_stat, _sys_fsstat, _isatty + .globl _sys_net, _gsleep, _gticks, _sys_stat, _sys_fsstat, _isatty, _sys_link, _settime, _gettime ;; 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. @@ -336,3 +336,21 @@ _isatty: ld c, #39 ; SYS_ISATTY rst #0x30 ret + + ;; unsigned char sys_link(void *req /*DE*/) -> A (see fs.asm sys_link) +_sys_link: + ld c, #40 ; SYS_LINK + rst #0x30 + ret + + ;; void settime(unsigned char buf[4] /*DE*/) - set the wall clock +_settime: + ld c, #41 ; SYS_SETTIME + rst #0x30 + ret + + ;; unsigned char gettime(unsigned char buf[4] /*DE*/) -> A = 1 if set +_gettime: + ld c, #42 ; SYS_GETTIME + rst #0x30 + ret diff --git a/usr/ln.c b/usr/ln.c new file mode 100644 index 0000000..6a98982 --- /dev/null +++ b/usr/ln.c @@ -0,0 +1,43 @@ +#include "gbos.h" +/* ln TARGET NAME: give an existing file a second name. + * + * A real hard link, not a copy: both names share one inode, so the data is + * stored once and `rm` only frees it when the last name goes. gbfs always had + * the link count - nothing had ever incremented it. + * + * The syscall takes a request block (two paths, and the trap needs HL for its + * dispatch table), and that block is a static living at $A000 - so the paths + * are copied to the stack first, like every other program that has statics. */ + +static unsigned char req[4]; + +void main(void) { + char paths[64]; + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + unsigned char i, j = 0; + char *tgt, *name; + + if (argc < 2) { + puts("usage: ln TARGET NAME"); + nl(); + sexit(2); + } + tgt = paths; + for (i = 0; argv[0][i] && j < 30; i++) paths[j++] = argv[0][i]; + paths[j++] = 0; + name = paths + j; + for (i = 0; argv[1][i] && j < 62; i++) paths[j++] = argv[1][i]; + paths[j] = 0; + + req[0] = (unsigned char)(unsigned int)tgt; + req[1] = (unsigned char)(((unsigned int)tgt) >> 8); + req[2] = (unsigned char)(unsigned int)name; + req[3] = (unsigned char)(((unsigned int)name) >> 8); + if (sys_link(req) != 0) { + puts("ln: cannot link "); + puts(name); + nl(); + sexit(1); + } +} |
