code wiki / _hdl_build / nx_fence.nx
nx_fence.nx source
↩ module page · 452 lines · 20309 B
1// nx_fence.nx -- THE MONOTONIC FENCE TOKEN for the claims plane (hive mechanics, 2026-08-09).
2//
3// WHY THIS EXISTS -- measured, not assumed
4// ----------------------------------------
5// 2026-08-08, corpus_complete=1 over 22,903 files: ONLY nx_claims and nx_seat reference
6// knowledge/status/claims.jrnl. ZERO mutation paths consult it -- not nx_fs_write, not /api/promote,
7// not /api/deploy, not any plane appender. The estate's own record agrees: "/api/deploy, /api/promote,
8// /api/restart are NOT yet lease-gated -- two sessions can still race a DEPLOY."
9// **** THE CLAIMS PLANE IS A WHITEBOARD, NOT A LOCK: IT CORRECTLY REFUSES A STALE *BEAT*, AND THEN
10// NOTHING STOPS THE SAME STALE SEAT FROM WRITING STRAIGHT TO PRODUCTION.
11//
12// The 2026 distributed-systems answer is the FENCING TOKEN (Kleppmann; etcd's per-key creation
13// revision; Raft terms): a monotonically increasing number issued at each acquisition, which the
14// RESOURCE persists and uses to reject any operation carrying a lower one. The cardinal rule is
15// **** NEVER TRUST A PROCESS TO KNOW THAT ITS OWN LOCK HAS EXPIRED -- so the check must live at the
16// resource, never in the lock holder.
17//
18// nx_lease already carries a `nonce` and it is NOT a fence: a pid-derived tiebreaker (LS_NONCE_MOD
19// 1000000) answering WHO WON A RACE, not WHO IS CURRENT. It has no ordering, so a resource cannot
20// reject a lower one. **** A TIEBREAKER IS NOT A FENCE.
21//
22// THE TOKEN, derived never stored (the claims plane's cardinal law):
23// fence(resource) = number of CLAIM frames for that resource in the append-only journal.
24// Monotonic BY CONSTRUCTION (appends only), no new state, no daemon, and NO FRAME-FORMAT CHANGE
25// (rule 19 additive) -- every existing reader is unaffected.
26//
27// HOLDERSHIP STAYS ONE COPY: state/holder come from FORKING `nx_claims state`, never re-derived here
28// (the nx_seat precedent -- its derived-holdership parser stays THE one copy). This organ owns
29// exactly one new thing: the count.
30// !! FC_CLAIMS_ELF is a DEPLOYMENT ASSUMPTION (a path constant). Named out loud so it is findable:
31// A PATH CONSTANT IS A DEPLOYMENT ASSUMPTION, and the estate has shipped silent ones before.
32//
33// nx_fence token <journal> <resource> -> fence=<N> + nx_claims state line; exit 0
34// nx_fence verify <journal> <resource> <actor> <fence> -> exit 0 ALLOW | 3 REFUSE reason=<R>
35// nx_fence selftest [scratch-dir] -> gate teeth, verdict carried by exit code
36//
37// FAIL DIRECTION: verify REFUSES anything it cannot PROVE (unreadable journal, non-numeric fence,
38// fork failure, missing holder field). **** A GUARD THAT GATES A DESTRUCTIVE ACTION MUST BE WRONG IN
39// THE DIRECTION OF DOING NOTHING. It ships with a POSITIVE CONTROL because a guard that refuses
40// everything passes every negative test -- four SSRF deny-tests once went green over a broken guard.
41// Every refusal NAMES WHICH RULE FIRED: a suite that only asks "was it refused?" is blind by
42// construction, and a compound assertion that will not name its failing conjunct is a false-alarm
43// generator.
44// expect_exit: 0 license_tier: ORIGINAL No hw writes (Rule 26).
45import "nx_syscalls.nx"
46import "nx_gatekit_lib.nx"
47import "nx_gate_verdict.nx"
48const FC_MAGIC_1024: i64 = 1024
49
50const FC_WIN: i64 = 4194304
51const FC_OUT: i64 = 8192
52const FC_SPAN: i64 = 64
53const FC_TAB: i64 = 9
54const FC_NL: i64 = 10
55const FC_D0: i64 = 48
56const FC_D9: i64 = 57
57const FC_EXIT_USAGE: i64 = 2
58const FC_EXIT_REFUSE: i64 = 3
59const FC_EXIT_IO: i64 = 4
60const FC_HOLDER_KEYLEN: i64 = 7
61
62// decision codes -- 0 is ALLOW, every nonzero NAMES a distinct rule
63const FC_ALLOW: i64 = 0
64const FC_R_UNREADABLE: i64 = 1
65const FC_R_BADFENCE: i64 = 2
66const FC_R_NOTHELD: i64 = 3
67const FC_R_NOTHOLDER: i64 = 4
68const FC_R_STALE: i64 = 5
69const FC_R_CLAIMSFAIL: i64 = 6
70
71// ---- column k of the line [ls,le) -- frame <ts>\t<VERB>\t<resource>\t<actor>\t<ttl>\t<note> -------
72// No loop-exit sentinel is ever written into the cursor: gk_eol exists precisely because that idiom
73// (`e = n + 9`) DESTROYS the position being searched for. Separate `fin` flag, cursor untouched.
74func fc_col(b: *u8, ls: i64, le: i64, k: i64, out: *i64) -> i64 {
75 var col: i64 = 0
76 var s: i64 = ls
77 var i: i64 = ls
78 var found: i64 = 0
79 var fin: i64 = 0
80 while fin == 0 {
81 if i >= le {
82 if col == k { out[0] = s; out[1] = le; found = 1 }
83 fin = 1
84 }
85 if fin == 0 {
86 if b[i] == FC_TAB as u8 {
87 if col == k { out[0] = s; out[1] = i; found = 1; fin = 1 }
88 if fin == 0 { col = col + 1; s = i + 1 }
89 }
90 if fin == 0 { i = i + 1 }
91 }
92 }
93 return found
94}
95
96// span [s,e) equals the WHOLE NUL-terminated z (length-exact, so "sess1" never matches "sess10")
97func fc_span_eq(b: *u8, s: i64, e: i64, z: *u8) -> i64 {
98 let m: i64 = gk_len(z)
99 if e - s != m { return 0 }
100 var i: i64 = 0
101 while i < m {
102 if b[s + i] != z[i] { return 0 }
103 i = i + 1
104 }
105 return 1
106}
107
108// THE TOKEN. Count CLAIM frames for this resource. Append-only journal => monotonic by construction.
109// `claimlit` arrives already hoisted: indexing an INLINE-CAST literal -- ("CLAIM" as *u8)[j] -- never
110// compares equal in this compiler. It builds clean and silently never matches, so the only symptom
111// would be a test that cannot fail.
112func fc_count(b: *u8, n: i64, res: *u8, claimlit: *u8) -> i64 {
113 let sp: *i64 = sys_mmap(FC_SPAN) as *i64
114 var cnt: i64 = 0
115 var i: i64 = 0
116 while i < n {
117 let le: i64 = gk_eol(b, i, n)
118 if fc_col(b, i, le, 1, sp) == 1 {
119 if fc_span_eq(b, sp[0], sp[1], claimlit) == 1 {
120 if fc_col(b, i, le, 2, sp) == 1 {
121 if fc_span_eq(b, sp[0], sp[1], res) == 1 { cnt = cnt + 1 }
122 }
123 }
124 }
125 i = le + 1
126 }
127 return cnt
128}
129
130// strict decimal: -1 on empty or ANY non-digit. A lenient atoi reads "abc" as 0, and 0 would compare
131// equal to a never-claimed resource's count -- A PARSER THAT CANNOT FAIL IS NOT A CHECK.
132func fc_atoi_strict(s: *u8) -> i64 {
133 let n: i64 = gk_len(s)
134 if n == 0 { return 0 - 1 }
135 var v: i64 = 0
136 var i: i64 = 0
137 var bad: i64 = 0
138 while i < n {
139 let c: i64 = s[i] as i64
140 if c < FC_D0 { bad = 1 }
141 if c > FC_D9 { bad = 1 }
142 if bad == 0 { v = v * 10 + (c - FC_D0) }
143 i = i + 1
144 }
145 if bad == 1 { return 0 - 1 }
146 return v
147}
148
149// buf[from..] equals z AND ends at newline/EOF -- exact field match, never a prefix match
150func fc_tail_eq(buf: *u8, n: i64, from: i64, z: *u8) -> i64 {
151 let m: i64 = gk_len(z)
152 var i: i64 = 0
153 while i < m {
154 if from + i >= n { return 0 }
155 if buf[from + i] != z[i] { return 0 }
156 i = i + 1
157 }
158 if from + m >= n { return 1 }
159 if buf[from + m] == FC_NL as u8 { return 1 }
160 return 0
161}
162
163// ---- THE DECISION, as a PURE function so the gate can prove every branch in-process -------------
164// readable: journal read ok (1) | claims_rc: 0 = nx_claims answered | held: state=HELD present
165// holder_ok: holder field equals actor exactly | want: presented fence (-1 = unparseable) | cur: count
166func fc_decide(readable: i64, claims_rc: i64, held: i64, holder_ok: i64, want: i64, cur: i64) -> i64 {
167 if readable != 1 { return FC_R_UNREADABLE }
168 if want < 0 { return FC_R_BADFENCE }
169 if claims_rc != 0 { return FC_R_CLAIMSFAIL }
170 if held != 1 { return FC_R_NOTHELD }
171 if holder_ok != 1 { return FC_R_NOTHOLDER }
172 if want != cur { return FC_R_STALE }
173 return FC_ALLOW
174}
175
176func fc_reason_name(c: i64) -> *u8 {
177 if c == FC_ALLOW { return "ALLOW" as *u8 }
178 if c == FC_R_UNREADABLE { return "UNREADABLE" as *u8 }
179 if c == FC_R_BADFENCE { return "BAD-FENCE" as *u8 }
180 if c == FC_R_NOTHELD { return "NOT-HELD" as *u8 }
181 if c == FC_R_NOTHOLDER { return "NOT-HOLDER" as *u8 }
182 if c == FC_R_STALE { return "STALE-FENCE" as *u8 }
183 if c == FC_R_CLAIMSFAIL { return "CLAIMS-FORK-FAILED" as *u8 }
184 return "UNKNOWN" as *u8
185}
186
187// Fork `nx_claims state <journal> <resource>` -- holdership derivation stays ONE copy, in the organ
188// that owns it. Fork failure surfaces as a nonzero rc and the caller REFUSES (fail-closed).
189func fc_claims_state(journal: *u8, res: *u8, outbuf: *u8, outlen: *i64) -> i64 {
190 let elf: *u8 = "/volume1/homes/elderwesto/nishihost/nx_claims.elf" as *u8
191 let vstate: *u8 = "state" as *u8
192 return gk_run_capture(elf, vstate, journal, res, 0 as *u8, outbuf, FC_OUT - 8, outlen)
193}
194
195// ---- GATE. Teeth are in-process over the pure predicates + a real journal fixture ----------------
196// The ANTI-VACUITY tooth is T8: the trivial wrong implementation (prefix compare on the holder)
197// passes T1..T7 and ONLY T8 refutes it. A gate whose wrong implementation also passes is not a gate.
198func fc_selftest(scratch: *u8) -> i64 {
199 let r: *i64 = sys_mmap(FC_SPAN) as *i64
200 gk_head("NX-FENCE-GATE" as *u8)
201 let claimlit: *u8 = "CLAIM" as *u8
202
203 let jr: *u8 = sys_mmap(FC_WIN)
204 let path: *u8 = sys_mmap(FC_MAGIC_1024)
205 gk_join(path, scratch, "fence_fixture.jrnl" as *u8)
206 gk_rm(path)
207
208 // fixture: resA claimed twice (fence 2), released once between; resB claimed once (fence 1)
209 let fx: *u8 = "100\tCLAIM\tresA\tsess1\t3600\tfirst\n110\tCLAIM\tresB\tsess9\t3600\tother\n120\tRELEASE\tresA\tsess1\t0\tdone\n130\tCLAIM\tresA\tsess10\t3600\tsecond\n" as *u8
210 gk_write(path, fx)
211 let n: i64 = gk_read(path, jr, FC_WIN - 8)
212
213 // T0 ASSERT THE FIXTURE REACHED THE CONDITION before asserting any outcome -- a fixture the
214 // defect cannot fail is not a test, and four vacuous fixtures shipped in one day for want of this
215 gk_ok(r, n > 0, "T0 fixture readable (non-empty journal)" as *u8)
216
217 // T1 POSITIVE CONTROL -- without this a refuse-everything guard scores 100%
218 gk_eq(r, fc_decide(1, 0, 1, 1, 2, 2), FC_ALLOW, "T1 POSITIVE-CONTROL holder+current fence ALLOWED" as *u8)
219
220 // T2..T6 negatives, each asserting WHICH rule fired, not merely that it refused
221 gk_eq(r, fc_decide(1, 0, 1, 1, 1, 2), FC_R_STALE, "T2 neg-control stale fence REFUSED as STALE-FENCE" as *u8)
222 gk_eq(r, fc_decide(1, 0, 1, 0, 2, 2), FC_R_NOTHOLDER, "T3 neg-control wrong actor REFUSED as NOT-HOLDER" as *u8)
223 gk_eq(r, fc_decide(1, 0, 0, 1, 2, 2), FC_R_NOTHELD, "T4 neg-control expired/free lease REFUSED as NOT-HELD" as *u8)
224 gk_eq(r, fc_decide(1, 0, 1, 1, 0 - 1, 2), FC_R_BADFENCE, "T5 neg-control non-numeric fence REFUSED as BAD-FENCE" as *u8)
225 gk_eq(r, fc_decide(0, 0, 1, 1, 2, 2), FC_R_UNREADABLE, "T6 neg-control unreadable journal REFUSED (fail-closed)" as *u8)
226 gk_eq(r, fc_decide(1, 127, 1, 1, 2, 2), FC_R_CLAIMSFAIL, "T7 neg-control claims fork failure REFUSED" as *u8)
227
228 // T8 ANTI-VACUITY: exact-match holder. A prefix compare would say sess1 == sess10 and pass T1..T7.
229 let hline: *u8 = "NX-CLAIMS STATE resource=resA state=HELD holder=sess10\n" as *u8
230 let hl: i64 = gk_len(hline)
231 let hp: i64 = gk_out_pos(hline, hl, "holder=" as *u8)
232 gk_ok(r, hp > 0, "T8a holder field located" as *u8)
233 gk_eq(r, fc_tail_eq(hline, hl, hp + FC_HOLDER_KEYLEN, "sess1" as *u8), 0, "T8 ANTI-VACUITY prefix sess1 does NOT match holder sess10" as *u8)
234 gk_eq(r, fc_tail_eq(hline, hl, hp + FC_HOLDER_KEYLEN, "sess10" as *u8), 1, "T9 exact holder sess10 matches" as *u8)
235
236 // T10..T12 the TOKEN itself, over the real fixture
237 gk_eq(r, fc_count(jr, n, "resA" as *u8, claimlit), 2, "T10 fence counts CLAIM frames only (resA=2)" as *u8)
238 gk_eq(r, fc_count(jr, n, "resB" as *u8, claimlit), 1, "T11 fence is per-resource (resB=1)" as *u8)
239 gk_eq(r, fc_count(jr, n, "resZ" as *u8, claimlit), 0, "T12 never-claimed resource has fence 0" as *u8)
240
241 // T13 MONOTONIC: one more CLAIM strictly increments -- the defining fence property
242 let before: i64 = fc_count(jr, n, "resA" as *u8, claimlit)
243 let fx2: *u8 = "100\tCLAIM\tresA\tsess1\t3600\tfirst\n110\tCLAIM\tresB\tsess9\t3600\tother\n120\tRELEASE\tresA\tsess1\t0\tdone\n130\tCLAIM\tresA\tsess10\t3600\tsecond\n140\tCLAIM\tresA\tsess11\t3600\tthird\n" as *u8
244 gk_write(path, fx2)
245 let n2: i64 = gk_read(path, jr, FC_WIN - 8)
246 gk_ok(r, n2 > n, "T13a fixture grew (re-claim appended)" as *u8)
247 gk_eq(r, fc_count(jr, n2, "resA" as *u8, claimlit), before + 1, "T13 MONOTONIC re-claim increments fence by exactly 1" as *u8)
248
249 // T14 strict atoi rejects garbage rather than reading it as 0
250 gk_eq(r, fc_atoi_strict("7" as *u8), 7, "T14a strict atoi parses digits" as *u8)
251 gk_eq(r, fc_atoi_strict("abc" as *u8), 0 - 1, "T14 neg-control non-numeric fence rejected, NOT read as 0" as *u8)
252
253 gk_rm(path)
254 return gk_result(r)
255}
256
257func fc_refuse(code: i64, res: *u8, actor: *u8, want: i64, cur: i64) -> i64 {
258 gv_puts("NX-FENCE REFUSE reason=" as *u8)
259 gv_puts(fc_reason_name(code))
260 gv_puts(" resource=" as *u8)
261 gv_puts(res)
262 gv_puts(" actor=" as *u8)
263 gv_puts(actor)
264 gv_puts(" presented=" as *u8)
265 gv_num(want)
266 gv_puts(" current=" as *u8)
267 gv_num(cur)
268 gv_puts("\n" as *u8)
269 return FC_EXIT_REFUSE
270}
271
272func main(argc: i64, argv: *i64) -> i64 {
273 if argc < 3 {
274 gv_puts("usage: nx_fence {token <journal> <resource> | verify <journal> <resource> <actor> <fence> | selftest <scratch-dir>}\n" as *u8)
275 sys_exit(FC_EXIT_USAGE)
276 return FC_EXIT_USAGE
277 }
278 let verb: *u8 = argv[1] as *u8
279 let arg2: *u8 = argv[2] as *u8
280 if gk_streq(verb, "selftest" as *u8) == 1 {
281 let rc: i64 = fc_selftest(arg2)
282 sys_exit(rc)
283 return rc
284 }
285 if argc < 4 {
286 gv_puts("token|verify need <journal> <resource>\n" as *u8)
287 sys_exit(FC_EXIT_USAGE)
288 return FC_EXIT_USAGE
289 }
290 let journal: *u8 = arg2
291 let res: *u8 = argv[3] as *u8
292 let claimlit: *u8 = "CLAIM" as *u8
293 let heldlit: *u8 = "state=HELD" as *u8
294 let holderkey: *u8 = "holder=" as *u8
295
296 let q: *u8 = sys_mmap(FC_WIN)
297 let n: i64 = gk_read(journal, q, FC_WIN - 8)
298 let ob: *u8 = sys_mmap(FC_OUT)
299 let ol: *i64 = sys_mmap(FC_SPAN) as *i64
300 ol[0] = 0
301
302 if gk_streq(verb, "token" as *u8) == 1 {
303 if n < 0 {
304 gv_puts("NX-FENCE UNREADABLE journal=" as *u8)
305 gv_puts(journal)
306 gv_puts("\n" as *u8)
307 sys_exit(FC_EXIT_IO)
308 return FC_EXIT_IO
309 }
310 let f: i64 = fc_count(q, n, res, claimlit)
311 let src: i64 = fc_claims_state(journal, res, ob, ol)
312 gv_puts("NX-FENCE token resource=" as *u8)
313 gv_puts(res)
314 gv_puts(" fence=" as *u8)
315 gv_num(f)
316 gv_puts(" claims_rc=" as *u8)
317 gv_num(src)
318 gv_puts("\n" as *u8)
319 if ol[0] > 0 { sys_write(1, ob, ol[0]) }
320 sys_exit(0)
321 return 0
322 }
323
324 if gk_streq(verb, "verify" as *u8) == 1 {
325 if argc < 6 {
326 gv_puts("verify needs <journal> <resource> <actor> <fence>\n" as *u8)
327 sys_exit(FC_EXIT_USAGE)
328 return FC_EXIT_USAGE
329 }
330 let actor: *u8 = argv[4] as *u8
331 let want: i64 = fc_atoi_strict(argv[5] as *u8)
332 var readable: i64 = 1
333 if n < 0 { readable = 0 }
334 var cur: i64 = 0
335 if readable == 1 { cur = fc_count(q, n, res, claimlit) }
336 var src: i64 = 0
337 var held: i64 = 0
338 var holder_ok: i64 = 0
339 if readable == 1 {
340 src = fc_claims_state(journal, res, ob, ol)
341 if src == 0 {
342 if ol[0] > 0 {
343 if gk_out_has(ob, ol[0], heldlit) == 1 { held = 1 }
344 let hp: i64 = gk_out_pos(ob, ol[0], holderkey)
345 if hp >= 0 { holder_ok = fc_tail_eq(ob, ol[0], hp + FC_HOLDER_KEYLEN, actor) }
346 } else { src = 0 - 1 }
347 }
348 }
349 let d: i64 = fc_decide(readable, src, held, holder_ok, want, cur)
350 if d == FC_ALLOW {
351 gv_puts("NX-FENCE ALLOW resource=" as *u8)
352 gv_puts(res)
353 gv_puts(" actor=" as *u8)
354 gv_puts(actor)
355 gv_puts(" fence=" as *u8)
356 gv_num(cur)
357 gv_puts("\n" as *u8)
358 sys_exit(0)
359 return 0
360 }
361 fc_refuse(d, res, actor, want, cur)
362 sys_exit(FC_EXIT_REFUSE)
363 return FC_EXIT_REFUSE
364 }
365
366 // ---- guard <journal> <resource> <actor> -- THE ENFORCEMENT VERB FOR A MUTATION PATH -----------
367 // exit 0 ALLOW | 3 REFUSE. Designed to be forked by /api/promote and branched on by EXIT CODE, so
368 // the control plane never parses a string: **** A GUARD THE CALLER MUST PARSE IS A GUARD THE CALLER
369 // WILL PARSE WRONG.
370 //
371 // !! DELIBERATE INVERSION OF FAIL-CLOSED, AND IT IS THE WHOLE DESIGN. Everywhere else in this organ
372 // an inability to determine REFUSES. Here it ALLOWS. The subject is the estate's DEPLOY PATH: a
373 // guard that refuses whenever it cannot read claims.jrnl would stop every deploy the moment the
374 // claims plane hiccups -- **** A GUARD WHOSE PROBE IS DOWN MANUFACTURES THE OUTAGE IT REPORTS
375 // (sni_router, 10,146 restarts, same estate). So this verb REFUSES ONLY ON A POSITIVE FINDING that
376 // a DIFFERENT actor holds the lane, and abstains on everything else. That also makes it
377 // NON-BREAKING BY CONSTRUCTION: an unclaimed target behaves exactly as it does today, so no
378 // existing flow changes until a lane is deliberately claimed.
379 // ALLOW when: journal unreadable - fork failed - state is FREE/EXPIRED/ABSENT - holder IS the actor.
380 // REFUSE when: state=HELD by someone who is not the actor. That is the fence: a lock stops
381 // CONCURRENT promotes (the promote lease already does that); this stops a STALE one.
382 if gk_streq(verb, "guard" as *u8) == 1 {
383 if argc < 5 {
384 gv_puts("guard needs <journal> <resource> <actor>\n" as *u8)
385 sys_exit(FC_EXIT_USAGE)
386 return FC_EXIT_USAGE
387 }
388 let gactor: *u8 = argv[4] as *u8
389 if n < 0 {
390 gv_puts("NX-FENCE GUARD-ABSTAIN reason=UNREADABLE-JOURNAL resource=" as *u8)
391 gv_puts(res)
392 gv_puts(" (allowing: a deploy guard that cannot read the plane must not stop deploys)\n" as *u8)
393 sys_exit(0)
394 return 0
395 }
396 let gsrc: i64 = fc_claims_state(journal, res, ob, ol)
397 if gsrc != 0 {
398 gv_puts("NX-FENCE GUARD-ABSTAIN reason=CLAIMS-UNAVAILABLE resource=" as *u8)
399 gv_puts(res)
400 gv_puts("\n" as *u8)
401 sys_exit(0)
402 return 0
403 }
404 if ol[0] <= 0 {
405 gv_puts("NX-FENCE GUARD-ABSTAIN reason=CLAIMS-NO-OUTPUT resource=" as *u8)
406 gv_puts(res)
407 gv_puts("\n" as *u8)
408 sys_exit(0)
409 return 0
410 }
411 if gk_out_has(ob, ol[0], heldlit) == 0 {
412 gv_puts("NX-FENCE GUARD-ALLOW reason=UNCLAIMED resource=" as *u8)
413 gv_puts(res)
414 gv_puts(" (no live holder: nothing to fence)\n" as *u8)
415 sys_exit(0)
416 return 0
417 }
418 let ghp: i64 = gk_out_pos(ob, ol[0], holderkey)
419 if ghp < 0 {
420 gv_puts("NX-FENCE GUARD-ABSTAIN reason=NO-HOLDER-FIELD resource=" as *u8)
421 gv_puts(res)
422 gv_puts("\n" as *u8)
423 sys_exit(0)
424 return 0
425 }
426 if fc_tail_eq(ob, ol[0], ghp + FC_HOLDER_KEYLEN, gactor) == 1 {
427 gv_puts("NX-FENCE GUARD-ALLOW reason=IS-HOLDER resource=" as *u8)
428 gv_puts(res)
429 gv_puts(" actor=" as *u8)
430 gv_puts(gactor)
431 gv_puts(" fence=" as *u8)
432 gv_num(fc_count(q, n, res, claimlit))
433 gv_puts("\n" as *u8)
434 sys_exit(0)
435 return 0
436 }
437 gv_puts("NX-FENCE GUARD-REFUSE reason=HELD-BY-ANOTHER resource=" as *u8)
438 gv_puts(res)
439 gv_puts(" actor=" as *u8)
440 gv_puts(gactor)
441 gv_puts(" fence=" as *u8)
442 gv_num(fc_count(q, n, res, claimlit))
443 gv_puts(" holder=" as *u8)
444 sys_write(1, (ob as i64 + ghp + FC_HOLDER_KEYLEN) as *u8, ol[0] - ghp - FC_HOLDER_KEYLEN)
445 sys_exit(FC_EXIT_REFUSE)
446 return FC_EXIT_REFUSE
447 }
448
449 gv_puts("NX-FENCE UNKNOWN-VERB\n" as *u8)
450 sys_exit(FC_EXIT_USAGE)
451 return FC_EXIT_USAGE
452}