code wiki / _hdl_build / nx_lease.nx

nx_lease.nx source

↩ module page · 365 lines · 14781 B

1// nx_lease.nx -- THE SOVEREIGN LEASE PRIMITIVE (eats debt seq12/D002: "WMS write lane unlocked = 2// race debt; claims/lease it"). Advisory TTL leases for contended write lanes (ws_put / planes / 3// MEMORY-class shared state): ACQUIRE is atomic (mkdirat -- EEXIST = held), takeover of a stale or 4// released lease is CAS-VERIFIED (write own stamp via tmp+renameat, read back, own nonce wins -- 5// last-writer-wins + verify = the loser backs off), RELEASE flips the stamp to released and NEVER 6// unlinks or rmdirs (the never-unlink-lock law: lock dirs are additive; a released stamp is the 7// reusable slot). Self-mkdirs knowledge/lease (rule 20). Stamp = one line: 8// held|released<TAB>owner<TAB>epoch<TAB>ttl<TAB>nonce 9// Stale = state held AND now > epoch + ttl (the HOLDER's declared ttl -- crash-safe by construction: 10// a dead holder's lease self-expires; no daemon needed). 11// nx_lease acquire <name> <owner> <ttl-sec> exit 0 ACQUIRED | 3 BUSY (holder shown) | 4 io 12// nx_lease release <name> <owner> exit 0 RELEASED | 3 REFUSED not-holder | 4 io 13// nx_lease check <name> exit 0 prints stamp | 1 free 14// nx_lease selftest 7 teeth, verdict=GREEN/RED, exit 0/5 15// name grammar [a-zA-Z0-9_-] only (deny slash/dot/traversal by construction). 16// expect_exit: 0 license_tier: ORIGINAL No hw writes (Rule 26). 17import "nx_syscalls.nx" 18const LS_MAGIC_3600: i64 = 3600 19 20const LS_STDERR: i64 = 2 21const LS_PATHCAP: i64 = 256 22const LS_STAMPCAP: i64 = 512 23const LS_MODE: i64 = 0x1a4 24const LS_DMODE: i64 = 0x1ed 25const LS_MKDIRAT: i64 = 258 26const LS_ATFDCWD: i64 = 0 - 100 27const LS_GETPID: i64 = 172 28const LS_TAB: i64 = 9 29const LS_NL: i64 = 10 30const LS_D0: i64 = 48 31const LS_D9: i64 = 57 32const LS_B10: i64 = 10 33const LS_NUMB: i64 = 24 34const LS_EXIT_USAGE: i64 = 2 35const LS_EXIT_BUSY: i64 = 3 36const LS_EXIT_IO: i64 = 4 37const LS_EXIT_SELF: i64 = 5 38const LS_V_A: i64 = 97 39const LS_V_R: i64 = 114 40const LS_V_C: i64 = 99 41const LS_V_S: i64 = 115 42const LS_NONCE_MOD: i64 = 1000000 43 44func ls_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 45func ls_werr(s: *u8) -> i64 { sys_write(LS_STDERR, s, ls_slen(s)); return 0 } 46func ls_wn(v: i64) -> i64 { 47 if v == 0 { sys_write(LS_STDERR, "0" as *u8, 1); return 0 } 48 var m: i64 = v 49 if m < 0 { sys_write(LS_STDERR, "-" as *u8, 1); m = 0 - m } 50 let d: *u8 = sys_mmap(LS_NUMB) 51 var k: i64 = 0 52 while m > 0 { d[k] = (LS_D0 + (m % LS_B10)) as u8; m = m / LS_B10; k = k + 1 } 53 let o: *u8 = sys_mmap(LS_NUMB) 54 var i: i64 = 0 55 while i < k { o[i] = d[k - 1 - i]; i = i + 1 } 56 sys_write(LS_STDERR, o, k) 57 return 0 58} 59func ls_cat(d: *u8, off: i64, s: *u8) -> i64 { 60 var o: i64 = off 61 var j: i64 = 0 62 while s[j] != (0 as u8) { d[o] = s[j]; o = o + 1; j = j + 1 } 63 return o 64} 65func ls_catn(d: *u8, off: i64, v: i64) -> i64 { 66 var o: i64 = off 67 var m: i64 = v 68 if m == 0 { d[o] = LS_D0 as u8; return o + 1 } 69 if m < 0 { m = 0 - m } 70 let t: *u8 = sys_mmap(LS_NUMB) 71 var k: i64 = 0 72 while m > 0 { t[k] = (LS_D0 + (m % LS_B10)) as u8; m = m / LS_B10; k = k + 1 } 73 var i: i64 = 0 74 while i < k { d[o] = t[k - 1 - i]; o = o + 1; i = i + 1 } 75 return o 76} 77func ls_eq(a: *u8, b: *u8) -> i64 { 78 var i: i64 = 0 79 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 80 if b[i] != (0 as u8) { return 0 } 81 return 1 82} 83// name grammar: [a-zA-Z0-9_-]+ only 84func ls_name_ok(s: *u8) -> i64 { 85 var i: i64 = 0 86 if s[0] == (0 as u8) { return 0 } 87 while s[i] != (0 as u8) { 88 let c: i64 = s[i] 89 var ok: i64 = 0 90 if c >= 97 { if c <= 122 { ok = 1 } } 91 if c >= 65 { if c <= 90 { ok = 1 } } 92 if c >= LS_D0 { if c <= LS_D9 { ok = 1 } } 93 if c == 95 { ok = 1 } 94 if c == 45 { ok = 1 } 95 if ok == 0 { return 0 } 96 i = i + 1 97 } 98 return 1 99} 100func ls_mkdir(p: *u8) -> i64 { return __syscall(LS_MKDIRAT, LS_ATFDCWD, p as i64, LS_DMODE, 0, 0, 0) } 101func ls_dirpath(name: *u8, out: *u8) -> i64 { 102 var o: i64 = ls_cat(out, 0, "knowledge/lease/" as *u8) 103 o = ls_cat(out, o, name) 104 o = ls_cat(out, o, ".lock" as *u8) 105 out[o] = 0 as u8 106 return o 107} 108func ls_read(path: *u8, buf: *u8, cap: i64) -> i64 { 109 let fd: i64 = sys_openat_rd(path) 110 if fd < 0 { return 0 - 1 } 111 var n: i64 = 0 112 var go: i64 = 1 113 while go == 1 { 114 let base: i64 = buf as i64 115 let r: i64 = sys_read(fd, (base + n) as *u8, cap - n) 116 if r <= 0 { go = 0 } else { n = n + r } 117 if n >= cap { go = 0 } 118 } 119 sys_close(fd) 120 if n < cap { buf[n] = 0 as u8 } 121 return n 122} 123static g_st: i64 124static g_epoch: i64 125static g_ttl: i64 126static g_nonce: i64 127// field walker: fills ob with field1 (owner), returns numeric fields via statics 128func ls_parse2(buf: *u8, n: i64, ob: *u8, obcap: i64) -> i64 { 129 g_st = 0 130 g_epoch = 0 131 g_ttl = 0 132 g_nonce = 0 133 if n <= 0 { return 0 } 134 if buf[0] == (104 as u8) { g_st = 1 } 135 if buf[0] == (114 as u8) { g_st = 2 } 136 if g_st == 0 { return 0 } 137 var i: i64 = 0 138 var field: i64 = 0 139 var ok2: i64 = 0 140 var v: i64 = 0 141 while i < n { 142 let c: i64 = buf[i] 143 if c == LS_TAB { 144 if field == 2 { g_epoch = v } 145 if field == 3 { g_ttl = v } 146 field = field + 1 147 v = 0 148 if field == 1 { ok2 = 0 } 149 } else { 150 if field == 1 { if ok2 < obcap - 1 { ob[ok2] = c as u8; ok2 = ok2 + 1; ob[ok2] = 0 as u8 } } 151 if c >= LS_D0 { if c <= LS_D9 { v = v * LS_B10 + (c - LS_D0) } } 152 if c == LS_NL { i = n } 153 } 154 i = i + 1 155 } 156 g_nonce = v 157 return 1 158} 159// atomically install our stamp (tmp + renameat) then read back and verify our nonce won 160func ls_claim(dir: *u8, owner: *u8, ttl: i64, nonce: i64) -> i64 { 161 let sp: *u8 = sys_mmap(LS_PATHCAP) 162 var o: i64 = ls_cat(sp, 0, dir) 163 o = ls_cat(sp, o, "/stamp" as *u8) 164 sp[o] = 0 as u8 165 let tp: *u8 = sys_mmap(LS_PATHCAP) 166 var o2: i64 = ls_cat(tp, 0, dir) 167 o2 = ls_cat(tp, o2, "/stamp.t" as *u8) 168 o2 = ls_catn(tp, o2, nonce) 169 tp[o2] = 0 as u8 170 let sb: *u8 = sys_mmap(LS_STAMPCAP) 171 var so: i64 = ls_cat(sb, 0, "held" as *u8) 172 sb[so] = LS_TAB as u8 173 so = so + 1 174 so = ls_cat(sb, so, owner) 175 sb[so] = LS_TAB as u8 176 so = so + 1 177 so = ls_catn(sb, so, sys_now_realtime_sec()) 178 sb[so] = LS_TAB as u8 179 so = so + 1 180 so = ls_catn(sb, so, ttl) 181 sb[so] = LS_TAB as u8 182 so = so + 1 183 so = ls_catn(sb, so, nonce) 184 sb[so] = LS_NL as u8 185 so = so + 1 186 let fd: i64 = sys_openat_wr(tp, LS_MODE) 187 if fd < 0 { return 0 - 1 } 188 sys_write(fd, sb, so) 189 sys_close(fd) 190 if sys_renameat(tp, sp) < 0 { return 0 - 1 } 191 // CAS-verify: read back, our nonce must be the one installed 192 let rb: *u8 = sys_mmap(LS_STAMPCAP) 193 let rn: i64 = ls_read(sp, rb, LS_STAMPCAP - 1) 194 let ob: *u8 = sys_mmap(LS_PATHCAP) 195 if ls_parse2(rb, rn, ob, LS_PATHCAP) == 0 { return 0 - 1 } 196 if g_nonce == nonce { return 1 } 197 return 0 198} 199func ls_stamp_path(dir: *u8, out: *u8) -> i64 { 200 var o: i64 = ls_cat(out, 0, dir) 201 o = ls_cat(out, o, "/stamp" as *u8) 202 out[o] = 0 as u8 203 return o 204} 205// release = install a released-stamp (never unlink/rmdir) 206func ls_unclaim(dir: *u8, owner: *u8, nonce: i64) -> i64 { 207 let sp: *u8 = sys_mmap(LS_PATHCAP) 208 ls_stamp_path(dir, sp) 209 let tp: *u8 = sys_mmap(LS_PATHCAP) 210 var o2: i64 = ls_cat(tp, 0, dir) 211 o2 = ls_cat(tp, o2, "/stamp.r" as *u8) 212 o2 = ls_catn(tp, o2, nonce) 213 tp[o2] = 0 as u8 214 let sb: *u8 = sys_mmap(LS_STAMPCAP) 215 var so: i64 = ls_cat(sb, 0, "released" as *u8) 216 sb[so] = LS_TAB as u8 217 so = so + 1 218 so = ls_cat(sb, so, owner) 219 sb[so] = LS_TAB as u8 220 so = so + 1 221 so = ls_catn(sb, so, sys_now_realtime_sec()) 222 sb[so] = LS_TAB as u8 223 so = so + 1 224 so = ls_catn(sb, so, 0) 225 sb[so] = LS_TAB as u8 226 so = so + 1 227 so = ls_catn(sb, so, nonce) 228 sb[so] = LS_NL as u8 229 so = so + 1 230 let fd: i64 = sys_openat_wr(tp, LS_MODE) 231 if fd < 0 { return 0 - 1 } 232 sys_write(fd, sb, so) 233 sys_close(fd) 234 if sys_renameat(tp, sp) < 0 { return 0 - 1 } 235 return 1 236} 237func ls_pid() -> i64 { return __syscall(LS_GETPID, 0, 0, 0, 0, 0, 0) } 238// core acquire, returns 0 acquired / 1 busy / -1 io ; on busy the holder is in hb 239func ls_acquire(name: *u8, owner: *u8, ttl: i64, hb: *u8) -> i64 { 240 ls_mkdir("knowledge" as *u8) 241 ls_mkdir("knowledge/lease" as *u8) 242 let dir: *u8 = sys_mmap(LS_PATHCAP) 243 ls_dirpath(name, dir) 244 let nonce: i64 = ls_pid() * LS_NONCE_MOD + (sys_now_realtime_sec() % LS_NONCE_MOD) 245 let mk: i64 = ls_mkdir(dir) 246 if mk == 0 { 247 let c: i64 = ls_claim(dir, owner, ttl, nonce) 248 if c == 1 { return 0 } 249 return 0 - 1 250 } 251 // dir exists: read stamp 252 let sp: *u8 = sys_mmap(LS_PATHCAP) 253 ls_stamp_path(dir, sp) 254 let rb: *u8 = sys_mmap(LS_STAMPCAP) 255 let rn: i64 = ls_read(sp, rb, LS_STAMPCAP - 1) 256 if rn <= 0 { 257 // dir with no stamp (crashed mid-acquire) = claimable 258 let c2: i64 = ls_claim(dir, owner, ttl, nonce) 259 if c2 == 1 { return 0 } 260 if c2 == 0 { return 1 } 261 return 0 - 1 262 } 263 if ls_parse2(rb, rn, hb, LS_PATHCAP) == 0 { return 0 - 1 } 264 if g_st == 2 { 265 let c3: i64 = ls_claim(dir, owner, ttl, nonce) 266 if c3 == 1 { return 0 } 267 if c3 == 0 { return 1 } 268 return 0 - 1 269 } 270 // lease validity = [epoch, epoch+ttl) -- expiry is INCLUSIVE (ttl=0 => reclaimable at once) 271 let now: i64 = sys_now_realtime_sec() 272 if now >= g_epoch + g_ttl { 273 let c4: i64 = ls_claim(dir, owner, ttl, nonce) 274 if c4 == 1 { return 0 } 275 if c4 == 0 { return 1 } 276 return 0 - 1 277 } 278 return 1 279} 280func ls_release(name: *u8, owner: *u8) -> i64 { 281 let dir: *u8 = sys_mmap(LS_PATHCAP) 282 ls_dirpath(name, dir) 283 let sp: *u8 = sys_mmap(LS_PATHCAP) 284 ls_stamp_path(dir, sp) 285 let rb: *u8 = sys_mmap(LS_STAMPCAP) 286 let rn: i64 = ls_read(sp, rb, LS_STAMPCAP - 1) 287 if rn <= 0 { return 1 } 288 let ob: *u8 = sys_mmap(LS_PATHCAP) 289 if ls_parse2(rb, rn, ob, LS_PATHCAP) == 0 { return 0 - 1 } 290 if g_st != 1 { return 1 } 291 if ls_eq(ob, owner) == 0 { return 1 } 292 let nonce: i64 = ls_pid() * LS_NONCE_MOD + (sys_now_realtime_sec() % LS_NONCE_MOD) 293 if ls_unclaim(dir, owner, nonce) == 1 { return 0 } 294 return 0 - 1 295} 296 297func main(argc: i64, argv: *i64) -> i64 { 298 if argc < 2 { ls_werr("usage: nx_lease {acquire <name> <owner> <ttl-sec> | release <name> <owner> | check <name> | selftest}\n" as *u8); sys_exit(LS_EXIT_USAGE); return LS_EXIT_USAGE } 299 let v: *u8 = argv[1] as *u8 300 let hb: *u8 = sys_mmap(LS_PATHCAP) 301 302 if v[0] == (LS_V_S as u8) { 303 var pass: i64 = 1 304 if ls_name_ok("../evil" as *u8) == 1 { pass = 0 } 305 if ls_name_ok("a/b" as *u8) == 1 { pass = 0 } 306 if ls_name_ok("wms-write" as *u8) == 0 { pass = 0 } 307 if ls_acquire("lease_probe" as *u8, "tester-a" as *u8, LS_MAGIC_3600, hb) != 0 { pass = 0; ls_werr("t1 acquire fail\n" as *u8) } 308 if ls_acquire("lease_probe" as *u8, "tester-b" as *u8, LS_MAGIC_3600, hb) != 1 { pass = 0; ls_werr("t2 busy-expected fail\n" as *u8) } 309 if ls_release("lease_probe" as *u8, "tester-b" as *u8) != 1 { pass = 0; ls_werr("t3 wrong-owner-release fail\n" as *u8) } 310 if ls_release("lease_probe" as *u8, "tester-a" as *u8) != 0 { pass = 0; ls_werr("t4 release fail\n" as *u8) } 311 if ls_acquire("lease_probe" as *u8, "tester-b" as *u8, LS_MAGIC_3600, hb) != 0 { pass = 0; ls_werr("t5 reacquire-after-release fail\n" as *u8) } 312 if ls_release("lease_probe" as *u8, "tester-b" as *u8) != 0 { pass = 0; ls_werr("t6 cleanup fail\n" as *u8) } 313 if ls_acquire("lease_probe2" as *u8, "tester-a" as *u8, 0, hb) != 0 { pass = 0; ls_werr("t7a stale-setup fail\n" as *u8) } 314 if ls_acquire("lease_probe2" as *u8, "tester-b" as *u8, LS_MAGIC_3600, hb) != 0 { pass = 0; ls_werr("t7b stale-takeover fail\n" as *u8) } 315 if ls_release("lease_probe2" as *u8, "tester-b" as *u8) != 0 { pass = 0; ls_werr("t7c cleanup fail\n" as *u8) } 316 if pass == 1 { ls_werr("LS-SELFTEST OK verdict=GREEN teeth=7\n" as *u8); sys_exit(0); return 0 } 317 ls_werr("LS-SELFTEST FAIL verdict=RED\n" as *u8) 318 sys_exit(LS_EXIT_SELF) 319 return LS_EXIT_SELF 320 } 321 322 if argc < 3 { ls_werr("verb needs <name>\n" as *u8); sys_exit(LS_EXIT_USAGE); return LS_EXIT_USAGE } 323 let name: *u8 = argv[2] as *u8 324 if ls_name_ok(name) == 0 { ls_werr("LS-REFUSED bad name (grammar [a-zA-Z0-9_-])\n" as *u8); sys_exit(LS_EXIT_USAGE); return LS_EXIT_USAGE } 325 326 if v[0] == (LS_V_A as u8) { 327 if argc < 5 { ls_werr("acquire needs <name> <owner> <ttl-sec>\n" as *u8); sys_exit(LS_EXIT_USAGE); return LS_EXIT_USAGE } 328 let owner: *u8 = argv[3] as *u8 329 var ttl: i64 = 0 330 let ts: *u8 = argv[4] as *u8 331 var ti: i64 = 0 332 while ts[ti] != (0 as u8) { let c: i64 = ts[ti]; if c >= LS_D0 { if c <= LS_D9 { ttl = ttl * LS_B10 + (c - LS_D0) } } ti = ti + 1 } 333 let r: i64 = ls_acquire(name, owner, ttl, hb) 334 if r == 0 { ls_werr("LS-ACQUIRED " as *u8); ls_werr(name); ls_werr(" owner=" as *u8); ls_werr(owner); ls_werr(" ttl=" as *u8); ls_wn(ttl); ls_werr("\n" as *u8); sys_exit(0); return 0 } 335 if r == 1 { ls_werr("LS-BUSY " as *u8); ls_werr(name); ls_werr(" holder=" as *u8); ls_werr(hb); ls_werr("\n" as *u8); sys_exit(LS_EXIT_BUSY); return LS_EXIT_BUSY } 336 ls_werr("LS-FAIL io\n" as *u8) 337 sys_exit(LS_EXIT_IO) 338 return LS_EXIT_IO 339 } 340 if v[0] == (LS_V_R as u8) { 341 if argc < 4 { ls_werr("release needs <name> <owner>\n" as *u8); sys_exit(LS_EXIT_USAGE); return LS_EXIT_USAGE } 342 let owner2: *u8 = argv[3] as *u8 343 let r2: i64 = ls_release(name, owner2) 344 if r2 == 0 { ls_werr("LS-RELEASED " as *u8); ls_werr(name); ls_werr("\n" as *u8); sys_exit(0); return 0 } 345 if r2 == 1 { ls_werr("LS-REFUSED not-holder (or not held)\n" as *u8); sys_exit(LS_EXIT_BUSY); return LS_EXIT_BUSY } 346 ls_werr("LS-FAIL io\n" as *u8) 347 sys_exit(LS_EXIT_IO) 348 return LS_EXIT_IO 349 } 350 if v[0] == (LS_V_C as u8) { 351 let dir: *u8 = sys_mmap(LS_PATHCAP) 352 ls_dirpath(name, dir) 353 let sp: *u8 = sys_mmap(LS_PATHCAP) 354 ls_stamp_path(dir, sp) 355 let rb: *u8 = sys_mmap(LS_STAMPCAP) 356 let rn: i64 = ls_read(sp, rb, LS_STAMPCAP - 1) 357 if rn <= 0 { ls_werr("LS-FREE " as *u8); ls_werr(name); ls_werr("\n" as *u8); sys_exit(1); return 1 } 358 sys_write(1, rb, rn) 359 sys_exit(0) 360 return 0 361 } 362 ls_werr("usage: nx_lease {acquire|release|check|selftest}\n" as *u8) 363 sys_exit(LS_EXIT_USAGE) 364 return LS_EXIT_USAGE 365}