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=¬es=)\",\"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>← 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>← 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>← 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>← 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, "<" as *u8) }
1141 if c == CH_GT { return scopy(pbody, b, ">" as *u8) }
1142 if c == CH_AMP { return scopy(pbody, b, "&" 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>← 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> · <a href=prs>pull requests</a> · <a href=releases>releases</a> · <a href=ci>checks</a> · <a href=oplog>oplog</a> · <a href=bundle>bundle</a></p><p>protocol v2 · sha256 · partial-clone · shallow · 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 → " 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}