code wiki / (root) / nx_chat_demo_api.nx

nx_chat_demo_api.nx source

↩ module page · 430 lines · 18372 B

1// nx_chat_demo_api.nx -- the SAFE public front for a testable persistent-chat UI backed by C1's store. 2// v2 (2026-08-16): does the append/fetch IN-PROCESS on the seg-store, NOT by forking nx_chat_store. 3// WHY: MEASURED via curl to /mcp -- a list (read) returned in 44ms, but a post that FORKED nx_chat_store 4// to write hung to the tools-daemon exec deadline (~14s) and returned empty (exit -5), because the 5// write-path grandchild orphaned under the daemon's capture. A read fork was fine; a write fork was not. 6// In-process removes the fork entirely: this organ takes the plane lock, appends, and releases in its own 7// process -- the exact code path nx_chat_store's gate proves, just linked instead of exec'd. Fast + no orphan. 8// SECURITY BOUNDARY unchanged: only post/list, rooms forced into demo_, inputs sanitized + capped, budgets 9// from C1's conf refuse loudly. Destructive verbs do not exist here. REFEREE: nx_chat_demo_api_gate. 10// DEBT (named, not a silent twin): the append/fetch logic is duplicated from nx_chat_store; the consolidation 11// is to extract nx_chat_store_lib that BOTH this front and the CLI import (comms.plan chatstore-lib-extract). 12// license_tier: ORIGINAL No hw writes (Rule 26). 13import "nx_syscalls.nx" 14import "nx_itoa_lib.nx" 15import "nx_store_seed_lib.nx" 16import "nx_seg_store.nx" 17 18const CDA_EXIT_OK: i64 = 0 19const CDA_EXIT_USAGE: i64 = 2 20const CDA_EXIT_REFUSED: i64 = 3 21const CDA_ROOM_MAX: i64 = 24 22const CDA_NAME_MAX: i64 = 24 23const CDA_TEXT_MAX: i64 = 280 24const CDA_FETCH_MAX: i64 = 200 25const CDA_PFX_CAP: i64 = 256 26const CDA_ROW_OVH: i64 = 128 27const CDA_WSLACK: i64 = 65536 28const CDA_CONF_CAP: i64 = 192 29const CDA_OUTCAP: i64 = 262144 30const CDA_NL: i64 = 10 31const CDA_PIPE: i64 = 124 32const CDA_KIND_TEXT: i64 = 1 33const CDA_KIND_SYS: i64 = 4 34const CHATCONF: *u8 = "knowledge/comms/chatstore.conf" 35const CSA_ERR_CONF: i64 = 0 - 3 36const CSA_ERR_BODY: i64 = 0 - 10 37const CSA_ERR_LOCK: i64 = 0 - 4 38const CSA_ERR_MSGS: i64 = 0 - 11 39const CSA_ERR_BYTES: i64 = 0 - 12 40const CSA_ERR_CORRUPT: i64 = 0 - 5 41 42func cda_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 43func cda_wn(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 } 44func cda_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 45func cda_eq(a: *u8, b: *u8) -> i64 { 46 var i: i64 = 0 47 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 48 if b[i] != (0 as u8) { return 0 } 49 return 1 50} 51func cda_ident(src: *u8, dst: *u8, cap: i64) -> i64 { 52 var o: i64 = 0 53 var i: i64 = 0 54 while src[i] != (0 as u8) { 55 if o >= cap { break } 56 let c: i64 = src[i] as i64 57 if c >= 48 { if c <= 57 { dst[o] = c as u8; o = o + 1 } } 58 if c >= 97 { if c <= 122 { dst[o] = c as u8; o = o + 1 } } 59 if c >= 65 { if c <= 90 { dst[o] = (c + 32) as u8; o = o + 1 } } 60 if c == 95 { dst[o] = 95 as u8; o = o + 1 } 61 i = i + 1 62 } 63 dst[o] = 0 as u8 64 return o 65} 66func cda_text(src: *u8, dst: *u8, cap: i64) -> i64 { 67 var o: i64 = 0 68 var i: i64 = 0 69 while src[i] != (0 as u8) { 70 if o >= cap { break } 71 let c: i64 = src[i] as i64 72 if c >= 32 { if c <= 126 { dst[o] = c as u8; o = o + 1 } } 73 i = i + 1 74 } 75 dst[o] = 0 as u8 76 return o 77} 78func cda_demoroom(clean: *u8, out: *u8) -> i64 { 79 var o: i64 = 0 80 let pre: *u8 = "demo_" as *u8 81 while pre[o] != (0 as u8) { out[o] = pre[o]; o = o + 1 } 82 var i: i64 = 0 83 while clean[i] != (0 as u8) { out[o] = clean[i]; o = o + 1; i = i + 1 } 84 out[o] = 0 as u8 85 return o 86} 87func cda_jesc(out: *u8, o: i64, s: *u8, n: i64) -> i64 { 88 var i: i64 = 0 89 while i < n { 90 let c: i64 = s[i] as i64 91 if c == 34 { out[o] = 92 as u8; o = o + 1; out[o] = 34 as u8; o = o + 1 } else { 92 if c == 92 { out[o] = 92 as u8; o = o + 1; out[o] = 92 as u8; o = o + 1 } else { 93 if c < 32 { out[o] = 32 as u8; o = o + 1 } else { 94 out[o] = c as u8; o = o + 1 95 } } } 96 i = i + 1 97 } 98 return o 99} 100func cda_hexe(src: *u8, n: i64, dst: *u8) -> i64 { 101 var i: i64 = 0 102 while i < n { 103 let b: i64 = src[i] as i64 104 let hi: i64 = b / 16 105 let lo: i64 = b % 16 106 if hi < 10 { dst[i*2] = (48 + hi) as u8 } else { dst[i*2] = (87 + hi) as u8 } 107 if lo < 10 { dst[i*2+1] = (48 + lo) as u8 } else { dst[i*2+1] = (87 + lo) as u8 } 108 i = i + 1 109 } 110 return n * 2 111} 112func cda_hexv(c: i64) -> i64 { 113 if c >= 48 { if c <= 57 { return c - 48 } } 114 if c >= 97 { if c <= 102 { return c - 87 } } 115 return 0 - 1 116} 117func cda_catn(d: *u8, o: i64, v: i64) -> i64 { 118 if v == 0 { d[o] = 48 as u8; return o + 1 } 119 let tmp: *u8 = sys_mmap(24) 120 var k: i64 = 0 121 var m: i64 = v 122 while m > 0 { tmp[k] = (48 + m % 10) as u8; m = m / 10; k = k + 1 } 123 var j: i64 = k - 1 124 while j >= 0 { d[o] = tmp[j]; o = o + 1; j = j - 1 } 125 return o 126} 127func cda_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 } return o } 128func cda_confs(key: *u8, out: *u8, cap: i64) -> i64 { 129 let ol: *i64 = sts_mm(16) as *i64 130 let b: *u8 = sys_read_file(CHATCONF, ol) 131 let n: i64 = ol[0] 132 if b as i64 == 0 { return 0 - 1 } 133 if n <= 0 { return 0 - 1 } 134 let kl: i64 = cda_len(key) 135 var i: i64 = 0 136 while i < n { 137 var j: i64 = 0 138 var hit: i64 = 1 139 while j < kl { 140 if i + j >= n { hit = 0; break } 141 if b[i+j] != key[j] { hit = 0; break } 142 j = j + 1 143 } 144 if hit == 1 { if i + kl >= n { hit = 0 } else { if b[i+kl] != (CDA_PIPE as u8) { hit = 0 } } } 145 if hit == 1 { 146 var v: i64 = i + kl + 1 147 var o: i64 = 0 148 while v < n { if b[v] == (CDA_NL as u8) { break } if o < cap - 1 { out[o] = b[v]; o = o + 1 } v = v + 1 } 149 out[o] = 0 as u8 150 return o 151 } 152 while i < n { if b[i] == (CDA_NL as u8) { break } i = i + 1 } 153 i = i + 1 154 } 155 return 0 - 1 156} 157func cda_confn(key: *u8) -> i64 { 158 let v: *u8 = sts_mm(CDA_CONF_CAP) 159 let l: i64 = cda_confs(key, v, CDA_CONF_CAP) 160 if l <= 0 { return 0 - 1 } 161 return sts_atoi(v, l) 162} 163func cda_prefix(room: *u8, out: *u8) -> i64 { 164 let rl: i64 = cda_confs("store_root" as *u8, out, CDA_PFX_CAP - 96) 165 if rl <= 0 { return 0 - 2 } 166 var o: i64 = rl 167 o = ss_cat(out, o, "chat_" as *u8) 168 o = ss_cat(out, o, room) 169 o = ss_cat(out, o, "-" as *u8) 170 out[o] = 0 as u8 171 return o 172} 173func cda_rowbuild(seq: i64, kind: i64, sender: *u8, body: *u8, blen: i64, out: *u8) -> i64 { 174 var o: i64 = ss_cat(out, 0, "m|" as *u8) 175 o = ss_catn(out, o, seq) 176 o = ss_cat(out, o, "|" as *u8) 177 o = ss_catn(out, o, sys_now_realtime_ms()) 178 o = ss_cat(out, o, "|" as *u8) 179 o = ss_catn(out, o, kind) 180 o = ss_cat(out, o, "|" as *u8) 181 o = ss_cat(out, o, sender) 182 o = ss_cat(out, o, "|" as *u8) 183 o = o + cda_hexe(body, blen, (out as i64 + o) as *u8) 184 out[o] = 0 as u8 185 return o 186} 187func cda_delkey(seq: i64, out: *u8) -> i64 { 188 var o: i64 = ss_cat(out, 0, "del:" as *u8) 189 o = ss_catn(out, o, seq) 190 out[o] = 0 as u8 191 return o 192} 193func cda_put_key(prefix: *u8, key: *u8, val: *u8, vlen: i64) -> i64 { 194 let w: *i64 = ss_begin_cap(vlen + CDA_WSLACK) 195 if ss_add(w, 1, key, val, vlen) < 0 { return 0 - 1 } 196 if ss_commit_cas(prefix, w, ss_next_segid(prefix), SS_CAS_ANY) != 0 { return 0 - 1 } 197 return 0 198} 199func csa_append(room: *u8, sender: *u8, body: *u8, blen: i64) -> i64 { 200 let bmax: i64 = cda_confn("body_bytes_max" as *u8) 201 if bmax < 0 { return CSA_ERR_CONF } 202 let bcliff: i64 = cda_confn("room_bytes_cliff" as *u8) 203 if bcliff < 0 { return CSA_ERR_CONF } 204 let mcliff: i64 = cda_confn("room_msgs_cliff" as *u8) 205 if mcliff < 0 { return CSA_ERR_CONF } 206 if blen > bmax { return CSA_ERR_BODY } 207 let pfx: *u8 = sts_mm(CDA_PFX_CAP) 208 if cda_prefix(room, pfx) < 0 { return CSA_ERR_CONF } 209 let lk: i64 = sts_lock(pfx) 210 if lk < 0 { return CSA_ERR_LOCK } 211 let pq: *i64 = sts_mm(16) as *i64 212 let lq: *i64 = sts_mm(16) as *i64 213 var n: i64 = 0 214 if ss_get(pfx, "q:n" as *u8, pq, lq) == 1 { 215 n = sts_atoi(pq[0] as *u8, lq[0]) 216 } else { 217 // auto-open with a DEFERRED commit too (no fsync): write q:0=sysrow, q:n=1, meta:bytes in one 218 // segment. The old path used sts_seed which fsyncs, and under disk pressure that stalled past the 219 // caller's deadline BEFORE the room was created, so every retry re-stalled. Deferred fixes it. 220 let ob: *u8 = "room-open" as *u8 221 let obl: i64 = 9 222 let srow: *u8 = sts_mm(CDA_ROW_OVH + 64 + obl * 2 + 8) 223 let so: i64 = cda_rowbuild(1, CDA_KIND_SYS, sender, ob, obl, srow) 224 let k0: *u8 = sts_mm(64) 225 sts_rowkey(0, k0) 226 let c1: *u8 = sts_mm(32) 227 let cl1: i64 = ss_catn(c1, 0, 1) 228 let m0: *u8 = sts_mm(32) 229 let ml0: i64 = ss_catn(m0, 0, obl) 230 let ow: *i64 = ss_begin_cap(so + CDA_WSLACK) 231 if ss_add(ow, 1, k0, srow, so) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 232 if ss_add(ow, 1, "q:n" as *u8, c1, cl1) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 233 if ss_add(ow, 1, "meta:bytes" as *u8, m0, ml0) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 234 if ss_commit_deferred(pfx, ow, ss_next_segid(pfx)) != 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 235 n = 1 236 } 237 if n >= mcliff { sts_unlock(lk) return CSA_ERR_MSGS } 238 var bts: i64 = 0 239 if ss_get(pfx, "meta:bytes" as *u8, pq, lq) == 1 { bts = sts_atoi(pq[0] as *u8, lq[0]) } 240 if bts + blen > bcliff { sts_unlock(lk) return CSA_ERR_BYTES } 241 let seq: i64 = n + 1 242 let row: *u8 = sts_mm(CDA_ROW_OVH + 64 + blen * 2 + 8) 243 let rl: i64 = cda_rowbuild(seq, CDA_KIND_TEXT, sender, body, blen, row) 244 let key: *u8 = sts_mm(64) 245 sts_rowkey(n, key) 246 let cb: *u8 = sts_mm(32) 247 let cl: i64 = ss_catn(cb, 0, seq) 248 let mb: *u8 = sts_mm(32) 249 let ml: i64 = ss_catn(mb, 0, bts + blen) 250 let w: *i64 = ss_begin_cap(rl + CDA_WSLACK) 251 if ss_add(w, 1, key, row, rl) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 252 if ss_add(w, 1, "q:n" as *u8, cb, cl) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 253 if ss_add(w, 1, "meta:bytes" as *u8, mb, ml) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT } 254 // DEFERRED COMMIT (perf): write the segment + manifest to the page cache and return -- the message is 255 // visible to the next read immediately and survives process death; the fsync to disk happens on the 256 // store's background sync. We hold the plane flock across the whole read-modify-write, so no CAS is 257 // needed (nothing else can commit between our ss_get and this commit). This keeps a post FAST even 258 // when the box's disk is under pressure, instead of stalling on a synchronous fsync. 259 let rc: i64 = ss_commit_deferred(pfx, w, ss_next_segid(pfx)) 260 sts_unlock(lk) 261 if rc != 0 { return CSA_ERR_CORRUPT } 262 return seq 263} 264func csa_fetch(room: *u8, since: i64, maxn: i64, outbuf: *u8, outcap: i64) -> i64 { 265 let pfx: *u8 = sts_mm(CDA_PFX_CAP) 266 if cda_prefix(room, pfx) < 0 { return 0 - 1 } 267 let pq: *i64 = sts_mm(16) as *i64 268 let lq: *i64 = sts_mm(16) as *i64 269 if ss_get(pfx, "q:n" as *u8, pq, lq) != 1 { return 0 - 1 } 270 let n: i64 = sts_atoi(pq[0] as *u8, lq[0]) 271 let h: *i64 = ss_open_cached(pfx) 272 if h as i64 == 0 { return 0 - 5 } 273 let key: *u8 = sts_mm(64) 274 let dk: *u8 = sts_mm(64) 275 let pr2: *i64 = sts_mm(16) as *i64 276 let lr2: *i64 = sts_mm(16) as *i64 277 var o: i64 = 0 278 var returned: i64 = 0 279 var i: i64 = since 280 while i < n { 281 if returned >= maxn { break } 282 sts_rowkey(i, key) 283 if ss_hget(h, key, pq, lq) != 1 { return 0 - 5 } 284 let rv: *u8 = pq[0] as *u8 285 let rl: i64 = lq[0] 286 cda_delkey(i + 1, dk) 287 if ss_hget(h, dk, pr2, lr2) == 1 { } else { 288 if o + rl + 1 < outcap { 289 var k: i64 = 0 290 while k < rl { outbuf[o] = rv[k]; o = o + 1; k = k + 1 } 291 outbuf[o] = CDA_NL as u8 292 o = o + 1 293 returned = returned + 1 294 } 295 } 296 i = i + 1 297 } 298 return o 299} 300func cda_post(room_in: *u8, name_in: *u8, text_in: *u8) -> i64 { 301 let rclean: *u8 = sts_mm(64) 302 if cda_ident(room_in, rclean, CDA_ROOM_MAX) < 1 { cda_w("{\"ok\":0,\"err\":\"bad-room\"}\n" as *u8) return CDA_EXIT_REFUSED } 303 let nclean: *u8 = sts_mm(64) 304 if cda_ident(name_in, nclean, CDA_NAME_MAX) < 1 { cda_w("{\"ok\":0,\"err\":\"bad-name\"}\n" as *u8) return CDA_EXIT_REFUSED } 305 let tclean: *u8 = sts_mm(CDA_TEXT_MAX + 8) 306 let tlen: i64 = cda_text(text_in, tclean, CDA_TEXT_MAX) 307 if tlen < 1 { cda_w("{\"ok\":0,\"err\":\"empty-text\"}\n" as *u8) return CDA_EXIT_REFUSED } 308 let room: *u8 = sts_mm(64) 309 cda_demoroom(rclean, room) 310 let seq: i64 = csa_append(room, nclean, tclean, tlen) 311 if seq < 0 { 312 if seq == CSA_ERR_MSGS { cda_w("{\"ok\":0,\"err\":\"room-full-messages\"}\n" as *u8) return CDA_EXIT_REFUSED } 313 if seq == CSA_ERR_BYTES { cda_w("{\"ok\":0,\"err\":\"room-full-bytes\"}\n" as *u8) return CDA_EXIT_REFUSED } 314 if seq == CSA_ERR_BODY { cda_w("{\"ok\":0,\"err\":\"body-too-big\"}\n" as *u8) return CDA_EXIT_REFUSED } 315 if seq == CSA_ERR_LOCK { cda_w("{\"ok\":0,\"err\":\"busy-retry\"}\n" as *u8) return CDA_EXIT_REFUSED } 316 cda_w("{\"ok\":0,\"err\":\"append-failed\"}\n" as *u8) 317 return CDA_EXIT_REFUSED 318 } 319 let jb: *u8 = sts_mm(256) 320 var o: i64 = cda_cat(jb, 0, "{\"ok\":1,\"room\":\"" as *u8) 321 o = cda_cat(jb, o, room) 322 o = cda_cat(jb, o, "\",\"seq\":" as *u8) 323 o = cda_catn(jb, o, seq) 324 o = cda_cat(jb, o, "}\n" as *u8) 325 cda_wn(jb, o) 326 return CDA_EXIT_OK 327} 328func cda_atoi(b: *u8, n: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < n { let c: i64 = b[i] as i64; if c < 48 { break } if c > 57 { break } v = v * 10 + (c - 48); i = i + 1 } return v } 329func cda_list(room_in: *u8, since_in: *u8) -> i64 { 330 let rclean: *u8 = sts_mm(64) 331 if cda_ident(room_in, rclean, CDA_ROOM_MAX) < 1 { cda_w("{\"ok\":0,\"err\":\"bad-room\"}\n" as *u8) return CDA_EXIT_REFUSED } 332 let room: *u8 = sts_mm(64) 333 cda_demoroom(rclean, room) 334 var since: i64 = 0 335 if since_in as i64 != 0 { since = cda_atoi(since_in, cda_len(since_in)) } 336 let raw: *u8 = sts_mm(CDA_OUTCAP) 337 let rn: i64 = csa_fetch(room, since, CDA_FETCH_MAX, raw, CDA_OUTCAP) 338 let jb: *u8 = sts_mm(CDA_OUTCAP) 339 var o: i64 = cda_cat(jb, 0, "{\"ok\":1,\"room\":\"" as *u8) 340 o = cda_cat(jb, o, room) 341 o = cda_cat(jb, o, "\",\"msgs\":[" as *u8) 342 var total: i64 = 0 343 if rn > 0 { 344 var i: i64 = 0 345 var first: i64 = 1 346 while i < rn { 347 var ls: i64 = i 348 while i < rn { if raw[i] == (CDA_NL as u8) { break } i = i + 1 } 349 let le: i64 = i 350 i = i + 1 351 if le - ls > 4 { if raw[ls] == (109 as u8) { if raw[ls+1] == (CDA_PIPE as u8) { 352 var p: i64 = ls + 2 353 var seq: i64 = 0 354 while p < le { let c: i64 = raw[p] as i64; if c == CDA_PIPE { break } seq = seq * 10 + (c - 48); p = p + 1 } 355 p = p + 1 356 var ep: i64 = 0 357 while p < le { let c2: i64 = raw[p] as i64; if c2 == CDA_PIPE { break } ep = ep * 10 + (c2 - 48); p = p + 1 } 358 p = p + 1 359 var kind: i64 = 0 360 while p < le { let c3: i64 = raw[p] as i64; if c3 == CDA_PIPE { break } kind = kind * 10 + (c3 - 48); p = p + 1 } 361 p = p + 1 362 let snds: i64 = p 363 while p < le { if raw[p] == (CDA_PIPE as u8) { break } p = p + 1 } 364 let snde: i64 = p 365 p = p + 1 366 let hxs: i64 = p 367 if kind == CDA_KIND_TEXT { 368 let hxlen: i64 = le - hxs 369 let bodyb: *u8 = sts_mm(hxlen / 2 + 4) 370 var bo: i64 = 0 371 var hh: i64 = hxs 372 var okhex: i64 = 1 373 while hh + 1 < le { 374 let hi: i64 = cda_hexv(raw[hh] as i64) 375 let lo: i64 = cda_hexv(raw[hh+1] as i64) 376 if hi < 0 { okhex = 0; break } 377 if lo < 0 { okhex = 0; break } 378 bodyb[bo] = (hi * 16 + lo) as u8 379 bo = bo + 1 380 hh = hh + 2 381 } 382 if okhex == 1 { 383 if first == 0 { jb[o] = 44 as u8; o = o + 1 } 384 first = 0 385 o = cda_cat(jb, o, "{\"seq\":" as *u8) 386 o = cda_catn(jb, o, seq) 387 o = cda_cat(jb, o, ",\"t\":" as *u8) 388 o = cda_catn(jb, o, ep) 389 o = cda_cat(jb, o, ",\"from\":\"" as *u8) 390 o = cda_jesc(jb, o, (raw as i64 + snds) as *u8, snde - snds) 391 o = cda_cat(jb, o, "\",\"text\":\"" as *u8) 392 o = cda_jesc(jb, o, bodyb, bo) 393 o = cda_cat(jb, o, "\"}" as *u8) 394 total = total + 1 395 } 396 } 397 } } } 398 } 399 } 400 o = cda_cat(jb, o, "],\"total\":" as *u8) 401 o = cda_catn(jb, o, total) 402 o = cda_cat(jb, o, "}\n" as *u8) 403 cda_wn(jb, o) 404 return CDA_EXIT_OK 405} 406func cda_usage() -> i64 { 407 cda_w("{\"ok\":0,\"err\":\"usage: post <room> <name> <text> | list <room> [since]\"}\n" as *u8) 408 return CDA_EXIT_USAGE 409} 410func main(argc: i64, argv: *i64) -> i64 { 411 if argc < 2 { let u: i64 = cda_usage() sys_exit(u) return u } 412 let v: *u8 = argv[1] as *u8 413 if cda_eq(v, "post" as *u8) == 1 { 414 if argc < 5 { let u: i64 = cda_usage() sys_exit(u) return u } 415 let r: i64 = cda_post(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8) 416 sys_exit(r) 417 return r 418 } 419 if cda_eq(v, "list" as *u8) == 1 { 420 if argc < 3 { let u: i64 = cda_usage() sys_exit(u) return u } 421 var since: *u8 = 0 as *u8 422 if argc > 3 { since = argv[3] as *u8 } 423 let r: i64 = cda_list(argv[2] as *u8, since) 424 sys_exit(r) 425 return r 426 } 427 let u2: i64 = cda_usage() 428 sys_exit(u2) 429 return u2 430}