code wiki / _hdl_build / nx_sovgit_push.nx

nx_sovgit_push.nx source

↩ module page · 1139 lines · 50886 B

1// nx_sovgit_push.nx -- ★SOVGIT F222: the SOVEREIGN GIT PUSHER. Snapshots a working tree and pushes it to a 2// sovgit host speaking the git receive-pack CLIENT wire, with ZERO /usr/bin/git anywhere in the path. 3// This is the no-git-law end-state for the replication leg: walk -> X0 sha256 objects -> X1b packfile -> 4// pkt-line ref-update + pack over HTTP -> report-status. The ark's crash-durability beat becomes sovereign. 5// 6// WHY A SNAPSHOT (not a git-history mirror): stock git CANNOT push a sha1 repo to a sha256 remote (no interop), 7// so the sovereign leg re-frames the working tree as sha256 objects. Each push chains a commit onto the remote 8// ref (parent = the advertised old sha) so history is preserved additively (rule 13) -- exactly the ark's job. 9// 10// SCALE ENVELOPE (declared in output, guarded in code, fail LOUD -- never a silent partial snapshot): 11// entries per directory <= 20000 name bytes per directory <= 2 MiB tree depth <= 32 12// single file <= 64 MiB pack <= 128 MiB path <= 4096 13// skipped by design: .git, symlinks + special files, and SECRET MATERIAL (deny-by-construction: 14// secret/passw/token/credential/.pem/.cap/.key/_key(s)/privkey/id_rsa/id_ed25519/_seed.bin/opaque) 15// -- every exclusion is COUNTED and NAMED on stderr, never silently dropped 16// ⚠the SERVER's request envelope also binds (nx_sovgit_git req buffer); a push above it is refused there. 17// Transport rung 1 = loopback TCP (127.0.0.1:<port>) -- the daemon is wall-only by design; the TLS/edge 18// transport for off-box pushes is the sibling rung (compose mcl_req-class primitives). 19// license_tier: ORIGINAL 20import "nx_sovgit_obj.nx" 21import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 22// TLS transport (rung 2) -- the SAME request bytes over sovereign TLS-1.3 so an OFF-BOX (laptop) ark 23// can push to the hub through the edge. Composes the proven nx_mgmt_client stack, nothing new invented. 24import "nx_acme_http.nx" 25import "nx_https_url_for_fetch.nx" 26import "nx_https_url_connect.nx" 27import "nx_tls13_client_session_run.nx" 28import "nx_tls13_client_session.nx" 29import "nx_tls13.nx" 30import "nx_tls13_record.nx" 31import "nx_tls13_read_record_from_fd.nx" 32import "nx_csprng.nx" 33const SGP_MAGIC_131072: i64 = 131072 34const SGP_MAGIC_61440: i64 = 61440 35const SGP_MAGIC_16384: i64 = 16384 36const SGP_MAGIC_32768: i64 = 32768 37const SGP_MAGIC_16645: i64 = 16645 38const SGP_MAGIC_2048: i64 = 2048 39const SGP_MAGIC_18691: i64 = 18691 40const SGP_MAGIC_4194304: i64 = 4194304 41const SGP_MAGIC_8192: i64 = 8192 42const SGP_MAGIC_1024: i64 = 1024 43const SGP_MAGIC_65536: i64 = 65536 44const SGP_MAGIC_4096: i64 = 4096 45 46// ★MEASURED 2026-07-20 (debt seq334): pushing a body larger than this THROUGH THE EDGE does not merely 47// fail -- it drove sites.elf into a crash loop and took every public site down twice. Bodies <=1.4MB 48// succeeded consistently. So the client REFUSES to send an over-safe body over the edge rather than 49// discover the limit by knocking the web surface over. Loopback (on-box, no edge in the path) is 50// unaffected. Remove this only when the edge itself refuses over-cap bodies with a bounded 413. 51const SGP_EDGE_SAFE: i64 = 1400000 52const SGP_MAX_OBJ: i64 = 200000 53const SGP_HS_SIZE: i64 = 524288 54const SGP_MAX_ENT: i64 = 20000 55const SGP_NAMEARENA: i64 = 2097152 56const SGP_MAX_DEPTH: i64 = 32 57const SGP_MAX_FILE: i64 = 67108864 58const SGP_PACK_CAP: i64 = 134217728 59const SGP_PATHBUF: i64 = 4096 60const SGP_RESP: i64 = 65536 61 62// ---- byte / io helpers ---- 63func sgp_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 64func sgp_we(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 } 65func sgp_wn(v: i64) -> i64 { 66 let b: *u8 = sys_mmap(32) 67 var x: i64 = v 68 var neg: i64 = 0 69 if x < 0 { neg = 1; x = 0 - x } 70 var i: i64 = 31 71 if x == 0 { b[i] = 48 as u8; i = i - 1 } 72 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 } 73 if neg == 1 { b[i] = 45 as u8; i = i - 1 } 74 sys_write(1, (b as i64 + i + 1) as *u8, 31 - i) 75 sys_munmap(b, 32) 76 return 0 77} 78func sgp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 79func sgp_cat(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 } 80func sgp_dec(out: *u8, at: i64, v: i64) -> i64 { 81 let tmp: *u8 = sys_mmap(32) 82 var x: i64 = v 83 var i: i64 = 31 84 if x == 0 { tmp[i] = 48 as u8; i = i - 1 } 85 while x > 0 { tmp[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 } 86 var a: i64 = at 87 var j: i64 = i + 1 88 while j < 32 { out[a] = tmp[j]; a = a + 1; j = j + 1 } 89 sys_munmap(tmp, 32) 90 return a 91} 92func sgp_atoi(s: *u8) -> i64 { 93 var v: i64 = 0 94 var i: i64 = 0 95 while s[i] != (0 as u8) { 96 let c: i64 = s[i] as i64 97 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } 98 i = i + 1 99 } 100 return v 101} 102func sgp_streq(a: *u8, b: *u8) -> i64 { 103 var i: i64 = 0 104 var r: i64 = 1 105 var go: i64 = 1 106 while go == 1 { 107 let ca: i64 = a[i] as i64 108 let cb: i64 = b[i] as i64 109 if ca != cb { r = 0; go = 0 } else { if ca == 0 { go = 0 } else { i = i + 1 } } 110 } 111 return r 112} 113// ---- SECRET EXCLUSION, deny-by-construction (rule 12: defensive at boundaries) ---- 114// A tool that replicates a filesystem into a remote repo IS a boundary: without this, one push 115// silently exfiltrates private keys into a clonable repo forever (git history is additive -- an 116// accidental key push cannot be un-published). Mirrors the compiled-in nx_fs_write deny-list. 117// Needles are TARGETED so source files that merely IMPLEMENT crypto (nx_ed25519.nx) still ship, 118// while key MATERIAL (opaque_keys.bin, node_ed25519_seed.bin, tools_cap_secret.key) never does. 119// Every exclusion is COUNTED and NAMED on stderr -- over-denying is safe, silent denying is not. 120func sgp_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 121func sgp_needle_ci(name: *u8, needle: *u8) -> i64 { 122 let n: i64 = sgp_slen(name) 123 let m: i64 = sgp_slen(needle) 124 if m == 0 { return 0 } 125 var i: i64 = 0 126 while i + m <= n { 127 var k: i64 = 0 128 while k < m { if sgp_lc(name[i + k] as i64) != sgp_lc(needle[k] as i64) { k = m + 9 } else { k = k + 1 } } 129 if k == m { return 1 } 130 i = i + 1 131 } 132 return 0 133} 134// ---- DATA-DRIVEN IGNORE (rule 11: policy is DATA, never buried in code) ---- 135// `<srcdir>/.sovgitignore`, one substring pattern per line (# comments + blanks skipped). Matched 136// case-insensitively against the ENTRY NAME. Needed for real trees: a huge constantly-rewritten log 137// (memory/worklog/worklog.tsv, 15MB) would blow the per-beat wire budget on every single beat. 138// Like the secret list, every exclusion is COUNTED and NAMED -- silent dropping is never acceptable. 139// pst[7] = pattern buffer ptr, pst[8] = pattern count, pst[9] = entries ignored. 140func sgp_load_ignore(srcdir: *u8, ibuf: *u8, icap: i64, pst: *i64) -> i64 { 141 pst[7] = 0 142 pst[8] = 0 143 pst[9] = 0 144 let p: *u8 = sys_mmap(SGP_PATHBUF) 145 var o: i64 = sgp_cat(p, 0, srcdir) 146 o = sgp_cat(p, o, "/.sovgitignore" as *u8) 147 p[o] = 0 as u8 148 let szp: *i64 = sys_mmap(16) as *i64 149 let b: *u8 = sgp_read_file(p, szp) 150 sys_munmap(p, SGP_PATHBUF) 151 if (b as i64) == 0 { sys_munmap(szp as *u8, 16); return 0 } 152 let n: i64 = szp[0] 153 sys_munmap(szp as *u8, 16) 154 var out: i64 = 0 155 var cnt: i64 = 0 156 var i: i64 = 0 157 while i < n { 158 // one line: [s, e) where e is the '\n' index (or n). The position MUST survive the scan -- 159 // collapsing the loop variable to sentinel loses it and yields ONE pattern = the whole file. 160 let s: i64 = i 161 var e: i64 = s 162 var scan: i64 = 1 163 while scan == 1 { 164 if e >= n { scan = 0 } else { 165 if b[e] == (10 as u8) { scan = 0 } else { e = e + 1 } 166 } 167 } 168 i = e + 1 169 var t: i64 = e 170 if t > s { if b[t - 1] == (13 as u8) { t = t - 1 } } // CRLF tolerated (Windows-authored) 171 let len: i64 = t - s 172 var keep: i64 = 1 173 if len <= 0 { keep = 0 } 174 if keep == 1 { if b[s] == (35 as u8) { keep = 0 } } // '#' 175 if keep == 1 { if out + len + 2 >= icap { keep = 0 } } 176 if keep == 1 { 177 var k: i64 = 0 178 while k < len { ibuf[out + k] = b[s + k]; k = k + 1 } 179 ibuf[out + len] = 0 as u8 180 out = out + len + 1 181 cnt = cnt + 1 182 } 183 } 184 sys_munmap(b, n + 16) 185 pst[7] = ibuf as i64 186 pst[8] = cnt 187 return cnt 188} 189func sgp_is_ignored(name: *u8, pst: *i64) -> i64 { 190 if pst[8] <= 0 { return 0 } 191 let ibuf: *u8 = pst[7] as *u8 192 var idx: i64 = 0 193 var off: i64 = 0 194 while idx < pst[8] { 195 let pat: *u8 = (ibuf as i64 + off) as *u8 196 if sgp_needle_ci(name, pat) == 1 { return 1 } 197 off = off + sgp_slen(pat) + 1 198 idx = idx + 1 199 } 200 return 0 201} 202// ⚠MEASURED 2026-07-20 on the real memory corpus: bare substring needles ATE 17 legitimate knowledge 203// files (feedback-token-economy-ai-layer-cost-axis.md, feedback-no-hardcoded-secrets-config-driven.md, 204// flux-token-taxonomy.md ...). For a BACKUP that is data loss. Key material is never a document, so 205// document/source extensions are exempt from the needle sweep. This does NOT weaken real protection: 206// filename matching never inspected content, so it never protected against a token QUOTED inside a 207// .md anyway -- it only ever guarded key FILES (.key/.pem/.bin/.cap), which stay fully covered. 208func sgp_is_doc_ext(name: *u8) -> i64 { 209 let n: i64 = sgp_slen(name) 210 if n >= 3 { if sgp_needle_ci((name as i64 + n - 3) as *u8, ".md" as *u8) == 1 { return 1 } } 211 if n >= 3 { if sgp_needle_ci((name as i64 + n - 3) as *u8, ".nx" as *u8) == 1 { return 1 } } 212 if n >= 4 { if sgp_needle_ci((name as i64 + n - 4) as *u8, ".css" as *u8) == 1 { return 1 } } 213 if n >= 5 { if sgp_needle_ci((name as i64 + n - 5) as *u8, ".html" as *u8) == 1 { return 1 } } 214 return 0 215} 216func sgp_is_secret(name: *u8) -> i64 { 217 if sgp_is_doc_ext(name) == 1 { return 0 } 218 if sgp_needle_ci(name, "secret" as *u8) == 1 { return 1 } 219 if sgp_needle_ci(name, "passw" as *u8) == 1 { return 1 } 220 if sgp_needle_ci(name, "token" as *u8) == 1 { return 1 } 221 if sgp_needle_ci(name, "credential" as *u8) == 1 { return 1 } 222 if sgp_needle_ci(name, ".pem" as *u8) == 1 { return 1 } 223 if sgp_needle_ci(name, ".cap" as *u8) == 1 { return 1 } 224 if sgp_needle_ci(name, ".key" as *u8) == 1 { return 1 } 225 if sgp_needle_ci(name, "_key" as *u8) == 1 { return 1 } 226 if sgp_needle_ci(name, "_keys" as *u8) == 1 { return 1 } 227 if sgp_needle_ci(name, "privkey" as *u8) == 1 { return 1 } 228 if sgp_needle_ci(name, "private_key" as *u8) == 1 { return 1 } 229 if sgp_needle_ci(name, "id_rsa" as *u8) == 1 { return 1 } 230 if sgp_needle_ci(name, "id_ed25519" as *u8) == 1 { return 1 } 231 if sgp_needle_ci(name, "_seed.bin" as *u8) == 1 { return 1 } 232 if sgp_needle_ci(name, "opaque" as *u8) == 1 { return 1 } 233 return 0 234} 235func sgp_isdots(nm: *u8) -> i64 { 236 if nm[0] == (46 as u8) { 237 if nm[1] == (0 as u8) { return 1 } 238 if nm[1] == (46 as u8) { if nm[2] == (0 as u8) { return 1 } } 239 } 240 return 0 241} 242// path[base] = '/' then name; NUL-terminate; return new length 243func sgp_join(buf: *u8, base: i64, name: *u8) -> i64 { 244 buf[base] = 47 as u8 245 var o: i64 = base + 1 246 var i: i64 = 0 247 while name[i] != (0 as u8) { buf[o] = name[i]; o = o + 1; i = i + 1 } 248 buf[o] = 0 as u8 249 return o 250} 251// little-endian u32 out of a struct stat field 252func sgp_u32(b: *u8, at: i64) -> i64 { 253 return (b[at] as i64) | ((b[at + 1] as i64) << 8) | ((b[at + 2] as i64) << 16) | ((b[at + 3] as i64) << 24) 254} 255// bounded file read (NEVER sys_read_file: its 4 GiB fallback cap fires on every 0-byte file). 256// FAILS LOUD on a short read -- a truncated blob would hash + push as VALID-looking wrong content, 257// the worst failure class. On success out_len[0]==the mapped size, so the caller's munmap is exact. 258func sgp_read_file(path: *u8, out_len: *i64) -> *u8 { 259 out_len[0] = 0 260 let fd: i64 = sys_openat_rd(path) 261 if fd < 0 { return 0 as *u8 } 262 let sz: i64 = sys_lseek(fd, 0, 2) 263 sys_lseek(fd, 0, 0) 264 if sz < 0 { sys_close(fd); return 0 as *u8 } 265 let buf: *u8 = sys_mmap(sz + 16) 266 var tot: i64 = 0 267 var go: i64 = 1 268 while go == 1 { 269 if tot >= sz { go = 0 } else { 270 let n: i64 = sys_read(fd, (buf as i64 + tot) as *u8, sz - tot) 271 if n <= 0 { go = 0 } else { tot = tot + n } 272 } 273 } 274 sys_close(fd) 275 if tot != sz { sys_munmap(buf, sz + 16); return 0 as *u8 } 276 out_len[0] = tot 277 return buf 278} 279 280// ---- packfile (X1b) ---- 281func sgp_be32(out: *u8, at: i64, v: i64) -> i64 { 282 out[at] = ((v >> 24) & 255) as u8 283 out[at + 1] = ((v >> 16) & 255) as u8 284 out[at + 2] = ((v >> 8) & 255) as u8 285 out[at + 3] = (v & 255) as u8 286 return at + 4 287} 288func sgp_objhdr(out: *u8, at: i64, typ: i64, size: i64) -> i64 { 289 var a: i64 = at 290 var b: i64 = (typ << 4) | (size & 15) 291 var s: i64 = size >> 4 292 while s > 0 { out[a] = (b | 128) as u8; a = a + 1; b = s & 127; s = s >> 7 } 293 out[a] = b as u8; a = a + 1 294 return a 295} 296// pst: [0]=offset [1]=object count [2]=overflow flag 297func sgp_pack_begin(pack: *u8, pst: *i64) -> i64 { 298 pack[0] = 80 as u8; pack[1] = 65 as u8; pack[2] = 67 as u8; pack[3] = 75 as u8 299 var o: i64 = sgp_be32(pack, 4, 2) 300 o = sgp_be32(pack, o, 0) 301 pst[0] = o 302 pst[1] = 0 303 pst[2] = 0 304 return 0 305} 306// append one object; fixed-Huffman deflate EXPANDS high-entropy data ~1.3x so the guard reserves 1.5x 307func sgp_pack_add(pack: *u8, pst: *i64, typ: i64, content: *u8, clen: i64) -> i64 { 308 if pst[2] != 0 { return 0 - 1 } 309 if pst[0] + clen + (clen >> 1) + 256 > SGP_PACK_CAP - 64 { pst[2] = 1; return 0 - 1 } 310 var a: i64 = sgp_objhdr(pack, pst[0], typ, clen) 311 let z: *u8 = (pack as i64 + a) as *u8 312 let zl: i64 = sg_zwrap(content, clen, z) 313 pst[0] = a + zl 314 pst[1] = pst[1] + 1 315 return 0 316} 317// ---- INCREMENTAL PACKS: a PROVEN-have cache ---- 318// Soundness: the remote's ref only advances AFTER `unpack ok`, so if the advert's old ref equals the 319// commit we cached from our last successful push, the remote PROVABLY holds that snapshot's whole 320// object set -- those objects can be skipped. ANY mismatch (someone else pushed, cache lost, repo 321// reset) falls back to a full thick pack. Fail-safe BY CONSTRUCTION: we never skip on an assumption, 322// only on a server-confirmed fact. A wrong skip would produce a repo that clones with "missing blob". 323// Cache file = [32B commit][32B object]*N under knowledge/sovgit_have/. 324func sgp_hs_hash(sha: *u8, mask: i64) -> i64 { 325 var h: i64 = 0 326 var i: i64 = 0 327 while i < 7 { h = (h << 8) | (sha[i] as i64); i = i + 1 } // 7 bytes: never sets the sign bit 328 return h & mask 329} 330func sgp_hs_build(shas: *u8, n: i64, table: *i64, tsize: i64) -> i64 { 331 var i: i64 = 0 332 while i < tsize { table[i] = 0; i = i + 1 } 333 let mask: i64 = tsize - 1 334 i = 0 335 while i < n { 336 let s: *u8 = (shas as i64 + i * 32) as *u8 337 var slot: i64 = sgp_hs_hash(s, mask) 338 var placed: i64 = 0 339 while placed == 0 { 340 if table[slot] == 0 { table[slot] = i + 1; placed = 1 } else { slot = (slot + 1) & mask } 341 } 342 i = i + 1 343 } 344 return 0 345} 346func sgp_hs_has(shas: *u8, table: *i64, tsize: i64, q: *u8) -> i64 { 347 if tsize <= 0 { return 0 } 348 let mask: i64 = tsize - 1 349 var slot: i64 = sgp_hs_hash(q, mask) 350 var res: i64 = 0 351 var go: i64 = 1 352 while go == 1 { 353 let v: i64 = table[slot] 354 if v == 0 { go = 0 } else { 355 let s: *u8 = (shas as i64 + (v - 1) * 32) as *u8 356 var k: i64 = 0 357 while k < 32 { if s[k] != q[k] { k = 40 } else { k = k + 1 } } 358 if k == 32 { res = 1; go = 0 } else { slot = (slot + 1) & mask } 359 } 360 } 361 return res 362} 363// cache path: knowledge/sovgit_have/<repo>_<ref with '/' -> '_'> 364func sgp_havepath(dst: *u8, repo: *u8, ref: *u8) -> i64 { 365 var o: i64 = sgp_cat(dst, 0, "knowledge/sovgit_have/" as *u8) 366 o = sgp_cat(dst, o, repo) 367 dst[o] = 95 as u8; o = o + 1 368 var i: i64 = 0 369 while ref[i] != (0 as u8) { 370 if ref[i] == (47 as u8) { dst[o] = 95 as u8 } else { dst[o] = ref[i] } 371 o = o + 1; i = i + 1 372 } 373 dst[o] = 0 as u8 374 return o 375} 376// record every object of the snapshot, and pack it ONLY if the remote does not provably have it 377func sgp_emit(pack: *u8, pst: *i64, typ: i64, content: *u8, clen: i64, raw32: *u8, allshas: *u8, hshas: *u8, htab: *i64) -> i64 { 378 if pst[3] >= SGP_MAX_OBJ { pst[2] = 1; return 0 - 1 } 379 let d: *u8 = (allshas as i64 + pst[3] * 32) as *u8 380 var i: i64 = 0 381 while i < 32 { d[i] = raw32[i]; i = i + 1 } 382 pst[3] = pst[3] + 1 383 if pst[5] > 0 { if sgp_hs_has(hshas, htab, pst[5], raw32) == 1 { pst[6] = pst[6] + 1; return 0 } } 384 return sgp_pack_add(pack, pst, typ, content, clen) 385} 386func sgp_pack_end(pack: *u8, pst: *i64) -> i64 { 387 sgp_be32(pack, 8, pst[1]) 388 let dig: *u8 = sys_mmap(32) 389 sha256_digest(pack, pst[0], dig) 390 var i: i64 = 0 391 while i < 32 { pack[pst[0]] = dig[i]; pst[0] = pst[0] + 1; i = i + 1 } 392 sys_munmap(dig, 32) 393 return pst[0] 394} 395 396// ---- git tree entry ordering: names compare as if a directory carried a trailing '/' ---- 397func sgp_chr(s: *u8, n: i64, isdir: i64, i: i64) -> i64 { 398 if i < n { return s[i] as i64 } 399 if i == n { if isdir == 1 { return 47 } } 400 return 0 - 1 401} 402func sgp_keycmp(a: *u8, an: i64, ad: i64, b: *u8, bn: i64, bd: i64) -> i64 { 403 var i: i64 = 0 404 var r: i64 = 0 405 var go: i64 = 1 406 while go == 1 { 407 let ca: i64 = sgp_chr(a, an, ad, i) 408 let cb: i64 = sgp_chr(b, bn, bd, i) 409 if ca < cb { r = 0 - 1; go = 0 } else { 410 if ca > cb { r = 1; go = 0 } else { 411 if ca < 0 { r = 0; go = 0 } else { i = i + 1 } 412 } 413 } 414 } 415 return r 416} 417func sgp_entcmp(arena: *u8, noff: *i64, nlen: *i64, isdir: *i64, x: i64, y: i64) -> i64 { 418 let ax: *u8 = (arena as i64 + noff[x]) as *u8 419 let ay: *u8 = (arena as i64 + noff[y]) as *u8 420 return sgp_keycmp(ax, nlen[x], isdir[x], ay, nlen[y], isdir[y]) 421} 422// bottom-up merge sort (O(n log n)): a 15k-file directory makes insertion sort quadratic = minutes 423func sgp_msort(arena: *u8, noff: *i64, nlen: *i64, isdir: *i64, idx: *i64, aux: *i64, n: i64) -> i64 { 424 var width: i64 = 1 425 while width < n { 426 var i: i64 = 0 427 while i < n { 428 var mid: i64 = i + width 429 var hi: i64 = i + width + width 430 if mid > n { mid = n } 431 if hi > n { hi = n } 432 var a: i64 = i 433 var b: i64 = mid 434 var o: i64 = i 435 while o < hi { 436 var take: i64 = 0 437 if a >= mid { take = 1 } else { 438 if b >= hi { take = 0 } else { 439 let c: i64 = sgp_entcmp(arena, noff, nlen, isdir, idx[a], idx[b]) 440 if c > 0 { take = 1 } else { take = 0 } 441 } 442 } 443 if take == 1 { aux[o] = idx[b]; b = b + 1 } else { aux[o] = idx[a]; a = a + 1 } 444 o = o + 1 445 } 446 i = i + width + width 447 } 448 var k: i64 = 0 449 while k < n { idx[k] = aux[k]; k = k + 1 } 450 width = width + width 451 } 452 return 0 453} 454 455// ---- the recursive snapshot walk ---- 456// stats: [0]=files [1]=dirs [2]=skipped-special [3]=content bytes 457// out_ecount receives this directory's entry count (0 => an empty dir; git cannot track one, so the 458// PARENT omits it, keeping a stock-git round-trip diff clean). 459func sgp_walk(path: *u8, plen: i64, pack: *u8, pst: *i64, depth: i64, out_sha32: *u8, out_ecount: *i64, stats: *i64, allshas: *u8, hshas: *u8, htab: *i64) -> i64 { 460 out_ecount[0] = 0 461 if depth > SGP_MAX_DEPTH { return 0 - 3 } 462 path[plen] = 0 as u8 463 let dfd: i64 = sys_openat_rd(path) 464 if dfd < 0 { return 0 - 4 } 465 let arena: *u8 = sys_mmap(SGP_NAMEARENA) 466 let noff: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64 467 let nlen: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64 468 let isdir: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64 469 let isexe: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64 470 let idx: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64 471 let aux: *i64 = sys_mmap(SGP_MAX_ENT * 8) as *i64 472 let shas: *u8 = sys_mmap(SGP_MAX_ENT * 32) 473 let dbuf: *u8 = sys_mmap(SGP_MAGIC_131072) 474 let stbuf: *u8 = sys_mmap(160) 475 let szp: *i64 = sys_mmap(16) as *i64 476 let ecb: *i64 = sys_mmap(16) as *i64 477 var nent: i64 = 0 478 var apos: i64 = 0 479 var err: i64 = 0 480 var go: i64 = 1 481 while go == 1 { 482 let nr: i64 = sys_getdents64(dfd, dbuf, SGP_MAGIC_131072) 483 if nr <= 0 { go = 0 } else { 484 var off: i64 = 0 485 while off < nr { 486 let rec: *u8 = (dbuf as i64 + off) as *u8 487 let nm: *u8 = dirent_name(rec) 488 var skip: i64 = 0 489 if err != 0 { skip = 1 } 490 if sgp_isdots(nm) == 1 { skip = 1 } 491 if sgp_streq(nm, ".git" as *u8) == 1 { skip = 1 } 492 if skip == 0 { if sgp_is_ignored(nm, pst) == 1 { 493 skip = 1 494 pst[9] = pst[9] + 1 495 sgp_we("SKIP-IGNORED " as *u8); sgp_we(nm); sgp_we("\n" as *u8) 496 } } 497 if skip == 0 { if sgp_is_secret(nm) == 1 { 498 skip = 1 499 stats[4] = stats[4] + 1 500 sgp_we("SKIP-SECRET " as *u8); sgp_we(nm); sgp_we("\n" as *u8) 501 } } 502 if skip == 0 { 503 let nl: i64 = sgp_slen(nm) 504 if nent >= SGP_MAX_ENT { err = 0 - 5 } else { 505 if apos + nl + 2 >= SGP_NAMEARENA { err = 0 - 6 } else { 506 if plen + nl + 2 >= SGP_PATHBUF { err = 0 - 7 } else { 507 let cl: i64 = sgp_join(path, plen, nm) 508 var keep: i64 = 0 509 var kdir: i64 = 0 510 var kexe: i64 = 0 511 let csha: *u8 = (shas as i64 + nent * 32) as *u8 512 if sys_fstatat(path, stbuf) < 0 { stats[2] = stats[2] + 1 } else { 513 let mode: i64 = sgp_u32(stbuf, 24) 514 let fmt: i64 = mode & SGP_MAGIC_61440 515 if fmt == SGP_MAGIC_16384 { 516 let rr: i64 = sgp_walk(path, cl, pack, pst, depth + 1, csha, ecb, stats, allshas, hshas, htab) 517 if rr != 0 { err = rr } else { 518 if ecb[0] > 0 { keep = 1; kdir = 1; stats[1] = stats[1] + 1 } 519 } 520 } else { 521 if fmt == SGP_MAGIC_32768 { 522 let content: *u8 = sgp_read_file(path, szp) 523 if (content as i64) == 0 { err = 0 - 8 } else { 524 let clen: i64 = szp[0] 525 if clen > SGP_MAX_FILE { err = 0 - 9 } else { 526 sg_oid_raw("blob" as *u8, content, clen, csha) 527 if sgp_emit(pack, pst, 3, content, clen, csha, allshas, hshas, htab) != 0 { err = 0 - 10 } else { 528 keep = 1 529 if (mode & 73) != 0 { kexe = 1 } 530 stats[0] = stats[0] + 1 531 stats[3] = stats[3] + clen 532 } 533 } 534 sys_munmap(content, szp[0] + 16) 535 } 536 } else { stats[2] = stats[2] + 1 } 537 } 538 } 539 if keep == 1 { 540 noff[nent] = apos 541 nlen[nent] = nl 542 var k: i64 = 0 543 while k < nl { arena[apos + k] = nm[k]; k = k + 1 } 544 arena[apos + nl] = 0 as u8 545 apos = apos + nl + 1 546 isdir[nent] = kdir 547 isexe[nent] = kexe 548 nent = nent + 1 549 } 550 } } } 551 } 552 off = off + dirent_reclen(rec) 553 } 554 } 555 } 556 sys_close(dfd) 557 if err == 0 { 558 var i: i64 = 0 559 while i < nent { idx[i] = i; i = i + 1 } 560 sgp_msort(arena, noff, nlen, isdir, idx, aux, nent) 561 var tcap: i64 = 128 562 i = 0 563 while i < nent { tcap = tcap + nlen[i] + 48; i = i + 1 } 564 let tree: *u8 = sys_mmap(tcap) 565 var to: i64 = 0 566 i = 0 567 while i < nent { 568 let e: i64 = idx[i] 569 let nmp: *u8 = (arena as i64 + noff[e]) as *u8 570 var modestr: *u8 = "100644" as *u8 571 if isdir[e] == 1 { modestr = "40000" as *u8 } else { if isexe[e] == 1 { modestr = "100755" as *u8 } } 572 let rawp: *u8 = (shas as i64 + e * 32) as *u8 573 to = sg_tree_entry(tree, to, modestr, nmp, rawp) 574 i = i + 1 575 } 576 sg_oid_raw("tree" as *u8, tree, to, out_sha32) 577 if sgp_emit(pack, pst, 2, tree, to, out_sha32, allshas, hshas, htab) != 0 { err = 0 - 10 } 578 sys_munmap(tree, tcap) 579 out_ecount[0] = nent 580 } 581 sys_munmap(arena, SGP_NAMEARENA) 582 sys_munmap(noff as *u8, SGP_MAX_ENT * 8) 583 sys_munmap(nlen as *u8, SGP_MAX_ENT * 8) 584 sys_munmap(isdir as *u8, SGP_MAX_ENT * 8) 585 sys_munmap(isexe as *u8, SGP_MAX_ENT * 8) 586 sys_munmap(idx as *u8, SGP_MAX_ENT * 8) 587 sys_munmap(aux as *u8, SGP_MAX_ENT * 8) 588 sys_munmap(shas, SGP_MAX_ENT * 32) 589 sys_munmap(dbuf, SGP_MAGIC_131072) 590 sys_munmap(stbuf, 160) 591 sys_munmap(szp as *u8, 16) 592 sys_munmap(ecb as *u8, 16) 593 return err 594} 595 596// ---- loopback HTTP transport (the daemon is wall-only: 127.0.0.1 by construction) ---- 597func sgp_connect(port: i64) -> i64 { 598 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 599 if fd < 0 { return 0 - 1 } 600 let sa: *u8 = sys_mmap(16) 601 var i: i64 = 0 602 while i < 16 { sa[i] = 0 as u8; i = i + 1 } 603 sa[0] = 2 as u8 604 sa[2] = ((port >> 8) & 255) as u8 605 sa[3] = (port & 255) as u8 606 sa[4] = 127 as u8; sa[5] = 0 as u8; sa[6] = 0 as u8; sa[7] = 1 as u8 607 let cr: i64 = nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) 608 sys_munmap(sa, 16) 609 if cr < 0 { sys_close(fd); return 0 - 2 } 610 return fd 611} 612func sgp_write_all(fd: i64, buf: *u8, n: i64) -> i64 { 613 var off: i64 = 0 614 var bad: i64 = 0 615 while off < n { 616 let w: i64 = sys_write(fd, (buf as i64 + off) as *u8, n - off) 617 if w <= 0 { off = n; bad = 1 } else { off = off + w } 618 } 619 return bad 620} 621func sgp_drain(fd: i64, buf: *u8, cap: i64) -> i64 { 622 var off: i64 = 0 623 var go: i64 = 1 624 while go == 1 { 625 if off >= cap { go = 0 } else { 626 let r: i64 = sys_read(fd, (buf as i64 + off) as *u8, cap - off) 627 if r <= 0 { go = 0 } else { off = off + r } 628 } 629 } 630 return off 631} 632// build an HTTP/1.1 request; capstr (may be 0) rides as X-Nishi-Cap (the API wire form the login gate accepts) 633func sgp_build_req(out: *u8, method: *u8, path: *u8, host: *u8, capstr: *u8, ct: *u8, body: *u8, blen: i64) -> i64 { 634 var o: i64 = sgp_cat(out, 0, method) 635 out[o] = 32 as u8; o = o + 1 636 o = sgp_cat(out, o, path) 637 o = sgp_cat(out, o, " HTTP/1.1\r\nHost: " as *u8) 638 o = sgp_cat(out, o, host) 639 o = sgp_cat(out, o, "\r\n" as *u8) 640 if (capstr as i64) != 0 { 641 o = sgp_cat(out, o, "X-Nishi-Cap: " as *u8) 642 o = sgp_cat(out, o, capstr) 643 o = sgp_cat(out, o, "\r\n" as *u8) 644 } 645 if blen > 0 { 646 o = sgp_cat(out, o, "Content-Type: " as *u8) 647 o = sgp_cat(out, o, ct) 648 o = sgp_cat(out, o, "\r\nContent-Length: " as *u8) 649 o = sgp_dec(out, o, blen) 650 o = sgp_cat(out, o, "\r\n" as *u8) 651 } 652 o = sgp_cat(out, o, "Connection: close\r\n\r\n" as *u8) 653 var i: i64 = 0 654 while i < blen { out[o + i] = body[i]; i = i + 1 } 655 return o + blen 656} 657func sgp_http(port: i64, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 { 658 let fd: i64 = sgp_connect(port) 659 if fd < 0 { return 0 - 1 } 660 if sgp_write_all(fd, req, reqlen) != 0 { sys_close(fd); return 0 - 2 } 661 let n: i64 = sgp_drain(fd, out, outcap) 662 sys_close(fd) 663 return n 664} 665 666// ---- TLS transport (rung 2): identical request bytes, sovereign TLS-1.3 ---- 667// Mirrors the PROVEN nx_mgmt_client send/drain (nx_mgmt_client.nx:220-299): fragment the request into 668// <=16KB records (RFC 8446 max plaintext 2^14 -- a single oversized record is protocol-invalid and was 669// the historic transport rc=-2 root), then decrypt-drain until the peer closes. 670func sgp_tls_send_drain(s: *Tls13ClientSession, fd: i64, req: *u8, req_len: i64, out: *u8, out_cap: i64) -> i64 { 671 if s.state != NX_TLS13_CSESSION_STATE_CONNECTED { return 0 - 1 } 672 let rec_buf: *u8 = sys_mmap(SGP_MAGIC_16384 + 64) 673 var snd_off: i64 = 0 674 var more: i64 = 1 675 var senderr: i64 = 0 676 while more == 1 { more = 0 677 var frag: i64 = req_len - snd_off 678 if frag > SGP_MAGIC_16384 { frag = SGP_MAGIC_16384 } 679 let header_out: *u8 = rec_buf 680 let ct_out: *u8 = ((rec_buf as i64) + NX_TLS13_RECORD_HEADER_LEN) as *u8 681 let tag_out: *u8 = ((rec_buf as i64) + NX_TLS13_RECORD_HEADER_LEN + frag + 1) as *u8 682 let enc_v: i64 = nx_tls13_record_encrypt_v2(s.cipher_suite, s.client_app_traffic_key, s.client_app_iv, s.client_app_seq, ((req as i64) + snd_off) as *u8, frag, NX_TLS13_CT_APPLICATION_DATA, 0, header_out, ct_out, tag_out) 683 s.client_app_seq = s.client_app_seq + 1 684 if enc_v != NX_TLS13_REC_VERDICT_OK { senderr = 0 - 2 } else { 685 let total: i64 = NX_TLS13_RECORD_HEADER_LEN + frag + 1 + NX_TLS13_RECORD_TAG_LEN 686 if sgp_write_all(fd, rec_buf, total) != 0 { senderr = 0 - 3 } else { 687 snd_off = snd_off + frag 688 if snd_off < req_len { more = 1 } 689 } 690 } 691 } 692 sys_munmap(rec_buf, SGP_MAGIC_16384 + 64) 693 if senderr != 0 { return senderr } 694 var acc: i64 = 0 695 let rec_in: *u8 = sys_mmap(SGP_MAGIC_16645) 696 let plain: *u8 = sys_mmap(SGP_MAGIC_16645) 697 let cttype: *i64 = sys_mmap(16) as *i64 698 let ptlen: *i64 = sys_mmap(16) as *i64 699 var go: i64 = 1 700 while go == 1 { 701 if acc >= out_cap { go = 0 } else { 702 let rin: i64 = nx_tls13_read_record_from_fd(fd, rec_in, SGP_MAGIC_16645) 703 if rin < 0 { go = 0 } else { 704 let ctlen: i64 = rin - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN 705 let rin_ct: *u8 = ((rec_in as i64) + NX_TLS13_RECORD_HEADER_LEN) as *u8 706 let rin_tag: *u8 = ((rec_in as i64) + rin - NX_TLS13_RECORD_TAG_LEN) as *u8 707 let dv: i64 = nx_tls13_record_decrypt_v2(s.cipher_suite, s.server_app_traffic_key, s.server_app_iv, s.server_app_seq, rec_in, rin_ct, ctlen, rin_tag, plain, cttype, ptlen) 708 s.server_app_seq = s.server_app_seq + 1 709 if dv != NX_TLS13_REC_VERDICT_OK { go = 0 } else { 710 if cttype[0] == NX_TLS13_CT_ALERT { go = 0 } else { 711 if cttype[0] == NX_TLS13_CT_APPLICATION_DATA { 712 let tc: i64 = ptlen[0] 713 if acc + tc > out_cap { go = 0 } else { 714 var i: i64 = 0 715 while i < tc { out[acc + i] = plain[i]; i = i + 1 } 716 acc = acc + tc 717 } 718 } 719 } 720 } 721 } 722 } 723 } 724 sys_munmap(rec_in, SGP_MAGIC_16645) 725 sys_munmap(plain, SGP_MAGIC_16645) 726 sys_munmap(cttype as *u8, 16) 727 sys_munmap(ptlen as *u8, 16) 728 return acc 729} 730// connect + validated TLS-1.3 handshake to `url`'s host:port, send req, drain the response 731func sgp_https(store: *TrustStore, url: *u8, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 { 732 let url_p: *NxUrl = nx_url_new() 733 let target_raw: *u8 = sys_mmap(64) 734 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget 735 target.url = url_p 736 target.port = 0 737 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { return 0 - 10 } 738 let now: i64 = sys_now_realtime_sec() 739 let fd_p: *i64 = sys_mmap(16) as *i64 740 if nx_https_url_connect(target, url, now, fd_p) != NX_HTTPS_CONNECT_OK { return 0 - 11 } 741 let fd: i64 = fd_p[0] 742 let host: *u8 = ((url as i64) + target.url.host_off) as *u8 743 let hlen: i64 = target.url.host_len 744 let cr: *u8 = sys_mmap(32) 745 let priv: *u8 = sys_mmap(32) 746 nx_csprng_fill(cr, 32) // fresh ephemerals -> forward secrecy per push 747 nx_csprng_fill(priv, 32) 748 let vc: *TlsValidationContext = sys_mmap(128) as *TlsValidationContext 749 vc.store = store 750 vc.sni_host = host 751 vc.sni_host_len = hlen 752 vc.now_epoch = now 753 let sr: i64 = nx_tls13_client_session_run(fd, host, hlen, cr, priv, vc) 754 if sr <= 0 { sys_close(fd); return 0 - 12 } 755 let session: *Tls13ClientSession = sr as *Tls13ClientSession 756 let n: i64 = sgp_tls_send_drain(session, fd, req, reqlen, out, outcap) 757 sys_close(fd) 758 return n 759} 760// one dispatcher, two transports: mode 0 = loopback plain TCP, mode 1 = sovereign TLS to urlbase+path 761func sgp_xfer_once(mode: i64, port: i64, store: *TrustStore, urlbase: *u8, path: *u8, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 { 762 if mode == 0 { return sgp_http(port, req, reqlen, out, outcap) } 763 let url: *u8 = sys_mmap(SGP_MAGIC_2048) 764 var u: i64 = sgp_cat(url, 0, urlbase) 765 u = sgp_cat(url, u, path) 766 url[u] = 0 as u8 767 let n: i64 = sgp_https(store, url, req, reqlen, out, outcap) 768 sys_munmap(url, SGP_MAGIC_2048) 769 return n 770} 771// BOUNDED RETRY on TRANSPORT failure only (rc<=0 = we never got a response). An unattended 30-min ark 772// beat must survive the known flake classes (transient TLS handshake/cert-verify, the DSM co-squat). 773// SAFE for BOTH calls: the advert is an idempotent read, and a receive-pack retry replays the SAME 774// (old,new,ref) command -- the server writes the same ref value, so a lost response cannot double-apply. 775// A SERVER answer (any bytes) is never retried -- protocol/auth rejections must surface, not be masked. 776func sgp_xfer(mode: i64, port: i64, store: *TrustStore, urlbase: *u8, path: *u8, req: *u8, reqlen: i64, out: *u8, outcap: i64) -> i64 { 777 var attempt: i64 = 0 778 var n: i64 = 0 - 1 779 var go: i64 = 1 780 while go == 1 { 781 n = sgp_xfer_once(mode, port, store, urlbase, path, req, reqlen, out, outcap) 782 if n > 0 { go = 0 } else { 783 attempt = attempt + 1 784 if attempt >= 3 { go = 0 } else { 785 sgp_we("RETRY transport rc=" as *u8) 786 let b: *u8 = sys_mmap(32) 787 var x: i64 = 0 - n 788 var i: i64 = 31 789 if x == 0 { b[i] = 48 as u8; i = i - 1 } 790 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 } 791 sys_write(2, (b as i64 + i + 1) as *u8, 31 - i) 792 sys_munmap(b, 32) 793 sgp_we(" (transient; the response never arrived) attempt " as *u8) 794 let c: *u8 = sys_mmap(8) 795 c[0] = (48 + attempt) as u8 796 sys_write(2, c, 1) 797 sys_munmap(c, 8) 798 sgp_we("/3\n" as *u8) 799 } 800 } 801 } 802 return n 803} 804// bounded diagnostic dump (never reads past what the peer actually sent) 805func sgp_dump(buf: *u8, n: i64, want: i64) -> i64 { 806 var m: i64 = want 807 if n < m { m = n } 808 if m > 0 { sys_write(1, buf, m) } 809 return 0 810} 811// substring search 812func sgp_find(hay: *u8, hn: i64, needle: *u8) -> i64 { 813 let m: i64 = sgp_slen(needle) 814 if m == 0 { return 0 } 815 var i: i64 = 0 816 while i + m <= hn { 817 var k: i64 = 0 818 while k < m { if hay[i + k] != needle[k] { k = m + 9 } else { k = k + 1 } } 819 if k == m { return i } 820 i = i + 1 821 } 822 return 0 - 1 823} 824// find "<64hex> refs/heads/<ref>" in a receive-pack advert; writes the 64 hex to out64. 1 = found. 825func sgp_find_ref(buf: *u8, n: i64, refpath: *u8, out64: *u8) -> i64 { 826 let needle: *u8 = sys_mmap(320) 827 var o: i64 = sgp_cat(needle, 0, " " as *u8) 828 o = sgp_cat(needle, o, refpath) 829 needle[o] = 0 as u8 830 let nl: i64 = o 831 var found: i64 = 0 832 var i: i64 = 0 833 while i + nl <= n { 834 var k: i64 = 0 835 while k < nl { if buf[i + k] != needle[k] { k = nl + 9 } else { k = k + 1 } } 836 if k == nl { 837 if i >= 64 { 838 let after: i64 = buf[i + nl] as i64 839 var okend: i64 = 0 840 if after == 0 { okend = 1 } 841 if after == 10 { okend = 1 } 842 if okend == 1 { 843 var j: i64 = 0 844 while j < 64 { out64[j] = buf[i - 64 + j]; j = j + 1 } 845 out64[64] = 0 as u8 846 found = 1 847 i = n 848 } 849 } 850 } 851 i = i + 1 852 } 853 sys_munmap(needle, 320) 854 return found 855} 856 857func main(argc: i64, argv: *i64) -> i64 { 858 if argc < 3 { 859 sgp_w("usage: nx_sovgit_push <srcdir> <repo> [<port>|<host>:<port>] [ref] [capfile] [message]\n" as *u8) 860 sgp_w(" target '18691' = loopback plain TCP (on-box) | 'nishifamily.com:8443' = sovereign TLS-1.3\n" as *u8) 861 sgp_w(" ENVELOPE entries/dir<=20000 namebytes/dir<=2MiB depth<=32 file<=64MiB pack<=128MiB\n" as *u8) 862 sgp_w(" skips .git + symlinks/special (counted); transport=loopback 127.0.0.1:<port> (default 18691)\n" as *u8) 863 return 2 864 } 865 let srcdir: *u8 = argv[1] as *u8 866 let repo: *u8 = argv[2] as *u8 867 // target: "<port>" = loopback plain TCP (on-box) | "<host>:<port>" = sovereign TLS through the edge 868 var port: i64 = SGP_MAGIC_18691 869 var mode: i64 = 0 870 let hostbuf: *u8 = sys_mmap(320) 871 var hb: i64 = sgp_cat(hostbuf, 0, "nishi-git" as *u8) 872 hostbuf[hb] = 0 as u8 873 if argc > 3 { 874 let t: *u8 = argv[3] as *u8 875 var ci: i64 = 0 - 1 876 var ti: i64 = 0 877 while t[ti] != (0 as u8) { if t[ti] == (58 as u8) { ci = ti } ti = ti + 1 } 878 if ci < 0 { port = sgp_atoi(t) } else { 879 mode = 1 880 var k: i64 = 0 881 while k < ci { hostbuf[k] = t[k]; k = k + 1 } 882 hostbuf[ci] = 0 as u8 883 port = sgp_atoi((t as i64 + ci + 1) as *u8) 884 } 885 } 886 var store: *TrustStore = 0 as *TrustStore 887 let urlbase: *u8 = sys_mmap(512) 888 urlbase[0] = 0 as u8 889 if mode == 1 { 890 let tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, SGP_MAGIC_4194304) 891 if tr <= 0 { sgp_w("PUSH-FAIL trust-store load (run from the nxc2 dir) verdict=RED\n" as *u8); sys_exit(10); return 10 } 892 store = tr as *TrustStore 893 var ub: i64 = sgp_cat(urlbase, 0, "https://" as *u8) 894 ub = sgp_cat(urlbase, ub, hostbuf) 895 urlbase[ub] = 58 as u8; ub = ub + 1 896 ub = sgp_dec(urlbase, ub, port) 897 urlbase[ub] = 0 as u8 898 } 899 var ref: *u8 = "master" as *u8 900 if argc > 4 { ref = argv[4] as *u8 } 901 var capstr: *u8 = 0 as *u8 902 if argc > 5 { 903 let cszp: *i64 = sys_mmap(16) as *i64 904 let cb: *u8 = sgp_read_file(argv[5] as *u8, cszp) 905 if (cb as i64) != 0 { 906 var cn: i64 = cszp[0] 907 while cn > 0 { 908 let lc: i64 = cb[cn - 1] as i64 909 if lc == 10 { cn = cn - 1 } else { if lc == 13 { cn = cn - 1 } else { cn = 0 - cn } } 910 } 911 if cn < 0 { cn = 0 - cn } 912 cb[cn] = 0 as u8 913 if cn > 0 { capstr = cb } 914 } 915 } 916 var msg: *u8 = "nishi sovereign snapshot" as *u8 917 if argc > 6 { msg = argv[6] as *u8 } 918 919 let refpath: *u8 = sys_mmap(320) 920 var rpo: i64 = sgp_cat(refpath, 0, "refs/heads/" as *u8) 921 rpo = sgp_cat(refpath, rpo, ref) 922 refpath[rpo] = 0 as u8 923 924 sgp_w("SOVGIT-PUSH src=" as *u8); sgp_w(srcdir) 925 sgp_w(" repo=" as *u8); sgp_w(repo) 926 sgp_w(" ref=" as *u8); sgp_w(refpath) 927 if mode == 1 { sgp_w(" transport=TLS host=" as *u8); sgp_w(hostbuf) } else { sgp_w(" transport=loopback" as *u8) } 928 sgp_w(" port=" as *u8); sgp_wn(port); sgp_w("\n" as *u8) 929 930 // ---- 1. receive-pack advert -> the old ref value (parent) ---- 931 let resp: *u8 = sys_mmap(SGP_RESP) 932 let req0: *u8 = sys_mmap(SGP_MAGIC_8192) 933 let apath: *u8 = sys_mmap(SGP_MAGIC_1024) 934 var ao: i64 = sgp_cat(apath, 0, "/git/" as *u8) 935 ao = sgp_cat(apath, ao, repo) 936 ao = sgp_cat(apath, ao, "/info/refs?service=git-receive-pack" as *u8) 937 apath[ao] = 0 as u8 938 let r0len: i64 = sgp_build_req(req0, "GET" as *u8, apath, hostbuf, capstr, "" as *u8, 0 as *u8, 0) 939 let an: i64 = sgp_xfer(mode, port, store, urlbase, apath, req0, r0len, resp, SGP_RESP) 940 if an <= 0 { sgp_w("PUSH-FAIL advert transport rc=" as *u8); sgp_wn(an); sgp_w(" verdict=RED\n" as *u8); sys_exit(3); return 3 } 941 if sgp_find(resp, an, "200 OK" as *u8) < 0 { 942 sgp_w("PUSH-FAIL advert status (auth? repo?) verdict=RED\n" as *u8) 943 sgp_dump(resp, an, 200) 944 sgp_w("\n" as *u8) 945 sys_exit(4); return 4 946 } 947 let oldsha: *u8 = sys_mmap(80) 948 let hasold: i64 = sgp_find_ref(resp, an, refpath, oldsha) 949 if hasold == 0 { var z: i64 = 0; while z < 64 { oldsha[z] = 48 as u8; z = z + 1 } oldsha[64] = 0 as u8 } 950 sgp_w(" old=" as *u8); sgp_w(oldsha); sgp_w("\n" as *u8) 951 952 // ---- 2. snapshot the tree into a packfile ---- 953 let pack: *u8 = sys_mmap(SGP_PACK_CAP) 954 let pst: *i64 = sys_mmap(128) as *i64 955 sgp_pack_begin(pack, pst) 956 let ibuf: *u8 = sys_mmap(SGP_MAGIC_65536) 957 sgp_load_ignore(srcdir, ibuf, SGP_MAGIC_65536, pst) 958 if pst[8] > 0 { sgp_w(" .sovgitignore: " as *u8); sgp_wn(pst[8]); sgp_w(" patterns\n" as *u8) } 959 let stats: *i64 = sys_mmap(64) as *i64 960 stats[0] = 0; stats[1] = 0; stats[2] = 0; stats[3] = 0; stats[4] = 0 961 // ---- proven-have cache: skip objects the remote is CONFIRMED to hold (else full pack) ---- 962 let allshas: *u8 = sys_mmap(SGP_MAX_OBJ * 32) 963 let hshas: *u8 = sys_mmap(SGP_MAX_OBJ * 32) 964 let htab: *i64 = sys_mmap(SGP_HS_SIZE * 8) as *i64 965 pst[3] = 0 // objects recorded this snapshot 966 pst[5] = 0 // hash-table size (0 = no have-set => full pack) 967 pst[6] = 0 // objects skipped as provably-present 968 let hpath: *u8 = sys_mmap(SGP_MAGIC_1024) 969 sgp_havepath(hpath, repo, refpath) 970 var haven: i64 = 0 971 if hasold == 1 { 972 let hszp: *i64 = sys_mmap(16) as *i64 973 let hbuf: *u8 = sgp_read_file(hpath, hszp) 974 if (hbuf as i64) != 0 { 975 let hn: i64 = hszp[0] 976 if hn >= 32 { 977 let chex: *u8 = sys_mmap(80) 978 sg_hex(hbuf, chex) 979 chex[64] = 0 as u8 980 if sgp_streq(chex, oldsha) == 1 { // the advert CONFIRMS this snapshot landed 981 var cnt: i64 = (hn - 32) / 32 982 if cnt > SGP_MAX_OBJ { cnt = SGP_MAX_OBJ } 983 var ci: i64 = 0 984 while ci < cnt * 32 { hshas[ci] = hbuf[32 + ci]; ci = ci + 1 } 985 haven = cnt 986 } 987 sys_munmap(chex, 80) 988 } 989 sys_munmap(hbuf, hn + 16) 990 } 991 sys_munmap(hszp as *u8, 16) 992 } 993 if haven > 0 { 994 sgp_hs_build(hshas, haven, htab, SGP_HS_SIZE) 995 pst[5] = SGP_HS_SIZE 996 sgp_w(" incremental: remote CONFIRMED to hold " as *u8); sgp_wn(haven); sgp_w(" objects (delta pack)\n" as *u8) 997 } else { sgp_w(" incremental: no confirmed have-set for this ref (FULL pack, fail-safe)\n" as *u8) } 998 let pathbuf: *u8 = sys_mmap(SGP_PATHBUF) 999 var pl: i64 = sgp_cat(pathbuf, 0, srcdir) 1000 while pl > 1 { if pathbuf[pl - 1] == (47 as u8) { pl = pl - 1 } else { pl = 0 - pl } } 1001 if pl < 0 { pl = 0 - pl } 1002 pathbuf[pl] = 0 as u8 1003 let troot: *u8 = sys_mmap(40) 1004 let ecb: *i64 = sys_mmap(16) as *i64 1005 let wr: i64 = sgp_walk(pathbuf, pl, pack, pst, 0, troot, ecb, stats, allshas, hshas, htab) 1006 if wr != 0 { 1007 sgp_w("PUSH-FAIL walk rc=" as *u8); sgp_wn(wr) 1008 sgp_w(" (-3 depth -4 opendir -5 entries/dir -6 namebytes -7 pathlen -8 read -9 filesize -10 pack-envelope)" as *u8) 1009 sgp_w(" verdict=RED\n" as *u8) 1010 sys_exit(5); return 5 1011 } 1012 let trhex: *u8 = sys_mmap(80) 1013 sg_hex(troot, trhex) 1014 trhex[64] = 0 as u8 1015 1016 // ---- 3. the commit (parent = advertised old ref => additive history) ---- 1017 let cbody: *u8 = sys_mmap(SGP_MAGIC_4096) 1018 var co: i64 = sgp_cat(cbody, 0, "tree " as *u8) 1019 co = sgp_cat(cbody, co, trhex) 1020 cbody[co] = 10 as u8; co = co + 1 1021 if hasold == 1 { 1022 co = sgp_cat(cbody, co, "parent " as *u8) 1023 co = sgp_cat(cbody, co, oldsha) 1024 cbody[co] = 10 as u8; co = co + 1 1025 } 1026 let now: i64 = sys_now_realtime_sec() 1027 co = sgp_cat(cbody, co, "author nishi-sovgit <ark@nishi> " as *u8) 1028 co = sgp_dec(cbody, co, now) 1029 co = sgp_cat(cbody, co, " +0000\n" as *u8) 1030 co = sgp_cat(cbody, co, "committer nishi-sovgit <ark@nishi> " as *u8) 1031 co = sgp_dec(cbody, co, now) 1032 co = sgp_cat(cbody, co, " +0000\n\n" as *u8) 1033 co = sgp_cat(cbody, co, msg) 1034 cbody[co] = 10 as u8; co = co + 1 1035 let craw: *u8 = sys_mmap(40) 1036 sg_oid_raw("commit" as *u8, cbody, co, craw) 1037 let newsha: *u8 = sys_mmap(80) 1038 sg_hex(craw, newsha) 1039 newsha[64] = 0 as u8 1040 if sgp_emit(pack, pst, 1, cbody, co, craw, allshas, hshas, htab) != 0 { 1041 sgp_w("PUSH-FAIL pack/object envelope exceeded verdict=RED\n" as *u8); sys_exit(6); return 6 1042 } 1043 let packlen: i64 = sgp_pack_end(pack, pst) 1044 sgp_w(" snapshot files=" as *u8); sgp_wn(stats[0]) 1045 sgp_w(" dirs=" as *u8); sgp_wn(stats[1]) 1046 sgp_w(" skipped=" as *u8); sgp_wn(stats[2]) 1047 sgp_w(" secrets-excluded=" as *u8); sgp_wn(stats[4]) 1048 sgp_w(" ignored=" as *u8); sgp_wn(pst[9]) 1049 sgp_w(" bytes=" as *u8); sgp_wn(stats[3]) 1050 sgp_w(" snapshot-objects=" as *u8); sgp_wn(pst[3]) 1051 sgp_w(" packed=" as *u8); sgp_wn(pst[1]) 1052 sgp_w(" skipped-present=" as *u8); sgp_wn(pst[6]) 1053 sgp_w(" pack=" as *u8); sgp_wn(packlen); sgp_w("\n" as *u8) 1054 sgp_w(" tree=" as *u8); sgp_w(trhex); sgp_w("\n" as *u8) 1055 sgp_w(" new=" as *u8); sgp_w(newsha); sgp_w("\n" as *u8) 1056 1057 // ---- 4. receive-pack: pkt-line ref-update + flush + pack ---- 1058 let body: *u8 = sys_mmap(SGP_PACK_CAP + SGP_MAGIC_65536) 1059 let cmd: *u8 = sys_mmap(SGP_MAGIC_1024) 1060 var mo: i64 = sgp_cat(cmd, 0, oldsha) 1061 cmd[mo] = 32 as u8; mo = mo + 1 1062 mo = sgp_cat(cmd, mo, newsha) 1063 cmd[mo] = 32 as u8; mo = mo + 1 1064 mo = sgp_cat(cmd, mo, refpath) 1065 cmd[mo] = 0 as u8; mo = mo + 1 1066 mo = sgp_cat(cmd, mo, "report-status object-format=sha256 agent=nishi-sovgit-push/1" as *u8) 1067 cmd[mo] = 10 as u8; mo = mo + 1 1068 // pkt-line: 4 hex length (payload + 4) then payload 1069 let tl: i64 = mo + 4 1070 var bo: i64 = 0 1071 var hx: i64 = 0 1072 while hx < 4 { 1073 let nib: i64 = (tl >> ((3 - hx) * 4)) & 15 1074 var ch: i64 = 48 + nib 1075 if nib > 9 { ch = 87 + nib } 1076 body[bo] = ch as u8; bo = bo + 1 1077 hx = hx + 1 1078 } 1079 var ci: i64 = 0 1080 while ci < mo { body[bo] = cmd[ci]; bo = bo + 1; ci = ci + 1 } 1081 body[bo] = 48 as u8; body[bo + 1] = 48 as u8; body[bo + 2] = 48 as u8; body[bo + 3] = 48 as u8 1082 bo = bo + 4 1083 var pi: i64 = 0 1084 while pi < packlen { body[bo + pi] = pack[pi]; pi = pi + 1 } 1085 bo = bo + packlen 1086 1087 // refuse-before-send: never learn the edge's limit by taking the public web surface down 1088 if mode == 1 { if bo > SGP_EDGE_SAFE { 1089 sgp_w("PUSH-REFUSED body=" as *u8); sgp_wn(bo) 1090 sgp_w("B exceeds the MEASURED edge-safe cap " as *u8); sgp_wn(SGP_EDGE_SAFE) 1091 sgp_w("B (seq334: oversized POSTs crash-loop sites.elf and down every site).\n" as *u8) 1092 sgp_w(" push a SMALLER SCOPE (fewer files per push -- incremental packs make batching cheap)," as *u8) 1093 sgp_w(" or run on-box against loopback where no edge is in the path. verdict=RED\n" as *u8) 1094 sys_exit(11); return 11 1095 } } 1096 let ppath: *u8 = sys_mmap(SGP_MAGIC_1024) 1097 var po: i64 = sgp_cat(ppath, 0, "/git/" as *u8) 1098 po = sgp_cat(ppath, po, repo) 1099 po = sgp_cat(ppath, po, "/git-receive-pack" as *u8) 1100 ppath[po] = 0 as u8 1101 let req1: *u8 = sys_mmap(SGP_PACK_CAP + SGP_MAGIC_131072) 1102 let r1len: i64 = sgp_build_req(req1, "POST" as *u8, ppath, hostbuf, capstr, "application/x-git-receive-pack-request" as *u8, body, bo) 1103 let pn: i64 = sgp_xfer(mode, port, store, urlbase, ppath, req1, r1len, resp, SGP_RESP) 1104 if pn <= 0 { sgp_w("PUSH-FAIL receive-pack transport rc=" as *u8); sgp_wn(pn); sgp_w(" verdict=RED\n" as *u8); sys_exit(7); return 7 } 1105 1106 // ---- 5. report-status ---- 1107 var okline: *u8 = sys_mmap(320) 1108 var oo: i64 = sgp_cat(okline, 0, "ok " as *u8) 1109 oo = sgp_cat(okline, oo, refpath) 1110 okline[oo] = 0 as u8 1111 let unpackok: i64 = sgp_find(resp, pn, "unpack ok" as *u8) 1112 let refok: i64 = sgp_find(resp, pn, okline) 1113 if unpackok < 0 { 1114 sgp_w("PUSH-FAIL server reported unpack failure verdict=RED\n" as *u8) 1115 sgp_dump(resp, pn, 400); sgp_w("\n" as *u8) 1116 sys_exit(8); return 8 1117 } 1118 if refok < 0 { 1119 sgp_w("PUSH-FAIL ref not updated (ng) verdict=RED\n" as *u8) 1120 sgp_dump(resp, pn, 400); sgp_w("\n" as *u8) 1121 sys_exit(9); return 9 1122 } 1123 // The ref ADVANCED, so the remote now provably holds every object of this snapshot -- persist the 1124 // have-set so the next beat sends only what changed. Written ONLY after a confirmed ok, never before. 1125 sys_mkdir("knowledge/sovgit_have" as *u8, 0x1ed) 1126 let hfd: i64 = sys_openat_wr(hpath, 0x1a4) 1127 if hfd >= 0 { 1128 sys_write(hfd, craw, 32) 1129 sys_write(hfd, allshas, pst[3] * 32) 1130 sys_close(hfd) 1131 } else { sgp_we("WARN could not persist have-set (next push falls back to a full pack)\n" as *u8) } 1132 sgp_w("SOVGIT-PUSH OK verdict=GREEN ref=" as *u8); sgp_w(refpath) 1133 sgp_w(" " as *u8); sgp_w(oldsha) 1134 sgp_w(" -> " as *u8); sgp_w(newsha) 1135 sgp_w(" objects=" as *u8); sgp_wn(pst[1]) 1136 sgp_w(" pack=" as *u8); sgp_wn(packlen) 1137 sgp_w(" ZERO-GIT-BINARY\n" as *u8) 1138 return 0 1139}