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}