code wiki / _hdl_build / nx_sovgit_git.nx

nx_sovgit_git.nx source

↩ module page · 1843 lines · 94430 B

1// nx_sovgit_git.nx -- ★SOVGIT X2: SOVEREIGN MULTI-REPO SMART-HTTP git host on nx_http_server sockets, 2// NO nginx/framework. v0 smart protocol (pkt-line advert + upload-pack pack stream + receive-pack push), 3// multi-repo routed by path (/git/<repo>/...), push-to-create, multi-ref push, HEAD symref advert. 4// Legacy single-repo paths (/git/info/refs) keep serving knowledge/_sovgit_repo (back-compat, X1 demo). 5// SCALE ENVELOPE (declared in banner, guarded in code -- fail LOUD, never a corrupt pack): 6// push request <= REQ cap (128 MiB buffer; the TLS edge proxies bodies <= ~2 MiB today = the wire cap, 7// so an over-edge push must stay small -- incremental packs keep beats tiny; big seeds go loopback) 8// clone pack <= PACK cap (128 MiB; overflow -> 500, never truncated silently) 9// one object <= 64 MiB inflated 10// branch names: single-level refs/heads/<name> (no '/' subdirs yet); ref deletes: unsupported (ng) 11// Buffers are allocated ONCE in main and reused per request (bounded-VSZ discipline). 12// Reuses X0 (objects/sha/zlib) + X1b/X1d (pack gen/parse). license_tier: ORIGINAL 13import "nx_sovgit_obj.nx" 14import "nx_http_server.nx" 15import "nx_login_gate.nx" 16const K_MAGIC_65536: i64 = 65536 17const K_MAGIC_4096: i64 = 4096 18const K_MAGIC_1024: i64 = 1024 19const K_MAGIC_2048: i64 = 2048 20const K_MAGIC_8192: i64 = 8192 21const K_MAGIC_18691: i64 = 18691 22// Rule 11: character codes are NAMED, not literals. These are the exact bytes the path-portability 23// gate, the HTML escaper and the URL decoder reason about -- an unnamed 60 tells a reader nothing. 24// MUST sit above every reader: nx_cc refuses a const used before declaration rather than silently 25// reading 0 (the fails-open-decl defense -- it caught this exact mistake on my first attempt). 26const CH_LF: i64 = 10 27const CH_SP: i64 = 32 28const CH_DQUOTE: i64 = 34 29const CH_PCT: i64 = 37 30const CH_AMP: i64 = 38 31const CH_STAR: i64 = 42 32const CH_PLUS: i64 = 43 33const CH_DOT: i64 = 46 34const CH_SLASH: i64 = 47 35const CH_0: i64 = 48 36const CH_9: i64 = 57 37const CH_COLON: i64 = 58 38const CH_LT: i64 = 60 39const CH_GT: i64 = 62 40const CH_QUERY: i64 = 63 41const CH_A_UP: i64 = 65 42const CH_F_UP: i64 = 70 43const CH_Z_UP: i64 = 90 44const CH_BSLASH: i64 = 92 45const CH_A_LO: i64 = 97 46const CH_F_LO: i64 = 102 47const CH_PIPE: i64 = 124 48const CH_DEL: i64 = 127 49const HEX_BASE: i64 = 16 50const CASE_DELTA: i64 = 32 51const HEXA_LO_ADJ: i64 = 87 52const HEXA_UP_ADJ: i64 = 55 53const NAME_MAX: i64 = 255 54 55func lg(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 } 56func lgn(v: i64) -> i64 { 57 let b: *u8 = sys_mmap(32) 58 var x: i64 = v 59 var neg: i64 = 0 60 if x < 0 { neg = 1; x = 0 - x } 61 var i: i64 = 31 62 if x == 0 { b[i] = 48 as u8; i = i - 1 } 63 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 } 64 if neg == 1 { b[i] = 45 as u8; i = i - 1 } 65 sys_write(2, (b as i64 + i + 1) as *u8, 31 - i) 66 sys_munmap(b, 32) 67 return 0 68} 69func sstrlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 70func scopy(dst: *u8, at: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[at + i] = s[i]; i = i + 1 } return at + i } 71func udec(out: *u8, at: i64, v: i64) -> i64 { 72 let tmp: *u8 = sys_mmap(32); var x: i64 = v; var i: i64 = 31 73 if x == 0 { tmp[i] = 48 as u8; i = i - 1 } 74 while x > 0 { tmp[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 } 75 var a: i64 = at; var j: i64 = i + 1; while j < 32 { out[a] = tmp[j]; a = a + 1; j = j + 1 } return a 76} 77func wfile(path: *u8, buf: *u8, n: i64) -> i64 { let fd: i64 = sys_openat_wr(path, 0x1a4); if fd < 0 { return 0 - 1 } sys_write(fd, buf, n); sys_close(fd); return 0 } 78func wstr(path: *u8, s: *u8) -> i64 { return wfile(path, s, sstrlen(s)) } 79// substring: needle (NUL-term) inside p[0..plen]? 80func pcontains(p: *u8, plen: i64, needle: *u8) -> i64 { 81 let m: i64 = sstrlen(needle) 82 if m == 0 { return 1 } 83 var i: i64 = 0 84 while i + m <= plen { 85 var k: i64 = 0 86 while k < m { if p[i + k] != needle[k] { k = m + 9 } else { k = k + 1 } } 87 if k == m { return 1 } 88 i = i + 1 89 } 90 return 0 91} 92// exact match: a[0..alen] == NUL-term b? 93func seq(a: *u8, alen: i64, b: *u8) -> i64 { 94 let bl: i64 = sstrlen(b) 95 if alen != bl { return 0 } 96 var i: i64 = 0 97 while i < alen { if a[i] != b[i] { return 0 } i = i + 1 } 98 return 1 99} 100 101// ---- pkt-line ---- 102func hex4(out: *u8, at: i64, v: i64) -> i64 { 103 var i: i64 = 0 104 while i < 4 { 105 let nib: i64 = (v >> ((3 - i) * 4)) & 15 106 var c: i64 = 48 + nib 107 if nib > 9 { c = 87 + nib } 108 out[at + i] = c as u8; i = i + 1 109 } 110 return at + 4 111} 112func pkt(out: *u8, at: i64, payload: *u8, plen: i64) -> i64 { 113 var a: i64 = hex4(out, at, plen + 4) 114 var i: i64 = 0; while i < plen { out[a + i] = payload[i]; i = i + 1 } 115 return a + plen 116} 117func pkts(out: *u8, at: i64, s: *u8) -> i64 { return pkt(out, at, s, sstrlen(s)) } 118func pktflush(out: *u8, at: i64) -> i64 { out[at] = 48 as u8; out[at + 1] = 48 as u8; out[at + 2] = 48 as u8; out[at + 3] = 48 as u8; return at + 4 } 119// read a 4-hex pkt-line length at b[at] 120func rd_hex4(b: *u8, at: i64) -> i64 { 121 var v: i64 = 0; var i: i64 = 0 122 while i < 4 { 123 let c: i64 = b[at + i] as i64 124 var d: i64 = 0 125 if c >= 48 { if c <= 57 { d = c - 48 } } 126 if c >= 97 { if c <= 102 { d = c - 87 } } 127 if c >= 65 { if c <= 70 { d = c - 55 } } 128 v = v * 16 + d; i = i + 1 129 } 130 return v 131} 132 133// ---- pack gen (X1b) ---- 134func be32w(out: *u8, at: i64, v: i64) -> i64 { out[at] = ((v >> 24) & 255) as u8; out[at + 1] = ((v >> 16) & 255) as u8; out[at + 2] = ((v >> 8) & 255) as u8; out[at + 3] = (v & 255) as u8; return at + 4 } 135func pk_objhdr(out: *u8, at: i64, typ: i64, size: i64) -> i64 { 136 var a: i64 = at; var b: i64 = (typ << 4) | (size & 15); var s: i64 = size >> 4 137 while s > 0 { out[a] = (b | 128) as u8; a = a + 1; b = s & 127; s = s >> 7 } 138 out[a] = b as u8; a = a + 1; return a 139} 140func pk_object(out: *u8, at: i64, typ: i64, content: *u8, clen: i64) -> i64 { 141 var a: i64 = pk_objhdr(out, at, typ, clen) 142 let z: *u8 = (out as i64 + a) as *u8 143 let zl: i64 = sg_zwrap(content, clen, z) 144 return a + zl 145} 146 147// ---- pack parse (X1d): unpack `pack`[0..plen] -> loose objects under objroot. returns object count (or -1). ---- 148// ---- PATH-PORTABILITY GATE (receive-pack) ---- 149// A forge that accepts a path one of its own clients cannot check out has accepted a corrupt 150// commit. Measured 2026-07-31: 10 such paths in HEAD made the tree UN-CHECKOUTABLE on Windows -- 151// read-tree/checkout/clone all abort -- and the check-in loop silently no-opped for 5 days. 152// git ships core.protectNTFS/protectHFS for exactly this; we had NOTHING. Fail-closed on the 153// WHOLE push (unpack failed -> every ref ng), never a partial accept. 154func sg_lc(c: i64) -> i64 { if c >= CH_A_UP { if c <= CH_Z_UP { return c + CASE_DELTA } } return c } 155func sg_ieq(nm: *u8, nl: i64, lit: *u8) -> i64 { 156 let ll: i64 = sstrlen(lit) 157 if nl != ll { return 0 } 158 var i: i64 = 0 159 while i < nl { if sg_lc(nm[i] as i64) != sg_lc(lit[i] as i64) { return 0 } i = i + 1 } 160 return 1 161} 162func sg_name_portable(nm: *u8, nl: i64) -> i64 { 163 if nl <= 0 { return 0 } 164 if nl > NAME_MAX { return 0 } 165 var i: i64 = 0 166 while i < nl { 167 let c: i64 = nm[i] as i64 168 if c < CH_SP { return 0 } 169 if c == CH_DEL { return 0 } 170 if c == CH_LT { return 0 } 171 if c == CH_GT { return 0 } 172 if c == CH_COLON { return 0 } 173 if c == CH_DQUOTE { return 0 } 174 if c == CH_PIPE { return 0 } 175 if c == CH_QUERY { return 0 } 176 if c == CH_STAR { return 0 } 177 if c == CH_BSLASH { return 0 } 178 if c == CH_SLASH { return 0 } 179 i = i + 1 180 } 181 let last: i64 = nm[nl - 1] as i64 182 if last == CH_DOT { return 0 } 183 if last == CH_SP { return 0 } 184 if nl == 1 { if nm[0] == (46 as u8) { return 0 } } 185 if nl == 2 { if nm[0] == (46 as u8) { if nm[1] == (46 as u8) { return 0 } } } 186 if sg_ieq(nm, nl, ".git" as *u8) == 1 { return 0 } 187 var sl: i64 = nl 188 var k: i64 = 0 189 var seen: i64 = 0 190 while k < nl { 191 if seen == 0 { if nm[k] == (46 as u8) { sl = k; seen = 1 } } 192 k = k + 1 193 } 194 if sg_ieq(nm, sl, "nul" as *u8) == 1 { return 0 } 195 if sg_ieq(nm, sl, "con" as *u8) == 1 { return 0 } 196 if sg_ieq(nm, sl, "prn" as *u8) == 1 { return 0 } 197 if sg_ieq(nm, sl, "aux" as *u8) == 1 { return 0 } 198 if sg_ieq(nm, sl, "conin$" as *u8) == 1 { return 0 } 199 if sg_ieq(nm, sl, "conout$" as *u8) == 1 { return 0 } 200 if sl == 4 { 201 let c0: i64 = sg_lc(nm[0] as i64) 202 let d: i64 = nm[3] as i64 203 if d >= 48 { if d <= 57 { 204 if c0 == 99 { if sg_lc(nm[1] as i64) == 111 { if sg_lc(nm[2] as i64) == 109 { return 0 } } } 205 if c0 == 108 { if sg_lc(nm[1] as i64) == 112 { if sg_lc(nm[2] as i64) == 116 { return 0 } } } 206 } } 207 } 208 return 1 209} 210func sg_tree_names_ok(t: *u8, tlen: i64, oidlen: i64) -> i64 { 211 var p: i64 = 0 212 var bad: i64 = 0 213 var run: i64 = 1 214 while run == 1 { 215 if p >= tlen { run = 0 } else { 216 var q: i64 = p 217 var fs: i64 = 0 218 while fs == 0 { 219 if q >= tlen { fs = 2 } else { if t[q] == (32 as u8) { fs = 1 } else { q = q + 1 } } 220 } 221 if fs == 2 { bad = 1; run = 0 } else { 222 q = q + 1 223 let ns: i64 = q 224 var fz: i64 = 0 225 while fz == 0 { 226 if q >= tlen { fz = 2 } else { if t[q] == (0 as u8) { fz = 1 } else { q = q + 1 } } 227 } 228 if fz == 2 { bad = 1; run = 0 } else { 229 let nl: i64 = q - ns 230 if sg_name_portable((t as i64 + ns) as *u8, nl) == 0 { bad = 1; run = 0 } 231 p = q + 1 + oidlen 232 } 233 } 234 } 235 } 236 if bad == 1 { return 0 } 237 return 1 238} 239 240func unpack_into(objroot: *u8, pack: *u8, plen: i64) -> i64 { 241 if plen < 12 { return 0 - 1 } 242 if pack[0] != (80 as u8) { return 0 - 1 } 243 let count: i64 = ((pack[8] as i64) << 24) | ((pack[9] as i64) << 16) | ((pack[10] as i64) << 8) | (pack[11] as i64) 244 var pos: i64 = 12 245 var i: i64 = 0 246 var ok: i64 = 1 247 let wh: *u8 = sys_mmap(80) 248 while i < count { 249 if ok == 1 { 250 if pos >= plen { ok = 0 } else { 251 var byte: i64 = pack[pos] as i64; pos = pos + 1 252 let typ: i64 = (byte >> 4) & 7 253 var osz: i64 = byte & 15 254 var shift: i64 = 4 255 while (byte & 128) != 0 { if pos >= plen { byte = 0; ok = 0 } else { byte = pack[pos] as i64; pos = pos + 1; osz = osz + ((byte & 127) << shift); shift = shift + 7 } } 256 if ok == 1 { 257 // exact inflate cap from the pack varint size, clamped to the 64MiB object envelope 258 // (an over-envelope object fails the inflate -> whole push rejected LOUD, never truncated) 259 var icap: i64 = osz + K_MAGIC_65536 260 if icap > (1 << 26) { icap = 1 << 26 } 261 let r: *NxZlibResult = nx_zlib_inflate((pack as i64 + pos) as *u8, plen - pos, icap) 262 if r.error_code != 0 { ok = 0 } else { 263 pos = pos + r.bytes_consumed 264 var ts: *u8 = "?" as *u8 265 if typ == 1 { ts = "commit" as *u8 } 266 if typ == 2 { ts = "tree" as *u8 } 267 if typ == 3 { ts = "blob" as *u8 } 268 if typ == 4 { ts = "tag" as *u8 } 269 var portable: i64 = 1 270 if typ == 2 { portable = sg_tree_names_ok(r.output_data, r.output_size, 32) } 271 if portable == 0 { 272 lg("RCVPACK REFUSED: tree carries a name no client can check out (control char, reserved device name, illegal char, trailing dot/space, or .git)\n" as *u8) 273 ok = 0 274 } else { 275 if sg_write_loose(objroot, ts, r.output_data, r.output_size, wh) != 0 { ok = 0 } else { i = i + 1 } 276 } 277 } 278 if (r.output_data as i64) != 0 { sys_munmap(r.output_data, icap) } 279 sys_munmap(r as *u8, K_MAGIC_4096) 280 } 281 } 282 } else { i = count } 283 } 284 sys_munmap(wh, 80) 285 if ok == 1 { return count } 286 return 0 - 1 287} 288 289// ---- repo routing (X2) ---- 290func sg_ishex(c: i64) -> i64 { 291 if c >= 48 { if c <= 57 { return 1 } } 292 if c >= 97 { if c <= 102 { return 1 } } 293 if c >= 65 { if c <= 70 { return 1 } } 294 return 0 295} 296func sg_namelen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 297func repo_char_ok(c: i64) -> i64 { 298 if c >= 48 { if c <= 57 { return 1 } } 299 if c >= 97 { if c <= 122 { return 1 } } 300 if c >= 65 { if c <= 90 { return 1 } } 301 if c == 95 { return 1 } 302 if c == 45 { return 1 } 303 return 0 304} 305// refname policy: must start refs/, chars [A-Za-z0-9_./-], no "..", len 6..200 306func ref_ok(r: *u8) -> i64 { 307 let n: i64 = sstrlen(r) 308 if n < 6 { return 0 } 309 if n > 200 { return 0 } 310 if r[0] != (114 as u8) { return 0 } 311 if r[1] != (101 as u8) { return 0 } 312 if r[2] != (102 as u8) { return 0 } 313 if r[3] != (115 as u8) { return 0 } 314 if r[4] != (47 as u8) { return 0 } 315 var i: i64 = 0 316 while i < n { 317 let c: i64 = r[i] as i64 318 var okc: i64 = repo_char_ok(c) 319 if c == 46 { okc = 1 } 320 if c == 47 { okc = 1 } 321 if okc == 0 { return 0 } 322 if c == 46 { if i + 1 < n { if r[i + 1] == (46 as u8) { return 0 } } } 323 i = i + 1 324 } 325 return 1 326} 327// parse the repo segment out of the request path. Returns: -1 invalid repo name, 0 legacy (no repo 328// segment; single-repo X1 paths), else segment length written to segout (NUL-terminated). 329// *rest_off = offset in path where the service sub-path begins (points at '/' or '?' or plen). 330func parse_repo(path: *u8, plen: i64, segout: *u8, rest_off: *i64) -> i64 { 331 var i: i64 = 0 332 if plen > 0 { if path[0] == (47 as u8) { i = 1 } } 333 // optional leading "git/" prefix (edge forwards the full /git/... path) 334 if i + 4 <= plen { 335 if path[i] == (103 as u8) { if path[i + 1] == (105 as u8) { if path[i + 2] == (116 as u8) { if path[i + 3] == (47 as u8) { 336 i = i + 4 337 } } } } 338 } 339 let s: i64 = i 340 var j: i64 = i 341 var go: i64 = 1 342 while go == 1 { 343 if j >= plen { go = 0 } else { 344 let c: i64 = path[j] as i64 345 if c == 47 { go = 0 } else { if c == 63 { go = 0 } else { j = j + 1 } } 346 } 347 } 348 var seglen: i64 = j - s 349 let segp: *u8 = (path as i64 + s) as *u8 350 // known service names / empty at this position => legacy single-repo paths 351 if seglen == 0 { rest_off[0] = s - 1; if rest_off[0] < 0 { rest_off[0] = 0 } return 0 } 352 if seq(segp, seglen, "info" as *u8) == 1 { rest_off[0] = s - 1; return 0 } 353 if seq(segp, seglen, "git-upload-pack" as *u8) == 1 { rest_off[0] = s - 1; return 0 } 354 if seq(segp, seglen, "git-receive-pack" as *u8) == 1 { rest_off[0] = s - 1; return 0 } 355 if seq(segp, seglen, "objects" as *u8) == 1 { rest_off[0] = s - 1; return 0 } 356 if seq(segp, seglen, "HEAD" as *u8) == 1 { rest_off[0] = s - 1; return 0 } 357 // repo segment: strip trailing ".git" 358 if seglen > 4 { 359 if segp[seglen - 4] == (46 as u8) { if segp[seglen - 3] == (103 as u8) { if segp[seglen - 2] == (105 as u8) { if segp[seglen - 1] == (116 as u8) { 360 seglen = seglen - 4 361 } } } } 362 } 363 if seglen < 1 { return 0 - 1 } 364 if seglen > 64 { return 0 - 1 } 365 var k: i64 = 0 366 while k < seglen { 367 if repo_char_ok(segp[k] as i64) == 0 { return 0 - 1 } 368 k = k + 1 369 } 370 var o: i64 = 0 371 while o < seglen { segout[o] = segp[o]; o = o + 1 } 372 segout[o] = 0 as u8 373 rest_off[0] = j 374 return seglen 375} 376// build the on-disk repo root for a parsed segment (seglen==0 => legacy demo repo) 377func repo_root(dst: *u8, seg: *u8, seglen: i64) -> i64 { 378 if seglen == 0 { let o0: i64 = scopy(dst, 0, "knowledge/_sovgit_repo" as *u8); dst[o0] = 0 as u8; return o0 } 379 var o: i64 = scopy(dst, 0, "knowledge/sovgit_repos/" as *u8) 380 var i: i64 = 0 381 while i < seglen { dst[o] = seg[i]; o = o + 1; i = i + 1 } 382 dst[o] = 0 as u8 383 return o 384} 385// join root + "/" + rel into dst (NUL-terminated), return len 386func rr_path(dst: *u8, root: *u8, rel: *u8) -> i64 { 387 var o: i64 = scopy(dst, 0, root) 388 dst[o] = 47 as u8; o = o + 1 389 o = scopy(dst, o, rel) 390 dst[o] = 0 as u8 391 return o 392} 393func repo_exists(root: *u8) -> i64 { 394 let fd: i64 = sys_openat_rd(root) 395 if fd < 0 { return 0 } 396 sys_close(fd) 397 return 1 398} 399// idempotent bare-repo skeleton (push-to-create); never clobbers existing HEAD/config/refs 400func ensure_repo(root: *u8) -> i64 { 401 sys_mkdir("knowledge/sovgit_repos" as *u8, 0x1ed) 402 sys_mkdir(root, 0x1ed) 403 let p: *u8 = sys_mmap(512) 404 rr_path(p, root, "refs" as *u8); sys_mkdir(p, 0x1ed) 405 rr_path(p, root, "refs/heads" as *u8); sys_mkdir(p, 0x1ed) 406 rr_path(p, root, "objects" as *u8); sys_mkdir(p, 0x1ed) 407 rr_path(p, root, "HEAD" as *u8) 408 let szp: *i64 = sys_mmap(16) as *i64 409 let hb: *u8 = sys_read_file(p, szp) 410 if (hb as i64) == 0 { wstr(p, "ref: refs/heads/master\n" as *u8) } else { if szp[0] > 0 { sys_munmap(hb, szp[0]) } else { sys_munmap(hb, K_MAGIC_4096) } } 411 rr_path(p, root, "config" as *u8) 412 let cb: *u8 = sys_read_file(p, szp) 413 if (cb as i64) == 0 { wstr(p, "[core]\n repositoryformatversion = 1\n bare = true\n[extensions]\n objectformat = sha256\n" as *u8) } else { if szp[0] > 0 { sys_munmap(cb, szp[0]) } else { sys_munmap(cb, K_MAGIC_4096) } } 414 sys_munmap(p, 512) 415 sys_munmap(szp as *u8, 16) 416 return 0 417} 418// read <root>/<refrel> as a 64-hex ref value into hexout; 1 = ok (bounded-VSZ: all maps freed) 419func read_ref_at(root: *u8, refrel: *u8, hexout: *u8) -> i64 { 420 let p: *u8 = sys_mmap(512) 421 rr_path(p, root, refrel) 422 let szp: *i64 = sys_mmap(16) as *i64 423 let b: *u8 = sys_read_file(p, szp) 424 sys_munmap(p, 512) 425 var ret: i64 = 0 426 var bn: i64 = 0 427 if (b as i64) != 0 { 428 bn = szp[0] 429 if bn >= 64 { 430 var i: i64 = 0; while i < 64 { hexout[i] = b[i]; i = i + 1 } 431 hexout[64] = 0 as u8 432 ret = 1 433 } 434 } 435 sys_munmap(szp as *u8, 16) 436 if (b as i64) != 0 { if bn > 0 { sys_munmap(b, bn) } else { sys_munmap(b, K_MAGIC_4096) } } 437 return ret 438} 439// read HEAD symref target ("refs/heads/x") into tgt; returns len (0 = none) (bounded-VSZ) 440func head_target(root: *u8, tgt: *u8) -> i64 { 441 let p: *u8 = sys_mmap(512) 442 rr_path(p, root, "HEAD" as *u8) 443 let szp: *i64 = sys_mmap(16) as *i64 444 let b: *u8 = sys_read_file(p, szp) 445 sys_munmap(p, 512) 446 if (b as i64) == 0 { sys_munmap(szp as *u8, 16); return 0 } 447 let n: i64 = szp[0] 448 sys_munmap(szp as *u8, 16) 449 var o: i64 = 0 450 var okhdr: i64 = 1 451 if n < 6 { okhdr = 0 } 452 if okhdr == 1 { if b[0] != (114 as u8) { okhdr = 0 } } 453 if okhdr == 1 { if b[4] != (32 as u8) { okhdr = 0 } } 454 if okhdr == 1 { 455 var i: i64 = 5 456 var go: i64 = 1 457 while go == 1 { 458 if i >= n { go = 0 } else { 459 let c: i64 = b[i] as i64 460 if c == 10 { go = 0 } else { if c == 13 { go = 0 } else { 461 if o < 200 { tgt[o] = b[i]; o = o + 1 } 462 i = i + 1 463 } } 464 } 465 } 466 tgt[o] = 0 as u8 467 } 468 if n > 0 { sys_munmap(b, n) } else { sys_munmap(b, K_MAGIC_4096) } 469 return o 470} 471 472// ---- pack from disk (X2: per-repo root + overflow guard + reused content buffer) ---- 473// packs EVERY loose object under <root>/objects (superset pack: the client keeps what the advertised 474// refs reach). outcap-guarded: on would-overflow returns -1 (caller sends 500; NEVER a truncated pack). 475// first-parent walk from tip, bounded by depth (so a cycle CANNOT hang the daemon). 476// Commit layout is fixed: "tree "(5) + 64 hex + "\n"(1) = 70, then optional "parent "(7) + 64. 477// That fixed offset means no general parser is needed for the first parent. 478func walk_depth(root: *u8, tip: *u8, depth: i64, out: *u8, content: *u8) -> i64 { 479 let objroot: *u8 = sys_mmap(512) 480 rr_path(objroot, root, "objects" as *u8) 481 let typ: *u8 = sys_mmap(16) 482 let cur: *u8 = sys_mmap(80) 483 var i0: i64 = 0 484 while i0 < 64 { cur[i0] = tip[i0]; i0 = i0 + 1 } 485 cur[64] = 0 as u8 486 var n: i64 = 0 487 var go: i64 = 1 488 while go == 1 { 489 if n >= depth { go = 0 } else { 490 var i: i64 = 0 491 while i < 64 { out[n * 64 + i] = cur[i]; i = i + 1 } 492 n = n + 1 493 let clen: i64 = sg_read_loose(objroot, cur, content, typ) 494 if clen < 141 { go = 0 } else { 495 if seq((content as i64 + 70) as *u8, 7, "parent " as *u8) == 0 { go = 0 } else { 496 var k: i64 = 0 497 while k < 64 { cur[k] = content[77 + k]; k = k + 1 } 498 cur[64] = 0 as u8 499 } 500 } 501 } 502 } 503 sys_munmap(objroot, 512); sys_munmap(typ, 16); sys_munmap(cur, 80) 504 return n 505} 506func pack_disk(root: *u8, out: *u8, outcap: i64, content: *u8, noblob: i64, allow: *u8, nallow: i64) -> i64 { 507 out[0] = 80 as u8; out[1] = 65 as u8; out[2] = 67 as u8; out[3] = 75 as u8 508 var o: i64 = be32w(out, 4, 2) 509 o = be32w(out, o, 0) // object-count placeholder @ offset 8, backfilled below 510 var count: i64 = 0 511 var overflow: i64 = 0 512 let objroot: *u8 = sys_mmap(512) 513 rr_path(objroot, root, "objects" as *u8) 514 let hex: *u8 = sys_mmap(80) 515 let sub: *u8 = sys_mmap(512) 516 let typ: *u8 = sys_mmap(16) 517 let dbuf: *u8 = sys_mmap(K_MAGIC_65536) 518 let sbuf: *u8 = sys_mmap(K_MAGIC_65536) 519 let dfd: i64 = sys_openat_rd(objroot) 520 if dfd >= 0 { 521 var g: i64 = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 522 while g > 0 { 523 var p: i64 = 0 524 while p < g { 525 let rec: *u8 = (dbuf as i64 + p) as *u8 526 let nm: *u8 = dirent_name(rec) 527 if sg_namelen(nm) == 2 { if sg_ishex(nm[0] as i64) == 1 { if sg_ishex(nm[1] as i64) == 1 { 528 var so: i64 = scopy(sub, 0, objroot) 529 sub[so] = 47 as u8; so = so + 1; sub[so] = nm[0]; so = so + 1; sub[so] = nm[1]; so = so + 1; sub[so] = 0 as u8 530 let sfd: i64 = sys_openat_rd(sub) 531 if sfd >= 0 { 532 var g2: i64 = sys_getdents64(sfd, sbuf, K_MAGIC_65536) 533 while g2 > 0 { 534 var q: i64 = 0 535 while q < g2 { 536 let srec: *u8 = (sbuf as i64 + q) as *u8 537 let snm: *u8 = dirent_name(srec) 538 if sg_namelen(snm) == 62 { 539 hex[0] = nm[0]; hex[1] = nm[1] 540 var k: i64 = 0; while k < 62 { hex[2 + k] = snm[k]; k = k + 1 } 541 hex[64] = 0 as u8 542 let clen: i64 = sg_read_loose(objroot, hex, content, typ) 543 if clen >= 0 { if overflow == 0 { 544 // worst-case fixed-Huffman EXPANSION is ~1.3x on high-entropy data 545 // (sg_zwrap contract) -- guard with 1.5x + headroom, never overrun 546 if o + clen + (clen >> 1) + 256 > outcap - 40 { overflow = 1 } else { 547 var tn: i64 = 3 548 if typ[0] == (99 as u8) { tn = 1 } 549 if typ[0] == (98 as u8) { tn = 3 } 550 if typ[0] == (116 as u8) { if typ[1] == (114 as u8) { tn = 2 } else { tn = 4 } } 551 var emit: i64 = 1 552 if noblob == 1 { if tn == 3 { emit = 0 } } 553 if noblob == 2 { emit = 0 } 554 if nallow > 0 { if tn == 1 { 555 var inlist: i64 = 0 556 var ai: i64 = 0 557 while ai < nallow { 558 var same: i64 = 1 559 var c2: i64 = 0 560 while c2 < 64 { if hex[c2] != allow[ai * 64 + c2] { same = 0 } c2 = c2 + 1 } 561 if same == 1 { inlist = 1 } 562 ai = ai + 1 563 } 564 if inlist == 0 { emit = 0 } 565 } } 566 if emit == 1 { 567 o = pk_object(out, o, tn, content, clen) 568 count = count + 1 569 } 570 } 571 } } 572 } 573 q = q + dirent_reclen(srec) 574 } 575 g2 = sys_getdents64(sfd, sbuf, K_MAGIC_65536) 576 } 577 sys_close(sfd) 578 } 579 } } } 580 p = p + dirent_reclen(rec) 581 } 582 g = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 583 } 584 sys_close(dfd) 585 } 586 sys_munmap(objroot, 512) 587 sys_munmap(hex, 80) 588 sys_munmap(sub, 512) 589 sys_munmap(typ, 16) 590 sys_munmap(dbuf, K_MAGIC_65536) 591 sys_munmap(sbuf, K_MAGIC_65536) 592 if overflow == 1 { return 0 - 1 } 593 be32w(out, 8, count) 594 let dig: *u8 = sys_mmap(32); sha256_digest(out, o, dig) 595 var i: i64 = 0; while i < 32 { out[o] = dig[i]; o = o + 1; i = i + 1 } 596 sys_munmap(dig, 32) 597 return o 598} 599 600// ---- ref advertisement (X2: enumerate refs/heads + HEAD symref; svc 0=upload-pack 1=receive-pack) ---- 601func advert_body(body: *u8, bcap: i64, root: *u8, svc: i64) -> i64 { 602 var b: i64 = 0 603 if svc == 0 { b = pkts(body, 0, "# service=git-upload-pack\n" as *u8) } else { b = pkts(body, 0, "# service=git-receive-pack\n" as *u8) } 604 b = pktflush(body, b) 605 var emitted: i64 = 0 606 let line: *u8 = sys_mmap(512) 607 let hexv: *u8 = sys_mmap(80) 608 let tgt: *u8 = sys_mmap(256) 609 // HEAD symref first (upload-pack only) 610 if svc == 0 { 611 let tl: i64 = head_target(root, tgt) 612 if tl > 0 { if read_ref_at(root, tgt, hexv) == 1 { 613 var r1: i64 = sg_cpy(line, 0, hexv, 0, 64) 614 r1 = scopy(line, r1, " HEAD" as *u8) 615 line[r1] = 0 as u8; r1 = r1 + 1 616 r1 = scopy(line, r1, "object-format=sha256 symref=HEAD:" as *u8) 617 r1 = scopy(line, r1, tgt) 618 r1 = scopy(line, r1, " agent=nishi-sovgit/2" as *u8) 619 line[r1] = 10 as u8; r1 = r1 + 1 620 if b + r1 + 8 < bcap { b = pkt(body, b, line, r1); emitted = 1 } 621 } } 622 } 623 // enumerate refs/heads 624 let hdir: *u8 = sys_mmap(512) 625 rr_path(hdir, root, "refs/heads" as *u8) 626 let dbuf: *u8 = sys_mmap(K_MAGIC_65536) 627 let rel: *u8 = sys_mmap(320) 628 let dfd: i64 = sys_openat_rd(hdir) 629 if dfd >= 0 { 630 var g: i64 = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 631 while g > 0 { 632 var p: i64 = 0 633 while p < g { 634 let rec: *u8 = (dbuf as i64 + p) as *u8 635 let nm: *u8 = dirent_name(rec) 636 let nl: i64 = sg_namelen(nm) 637 var skip: i64 = 0 638 if nl == 1 { if nm[0] == (46 as u8) { skip = 1 } } 639 if nl == 2 { if nm[0] == (46 as u8) { if nm[1] == (46 as u8) { skip = 1 } } } 640 if nl > 250 { skip = 1 } 641 if skip == 0 { if nl > 0 { 642 var ro: i64 = scopy(rel, 0, "refs/heads/" as *u8) 643 var ni: i64 = 0; while ni < nl { rel[ro] = nm[ni]; ro = ro + 1; ni = ni + 1 } 644 rel[ro] = 0 as u8 645 if read_ref_at(root, rel, hexv) == 1 { 646 var r2: i64 = sg_cpy(line, 0, hexv, 0, 64) 647 line[r2] = 32 as u8; r2 = r2 + 1 648 r2 = scopy(line, r2, rel) 649 if emitted == 0 { 650 // v0: FIRST advertised ref carries the capability list after NUL 651 line[r2] = 0 as u8; r2 = r2 + 1 652 if svc == 0 { r2 = scopy(line, r2, "object-format=sha256 agent=nishi-sovgit/2" as *u8) } else { r2 = scopy(line, r2, "report-status object-format=sha256 agent=nishi-sovgit/2" as *u8) } 653 } 654 line[r2] = 10 as u8; r2 = r2 + 1 655 if b + r2 + 8 < bcap { b = pkt(body, b, line, r2); emitted = emitted + 1 } 656 } 657 } } 658 p = p + dirent_reclen(rec) 659 } 660 g = sys_getdents64(dfd, dbuf, K_MAGIC_65536) 661 } 662 sys_close(dfd) 663 } 664 if emitted == 0 { 665 // empty repo (push-to-create window): zero-id capabilities^{} line 666 var z: i64 = 0 667 while z < 64 { line[z] = 48 as u8; z = z + 1 } 668 var r3: i64 = 64 669 r3 = scopy(line, r3, " capabilities^{}" as *u8) 670 line[r3] = 0 as u8; r3 = r3 + 1 671 if svc == 0 { r3 = scopy(line, r3, "object-format=sha256 agent=nishi-sovgit/2" as *u8) } else { r3 = scopy(line, r3, "report-status object-format=sha256 agent=nishi-sovgit/2" as *u8) } 672 line[r3] = 10 as u8; r3 = r3 + 1 673 b = pkt(body, b, line, r3) 674 } 675 b = pktflush(body, b) 676 sys_munmap(line, 512) 677 sys_munmap(hexv, 80) 678 sys_munmap(tgt, 256) 679 sys_munmap(hdir, 512) 680 sys_munmap(dbuf, K_MAGIC_65536) 681 sys_munmap(rel, 320) 682 return b 683} 684 685// build "HTTP/1.1 200 OK\r\nContent-Type: <ctype>\r\nContent-Length: <n>\r\nConnection: close\r\n\r\n" into resp at 0; return offset 686func resp_hdr(resp: *u8, ctype: *u8, blen: i64) -> i64 { 687 var o: i64 = scopy(resp, 0, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8) 688 o = scopy(resp, o, ctype) 689 o = scopy(resp, o, "\r\nContent-Length: " as *u8) 690 o = udec(resp, o, blen) 691 o = scopy(resp, o, "\r\nConnection: close\r\n\r\n" as *u8) 692 return o 693} 694// plain-text non-200 (status line like "404 Not Found"), msg NUL-term 695func send_txt(cfd: i64, resp: *u8, status: *u8, msg: *u8) -> i64 { 696 let mn: i64 = sstrlen(msg) 697 var o: i64 = scopy(resp, 0, "HTTP/1.1 " as *u8) 698 o = scopy(resp, o, status) 699 o = scopy(resp, o, "\r\nContent-Type: text/plain\r\nContent-Length: " as *u8) 700 o = udec(resp, o, mn) 701 o = scopy(resp, o, "\r\nConnection: close\r\n\r\n" as *u8) 702 o = scopy(resp, o, msg) 703 nx_http_server_send_response(cfd, resp, o) 704 return 0 705} 706 707// dumb static file serve under a repo root (path-traversal refused; bounded-VSZ) 708func serve_static(cfd: i64, root: *u8, relpath: *u8, rlen: i64, resp: *u8) -> i64 { 709 let fp: *u8 = sys_mmap(K_MAGIC_1024) 710 var fo: i64 = scopy(fp, 0, root) 711 var i: i64 = 0; var bad: i64 = 0 712 while i < rlen { 713 let c: i64 = relpath[i] as i64 714 if c == 63 { i = rlen } else { 715 if c == 46 { if i + 1 < rlen { if relpath[i + 1] == (46 as u8) { bad = 1 } } } 716 fp[fo] = relpath[i]; fo = fo + 1; i = i + 1 717 } 718 } 719 fp[fo] = 0 as u8 720 if bad == 1 { send_txt(cfd, resp, "400 Bad Request" as *u8, "bad path\n" as *u8); sys_munmap(fp, K_MAGIC_1024); return 0 } 721 let szp: *i64 = sys_mmap(16) as *i64 722 let body: *u8 = sys_read_file(fp, szp) 723 sys_munmap(fp, K_MAGIC_1024) 724 if (body as i64) == 0 { send_txt(cfd, resp, "404 Not Found" as *u8, "not found\n" as *u8); sys_munmap(szp as *u8, 16); return 0 } 725 let n: i64 = szp[0] 726 sys_munmap(szp as *u8, 16) 727 var ro: i64 = resp_hdr(resp, "application/octet-stream" as *u8, n) 728 ro = sg_cpy(resp, ro, body, 0, n) 729 nx_http_server_send_response(cfd, resp, ro) 730 if n > 0 { sys_munmap(body, n) } else { sys_munmap(body, K_MAGIC_4096) } 731 return 0 732} 733 734// smart-HTTP ref advertisement response (svc 0=upload 1=receive) 735func serve_advert(cfd: i64, resp: *u8, root: *u8, svc: i64) -> i64 { 736 let body: *u8 = sys_mmap(1 << 20) 737 let b: i64 = advert_body(body, 1 << 20, root, svc) 738 var ro: i64 = 0 739 if svc == 0 { ro = resp_hdr(resp, "application/x-git-upload-pack-advertisement" as *u8, b) } else { ro = resp_hdr(resp, "application/x-git-receive-pack-advertisement" as *u8, b) } 740 ro = sg_cpy(resp, ro, body, 0, b) 741 nx_http_server_send_response(cfd, resp, ro) 742 sys_munmap(body, 1 << 20) 743 return 0 744} 745 746// smart-HTTP upload-pack result: NAK + packfile (superset pack from disk) 747func serve_uploadpack_result(cfd: i64, resp: *u8, root: *u8, pack: *u8, packcap: i64, pbody: *u8, content: *u8) -> i64 { 748 let plen: i64 = pack_disk(root, pack, packcap, content, 0, 0 as *u8, 0) 749 if plen < 0 { 750 lg("nx_sovgit_git: PACK OVERFLOW (repo exceeds 128MiB pack envelope)\n" as *u8) 751 send_txt(cfd, resp, "507 Insufficient Storage" as *u8, "nishi-git: repo exceeds the declared 128MiB pack envelope\n" as *u8) 752 return 0 753 } 754 var b: i64 = pkts(pbody, 0, "NAK\n" as *u8) 755 var i: i64 = 0; while i < plen { pbody[b + i] = pack[i]; i = i + 1 } 756 b = b + plen 757 var ro: i64 = resp_hdr(resp, "application/x-git-upload-pack-result" as *u8, b) 758 ro = sg_cpy(resp, ro, pbody, 0, b) 759 nx_http_server_send_response(cfd, resp, ro) 760 return 0 761} 762 763// ================= GIT PROTOCOL v2 (gitprotocol-v2) ================= 764// Engaged ONLY when the client sends "Git-Protocol: version=2". The v0 path above is untouched, 765// so a v2 bug cannot regress an existing client. v2 is ALL-OR-NOTHING: a client that sees 766// "version 2" advertised and then cannot run fetch fails outright, so ls-refs and fetch ship 767// together or not at all. 768func v2_caps_body(body: *u8, bcap: i64) -> i64 { 769 var b: i64 = 0 770 b = pkts(body, b, "version 2\n" as *u8) 771 b = pkts(body, b, "agent=nishi-sovgit/2\n" as *u8) 772 b = pkts(body, b, "ls-refs\n" as *u8) 773 b = pkts(body, b, "fetch=filter shallow\n" as *u8) 774 b = pkts(body, b, "bundle-uri\n" as *u8) 775 b = pkts(body, b, "object-format=sha256\n" as *u8) 776 b = pktflush(body, b) 777 return b 778} 779func serve_v2_caps(cfd: i64, resp: *u8) -> i64 { 780 let body: *u8 = sys_mmap(4096) 781 let b: i64 = v2_caps_body(body, 4096) 782 var ro: i64 = resp_hdr(resp, "application/x-git-upload-pack-advertisement" as *u8, b) 783 ro = sg_cpy(resp, ro, body, 0, b) 784 nx_http_server_send_response(cfd, resp, ro) 785 sys_munmap(body, 4096) 786 return 0 787} 788func v2_lsrefs_body(body: *u8, bcap: i64, root: *u8) -> i64 { 789 var b: i64 = 0 790 let line: *u8 = sys_mmap(512) 791 let hexv: *u8 = sys_mmap(80) 792 let tgt: *u8 = sys_mmap(256) 793 let tl: i64 = head_target(root, tgt) 794 if tl > 0 { if read_ref_at(root, tgt, hexv) == 1 { 795 var r1: i64 = sg_cpy(line, 0, hexv, 0, 64) 796 r1 = scopy(line, r1, " HEAD symref-target:" as *u8) 797 r1 = scopy(line, r1, tgt) 798 line[r1] = 10 as u8; r1 = r1 + 1 799 if b + r1 + 8 < bcap { b = pkt(body, b, line, r1) } 800 } } 801 let hdir: *u8 = sys_mmap(512) 802 rr_path(hdir, root, "refs/heads" as *u8) 803 let dbuf: *u8 = sys_mmap(65536) 804 let rel: *u8 = sys_mmap(320) 805 let dfd: i64 = sys_openat_rd(hdir) 806 if dfd >= 0 { 807 var g: i64 = sys_getdents64(dfd, dbuf, 65536) 808 while g > 0 { 809 var p: i64 = 0 810 while p < g { 811 let rec: *u8 = (dbuf as i64 + p) as *u8 812 let nm: *u8 = dirent_name(rec) 813 let nl: i64 = sg_namelen(nm) 814 var skip: i64 = 0 815 if nl == 1 { if nm[0] == (46 as u8) { skip = 1 } } 816 if nl == 2 { if nm[0] == (46 as u8) { if nm[1] == (46 as u8) { skip = 1 } } } 817 if nl > 250 { skip = 1 } 818 if skip == 0 { if nl > 0 { 819 var ro2: i64 = scopy(rel, 0, "refs/heads/" as *u8) 820 var ni: i64 = 0; while ni < nl { rel[ro2] = nm[ni]; ro2 = ro2 + 1; ni = ni + 1 } 821 rel[ro2] = 0 as u8 822 if read_ref_at(root, rel, hexv) == 1 { 823 var r2: i64 = sg_cpy(line, 0, hexv, 0, 64) 824 line[r2] = 32 as u8; r2 = r2 + 1 825 r2 = scopy(line, r2, rel) 826 line[r2] = 10 as u8; r2 = r2 + 1 827 if b + r2 + 8 < bcap { b = pkt(body, b, line, r2) } 828 } 829 } } 830 p = p + dirent_reclen(rec) 831 } 832 g = sys_getdents64(dfd, dbuf, 65536) 833 } 834 sys_close(dfd) 835 } 836 b = pktflush(body, b) 837 sys_munmap(line, 512); sys_munmap(hexv, 80); sys_munmap(tgt, 256) 838 sys_munmap(hdir, 512); sys_munmap(dbuf, 65536); sys_munmap(rel, 320) 839 return b 840} 841func sb_chunk(out: *u8, at: i64, band: i64, data: *u8, dlen: i64) -> i64 { 842 var a: i64 = hex4(out, at, dlen + 5) 843 out[a] = band as u8; a = a + 1 844 var i: i64 = 0 845 while i < dlen { out[a + i] = data[i]; i = i + 1 } 846 return a + dlen 847} 848// BUNDLE-URI. A git bundle v2 is just: "# v2 git bundle\n" + "<oid> <ref>\n" lines + blank line + 849// the packfile -- and pack_disk already produces the pack, so this is a header, not a new subsystem. 850// I had estimated this as a large item; it is not. Serving a REAL bundle first means the advertised 851// URI resolves to something, instead of the lie a bare advertisement would be. 852func serve_bundle(cfd: i64, resp: *u8, root: *u8, pack: *u8, packcap: i64, pbody: *u8, content: *u8) -> i64 { 853 let plen: i64 = pack_disk(root, pack, packcap, content, 0, 0 as *u8, 0) 854 if plen < 0 { send_txt(cfd, resp, "507 Insufficient Storage" as *u8, "nishi-git: repo exceeds pack envelope\n" as *u8); return 0 } 855 var b: i64 = scopy(pbody, 0, "# v2 git bundle\n" as *u8) 856 let btgt: *u8 = sys_mmap(256) 857 let bhx: *u8 = sys_mmap(80) 858 let btl: i64 = head_target(root, btgt) 859 if btl > 0 { if read_ref_at(root, btgt, bhx) == 1 { 860 b = sg_cpy(pbody, b, bhx, 0, 64) 861 pbody[b] = 32 as u8; b = b + 1 862 b = scopy(pbody, b, btgt) 863 pbody[b] = 10 as u8; b = b + 1 864 } } 865 pbody[b] = 10 as u8; b = b + 1 866 var bi: i64 = 0 867 while bi < plen { pbody[b + bi] = pack[bi]; bi = bi + 1 } 868 b = b + plen 869 var ro: i64 = resp_hdr(resp, "application/x-git-bundle" as *u8, b) 870 ro = sg_cpy(resp, ro, pbody, 0, b) 871 nx_http_server_send_response(cfd, resp, ro) 872 sys_munmap(btgt, 256); sys_munmap(bhx, 80) 873 return 0 874} 875func serve_v2_bundleuri(cfd: i64, resp: *u8, pbody: *u8, seg: *u8, sl: i64) -> i64 { 876 var b: i64 = pkts(pbody, 0, "bundle.version=1\n" as *u8) 877 b = pkts(pbody, b, "bundle.mode=all\n" as *u8) 878 let bline: *u8 = sys_mmap(512) 879 // Rule 11/17: public host is CONFIG, not a literal. knowledge/status/sovgit_host.conf wins; 880 // the built-in is only a last-resort default so a missing file cannot break the advert. 881 var lo: i64 = scopy(bline, 0, "bundle.one.uri=https://" as *u8) 882 let hszp: *i64 = sys_mmap(16) as *i64 883 let hbuf: *u8 = sys_read_file("knowledge/status/sovgit_host.conf" as *u8, hszp) 884 if (hbuf as i64) != 0 { 885 var hi: i64 = 0 886 while hi < hszp[0] { if hbuf[hi] == (10 as u8) { hi = hszp[0] } else { bline[lo] = hbuf[hi]; lo = lo + 1; hi = hi + 1 } } 887 if hszp[0] > 0 { sys_munmap(hbuf, hszp[0]) } else { sys_munmap(hbuf, 4096) } 888 } else { lo = scopy(bline, lo, "nishifamily.com" as *u8) } 889 sys_munmap(hszp as *u8, 16) 890 lo = scopy(bline, lo, "/git/" as *u8) 891 var bj: i64 = 0 892 while bj < sl { bline[lo] = seg[bj]; lo = lo + 1; bj = bj + 1 } 893 lo = scopy(bline, lo, "/bundle" as *u8) 894 bline[lo] = 10 as u8; lo = lo + 1 895 b = pkt(pbody, b, bline, lo) 896 b = pktflush(pbody, b) 897 var ro: i64 = resp_hdr(resp, "application/x-git-upload-pack-result" as *u8, b) 898 ro = sg_cpy(resp, ro, pbody, 0, b) 899 nx_http_server_send_response(cfd, resp, ro) 900 sys_munmap(bline, 512) 901 return 0 902} 903// FORGE LAYER, first slice: a human-readable repo page served BY the git host itself. 904// Same data ls-refs serves, rendered as HTML, plus the clone and bundle URLs. No framework. 905// CI SURFACE: render the push evidence plane -- every push with its gate verdict -- as the run 906// history. This is gate-VERDICT SURFACING, not a build runner: the daemon does not exec anything 907// (an accept-loop that forks arbitrary gates is a DoS and a privilege surface). The verdicts come 908// from checks that ran INSIDE receive-pack (path-portability, unpack integrity). 909// OPENAPI: the host DESCRIBES ITSELF. A first-class API is one a client can discover without 910// reading our source -- so the description is served BY the same binary that serves the routes, 911// which makes it impossible for the doc to drift out of the deployment. 912func serve_openapi(cfd: i64, resp: *u8, pbody: *u8) -> i64 { 913 var b: i64 = scopy(pbody, 0, "{\"openapi\":\"3.1.0\",\"info\":{\"title\":\"nishi-git\",\"version\":\"2\",\"description\":\"Sovereign git host: smart-HTTP v0 + protocol v2 (ls-refs/fetch/side-band-64k/partial-clone/shallow/bundle-uri), sha256 object-format, receive-pack path-portability gate, and an in-binary forge (issues/PRs/releases/CI/oplog). No nginx, no framework.\"}," as *u8) 914 b = scopy(pbody, b, "\"components\":{\"securitySchemes\":{\"ocap\":{\"type\":\"http\",\"scheme\":\"basic\",\"description\":\"Authorization: Basic x:<ocap scope=git>\"}}},\"security\":[{\"ocap\":[]}],\"paths\":{" as *u8) 915 b = scopy(pbody, b, "\"/git/{repo}/info/refs\":{\"get\":{\"summary\":\"ref advertisement; send Git-Protocol: version=2 for the v2 capability list\",\"parameters\":[{\"name\":\"service\",\"in\":\"query\",\"required\":true,\"schema\":{\"enum\":[\"git-upload-pack\",\"git-receive-pack\"]}}],\"responses\":{\"200\":{\"description\":\"pkt-line advertisement\"},\"401\":{\"description\":\"ocap required\"}}}}," as *u8) 916 b = scopy(pbody, b, "\"/git/{repo}/git-upload-pack\":{\"post\":{\"summary\":\"v0 upload-pack, or v2 command=ls-refs|fetch|bundle-uri (fetch honours filter blob:none and deepen N)\",\"responses\":{\"200\":{\"description\":\"pack or section stream\"}}}}," as *u8) 917 b = scopy(pbody, b, "\"/git/{repo}/git-receive-pack\":{\"post\":{\"summary\":\"push; REFUSES any tree carrying a path no client can check out (control chars, reserved device names, trailing dot/space, .git)\",\"responses\":{\"200\":{\"description\":\"report-status\"}}}}," as *u8) 918 b = scopy(pbody, b, "\"/git/{repo}/bundle\":{\"get\":{\"summary\":\"git bundle v2 for clone offload\",\"responses\":{\"200\":{\"description\":\"application/x-git-bundle\"}}}}," as *u8) 919 b = scopy(pbody, b, "\"/git/{repo}/ui\":{\"get\":{\"summary\":\"repo page: refs, file tree at HEAD, blob view via ?blob=<oid>\",\"responses\":{\"200\":{\"description\":\"text/html\"}}}}," as *u8) 920 b = scopy(pbody, b, "\"/git/{repo}/issues\":{\"get\":{\"summary\":\"list issues\",\"responses\":{\"200\":{\"description\":\"text/html\"}}},\"post\":{\"summary\":\"open an issue (title=...); append-only, a close is a NEW row\",\"responses\":{\"200\":{\"description\":\"text/html\"}}}}," as *u8) 921 b = scopy(pbody, b, "\"/git/{repo}/prs\":{\"get\":{\"summary\":\"list pull requests\",\"responses\":{\"200\":{\"description\":\"text/html\"}}},\"post\":{\"summary\":\"open a PR (src=&dst=&title=)\",\"responses\":{\"200\":{\"description\":\"text/html\"}}}}," as *u8) 922 b = scopy(pbody, b, "\"/git/{repo}/releases\":{\"get\":{\"summary\":\"list releases\",\"responses\":{\"200\":{\"description\":\"text/html\"}}},\"post\":{\"summary\":\"publish a release (tag=&notes=)\",\"responses\":{\"200\":{\"description\":\"text/html\"}}}}," as *u8) 923 b = scopy(pbody, b, "\"/git/{repo}/ci\":{\"get\":{\"summary\":\"per-push gate verdicts from the append-only evidence plane\",\"responses\":{\"200\":{\"description\":\"text/html\"}}}}," as *u8) 924 b = scopy(pbody, b, "\"/git/{repo}/oplog\":{\"get\":{\"summary\":\"operation log: every ref update with its BEFORE-image, so undo is writing old back\",\"responses\":{\"200\":{\"description\":\"text/html\"}}}}," as *u8) 925 b = scopy(pbody, b, "\"/git/{repo}/openapi.json\":{\"get\":{\"summary\":\"this document, served by the same binary that serves the routes\",\"responses\":{\"200\":{\"description\":\"application/json\"}}}}}}" as *u8) 926 var aro: i64 = resp_hdr(resp, "application/json" as *u8, b) 927 aro = sg_cpy(resp, aro, pbody, 0, b) 928 nx_http_server_send_response(cfd, resp, aro) 929 return 0 930} 931// OPLOG page (Jujutsu-class operation log). Every accepted ref update is a row carrying its 932// BEFORE and AFTER oid, so the log is not a change feed -- it is a REVERSIBLE history. 933func serve_oplog(cfd: i64, resp: *u8, pbody: *u8) -> i64 { 934 var b: i64 = scopy(pbody, 0, "<!doctype html><meta charset=utf-8><title>oplog</title><style>body{font:12px/1.6 ui-monospace,monospace;max-width:78rem;margin:2rem auto;padding:0 1rem}li{margin:.3em 0}code{background:#f4f4f5;padding:.1em .3em}</style><p><a href=ui>&larr; repo</a></p><h1>operation log</h1><p>every accepted ref update, with its before-image. an undo is writing <code>old</code> back -- the log is never rewritten.</p><ul>" as *u8) 935 let oszp: *i64 = sys_mmap(16) as *i64 936 let obuf: *u8 = sys_read_file("knowledge/status/sovgit_oplog.log" as *u8, oszp) 937 if (obuf as i64) != 0 { 938 let oln: i64 = oszp[0] 939 var op2: i64 = 0 940 var ost: i64 = 0 941 while op2 <= oln { 942 var oe: i64 = 0 943 if op2 == oln { oe = 1 } else { if obuf[op2] == (10 as u8) { oe = 1 } } 944 if oe == 1 { 945 if op2 > ost { 946 b = scopy(pbody, b, "<li>" as *u8) 947 b = ui_row(pbody, b, obuf, ost, op2) 948 b = scopy(pbody, b, "</li>" as *u8) 949 } 950 ost = op2 + 1 951 } 952 op2 = op2 + 1 953 } 954 if oln > 0 { sys_munmap(obuf, oln) } else { sys_munmap(obuf, 4096) } 955 } else { b = scopy(pbody, b, "<li><em>no operations recorded yet</em></li>" as *u8) } 956 b = scopy(pbody, b, "</ul>" as *u8) 957 var oro: i64 = resp_hdr(resp, "text/html; charset=utf-8" as *u8, b) 958 oro = sg_cpy(resp, oro, pbody, 0, b) 959 nx_http_server_send_response(cfd, resp, oro) 960 sys_munmap(oszp as *u8, 16) 961 return 0 962} 963func serve_ci(cfd: i64, resp: *u8, pbody: *u8) -> i64 { 964 var b: i64 = scopy(pbody, 0, "<!doctype html><meta charset=utf-8><title>ci</title><style>body{font:13px/1.6 ui-monospace,monospace;max-width:70rem;margin:2rem auto;padding:0 1rem}li{margin:.3em 0}.p{color:#0a7f3f}.f{color:#b00}</style><p><a href=ui>&larr; repo</a></p><h1>push checks</h1><ul>" as *u8) 965 let cszp: *i64 = sys_mmap(16) as *i64 966 let cbuf2: *u8 = sys_read_file("knowledge/status/sovgit_push_evidence.log" as *u8, cszp) 967 if (cbuf2 as i64) != 0 { 968 let cln: i64 = cszp[0] 969 var cp: i64 = 0 970 var cst: i64 = 0 971 while cp <= cln { 972 var ceol: i64 = 0 973 if cp == cln { ceol = 1 } else { if cbuf2[cp] == (10 as u8) { ceol = 1 } } 974 if ceol == 1 { 975 if cp > cst { 976 var pass: i64 = 0 977 var sc: i64 = cst 978 while sc + 10 < cp { if pass == 0 { if seq((cbuf2 as i64 + sc) as *u8, 10, "pathgate=P" as *u8) == 1 { pass = 1 } } sc = sc + 1 } 979 if pass == 1 { b = scopy(pbody, b, "<li class=p>PASS " as *u8) } else { b = scopy(pbody, b, "<li class=f>FAIL " as *u8) } 980 b = ui_row(pbody, b, cbuf2, cst, cp) 981 b = scopy(pbody, b, "</li>" as *u8) 982 } 983 cst = cp + 1 984 } 985 cp = cp + 1 986 } 987 if cln > 0 { sys_munmap(cbuf2, cln) } else { sys_munmap(cbuf2, 4096) } 988 } else { b = scopy(pbody, b, "<li><em>no pushes recorded yet</em></li>" as *u8) } 989 b = scopy(pbody, b, "</ul>" as *u8) 990 var cro: i64 = resp_hdr(resp, "text/html; charset=utf-8" as *u8, b) 991 cro = sg_cpy(resp, cro, pbody, 0, b) 992 nx_http_server_send_response(cfd, resp, cro) 993 sys_munmap(cszp as *u8, 16) 994 return 0 995} 996func hexv1(c: i64) -> i64 { 997 if c >= CH_0 { if c <= CH_9 { return c - CH_0 } } 998 if c >= CH_A_LO { if c <= CH_F_LO { return c - HEXA_LO_ADJ } } 999 if c >= CH_A_UP { if c <= CH_F_UP { return c - HEXA_UP_ADJ } } 1000 return 0 - 1 1001} 1002// render buf[st..en] URL-DECODED (%XX, +) and HTML-escaped; '&' becomes a space so a stored 1003// form body reads as fields rather than a query string. 1004func ui_row(pbody: *u8, b0: i64, buf: *u8, st: i64, en: i64) -> i64 { 1005 var b: i64 = b0 1006 var i: i64 = st 1007 while i < en { 1008 let c0: i64 = buf[i] as i64 1009 var c: i64 = c0 1010 var adv: i64 = 1 1011 if c0 == CH_PLUS { c = CH_SP } 1012 if c0 == CH_AMP { c = CH_SP } 1013 if c0 == CH_PCT { if i + 2 < en { 1014 let h1: i64 = hexv1(buf[i + 1] as i64) 1015 let h2: i64 = hexv1(buf[i + 2] as i64) 1016 if h1 >= 0 { if h2 >= 0 { c = h1 * HEX_BASE + h2; adv = 3 } } 1017 } } 1018 b = ui_esc(pbody, b, c) 1019 i = i + adv 1020 } 1021 return b 1022} 1023// GENERIC FORGE SURFACE: one append-only per-repo log, parameterised by filename/heading/row-tag 1024// and the create form. PRs and releases are the SAME SHAPE as issues -- (some fields, a state, 1025// a timestamp) appended, never mutated -- so they share one implementation instead of three 1026// near-copies (Rule 15). Whole form body is stored, so adding a field needs no code change. 1027func serve_forge_log(cfd: i64, resp: *u8, root: *u8, pbody: *u8, body: *u8, blen: i64, fname: *u8, heading: *u8, rowtag: *u8, formhtml: *u8) -> i64 { 1028 let fpath: *u8 = sys_mmap(512) 1029 rr_path(fpath, root, fname) 1030 if blen > 0 { 1031 let ffd: i64 = sys_openat_append(fpath, 0x1a4) 1032 if ffd >= 0 { 1033 let frow: *u8 = sys_mmap(2048) 1034 var fo: i64 = scopy(frow, 0, rowtag) 1035 let fep: i64 = sys_now_realtime_sec() 1036 fo = scopy(frow, fo, " id=" as *u8) 1037 fo = udec(frow, fo, fep) 1038 fo = scopy(frow, fo, " epoch=" as *u8) 1039 fo = udec(frow, fo, fep) 1040 // a close is a NEW row, never an edit -- the log stays the audit trail (Rule 13) 1041 if pcontains(body, blen, "close=" as *u8) == 1 { fo = scopy(frow, fo, " state=closed " as *u8) } else { fo = scopy(frow, fo, " state=open " as *u8) } 1042 var fi: i64 = 0 1043 while fi < blen { if body[fi] == (10 as u8) { fi = blen } else { if fo < 2000 { frow[fo] = body[fi]; fo = fo + 1 } fi = fi + 1 } } 1044 frow[fo] = 10 as u8; fo = fo + 1 1045 sys_write(ffd, frow, fo) 1046 sys_close(ffd) 1047 sys_munmap(frow, 2048) 1048 } 1049 } 1050 var b: i64 = scopy(pbody, 0, "<!doctype html><meta charset=utf-8><style>body{font:14px/1.5 system-ui,sans-serif;max-width:60rem;margin:2rem auto;padding:0 1rem}li{margin:.35em 0}input{padding:.4em;width:20rem}</style><p><a href=ui>&larr; repo</a></p><h1>" as *u8) 1051 b = scopy(pbody, b, heading) 1052 b = scopy(pbody, b, "</h1>" as *u8) 1053 b = scopy(pbody, b, formhtml) 1054 b = scopy(pbody, b, "<ul>" as *u8) 1055 let fszp: *i64 = sys_mmap(16) as *i64 1056 let fbuf: *u8 = sys_read_file(fpath, fszp) 1057 if (fbuf as i64) != 0 { 1058 let fln: i64 = fszp[0] 1059 var fp2: i64 = 0 1060 var fst: i64 = 0 1061 while fp2 <= fln { 1062 var feol: i64 = 0 1063 if fp2 == fln { feol = 1 } else { if fbuf[fp2] == (10 as u8) { feol = 1 } } 1064 if feol == 1 { 1065 if fp2 > fst { 1066 b = scopy(pbody, b, "<li>" as *u8) 1067 b = ui_row(pbody, b, fbuf, fst, fp2) 1068 b = scopy(pbody, b, "</li>" as *u8) 1069 } 1070 fst = fp2 + 1 1071 } 1072 fp2 = fp2 + 1 1073 } 1074 if fln > 0 { sys_munmap(fbuf, fln) } else { sys_munmap(fbuf, 4096) } 1075 } else { b = scopy(pbody, b, "<li><em>none yet</em></li>" as *u8) } 1076 b = scopy(pbody, b, "</ul>" as *u8) 1077 var fro: i64 = resp_hdr(resp, "text/html; charset=utf-8" as *u8, b) 1078 fro = sg_cpy(resp, fro, pbody, 0, b) 1079 nx_http_server_send_response(cfd, resp, fro) 1080 sys_munmap(fpath, 512); sys_munmap(fszp as *u8, 16) 1081 return 0 1082} 1083// ISSUES: an append-only per-repo log. Storage is the SAME additive discipline as the push 1084// evidence plane -- no database, no mutation, history is sacred (Rule 13). A row is never edited; 1085// a state change is a NEW row, so the log IS the audit trail. 1086func serve_issues(cfd: i64, resp: *u8, root: *u8, pbody: *u8, seg: *u8, sl: i64, body: *u8, blen: i64) -> i64 { 1087 let ipath: *u8 = sys_mmap(512) 1088 rr_path(ipath, root, "issues.log" as *u8) 1089 // create when the request carries title=<text> 1090 var tpos: i64 = 0 - 1 1091 var ts2: i64 = 0 1092 while ts2 + 6 < blen { if tpos < 0 { if seq((body as i64 + ts2) as *u8, 6, "title=" as *u8) == 1 { tpos = ts2 + 6 } } ts2 = ts2 + 1 } 1093 if tpos >= 0 { 1094 let ifd: i64 = sys_openat_append(ipath, 0x1a4) 1095 if ifd >= 0 { 1096 let irow: *u8 = sys_mmap(1024) 1097 var io2: i64 = scopy(irow, 0, "ISSUE epoch=" as *u8) 1098 io2 = udec(irow, io2, sys_now_realtime_sec()) 1099 io2 = scopy(irow, io2, " state=open title=" as *u8) 1100 var ti2: i64 = tpos 1101 while ti2 < blen { if body[ti2] == (10 as u8) { ti2 = blen } else { if io2 < 1000 { irow[io2] = body[ti2]; io2 = io2 + 1 } ti2 = ti2 + 1 } } 1102 irow[io2] = 10 as u8; io2 = io2 + 1 1103 sys_write(ifd, irow, io2) 1104 sys_close(ifd) 1105 sys_munmap(irow, 1024) 1106 } 1107 } 1108 var b: i64 = scopy(pbody, 0, "<!doctype html><meta charset=utf-8><title>issues</title><style>body{font:14px/1.5 system-ui,sans-serif;max-width:60rem;margin:2rem auto;padding:0 1rem}li{margin:.35em 0}form{margin:1rem 0}input{padding:.4em;width:24rem}</style><p><a href=ui>&larr; repo</a></p><h1>issues</h1><form method=post><input name=title placeholder=\"new issue title\"><button>open</button></form><ul>" as *u8) 1109 let iszp: *i64 = sys_mmap(16) as *i64 1110 let ibuf: *u8 = sys_read_file(ipath, iszp) 1111 if (ibuf as i64) != 0 { 1112 let iln: i64 = iszp[0] 1113 var ip: i64 = 0 1114 var ist: i64 = 0 1115 while ip <= iln { 1116 var eol: i64 = 0 1117 if ip == iln { eol = 1 } else { if ibuf[ip] == (10 as u8) { eol = 1 } } 1118 if eol == 1 { 1119 if ip > ist { 1120 b = scopy(pbody, b, "<li>" as *u8) 1121 var ik: i64 = ist 1122 while ik < ip { b = ui_esc(pbody, b, ibuf[ik] as i64); ik = ik + 1 } 1123 b = scopy(pbody, b, "</li>" as *u8) 1124 } 1125 ist = ip + 1 1126 } 1127 ip = ip + 1 1128 } 1129 if iln > 0 { sys_munmap(ibuf, iln) } else { sys_munmap(ibuf, 4096) } 1130 } else { b = scopy(pbody, b, "<li><em>no issues yet</em></li>" as *u8) } 1131 b = scopy(pbody, b, "</ul>" as *u8) 1132 var iro: i64 = resp_hdr(resp, "text/html; charset=utf-8" as *u8, b) 1133 iro = sg_cpy(resp, iro, pbody, 0, b) 1134 nx_http_server_send_response(cfd, resp, iro) 1135 sys_munmap(ipath, 512); sys_munmap(iszp as *u8, 16) 1136 return 0 1137} 1138// escape one byte into HTML at pbody[b]; returns new b 1139func ui_esc(pbody: *u8, b: i64, c: i64) -> i64 { 1140 if c == CH_LT { return scopy(pbody, b, "&lt;" as *u8) } 1141 if c == CH_GT { return scopy(pbody, b, "&gt;" as *u8) } 1142 if c == CH_AMP { return scopy(pbody, b, "&amp;" as *u8) } 1143 pbody[b] = c as u8 1144 return b + 1 1145} 1146func serve_ui(cfd: i64, resp: *u8, root: *u8, pbody: *u8, seg: *u8, sl: i64, rest: *u8, rlen: i64) -> i64 { 1147 // BLOB VIEW: ?blob=<64hex> renders that object's bytes, HTML-escaped, in a <pre>. 1148 var bpos: i64 = 0 - 1 1149 var bs: i64 = 0 1150 while bs + 5 < rlen { if bpos < 0 { if seq((rest as i64 + bs) as *u8, 5, "blob=" as *u8) == 1 { bpos = bs + 5 } } bs = bs + 1 } 1151 if bpos >= 0 { if bpos + 64 <= rlen { 1152 let bhex2: *u8 = sys_mmap(80) 1153 var bh: i64 = 0 1154 while bh < 64 { bhex2[bh] = rest[bpos + bh]; bh = bh + 1 } 1155 bhex2[64] = 0 as u8 1156 let bobjr: *u8 = sys_mmap(512) 1157 rr_path(bobjr, root, "objects" as *u8) 1158 let btyp2: *u8 = sys_mmap(16) 1159 let bbuf: *u8 = sys_mmap(1 << 22) 1160 let bl2: i64 = sg_read_loose(bobjr, bhex2, bbuf, btyp2) 1161 var bb: i64 = scopy(pbody, 0, "<!doctype html><meta charset=utf-8><title>blob</title><style>body{font:13px/1.5 ui-monospace,monospace;margin:2rem}pre{background:#f7f7f8;padding:1rem;overflow:auto;white-space:pre-wrap}</style><p><a href=ui>&larr; repo</a></p><pre>" as *u8) 1162 if bl2 < 0 { bb = scopy(pbody, bb, "(object not found)" as *u8) } else { 1163 var bi2: i64 = 0 1164 while bi2 < bl2 { bb = ui_esc(pbody, bb, bbuf[bi2] as i64); bi2 = bi2 + 1 } 1165 } 1166 bb = scopy(pbody, bb, "</pre>" as *u8) 1167 var bro: i64 = resp_hdr(resp, "text/html; charset=utf-8" as *u8, bb) 1168 bro = sg_cpy(resp, bro, pbody, 0, bb) 1169 nx_http_server_send_response(cfd, resp, bro) 1170 sys_munmap(bhex2, 80); sys_munmap(bobjr, 512); sys_munmap(btyp2, 16); sys_munmap(bbuf, 1 << 22) 1171 return 0 1172 } } 1173 var b: i64 = scopy(pbody, 0, "<!doctype html><meta charset=utf-8><title>nishi-git</title>" as *u8) 1174 b = scopy(pbody, b, "<style>body{font:14px/1.5 system-ui,sans-serif;max-width:60rem;margin:2rem auto;padding:0 1rem}code{background:#f4f4f5;padding:.1em .35em;border-radius:3px}li{margin:.3em 0}h1{font-size:1.4rem}</style>" as *u8) 1175 b = scopy(pbody, b, "<h1>" as *u8) 1176 var si: i64 = 0 1177 while si < sl { pbody[b] = seg[si]; b = b + 1; si = si + 1 } 1178 b = scopy(pbody, b, "</h1><p>clone: <code>git clone https://nishifamily.com/git/" as *u8) 1179 si = 0 1180 while si < sl { pbody[b] = seg[si]; b = b + 1; si = si + 1 } 1181 b = scopy(pbody, b, "</code></p><p><a href=issues>issues</a> &middot; <a href=prs>pull requests</a> &middot; <a href=releases>releases</a> &middot; <a href=ci>checks</a> &middot; <a href=oplog>oplog</a> &middot; <a href=bundle>bundle</a></p><p>protocol v2 &middot; sha256 &middot; partial-clone &middot; shallow &middot; delta-negotiation</p><h2>refs</h2><ul>" as *u8) 1182 let uline: *u8 = sys_mmap(512) 1183 let uhex: *u8 = sys_mmap(80) 1184 let utgt: *u8 = sys_mmap(256) 1185 let utl: i64 = head_target(root, utgt) 1186 if utl > 0 { if read_ref_at(root, utgt, uhex) == 1 { 1187 b = scopy(pbody, b, "<li><code>" as *u8) 1188 b = sg_cpy(pbody, b, uhex, 0, 64) 1189 b = scopy(pbody, b, "</code> HEAD &rarr; " as *u8) 1190 b = scopy(pbody, b, utgt) 1191 b = scopy(pbody, b, "</li>" as *u8) 1192 } } 1193 let uhdir: *u8 = sys_mmap(512) 1194 rr_path(uhdir, root, "refs/heads" as *u8) 1195 let udbuf: *u8 = sys_mmap(65536) 1196 let urel: *u8 = sys_mmap(320) 1197 let udfd: i64 = sys_openat_rd(uhdir) 1198 if udfd >= 0 { 1199 var g: i64 = sys_getdents64(udfd, udbuf, 65536) 1200 while g > 0 { 1201 var p: i64 = 0 1202 while p < g { 1203 let rec: *u8 = (udbuf as i64 + p) as *u8 1204 let nm: *u8 = dirent_name(rec) 1205 let nl: i64 = sg_namelen(nm) 1206 var skip: i64 = 0 1207 if nl == 1 { if nm[0] == (46 as u8) { skip = 1 } } 1208 if nl == 2 { if nm[0] == (46 as u8) { if nm[1] == (46 as u8) { skip = 1 } } } 1209 if nl > 250 { skip = 1 } 1210 if skip == 0 { if nl > 0 { 1211 var ro2: i64 = scopy(urel, 0, "refs/heads/" as *u8) 1212 var ni: i64 = 0; while ni < nl { urel[ro2] = nm[ni]; ro2 = ro2 + 1; ni = ni + 1 } 1213 urel[ro2] = 0 as u8 1214 if read_ref_at(root, urel, uhex) == 1 { 1215 b = scopy(pbody, b, "<li><code>" as *u8) 1216 b = sg_cpy(pbody, b, uhex, 0, 64) 1217 b = scopy(pbody, b, "</code> " as *u8) 1218 b = scopy(pbody, b, urel) 1219 b = scopy(pbody, b, "</li>" as *u8) 1220 } 1221 } } 1222 p = p + dirent_reclen(rec) 1223 } 1224 g = sys_getdents64(udfd, udbuf, 65536) 1225 } 1226 sys_close(udfd) 1227 } 1228 b = scopy(pbody, b, "</ul>" as *u8) 1229 // CODE BROWSING: list HEAD's tree. Commit layout is fixed -- "tree "(5) + 64 hex -- so the tree 1230 // oid needs no parser, same trick as the first-parent walk. 1231 let uobjr: *u8 = sys_mmap(512) 1232 rr_path(uobjr, root, "objects" as *u8) 1233 let uctyp: *u8 = sys_mmap(16) 1234 let ucbuf: *u8 = sys_mmap(1 << 20) 1235 let utbuf: *u8 = sys_mmap(1 << 20) 1236 let uthex: *u8 = sys_mmap(80) 1237 if utl > 0 { if read_ref_at(root, utgt, uhex) == 1 { 1238 let uclen: i64 = sg_read_loose(uobjr, uhex, ucbuf, uctyp) 1239 if uclen > 69 { 1240 var uti: i64 = 0 1241 while uti < 64 { uthex[uti] = ucbuf[5 + uti]; uti = uti + 1 } 1242 uthex[64] = 0 as u8 1243 let utlen: i64 = sg_read_loose(uobjr, uthex, utbuf, uctyp) 1244 if utlen > 0 { 1245 b = scopy(pbody, b, "<h2>files at HEAD</h2><ul>" as *u8) 1246 var up: i64 = 0 1247 var urun: i64 = 1 1248 while urun == 1 { 1249 if up >= utlen { urun = 0 } else { 1250 var uq: i64 = up 1251 var ufs: i64 = 0 1252 while ufs == 0 { 1253 if uq >= utlen { ufs = 2 } else { if utbuf[uq] == (32 as u8) { ufs = 1 } else { uq = uq + 1 } } 1254 } 1255 if ufs == 2 { urun = 0 } else { 1256 let umode: i64 = uq - up 1257 uq = uq + 1 1258 let uns: i64 = uq 1259 var ufz: i64 = 0 1260 while ufz == 0 { 1261 if uq >= utlen { ufz = 2 } else { if utbuf[uq] == (0 as u8) { ufz = 1 } else { uq = uq + 1 } } 1262 } 1263 if ufz == 2 { urun = 0 } else { 1264 let unl: i64 = uq - uns 1265 b = scopy(pbody, b, "<li><code>" as *u8) 1266 var umi: i64 = 0 1267 while umi < umode { pbody[b] = utbuf[up + umi]; b = b + 1; umi = umi + 1 } 1268 b = scopy(pbody, b, "</code> <a href=\"ui?blob=" as *u8) 1269 let uohex: *u8 = sys_mmap(80) 1270 sg_hex((utbuf as i64 + uq + 1) as *u8, uohex) 1271 b = sg_cpy(pbody, b, uohex, 0, 64) 1272 sys_munmap(uohex, 80) 1273 b = scopy(pbody, b, "\">" as *u8) 1274 var uni: i64 = 0 1275 while uni < unl { b = ui_esc(pbody, b, utbuf[uns + uni] as i64); uni = uni + 1 } 1276 b = scopy(pbody, b, "</a></li>" as *u8) 1277 up = uq + 1 + 32 1278 } 1279 } 1280 } 1281 } 1282 b = scopy(pbody, b, "</ul>" as *u8) 1283 } 1284 } 1285 } } 1286 sys_munmap(uobjr, 512); sys_munmap(uctyp, 16); sys_munmap(ucbuf, 1 << 20) 1287 sys_munmap(utbuf, 1 << 20); sys_munmap(uthex, 80) 1288 var ro3: i64 = resp_hdr(resp, "text/html; charset=utf-8" as *u8, b) 1289 ro3 = sg_cpy(resp, ro3, pbody, 0, b) 1290 nx_http_server_send_response(cfd, resp, ro3) 1291 sys_munmap(uline, 512); sys_munmap(uhex, 80); sys_munmap(utgt, 256) 1292 sys_munmap(uhdir, 512); sys_munmap(udbuf, 65536); sys_munmap(urel, 320) 1293 return 0 1294} 1295func serve_v2_lsrefs(cfd: i64, resp: *u8, root: *u8, pbody: *u8) -> i64 { 1296 let b: i64 = v2_lsrefs_body(pbody, 1 << 20, root) 1297 var ro: i64 = resp_hdr(resp, "application/x-git-upload-pack-result" as *u8, b) 1298 ro = sg_cpy(resp, ro, pbody, 0, b) 1299 nx_http_server_send_response(cfd, resp, ro) 1300 return 0 1301} 1302func serve_v2_fetch(cfd: i64, resp: *u8, root: *u8, pack: *u8, packcap: i64, pbody: *u8, content: *u8, noblob: i64, body: *u8, blen: i64) -> i64 { 1303 // DELTA NEGOTIATION (have-based). git's have-invariant: a client only claims `have <oid>` for an 1304 // object whose FULL history it possesses. So if the client already has the tip we would serve, it 1305 // has everything reachable from it and the correct answer is an EMPTY pack -- no graph walk needed. 1306 // This is the dominant case for a polling beat: up-to-date fetches stop costing a full snapshot. 1307 var filt: i64 = noblob 1308 let ntgt: *u8 = sys_mmap(256) 1309 let nhx: *u8 = sys_mmap(80) 1310 let ntl: i64 = head_target(root, ntgt) 1311 if ntl > 0 { if read_ref_at(root, ntgt, nhx) == 1 { 1312 let needle: *u8 = sys_mmap(128) 1313 var no: i64 = scopy(needle, 0, "have " as *u8) 1314 no = sg_cpy(needle, no, nhx, 0, 64) 1315 needle[no] = 0 as u8 1316 if pcontains(body, blen, needle) == 1 { filt = 2; lg("V2 FETCH: client has our tip -> empty pack (up-to-date)\n" as *u8) } 1317 sys_munmap(needle, 128) 1318 } } 1319 // SHALLOW / deepen <N>: bound the commit set to the last N first-parent commits and declare the 1320 // boundary in a shallow-info section. Trees/blobs are still sent (a pack may carry extra objects; 1321 // it must not be MISSING any), so this stays correct while cutting deep history. 1322 let allow: *u8 = sys_mmap(65536) 1323 var nallow: i64 = 0 1324 var shallow_on: i64 = 0 1325 if pcontains(body, blen, "deepen " as *u8) == 1 { if ntl > 0 { 1326 var dep: i64 = 0 1327 var dp: i64 = 0 1328 while dp + 7 < blen { 1329 if shallow_on == 0 { if seq((body as i64 + dp) as *u8, 7, "deepen " as *u8) == 1 { 1330 var q2: i64 = dp + 7 1331 while q2 < blen { if body[q2] >= (48 as u8) { if body[q2] <= (57 as u8) { dep = dep * 10 + ((body[q2] as i64) - 48); q2 = q2 + 1 } else { q2 = blen + 9 } } else { q2 = blen + 9 } } 1332 shallow_on = 1 1333 } } 1334 dp = dp + 1 1335 } 1336 if dep < 1 { dep = 1 } 1337 if dep > 1000 { dep = 1000 } 1338 if shallow_on == 1 { if read_ref_at(root, ntgt, nhx) == 1 { nallow = walk_depth(root, nhx, dep, allow, content) } } 1339 } } 1340 sys_munmap(ntgt, 256); sys_munmap(nhx, 80) 1341 let plen: i64 = pack_disk(root, pack, packcap, content, filt, allow, nallow) 1342 if plen < 0 { 1343 lg("nx_sovgit_git: PACK OVERFLOW (v2 fetch)\n" as *u8) 1344 send_txt(cfd, resp, "507 Insufficient Storage" as *u8, "nishi-git: repo exceeds the declared 128MiB pack envelope\n" as *u8) 1345 return 0 1346 } 1347 var b: i64 = 0 1348 if nallow > 0 { 1349 b = pkts(pbody, b, "shallow-info\n" as *u8) 1350 let sl2: *u8 = sys_mmap(128) 1351 var so2: i64 = scopy(sl2, 0, "shallow " as *u8) 1352 var bi: i64 = 0 1353 while bi < 64 { sl2[so2 + bi] = allow[(nallow - 1) * 64 + bi]; bi = bi + 1 } 1354 so2 = so2 + 64 1355 sl2[so2] = 10 as u8; so2 = so2 + 1 1356 b = pkt(pbody, b, sl2, so2) 1357 b = hex4(pbody, b, 1) 1358 sys_munmap(sl2, 128) 1359 lg("V2 FETCH: shallow depth-limited commits=" as *u8); lgn(nallow); lg("\n" as *u8) 1360 } 1361 b = pkts(pbody, b, "packfile\n" as *u8) 1362 var off: i64 = 0 1363 while off < plen { 1364 var csz: i64 = plen - off 1365 if csz > 8192 { csz = 8192 } 1366 b = sb_chunk(pbody, b, 1, (pack as i64 + off) as *u8, csz) 1367 off = off + csz 1368 } 1369 b = pktflush(pbody, b) 1370 lg("V2 FETCH packlen=" as *u8); lgn(plen); lg(" framed=" as *u8); lgn(b); lg("\n" as *u8) 1371 var ro: i64 = resp_hdr(resp, "application/x-git-upload-pack-result" as *u8, b) 1372 ro = sg_cpy(resp, ro, pbody, 0, b) 1373 nx_http_server_send_response(cfd, resp, ro) 1374 return 0 1375} 1376 1377// smart-HTTP receive-pack (PUSH): parse ALL ref-update commands + unpack the packfile + update refs + report. 1378func serve_receivepack(cfd: i64, body: *u8, blen: i64, resp: *u8, root: *u8) -> i64 { 1379 ensure_repo(root) 1380 // command lists (max 16 per push -- declared envelope) 1381 let shas: *u8 = sys_mmap(K_MAGIC_2048) // 16 x 80 1382 let refs: *u8 = sys_mmap(K_MAGIC_4096) // 16 x 256 1383 var ncmd: i64 = 0 1384 var pos: i64 = 0 1385 var scan: i64 = 1 1386 while scan == 1 { 1387 if pos + 4 > blen { scan = 0 } else { 1388 let len: i64 = rd_hex4(body, pos) 1389 if len == 0 { pos = pos + 4; scan = 0 } else { 1390 if len < 4 { scan = 0 } else { 1391 if ncmd < 16 { if pos + 4 + 130 <= blen { 1392 let p: i64 = pos + 4 1393 let sd: *u8 = (shas as i64 + ncmd * 80) as *u8 1394 var j: i64 = 0 1395 while j < 64 { sd[j] = body[p + 65 + j]; j = j + 1 } 1396 sd[64] = 0 as u8 1397 let rd: *u8 = (refs as i64 + ncmd * 256) as *u8 1398 var k: i64 = 0 1399 var rp: i64 = p + 130 1400 var go: i64 = 1 1401 while go == 1 { 1402 if rp >= pos + len { go = 0 } else { 1403 let ch: i64 = body[rp] as i64 1404 if ch == 0 { go = 0 } else { if ch == 10 { go = 0 } else { 1405 if k < 250 { rd[k] = body[rp]; k = k + 1 } 1406 rp = rp + 1 1407 } } 1408 } 1409 } 1410 rd[k] = 0 as u8 1411 ncmd = ncmd + 1 1412 } } 1413 pos = pos + len 1414 } 1415 } 1416 } 1417 } 1418 let objroot: *u8 = sys_mmap(512) 1419 rr_path(objroot, root, "objects" as *u8) 1420 let uc: i64 = unpack_into(objroot, (body as i64 + pos) as *u8, blen - pos) 1421 lg("RCVPACK blen=" as *u8); lgn(blen); lg(" cmds=" as *u8); lgn(ncmd); lg(" packlen=" as *u8); lgn(blen - pos); lg(" uc=" as *u8); lgn(uc); lg("\n" as *u8) 1422 // apply each command (fail-closed per ref: bad name / zero-sha delete => ng) 1423 let applied: *u8 = sys_mmap(32) 1424 let rp2: *u8 = sys_mmap(768) 1425 let val: *u8 = sys_mmap(80) 1426 var c: i64 = 0 1427 var firstok: i64 = 0 - 1 1428 while c < ncmd { 1429 let sd2: *u8 = (shas as i64 + c * 80) as *u8 1430 let rd2: *u8 = (refs as i64 + c * 256) as *u8 1431 var okc: i64 = 0 1432 // OPLOG (Jujutsu-class): capture the ref's PRIOR oid BEFORE overwriting it. Without the 1433 // before-image an operation log cannot support undo -- it is just a change feed. This is the 1434 // server-side half of "every operation is recorded and reversible". 1435 let oldhex: *u8 = sys_mmap(80) 1436 var had_old: i64 = 0 1437 if ref_ok(rd2) == 1 { if read_ref_at(root, rd2, oldhex) == 1 { had_old = 1 } } 1438 if uc >= 0 { if ref_ok(rd2) == 1 { 1439 var zc: i64 = 0 1440 var zi: i64 = 0 1441 while zi < 64 { if sd2[zi] == (48 as u8) { zc = zc + 1 } zi = zi + 1 } 1442 if zc < 64 { 1443 rr_path(rp2, root, rd2) 1444 var vo: i64 = scopy(val, 0, sd2); val[vo] = 10 as u8; vo = vo + 1 1445 if wfile(rp2, val, vo) == 0 { okc = 1 } 1446 } 1447 } } 1448 // append the operation row: ref, before-image, after-image. Append-only, so the log IS the 1449 // history and `undo` is just "write the before-image back" -- no mutation, no rewrite. 1450 if okc == 1 { 1451 let opfd: i64 = sys_openat_append("knowledge/status/sovgit_oplog.log" as *u8, 0x1a4) 1452 if opfd >= 0 { 1453 let oprow: *u8 = sys_mmap(1024) 1454 var oo2: i64 = scopy(oprow, 0, "OP epoch=" as *u8) 1455 oo2 = udec(oprow, oo2, sys_now_realtime_sec()) 1456 oo2 = scopy(oprow, oo2, " repo=" as *u8) 1457 oo2 = scopy(oprow, oo2, root) 1458 oo2 = scopy(oprow, oo2, " ref=" as *u8) 1459 oo2 = scopy(oprow, oo2, rd2) 1460 oo2 = scopy(oprow, oo2, " old=" as *u8) 1461 if had_old == 1 { oo2 = sg_cpy(oprow, oo2, oldhex, 0, 64) } else { oo2 = scopy(oprow, oo2, "NEW-REF" as *u8) } 1462 oo2 = scopy(oprow, oo2, " new=" as *u8) 1463 oo2 = sg_cpy(oprow, oo2, sd2, 0, 64) 1464 oprow[oo2] = 10 as u8; oo2 = oo2 + 1 1465 sys_write(opfd, oprow, oo2) 1466 sys_close(opfd) 1467 sys_munmap(oprow, 1024) 1468 } 1469 } 1470 sys_munmap(oldhex, 80) 1471 applied[c] = okc as u8 1472 if okc == 1 { if firstok < 0 { firstok = c } } 1473 c = c + 1 1474 } 1475 // HEAD self-heal: if HEAD points at a missing ref and we just applied one, repoint (first push lands checkout-able) 1476 if firstok >= 0 { 1477 let tgt: *u8 = sys_mmap(256) 1478 let hx: *u8 = sys_mmap(80) 1479 let tl: i64 = head_target(root, tgt) 1480 var hmiss: i64 = 1 1481 if tl > 0 { if read_ref_at(root, tgt, hx) == 1 { hmiss = 0 } } 1482 if hmiss == 1 { 1483 let hp: *u8 = sys_mmap(512) 1484 rr_path(hp, root, "HEAD" as *u8) 1485 let hv: *u8 = sys_mmap(320) 1486 var ho: i64 = scopy(hv, 0, "ref: " as *u8) 1487 ho = scopy(hv, ho, (refs as i64 + firstok * 256) as *u8) 1488 hv[ho] = 10 as u8; ho = ho + 1 1489 wfile(hp, hv, ho) 1490 } 1491 } 1492 // EVIDENCE WIRING: make every push VISIBLE to the gate/evidence plane. Until now the git host 1493 // was invisible to it -- only 18 files in the whole tree mention sovgit and 16 ARE sovgit, so no 1494 // gate could ever see a push land. Append-only (sys_openat_append), one row per push, never 1495 // rewritten -- additive, so a later reader can audit every push that ever happened. 1496 let evfd: i64 = sys_openat_append("knowledge/status/sovgit_push_evidence.log" as *u8, 0x1a4) 1497 if evfd >= 0 { 1498 let evl: *u8 = sys_mmap(1024) 1499 var eo: i64 = scopy(evl, 0, "PUSH epoch=" as *u8) 1500 eo = udec(evl, eo, sys_now_realtime_sec()) 1501 eo = scopy(evl, eo, " repo=" as *u8) 1502 eo = scopy(evl, eo, root) 1503 eo = scopy(evl, eo, " unpack=" as *u8) 1504 if uc >= 0 { eo = scopy(evl, eo, "ok" as *u8) } else { eo = scopy(evl, eo, "REFUSED" as *u8) } 1505 eo = scopy(evl, eo, " pathgate=" as *u8) 1506 if uc >= 0 { eo = scopy(evl, eo, "PASS" as *u8) } else { eo = scopy(evl, eo, "FAIL-OR-BADPACK" as *u8) } 1507 eo = scopy(evl, eo, " cmds=" as *u8) 1508 eo = udec(evl, eo, ncmd) 1509 eo = scopy(evl, eo, " applied=" as *u8) 1510 var nap: i64 = 0 1511 var ci2: i64 = 0 1512 while ci2 < ncmd { if (applied[ci2] as i64) == 1 { nap = nap + 1 } ci2 = ci2 + 1 } 1513 eo = udec(evl, eo, nap) 1514 evl[eo] = 10 as u8; eo = eo + 1 1515 sys_write(evfd, evl, eo) 1516 sys_close(evfd) 1517 sys_munmap(evl, 1024) 1518 } 1519 // report-status 1520 let rep: *u8 = sys_mmap(K_MAGIC_8192) 1521 var b: i64 = 0 1522 if uc >= 0 { b = pkts(rep, b, "unpack ok\n" as *u8) } else { b = pkts(rep, b, "unpack failed\n" as *u8) } 1523 let okl: *u8 = sys_mmap(320) 1524 var c2: i64 = 0 1525 while c2 < ncmd { 1526 var oo: i64 = 0 1527 if (applied[c2] as i64) == 1 { oo = scopy(okl, 0, "ok " as *u8) } else { oo = scopy(okl, 0, "ng " as *u8) } 1528 oo = scopy(okl, oo, (refs as i64 + c2 * 256) as *u8) 1529 if (applied[c2] as i64) != 1 { oo = scopy(okl, oo, " refused" as *u8) } 1530 okl[oo] = 10 as u8; oo = oo + 1 1531 if b + oo + 8 < K_MAGIC_8192 { b = pkt(rep, b, okl, oo) } 1532 c2 = c2 + 1 1533 } 1534 b = pktflush(rep, b) 1535 var rr: i64 = resp_hdr(resp, "application/x-git-receive-pack-result" as *u8, b) 1536 rr = sg_cpy(resp, rr, rep, 0, b) 1537 nx_http_server_send_response(cfd, resp, rr) 1538 sys_munmap(shas, K_MAGIC_2048) 1539 sys_munmap(refs, K_MAGIC_4096) 1540 sys_munmap(objroot, 512) 1541 sys_munmap(applied, 32) 1542 sys_munmap(rp2, 768) 1543 sys_munmap(val, 80) 1544 sys_munmap(rep, K_MAGIC_8192) 1545 sys_munmap(okl, 320) 1546 return 0 1547} 1548 1549// ---- chunked transfer-encoding decode (git switches to chunked above http.postBuffer ~1MiB) ---- 1550// src[0..n] = raw chunked stream. Decodes into out (<= outcap). Returns decoded length, 1551// -1 = need more input (caller reads more and retries), -2 = malformed/overflow (fail loud). 1552func ch_decode(src: *u8, n: i64, out: *u8, outcap: i64) -> i64 { 1553 var pos: i64 = 0 1554 var olen: i64 = 0 1555 var go: i64 = 1 1556 var rc: i64 = 0 - 1 1557 while go == 1 { 1558 // parse hex chunk size (tolerate chunk extensions up to CR) 1559 var sz: i64 = 0 1560 var seen: i64 = 0 1561 var scan: i64 = 1 1562 while scan == 1 { 1563 if pos >= n { scan = 0; go = 0 } else { 1564 let c: i64 = src[pos] as i64 1565 if c == 13 { scan = 0 } else { 1566 if sg_ishex(c) == 1 { 1567 var d: i64 = 0 1568 if c >= 48 { if c <= 57 { d = c - 48 } } 1569 if c >= 97 { if c <= 102 { d = c - 87 } } 1570 if c >= 65 { if c <= 70 { d = c - 55 } } 1571 sz = sz * 16 + d; seen = seen + 1 1572 } 1573 pos = pos + 1 1574 } 1575 } 1576 } 1577 if go == 1 { 1578 if seen == 0 { rc = 0 - 2; go = 0 } else { 1579 if pos + 2 > n { go = 0 } else { 1580 pos = pos + 2 1581 if sz == 0 { rc = olen; go = 0 } else { 1582 if pos + sz + 2 > n { go = 0 } else { 1583 if olen + sz > outcap { rc = 0 - 2; go = 0 } else { 1584 var i: i64 = 0 1585 while i < sz { out[olen + i] = src[pos + i]; i = i + 1 } 1586 olen = olen + sz 1587 pos = pos + sz + 2 1588 } 1589 } 1590 } 1591 } 1592 } 1593 } 1594 } 1595 return rc 1596} 1597 1598// route one request: parse repo -> dispatch service (smart advert / upload / receive / static) 1599func serve(cfd: i64, path: *u8, plen: i64, body: *u8, blen: i64, resp: *u8, pack: *u8, packcap: i64, pbody: *u8, content: *u8, v2: i64) -> i64 { 1600 let seg: *u8 = sys_mmap(96) 1601 let restp: *i64 = sys_mmap(16) as *i64 1602 let root: *u8 = sys_mmap(512) 1603 let sl: i64 = parse_repo(path, plen, seg, restp) 1604 var handled: i64 = 0 1605 if sl < 0 { send_txt(cfd, resp, "400 Bad Request" as *u8, "nishi-git: invalid repo name (allowed: [A-Za-z0-9_-], max 64)\n" as *u8); handled = 1 } 1606 if handled == 0 { 1607 repo_root(root, seg, sl) 1608 let rest: *u8 = (path as i64 + restp[0]) as *u8 1609 let rlen: i64 = plen - restp[0] 1610 // receive-pack paths create the repo (push-to-create); everything else 404s on a missing repo 1611 if pcontains(rest, rlen, "/git-receive-pack" as *u8) == 1 { serve_receivepack(cfd, body, blen, resp, root); handled = 1 } 1612 if handled == 0 { if pcontains(rest, rlen, "/info/refs" as *u8) == 1 { 1613 if pcontains(path, plen, "service=git-receive-pack" as *u8) == 1 { ensure_repo(root); serve_advert(cfd, resp, root, 1); handled = 1 } 1614 } } 1615 if handled == 0 { if sl > 0 { if repo_exists(root) == 0 { send_txt(cfd, resp, "404 Not Found" as *u8, "nishi-git: no such repo\n" as *u8); handled = 1 } } } 1616 if handled == 0 { if v2 == 1 { if pcontains(rest, rlen, "/git-upload-pack" as *u8) == 1 { 1617 if pcontains(body, blen, "command=ls-refs" as *u8) == 1 { serve_v2_lsrefs(cfd, resp, root, pbody); handled = 1 } 1618 if handled == 0 { if pcontains(body, blen, "command=bundle-uri" as *u8) == 1 { serve_v2_bundleuri(cfd, resp, pbody, seg, sl); handled = 1 } } 1619 if handled == 0 { if pcontains(body, blen, "command=fetch" as *u8) == 1 { serve_v2_fetch(cfd, resp, root, pack, packcap, pbody, content, pcontains(body, blen, "filter blob:none" as *u8), body, blen); handled = 1 } } 1620 } } } 1621 if handled == 0 { if v2 == 1 { if pcontains(rest, rlen, "/info/refs" as *u8) == 1 { 1622 if pcontains(path, plen, "service=git-upload-pack" as *u8) == 1 { serve_v2_caps(cfd, resp); handled = 1 } 1623 } } } 1624 if handled == 0 { if pcontains(rest, rlen, "/git-upload-pack" as *u8) == 1 { serve_uploadpack_result(cfd, resp, root, pack, packcap, pbody, content); handled = 1 } } 1625 if handled == 0 { if pcontains(rest, rlen, "/info/refs" as *u8) == 1 { 1626 if pcontains(path, plen, "service=git-upload-pack" as *u8) == 1 { serve_advert(cfd, resp, root, 0); handled = 1 } 1627 } } 1628 if handled == 0 { if pcontains(rest, rlen, "/bundle" as *u8) == 1 { serve_bundle(cfd, resp, root, pack, packcap, pbody, content); handled = 1 } } 1629 if handled == 0 { if pcontains(rest, rlen, "/oplog" as *u8) == 1 { serve_oplog(cfd, resp, pbody); handled = 1 } } 1630 if handled == 0 { if pcontains(rest, rlen, "/ci" as *u8) == 1 { serve_ci(cfd, resp, pbody); handled = 1 } } 1631 if handled == 0 { if pcontains(rest, rlen, "/issues" as *u8) == 1 { serve_issues(cfd, resp, root, pbody, seg, sl, body, blen); handled = 1 } } 1632 if handled == 0 { if pcontains(rest, rlen, "/prs" as *u8) == 1 { serve_forge_log(cfd, resp, root, pbody, body, blen, "prs.log" as *u8, "pull requests" as *u8, "PR" as *u8, "<form method=post><input name=src placeholder=\"source ref\"> <input name=dst placeholder=\"target ref\"> <input name=title placeholder=title><button>open</button></form>" as *u8); handled = 1 } } 1633 if handled == 0 { if pcontains(rest, rlen, "/releases" as *u8) == 1 { serve_forge_log(cfd, resp, root, pbody, body, blen, "releases.log" as *u8, "releases" as *u8, "RELEASE" as *u8, "<form method=post><input name=tag placeholder=\"tag e.g. v1.0\"> <input name=notes placeholder=notes><button>publish</button></form>" as *u8); handled = 1 } } 1634 if handled == 0 { if pcontains(rest, rlen, "/ui" as *u8) == 1 { serve_ui(cfd, resp, root, pbody, seg, sl, rest, rlen); handled = 1 } } 1635 if handled == 0 { serve_static(cfd, root, rest, rlen, resp) } 1636 } 1637 sys_munmap(seg, 96) 1638 sys_munmap(restp as *u8, 16) 1639 sys_munmap(root, 512) 1640 return 0 1641} 1642 1643// legacy demo repo (X0/X1 back-compat): idempotent -- preserves any on-disk state incl pushes 1644func assemble_repo(chex_out: *u8) -> i64 { 1645 let lroot: *u8 = sys_mmap(96) 1646 let lo: i64 = scopy(lroot, 0, "knowledge/_sovgit_repo" as *u8) 1647 lroot[lo] = 0 as u8 1648 if read_ref_at(lroot, "refs/heads/master" as *u8, chex_out) == 1 { lg("nx_sovgit_git: existing repo preserved (head on disk)\n" as *u8); return 0 } 1649 sys_mkdir("knowledge/_sovgit_repo" as *u8, 0x1ed) 1650 sys_mkdir("knowledge/_sovgit_repo/refs" as *u8, 0x1ed) 1651 sys_mkdir("knowledge/_sovgit_repo/refs/heads" as *u8, 0x1ed) 1652 sys_mkdir("knowledge/_sovgit_repo/info" as *u8, 0x1ed) 1653 let objroot: *u8 = "knowledge/_sovgit_repo/objects" as *u8 1654 let bhex: *u8 = sys_mmap(80); sg_write_loose(objroot, "blob" as *u8, "hello" as *u8, 5, bhex) 1655 let raw: *u8 = sys_mmap(32); sg_oid_raw("blob" as *u8, "hello" as *u8, 5, raw) 1656 let tree: *u8 = sys_mmap(128); let tlen: i64 = sg_tree_entry(tree, 0, "100644" as *u8, "hello.txt" as *u8, raw) 1657 let thex: *u8 = sys_mmap(80); sg_write_loose(objroot, "tree" as *u8, tree, tlen, thex) 1658 let cbody: *u8 = "tree 06d48a30caf6b4263876dbc71cec3bedfa08df79a4a59d55fbb9ea106a1ecc9c\nauthor t <t@t> 1700000000 +0000\ncommitter t <t@t> 1700000000 +0000\n\nmsg\n" as *u8 1659 sg_write_loose(objroot, "commit" as *u8, cbody, sg_slen(cbody), chex_out) 1660 let mref: *u8 = sys_mmap(80); var mo: i64 = sg_cpy(mref, 0, chex_out, 0, 64); mref[mo] = 10 as u8; mo = mo + 1 1661 wfile("knowledge/_sovgit_repo/refs/heads/master" as *u8, mref, mo) 1662 wstr("knowledge/_sovgit_repo/HEAD" as *u8, "ref: refs/heads/master\n" as *u8) 1663 wstr("knowledge/_sovgit_repo/config" as *u8, "[core]\n repositoryformatversion = 1\n bare = true\n[extensions]\n objectformat = sha256\n" as *u8) 1664 let ir: *u8 = sys_mmap(96); var io: i64 = sg_cpy(ir, 0, chex_out, 0, 64); ir[io] = 9 as u8; io = io + 1 1665 io = scopy(ir, io, "refs/heads/master" as *u8); ir[io] = 10 as u8; io = io + 1 1666 wfile("knowledge/_sovgit_repo/info/refs" as *u8, ir, io) 1667 sys_mkdir("knowledge/_sovgit_repo/objects/info" as *u8, 0x1ed) 1668 wstr("knowledge/_sovgit_repo/objects/info/packs" as *u8, "\n" as *u8) 1669 return 0 1670} 1671 1672// 401 challenge with WWW-Authenticate: Basic -> a stock git client transparently resends the request carrying 1673// `Authorization: Basic base64(user:cap)`, which login_gate_cap accepts as an ocap. realm quote is byte 0x22 to 1674// avoid depending on string-escape support. Connection: close so git opens a fresh authenticated connection. 1675func send_401(cfd: i64, resp: *u8) -> i64 { 1676 let body: *u8 = "nishi-git: authentication required -- present an ocap (scope git) via Authorization: Basic x:<cap>\n" as *u8 1677 let bn: i64 = sstrlen(body) 1678 var o: i64 = 0 1679 o = scopy(resp, o, "HTTP/1.1 401 Unauthorized\r\n" as *u8) 1680 o = scopy(resp, o, "WWW-Authenticate: Basic realm=" as *u8) 1681 resp[o] = 34 as u8; o = o + 1 1682 o = scopy(resp, o, "nishi-git" as *u8) 1683 resp[o] = 34 as u8; o = o + 1 1684 o = scopy(resp, o, "\r\n" as *u8) 1685 o = scopy(resp, o, "Content-Type: text/plain\r\nContent-Length: " as *u8) 1686 o = udec(resp, o, bn) 1687 o = scopy(resp, o, "\r\nConnection: close\r\n\r\n" as *u8) 1688 o = scopy(resp, o, body) 1689 nx_http_server_send_response(cfd, resp, o) 1690 return 0 1691} 1692 1693func pg_case(nm: *u8, nl: i64, want: i64, label: *u8, fails: *i64) -> i64 { 1694 let got: i64 = sg_name_portable(nm, nl) 1695 if got == want { lg(" PASS " as *u8) } else { lg(" FAIL " as *u8); fails[0] = fails[0] + 1 } 1696 lg(label); lg(" want=" as *u8); lgn(want); lg(" got=" as *u8); lgn(got); lg("\n" as *u8) 1697 return 0 1698} 1699func pg_lit(s: *u8, want: i64, fails: *i64) -> i64 { return pg_case(s, sstrlen(s), want, s, fails) } 1700func sg_pathgate_selftest() -> i64 { 1701 let fails: *i64 = sys_mmap(16) as *i64 1702 fails[0] = 0 1703 lg("nx_sovgit_git pathgate selftest (0=REFUSE 1=ACCEPT)\n" as *u8) 1704 lg(" -- must REFUSE --\n" as *u8) 1705 pg_lit("nul.js" as *u8, 0, fails) 1706 pg_lit("NUL" as *u8, 0, fails) 1707 pg_lit("con.txt" as *u8, 0, fails) 1708 pg_lit("COM1.log" as *u8, 0, fails) 1709 pg_lit("lpt9" as *u8, 0, fails) 1710 pg_lit("aux" as *u8, 0, fails) 1711 pg_lit("we:ird" as *u8, 0, fails) 1712 pg_lit("star*x" as *u8, 0, fails) 1713 pg_lit("q?mark" as *u8, 0, fails) 1714 pg_lit("trail." as *u8, 0, fails) 1715 pg_lit("trail " as *u8, 0, fails) 1716 pg_lit(".." as *u8, 0, fails) 1717 pg_lit("." as *u8, 0, fails) 1718 pg_lit(".git" as *u8, 0, fails) 1719 pg_lit("sub/dir" as *u8, 0, fails) 1720 let ctl: *u8 = sys_mmap(16) 1721 ctl[0] = 97 as u8; ctl[1] = 1 as u8; ctl[2] = 98 as u8 1722 pg_case(ctl, 3, 0, "a-ctl-b" as *u8, fails) 1723 let bsl: *u8 = sys_mmap(16) 1724 bsl[0] = 97 as u8; bsl[1] = 92 as u8; bsl[2] = 98 as u8 1725 pg_case(bsl, 3, 0, "a-backslash-b" as *u8, fails) 1726 lg(" -- must ACCEPT (positive control) --\n" as *u8) 1727 pg_lit("nx_ok.nx" as *u8, 1, fails) 1728 pg_lit("README.md" as *u8, 1, fails) 1729 pg_lit("nul_.js" as *u8, 1, fails) 1730 pg_lit("conf.txt" as *u8, 1, fails) 1731 pg_lit("com.txt" as *u8, 1, fails) 1732 pg_lit("normal_file.sh" as *u8, 1, fails) 1733 pg_lit("nx_sovgit_git.nx" as *u8, 1, fails) 1734 lg(" -- tree walker --\n" as *u8) 1735 let tr: *u8 = sys_mmap(256) 1736 var o: i64 = scopy(tr, 0, "100644 good.nx" as *u8) 1737 tr[o] = 0 as u8; o = o + 1 1738 var z: i64 = 0 1739 while z < 32 { tr[o + z] = 7 as u8; z = z + 1 } 1740 o = o + 32 1741 let g1: i64 = sg_tree_names_ok(tr, o, 32) 1742 if g1 == 1 { lg(" PASS clean tree accepted\n" as *u8) } else { lg(" FAIL clean tree refused\n" as *u8); fails[0] = fails[0] + 1 } 1743 var o2: i64 = scopy(tr, 0, "100644 nul.js" as *u8) 1744 tr[o2] = 0 as u8; o2 = o2 + 1 1745 var z2: i64 = 0 1746 while z2 < 32 { tr[o2 + z2] = 7 as u8; z2 = z2 + 1 } 1747 o2 = o2 + 32 1748 let g2: i64 = sg_tree_names_ok(tr, o2, 32) 1749 if g2 == 0 { lg(" PASS tree with nul.js REFUSED\n" as *u8) } else { lg(" FAIL tree with nul.js accepted\n" as *u8); fails[0] = fails[0] + 1 } 1750 lg("PATHGATE fails=" as *u8); lgn(fails[0]); lg("\n" as *u8) 1751 if fails[0] == 0 { lg("PATHGATE VERDICT=GREEN\n" as *u8); return 0 } 1752 lg("PATHGATE VERDICT=RED\n" as *u8) 1753 return 1 1754} 1755 1756func main(argc: i64, argv: *i64) -> i64 { 1757 if argc > 1 { 1758 let a1: *u8 = argv[1] as *u8 1759 if seq(a1, sstrlen(a1), "pathgate" as *u8) == 1 { return sg_pathgate_selftest() } 1760 } 1761 let chex: *u8 = sys_mmap(80) 1762 assemble_repo(chex) 1763 // self-gate: a cap-secret path as argv[1] engages ocap auth (scope "git"); absent => OPEN (demo/gate parity). 1764 // Fail-closed once ON: every request must carry a valid unexpired unrevoked cap for scope git in any wire form. 1765 var auth_on: i64 = 0 1766 var gsec: *u8 = 0 as *u8 1767 var gsec_n: i64 = 0 1768 if argc > 1 { 1769 let sbox: *i64 = sys_mmap(16) as *i64; sbox[0] = 0 1770 gsec = sys_read_file(argv[1] as *u8, sbox) 1771 if (gsec as i64) != 0 { gsec_n = sbox[0]; if gsec_n > 0 { auth_on = 1 } } 1772 } 1773 if auth_on == 1 { lg("nx_sovgit_git/2: AUTH ON (ocap scope=git; 401 WWW-Authenticate: Basic)\n" as *u8) } else { lg("nx_sovgit_git/2: AUTH OFF (open -- pass cap-secret path as argv[1] to gate)\n" as *u8) } 1774 let addr: *u8 = sys_mmap(16); nx_http_server_addr_loopback(addr, K_MAGIC_18691) 1775 let ov: *i64 = sys_mmap(16) as *i64 1776 let lfd: i64 = nx_http_server_listen(addr, 16, ov) 1777 if lfd < 0 { lg("nx_sovgit_git: LISTEN FAILED\n" as *u8); sys_exit(1); return 1 } 1778 // ---- bounded buffers, allocated ONCE (bounded-VSZ law); the numbers ARE the declared envelope ---- 1779 // ⚠INCIDENT-CORRECTED 2026-07-20: I first raised this to 128 MiB. A body that big cannot cross the 1780 // edge (~2 MiB cap), so an oversized push left this SINGLE-THREADED accept loop blocked in sys_read 1781 // waiting for bytes that never came -- one bad request wedges the WHOLE daemon, and repeated 1782 // attempts pressured the box. 16 MiB gives loopback seeds real headroom (8x the edge cap) without 1783 // inviting that. The wedge itself is now closed BY CONSTRUCTION via the accept-side recv timeout. 1784 let reqcap: i64 = 1 << 24 // 16 MiB 1785 let packcap: i64 = 1 << 27 // 128 MiB max clone pack (overflow -> 507, never truncated) 1786 let req: *u8 = sys_mmap(reqcap) 1787 let resp: *u8 = sys_mmap((1 << 27) + K_MAGIC_65536) // pack + NAK + HTTP header headroom 1788 let pack: *u8 = sys_mmap(packcap) 1789 let pbody: *u8 = sys_mmap(1 << 27) 1790 let content: *u8 = sys_mmap(1 << 26) // 64 MiB max single object 1791 let chunkbuf: *u8 = sys_mmap(reqcap) // decoded chunked-transfer push bodies 1792 lg("nx_sovgit_git/2: multi-repo root=knowledge/sovgit_repos (legacy=_sovgit_repo); ENVELOPE req=16MiB pack=128MiB obj=64MiB refs<=16/push branch=refs/heads/<name> no-delete recv-timeout=25s\n" as *u8) 1793 lg("nx_sovgit_git/2: listening 127.0.0.1:18691 (git v0 smart-HTTP upload+receive, push-to-create, wall-only)\n" as *u8) 1794 let om: *i64 = sys_mmap(16) as *i64 1795 let opo: *i64 = sys_mmap(16) as *i64 1796 let opl: *i64 = sys_mmap(16) as *i64 1797 let ocl: *i64 = sys_mmap(16) as *i64 1798 let obo: *i64 = sys_mmap(16) as *i64 1799 let orn: *i64 = sys_mmap(16) as *i64 1800 var run: i64 = 1 1801 while run == 1 { 1802 let cfd: i64 = nx_http_server_accept_one(lfd, ov) 1803 if cfd >= 0 { 1804 // NEVER-WEDGE: this accept loop is single-threaded, so a peer that declares a Content-Length 1805 // it never finishes sending would otherwise block every other client forever (observed live 1806 // 2026-07-20). A recv/send timeout turns that permanent wedge into a bounded stall: the read 1807 // returns <=0, the request is abandoned, and the daemon serves the next caller. 1808 sys_set_socket_timeout(cfd, 25) // NEGCTL-MARKER 1809 let rc: i64 = nx_http_server_read_request(cfd, req, reqcap, om, opo, opl, ocl, obo, orn) 1810 if rc == 0 { 1811 var admit: i64 = 1 1812 if auth_on == 1 { if login_gate_cap(req, orn[0], gsec, gsec_n, "git" as *u8, 3, sys_now_realtime_sec()) == 0 { admit = 0 } } 1813 if admit == 1 { 1814 // chunked transfer (git push > http.postBuffer): 100-continue + read-to-terminator + decode 1815 var ischunked: i64 = pcontains(req, obo[0], "Transfer-Encoding: chunked" as *u8) 1816 if ischunked == 0 { ischunked = pcontains(req, obo[0], "transfer-encoding: chunked" as *u8) } 1817 if ischunked == 1 { 1818 if pcontains(req, obo[0], "100-continue" as *u8) == 1 { sys_write(cfd, "HTTP/1.1 100 Continue\r\n\r\n" as *u8, 25) } 1819 var total: i64 = orn[0] 1820 var dlen: i64 = ch_decode((req as i64 + obo[0]) as *u8, total - obo[0], chunkbuf, reqcap) 1821 var rgo: i64 = 1 1822 while rgo == 1 { 1823 if dlen != (0 - 1) { rgo = 0 } else { 1824 if total >= reqcap { rgo = 0 } else { 1825 let r2: i64 = sys_read(cfd, (req as i64 + total) as *u8, reqcap - total) 1826 if r2 <= 0 { rgo = 0 } else { 1827 total = total + r2 1828 dlen = ch_decode((req as i64 + obo[0]) as *u8, total - obo[0], chunkbuf, reqcap) 1829 } 1830 } 1831 } 1832 } 1833 lg("CHUNKED total=" as *u8); lgn(total); lg(" dlen=" as *u8); lgn(dlen); lg("\n" as *u8) 1834 if dlen >= 0 { serve(cfd, (req as i64 + opo[0]) as *u8, opl[0], chunkbuf, dlen, resp, pack, packcap, pbody, content, pcontains(req, obo[0], "version=2" as *u8)) } else { send_txt(cfd, resp, "400 Bad Request" as *u8, "nishi-git: chunked body exceeds 8MiB envelope or malformed\n" as *u8) } 1835 } else { 1836 serve(cfd, (req as i64 + opo[0]) as *u8, opl[0], (req as i64 + obo[0]) as *u8, orn[0] - obo[0], resp, pack, packcap, pbody, content, pcontains(req, obo[0], "version=2" as *u8)) 1837 } 1838 } else { send_401(cfd, resp); sys_close(cfd) } 1839 } else { sys_close(cfd) } 1840 } 1841 } 1842 return 0 1843}