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}