code wiki / (root) / nx_torrent_daemon.nx

nx_torrent_daemon.nx source

↩ module page · 1473 lines · 88825 B

1// nx_torrent_daemon.nx -- the UI enabler (X-TORRENT-UI-001): an HTTP daemon that 2// serves the operator's torrent page and wires it to the LIVE download worker. 3// 4// GET /torrent -> sites/nishifamily/torrent/index.html 5// GET /torrent/app.js -> the page's last-mile JS (renders REAL state only) 6// GET /torrent/api/me -> {"user":"local"} (local single-operator) 7// POST /torrent/api/login -> {"ok":true} 8// POST /torrent/api/add {magnet} -> fork+execve the worker elf -> {"id":"1"} 9// GET /torrent/api/status -> reads outdir/download.status -> {downloads:[...]} 10// POST /torrent/api/<id>/<action> -> {"ok":true} (pause/resume/remove noop) 11// 12// Sovereign: composes nx_http_server (bind/listen/accept) + the worker elf the team 13// already built. NO mocked numbers -- /status emits exactly what the worker wrote to 14// disk; before any download the worker file is absent -> {downloads:[]} (honest empty). 15// license_tier: ORIGINAL kind: last-mile-ui-bridge layer: L6 (over L4 http + L5 worker) 16// 17// module: nishi-core.torrent.daemon 18// depends: nishi-core.http_server, nishi-core.syscalls 19import "nx_http_server.nx" 20import "nx_syscalls.nx" 21import "nx_itoa_lib.nx" // shared MSB-first emitter: nxi_buf is the NUL-free buffer form 22import "nx_bencode.nx" // parse download.meta to locate the video file's byte-range for streaming 23import "nx_torrent_access.nx" // R2: deny-by-default AREA policy (SFW=media family+, NSFW=gallery owner-only) 24import "nx_ioadmit_lib.nx" // the I/O-storm admission ruler shared with nx_build_admit: ask before adding D-state 25const TORRENT_MAGIC_131072: i64 = 131072 26const TORRENT_MAGIC_1024: i64 = 1024 27const TORRENT_MAGIC_8192: i64 = 8192 28const TORRENT_MAGIC_262144: i64 = 262144 29const TORRENT_MAGIC_4000: i64 = 4000 30const TORRENT_MAGIC_2000: i64 = 2000 31const TORRENT_MAGIC_1048576: i64 = 1048576 32const TORRENT_MAGIC_4096: i64 = 4096 33const TORRENT_MAGIC_4095: i64 = 4095 34const TORRENT_MAGIC_16384: i64 = 16384 35const TORRENT_MAGIC_4200: i64 = 4200 36const TORRENT_MAGIC_8097: i64 = 8097 37const TORRENT_MAGIC_1000000000: i64 = 1000000000 38 39// R3 auth: the OPAQUE session->level resolution lives in the FRONT GATEWAY (runtime/_hdl_build/nx_torrent_authn, 40// proven by nx_torrent_authn_gate 14/14) -- NOT in this daemon, which stays LEAN (the full OPAQUE stack won't 41// co-compile here -> empty .s). The gateway authenticates, resolves the level, and injects it as a TRUSTED 42// internal header X-Nishi-Level (Cardinal 12: validate at the boundary, trust internal). LOCAL marker = the 43// operator's own box (a hub push never carries this workstation-local file) -> OWNER. 44const TORRENT_LOCAL_MARKER: *u8 = "/mnt/c/Users/elder/Downloads/nishi-torrents/.local_operator" 45 46// PERSISTENT RETRY (rung #2): how often the detached sweeper re-spawns workers for STUCK partials (a 47// worker gives up after ~20min of no new pieces, then the torrent sits idle). 10 min = tracker-friendly 48// re-announce cadence that still grabs a seed soon after it appears. Data-driven (Cardinal 11). 49const D_RETRY_INTERVAL_MS: i64 = 600000 50 51// ---- I/O ADMISSION FOR WORKER SPAWNS (2026-08-19) -------------------------------------------------- 52// MEASURED: the daemon resumed EVERY active torrent at once (startup + every retry sweep), with no budget 53// and no resource awareness. On an 8-CPU box that put 5 nx_torrent_get workers into D-state on the RAID, 54// 5 of the 8 blocked processes that tripped nx_build_admit's I/O-storm witness and refused every lane's 55// builds for days (load 12-17; store_put/fsync paths queued behind the same array). A RAID5 array gains 56// NOTHING from parallel piece-writers -- aggregate throughput is network-bound -- so parallel workers 57// buy only contention. EVERY spawn path (add, dedup-resume, resume, reannounce, the retry sweep) now 58// goes through d_spawn_worker, which asks the SAME ruler the build gate asks (nx_ioadmit_lib): 59// budget = min( storm-line headroom from /proc/stat , max_workers(conf) - live workers ) 60// A spawn the budget refuses is DEFERRED, never lost: the torrent stays `active` in its registry, the 61// retry sweep re-asks every D_RETRY_INTERVAL_MS, and the sweep ROTATES its start index per pass so a 62// small budget cannot starve the tail of the registry. The conf `<owner-root>/torrent_admit.conf` row 63// `max_workers N` is the operator's concurrency ceiling; absent -> the storm witness alone governs; 64// /proc/stat unreadable -> the conf ceiling alone; neither observable -> D_UNOBS_SPAWNS per ask 65// (conservative, never unbounded -- the same stance as nx_build_admit's BA_NCPU_FALLBACK). 66// Every ask prints `TORRENTD io-admit ...` so the effect is MEASURABLE in the daemon log, not asserted. 67const D_ADMIT_CONF: *u8 = "/torrent_admit.conf" 68const D_ADMIT_CONF_CAP: i64 = 4096 // a few `key value` rows; rm_read reports a full cap as its length 69const D_ADMIT_KEY_MAXW: *u8 = "max_workers" 70const D_WORKER_COMM: *u8 = "nx_torrent_get.sov.elf" // matched on its first IOA_COMM_MAX bytes, as the kernel truncates comm 71const D_UNOBS_SPAWNS: i64 = 1 72const D_QUEUED_STATUS: *u8 = "state=queued-io\n" // written ONLY when no status file exists yet (a first add that was deferred) 73// bud[] cells shared across one sweep (or one request): remaining budget, ncpu, blocked, live workers, 74// max_workers (-1 = no conf), spawned, deferred. Named so a reader never decodes indexes. 75const D_BUD_LEFT: i64 = 0 76const D_BUD_NCPU: i64 = 1 77const D_BUD_BLK: i64 = 2 78const D_BUD_LIVE: i64 = 3 79const D_BUD_MAXW: i64 = 4 80const D_BUD_SPAWNED: i64 = 5 81const D_BUD_DEFERRED: i64 = 6 82const D_BUD_CELLS: i64 = 8 83const D_BUD_BYTES: i64 = 64 // D_BUD_CELLS x 8 84 85// ---- small buffer-building helpers ------------------------------- 86func d_strlen(s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){i=i+1} return i } 87 88func d_puts(dst: *u8, off: i64, s: *u8) -> i64 { 89 var i: i64 = 0 90 while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } 91 return off + i 92} 93// MIGRATED to nxi_buf (debt 1785557603). The old body mmapped a reverse buffer per call and never 94// freed it -- 21 call sites in a LONG-LIVED daemon, so the leak was per-call. It also carried the 95// documented clone SIGN BUG: it did m = 0 - m for negatives but NEVER WROTE THE MINUS, so every 96// negative printed as positive. nxi_buf is MSB-first, allocates nothing, emits the sign (gate T9/T10) 97// and keeps this exact contract: digits only, NO NUL, returns off+len. 98func d_putn(dst: *u8, off: i64, v: i64) -> i64 { return nxi_buf(dst, off, v) } 99 100// read a whole file into buf (bounded by cap); returns bytes read or -1 if open fails 101func d_read_file(path: *u8, buf: *u8, cap: i64) -> i64 { 102 let fd: i64 = sys_openat_rd(path) 103 if fd < 0 { return 0 - 1 } 104 var off: i64 = 0; var go: i64 = 1 105 while go == 1 { 106 if off >= cap { go = 0 } else { 107 let r: i64 = sys_read(fd, buf + off, cap - off) 108 if r <= 0 { go = 0 } else { off = off + r } 109 } 110 } 111 sys_close(fd) 112 return off 113} 114 115// ---- THE REGISTRY IS READ WHOLE OR NOT AT ALL (2026-08-19) -------------------------------------------- 116// Every reader of <root>/torrents.idx used d_read_file with a 64 KiB cap: past ~1,600 entries the tail 117// torrents silently vanished from /status, resume and by-btih lookup, and d_idx_remove REWROTE the registry 118// from that truncated prefix -- a delete that would destroy every entry past 64 KiB. This loader composes 119// sys_read_file, which sizes its buffer from the file (lseek END) and cannot short-read; lenout[0] is the 120// byte count, and the buffer is freed by the caller with sys_free_file (the sweeper is a long-lived process 121// -- a per-sweep mapping that is never unmapped is the VmSize-balloon class this estate has already paid 122// for). EMPTY OR ABSENT -> NULL + 0 *before* sys_read_file is asked: for a zero-size file that primitive 123// falls back to its 4 GiB special-file reserve (debt 1787076780), which a daemon must never map per sweep. 124// st_size lives at offset 48 of the x86_64 stat buffer (the same layout this file already reads st_mtim 125// from at 88). 126const D_STAT_SIZE_OFF: i64 = 48 127func d_idx_load(idxpath: *u8, lenout: *i64) -> *u8 { 128 lenout[0] = 0 129 let stb: *u8 = sys_mmap(160) 130 if sys_fstatat(idxpath, stb) != 0 { return 0 as *u8 } 131 let szp: *i64 = (((stb as i64) + D_STAT_SIZE_OFF)) as *i64 132 if szp[0] <= 0 { return 0 as *u8 } 133 let b: *u8 = sys_read_file(idxpath, lenout) 134 if (b as i64) == 0 { lenout[0] = 0 } 135 return b 136} 137func d_idx_free(b: *u8, n: i64) -> i64 { if (b as i64) != 0 { sys_free_file(b, n) } return 0 } 138 139// build outdir+suffix into out (NUL-terminated); returns out. (status/control/magnet sidecars) 140func d_path(outdir: *u8, suf: *u8, out: *u8) -> *u8 { 141 var o: i64 = d_puts(out, 0, outdir) 142 o = d_puts(out, o, suf) 143 out[o] = 0 as u8 144 return out 145} 146// write n bytes to path (O_CREAT|O_WRONLY|O_TRUNC) -- the control + magnet sidecar files 147func d_write_file(path: *u8, s: *u8, n: i64) -> i64 { 148 let fd: i64 = sys_openat_wr(path, 0x1a4) 149 if fd < 0 { return 0 - 1 } 150 sys_write(fd, s, n) 151 sys_close(fd) 152 return 0 153} 154 155// ---- HTTP response writer (header + body, then close) ------------ 156func d_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 157 let h: *u8 = sys_mmap(512) 158 var o: i64 = 0 159 o = d_puts(h, o, "HTTP/1.1 " as *u8); o = d_puts(h, o, status) 160 o = d_puts(h, o, "\r\nContent-Type: " as *u8); o = d_puts(h, o, ctype) 161 o = d_puts(h, o, "\r\nContent-Length: " as *u8); o = d_putn(h, o, blen) 162 o = d_puts(h, o, "\r\nAccess-Control-Allow-Origin: *\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8) 163 sys_write(cfd, h, o) 164 if blen > 0 { sys_write(cfd, body, blen) } 165 sys_close(cfd) 166 return 0 167} 168func d_send_str(cfd: i64, status: *u8, ctype: *u8, s: *u8) -> i64 { 169 return d_send(cfd, status, ctype, s, d_strlen(s)) 170} 171func d_serve_file(cfd: i64, path: *u8, ctype: *u8) -> i64 { 172 let buf: *u8 = sys_mmap(TORRENT_MAGIC_131072) 173 let n: i64 = d_read_file(path, buf, TORRENT_MAGIC_131072) 174 if n <= 0 { d_send_str(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "missing\n" as *u8); return 0 } 175 d_send(cfd, "200 OK" as *u8, ctype, buf, n) 176 return 0 177} 178 179// ---- path matching on the parsed request ------------------------- 180func d_streq(buf: *u8, off: i64, len: i64, s: *u8) -> i64 { 181 var i: i64 = 0 182 while s[i] != (0 as u8) { 183 if i >= len { return 0 } 184 if buf[off+i] != s[i] { return 0 } 185 i = i + 1 186 } 187 if i != len { return 0 } 188 return 1 189} 190func d_starts(buf: *u8, off: i64, len: i64, s: *u8) -> i64 { 191 var i: i64 = 0 192 while s[i] != (0 as u8) { 193 if i >= len { return 0 } 194 if buf[off+i] != s[i] { return 0 } 195 i = i + 1 196 } 197 return 1 198} 199 200// ---- magnet extraction: copy the "magnet:..." value out of the JSON body ---- 201// finds the byte run m a g n e t : (the value; the key "magnet" is followed by a quote) 202// and copies to the next double-quote. Returns length. 203func d_extract_magnet(body: *u8, blen: i64, out: *u8, ocap: i64) -> i64 { 204 var i: i64 = 0; var start: i64 = 0 - 1 205 while i + 7 <= blen { 206 if body[i]==(109 as u8) { if body[i+1]==(97 as u8) { if body[i+2]==(103 as u8) { if body[i+3]==(110 as u8) { if body[i+4]==(101 as u8) { if body[i+5]==(116 as u8) { if body[i+6]==(58 as u8) { 207 start = i; i = blen + 100 208 }}}}}}} 209 i = i + 1 210 } 211 if start < 0 { return 0 } 212 var j: i64 = start; var k: i64 = 0; var go: i64 = 1 213 while go == 1 { 214 if j >= blen { go = 0 } else { 215 if body[j]==(34 as u8) { go = 0 } else { 216 if k < ocap - 1 { out[k] = body[j]; k = k + 1 } 217 j = j + 1 218 } 219 } 220 } 221 out[k] = 0 as u8 222 return k 223} 224 225// ---- spawn the worker elf (double-fork so it reparents to init: no zombie) ---- 226// has this torrent already made progress? (non-empty donefile) -> re-add should RESUME, not truncate. 227func d_has_progress(outdir: *u8) -> i64 { 228 let dp: *u8 = sys_mmap(640); var p: i64 = d_puts(dp, 0, outdir); p = d_puts(dp, p, "/download.done" as *u8); dp[p] = 0 as u8 229 let fd: i64 = sys_openat_rd(dp); if fd < 0 { return 0 } 230 let b: *u8 = sys_mmap(8); let r: i64 = sys_read(fd, b, 1); sys_close(fd) 231 if r > 0 { return 1 } 232 return 0 233} 234// Before exec in a forked child: close every fd the daemon held open (the listen socket + the 235// per-connection cfd + any transient fds). A long-lived worker must NEVER keep the client's 236// connection open -- if it did, an HTTP client that reads to EOF (rather than honoring 237// Content-Length) blocks until the whole download ends. The daemon's own gate hung exactly this 238// way. stdio (0,1,2 -> /dev/null) is kept; sys_close on an unopened fd returns EBADF, so the 239// blind 3..255 sweep is safe. This is the no-hang law extended to the client side. 240func d_close_inherited() -> i64 { 241 var fdx: i64 = 3 242 while fdx < 256 { sys_close(fdx); fdx = fdx + 1 } 243 return 0 244} 245// The directory THIS daemon binary lives in, read from /proc/self/cmdline argv[0]. The spawn elfs (worker, 246// discover, hls, video, ts2fmp4) are deployed as its SIBLINGS, so they resolve correctly on BOTH the dev box 247// (/tmp, where the WOMB builds them) and the NAS hub (/volume1/ai/torrent, where the bundle is deployed AND 248// /tmp is mounted NOEXEC -- the reason the old hardcoded /tmp exec failed on the NAS). Fallback "/tmp". 249func d_self_dir() -> *u8 { 250 let buf: *u8 = sys_mmap(TORRENT_MAGIC_1024) 251 let fd: i64 = sys_openat_rd("/proc/self/cmdline" as *u8) 252 var n: i64 = 0 253 if fd >= 0 { n = sys_read(fd, buf, 1023); sys_close(fd) } 254 if n <= 0 { let fb: *u8 = sys_mmap(8); fb[0]=47 as u8; fb[1]=116 as u8; fb[2]=109 as u8; fb[3]=112 as u8; fb[4]=0 as u8; return fb } 255 var z: i64 = 0; var dz: i64 = 0 256 while dz == 0 { if z >= n { dz = 1 } else { if buf[z] == (0 as u8) { dz = 1 } else { z = z + 1 } } } 257 var i: i64 = z - 1; var df: i64 = 0 258 while df == 0 { if i <= 0 { df = 1 } else { if buf[i] == (47 as u8) { buf[i] = 0 as u8; df = 1 } else { i = i - 1 } } } 259 if buf[0] == (0 as u8) { let fb: *u8 = sys_mmap(8); fb[0]=47 as u8; fb[1]=116 as u8; fb[2]=109 as u8; fb[3]=112 as u8; fb[4]=0 as u8; return fb } 260 return buf 261} 262// Measure once per sweep/request: fills bud[] (see D_BUD_*) and returns the spawn budget. 263func d_admit_budget(owner_root: *u8, bud: *i64) -> i64 { 264 let m: *i64 = sys_mmap(32) as *i64 265 let rc: i64 = ioa_measure(m) 266 var wb: i64 = IOA_UNREADABLE 267 if rc == 0 { wb = ioa_spawn_budget(m[0], m[1], IOA_BLOCKED_PER_CPU, IOA_RESERVE_SLOTS) } 268 let cp: *u8 = d_path(owner_root, D_ADMIT_CONF, sys_mmap(700)) 269 let cb: *u8 = sys_mmap(D_ADMIT_CONF_CAP) 270 let cn: i64 = rm_read(cp, cb, D_ADMIT_CONF_CAP) 271 var maxw: i64 = 0 - 1 272 if cn > 0 { maxw = rm_conf(cb, cn, D_ADMIT_KEY_MAXW, 0 - 1) } 273 let live: i64 = ioa_count_comm_roots(D_WORKER_COMM) // ROOT workers only: a worker forks handlers that keep its comm (measured: 2 workers read as 63 when the tree was counted) 274 var cbud: i64 = 0 - 1 275 if maxw >= 0 { if live >= 0 { cbud = maxw - live; if cbud < 0 { cbud = 0 } } } 276 var b: i64 = D_UNOBS_SPAWNS 277 if wb >= 0 { b = wb } 278 if cbud >= 0 { if wb >= 0 { if cbud < b { b = cbud } } else { b = cbud } } 279 bud[D_BUD_LEFT] = b 280 bud[D_BUD_NCPU] = m[0] 281 bud[D_BUD_BLK] = m[1] 282 bud[D_BUD_LIVE] = live 283 bud[D_BUD_MAXW] = maxw 284 bud[D_BUD_SPAWNED] = 0 285 bud[D_BUD_DEFERRED] = 0 286 return b 287} 288// One log line per ask, so the admission is MEASURABLE in /tmp/torrentd.log (nx_status tails it). 289func d_admit_log(tag: *u8, bud: *i64) -> i64 { 290 let lb: *u8 = sys_mmap(256) 291 var o: i64 = d_puts(lb, 0, "TORRENTD io-admit " as *u8) 292 o = d_puts(lb, o, tag) 293 o = d_puts(lb, o, " ncpu=" as *u8); o = d_putn(lb, o, bud[D_BUD_NCPU]) 294 o = d_puts(lb, o, " blocked=" as *u8); o = d_putn(lb, o, bud[D_BUD_BLK]) 295 o = d_puts(lb, o, " live_workers=" as *u8); o = d_putn(lb, o, bud[D_BUD_LIVE]) 296 o = d_puts(lb, o, " max_workers=" as *u8); o = d_putn(lb, o, bud[D_BUD_MAXW]) 297 o = d_puts(lb, o, " budget_left=" as *u8); o = d_putn(lb, o, bud[D_BUD_LEFT]) 298 o = d_puts(lb, o, " spawned=" as *u8); o = d_putn(lb, o, bud[D_BUD_SPAWNED]) 299 o = d_puts(lb, o, " deferred=" as *u8); o = d_putn(lb, o, bud[D_BUD_DEFERRED]) 300 lb[o] = 10 as u8 301 sys_write(1, lb, o + 1) 302 return 0 303} 304// The ONE spawn chokepoint. bud[] must come from d_admit_budget (shared across a sweep, fresh per request). 305// Returns 0 = spawned, 1 = DEFERRED (budget exhausted; the registry still says active, the sweep re-asks). 306func d_spawn_worker(magnet: *u8, outdir: *u8, resume: i64, bud: *i64) -> i64 { 307 if bud[D_BUD_LEFT] <= 0 { 308 let sp: *u8 = d_path(outdir, "/download.status" as *u8, sys_mmap(640)) 309 let sfd: i64 = sys_openat_rd(sp) 310 if sfd < 0 { d_write_file(sp, D_QUEUED_STATUS, d_strlen(D_QUEUED_STATUS)) } else { sys_close(sfd) } 311 bud[D_BUD_DEFERRED] = bud[D_BUD_DEFERRED] + 1 312 return 1 313 } 314 bud[D_BUD_LEFT] = bud[D_BUD_LEFT] - 1 315 bud[D_BUD_SPAWNED] = bud[D_BUD_SPAWNED] + 1 316 let wpath: *u8 = d_path(d_self_dir(), "/nx_torrent_get.sov.elf" as *u8, sys_mmap(640)) 317 let argv: *i64 = sys_mmap(64) as *i64 318 argv[0] = wpath as i64; argv[1] = magnet as i64; argv[2] = outdir as i64 319 if resume == 1 { argv[3] = "r" as i64; argv[4] = 0 } else { argv[3] = 0 } 320 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0 321 let p1: i64 = sys_fork() 322 if p1 == 0 { 323 let p2: i64 = sys_fork() 324 if p2 == 0 { d_close_inherited(); sys_execve(wpath, argv, envp); sys_exit(127) } 325 sys_exit(0) 326 } 327 let st: *i64 = sys_mmap(16) as *i64 328 sys_wait4(p1, st, 0) 329 return 0 330} 331// Request-path spawn (add / dedup-resume / resume / reannounce): one fresh measurement, one ask, one log line. 332func d_spawn_admitted(magnet: *u8, outdir: *u8, resume: i64, owner_root: *u8, tag: *u8) -> i64 { 333 let bud: *i64 = sys_mmap(D_BUD_BYTES) as *i64 334 d_admit_budget(owner_root, bud) 335 let r: i64 = d_spawn_worker(magnet, outdir, resume, bud) 336 d_admit_log(tag, bud) 337 return r 338} 339 340// ---- /status: parse the worker's newline-delimited status file -> JSON ---- 341func d_find_val(buf: *u8, n: i64, key: *u8) -> i64 { 342 let kl: i64 = d_strlen(key) 343 var i: i64 = 0 344 while i + kl <= n { 345 var j: i64 = 0; var ok: i64 = 1 346 while j < kl { if buf[i+j] != key[j] { ok = 0; j = kl } else { j = j + 1 } } 347 if ok == 1 { return i + kl } 348 i = i + 1 349 } 350 return 0 - 1 351} 352func d_int_at(buf: *u8, n: i64, pos: i64) -> i64 { 353 if pos < 0 { return 0 } 354 var v: i64 = 0; var i: i64 = pos; var go: i64 = 1 355 while go == 1 { 356 if i >= n { go = 0 } else { 357 let c: i64 = buf[i] as i64 358 if c >= 48 { if c <= 57 { v = v*10 + (c-48); i = i+1 } else { go = 0 } } else { go = 0 } 359 } 360 } 361 return v 362} 363func d_copy_line(buf: *u8, n: i64, pos: i64, out: *u8, ocap: i64) -> i64 { 364 out[0] = 0 as u8 365 if pos < 0 { return 0 } 366 var i: i64 = pos; var k: i64 = 0; var go: i64 = 1 367 while go == 1 { 368 if i >= n { go = 0 } else { 369 if buf[i]==(10 as u8) { go = 0 } else { 370 if k < ocap-1 { out[k]=buf[i]; k=k+1 } 371 i = i+1 372 } 373 } 374 } 375 out[k] = 0 as u8 376 return k 377} 378func d_status(cfd: i64, statpath: *u8, ctlpath: *u8) -> i64 { 379 // operator control overrides (file-based, written by pause/remove): removed -> empty list, 380 // paused -> force the rendered state to "paused" (the worker has self-stopped). 381 let cb: *u8 = sys_mmap(16); let cn: i64 = d_read_file(ctlpath, cb, 15) 382 var removed: i64 = 0; var paused: i64 = 0 383 if cn > 0 { let c0: i64 = cb[0] as i64; if c0 == 114 { removed = 1 } if c0 == 112 { paused = 1 } } 384 let buf: *u8 = sys_mmap(TORRENT_MAGIC_8192) 385 let n: i64 = d_read_file(statpath, buf, TORRENT_MAGIC_8192) 386 let out: *u8 = sys_mmap(TORRENT_MAGIC_8192) 387 var o: i64 = 0 388 if removed == 1 { 389 o = d_puts(out, o, "{\"downloads\":[]}" as *u8) 390 d_send(cfd, "200 OK" as *u8, "application/json" as *u8, out, o); return 0 391 } 392 if n <= 0 { 393 o = d_puts(out, o, "{\"downloads\":[]}" as *u8) 394 d_send(cfd, "200 OK" as *u8, "application/json" as *u8, out, o); return 0 395 } 396 let total: i64 = d_int_at(buf, n, d_find_val(buf, n, "total=" as *u8)) 397 let done: i64 = d_int_at(buf, n, d_find_val(buf, n, "done=" as *u8)) 398 let have: i64 = d_int_at(buf, n, d_find_val(buf, n, "have=" as *u8)) 399 let pieces: i64= d_int_at(buf, n, d_find_val(buf, n, "pieces=" as *u8)) 400 let peers: i64 = d_int_at(buf, n, d_find_val(buf, n, "peers=" as *u8)) 401 let stateb: *u8 = sys_mmap(64); d_copy_line(buf, n, d_find_val(buf, n, "state=" as *u8), stateb, 64) 402 if paused == 1 { var pz: i64 = 0; let ps2: *u8 = "paused" as *u8; while ps2[pz] != (0 as u8) { stateb[pz] = ps2[pz]; pz = pz + 1 } stateb[pz] = 0 as u8 } 403 let nameb: *u8 = sys_mmap(512); d_copy_line(buf, n, d_find_val(buf, n, "name=" as *u8), nameb, 512) 404 var vpct: i64 = 0; if pieces > 0 { vpct = have*100/pieces } 405 o = d_puts(out, o, "{\"downloads\":[{\"id\":\"1\",\"name\":\"" as *u8) 406 o = d_puts(out, o, nameb) 407 o = d_puts(out, o, "\",\"size\":" as *u8); o = d_putn(out, o, total) 408 o = d_puts(out, o, ",\"done\":" as *u8); o = d_putn(out, o, done) 409 o = d_puts(out, o, ",\"peers\":" as *u8); o = d_putn(out, o, peers) 410 o = d_puts(out, o, ",\"havePieces\":" as *u8); o = d_putn(out, o, have) 411 o = d_puts(out, o, ",\"totalPieces\":" as *u8); o = d_putn(out, o, pieces) 412 o = d_puts(out, o, ",\"verified\":" as *u8); o = d_putn(out, o, vpct) 413 o = d_puts(out, o, ",\"status\":\"" as *u8); o = d_puts(out, o, stateb) 414 o = d_puts(out, o, "\"}]}" as *u8) 415 d_send(cfd, "200 OK" as *u8, "application/json" as *u8, out, o) 416 return 0 417} 418 419// ---- discover: extract a page URL from the JSON body ---- 420func d_extract_url(body: *u8, blen: i64, out: *u8, ocap: i64) -> i64 { 421 var i: i64 = 0; var start: i64 = 0 - 1 422 while i + 4 <= blen { 423 if body[i]==(104 as u8) { if body[i+1]==(116 as u8) { if body[i+2]==(116 as u8) { if body[i+3]==(112 as u8) { 424 start = i; i = blen + 100 425 }}}} 426 i = i + 1 427 } 428 if start < 0 { return 0 } 429 var j: i64 = start; var k: i64 = 0; var go: i64 = 1 430 while go == 1 { 431 if j >= blen { go = 0 } else { 432 let c: i64 = body[j] as i64 433 if c==34 { go=0 } else { if c==39 { go=0 } else { if c==32 { go=0 } else { if c==60 { go=0 } else { if c==62 { go=0 } else { if c==10 { go=0 } else { if c==13 { go=0 } else { 434 if k < ocap - 1 { out[k] = body[j]; k = k + 1 } 435 j = j + 1 436 }}}}}}} 437 } 438 } 439 out[k] = 0 as u8 440 return k 441} 442 443// spawn the discovery elf (double-fork, reparents to init) 444func d_spawn_discover(url: *u8, dpath: *u8) -> i64 { 445 let wpath: *u8 = d_path(d_self_dir(), "/nx_magnet_discover.sov.elf" as *u8, sys_mmap(640)) 446 let argv: *i64 = sys_mmap(64) as *i64 447 argv[0] = wpath as i64; argv[1] = url as i64; argv[2] = dpath as i64; argv[3] = 0 448 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0 449 let p1: i64 = sys_fork() 450 if p1 == 0 { 451 let p2: i64 = sys_fork() 452 if p2 == 0 { d_close_inherited(); sys_execve(wpath, argv, envp); sys_exit(127) } 453 sys_exit(0) 454 } 455 let st: *i64 = sys_mmap(16) as *i64 456 sys_wait4(p1, st, 0) 457 return 0 458} 459 460// Build a same-origin Referer ("https://<host>/") from a media URL. Most CDNs gate direct .mp4 links on a 461// Referer header; the downloader already sends a browser UA, so an EMPTY referer is the #1 reason a 462// correctly-routed direct video still 403s. out = NUL-terminated referer (empty if the URL has no scheme). 463func d_url_referer(url: *u8, out: *u8) -> *u8 { 464 var i: i64 = 0; var n: i64 = 0; while url[n] != (0 as u8) { n = n + 1 } 465 var sce: i64 = 0 - 1 466 while i + 3 <= n { if url[i]==(58 as u8) { if url[i+1]==(47 as u8) { if url[i+2]==(47 as u8) { sce = i + 3; i = n + 100 } } } i = i + 1 } 467 if sce < 0 { out[0] = 0 as u8; return out } 468 var he: i64 = sce; var hgo: i64 = 1 469 while hgo == 1 { if he >= n { hgo = 0 } else { let c: i64 = url[he] as i64; if c==47 { hgo=0 } else { if c==58 { hgo=0 } else { if c==63 { hgo=0 } else { he = he + 1 } } } } } 470 var o: i64 = 0; var k: i64 = 0 471 while k < he { out[o] = url[k]; o = o + 1; k = k + 1 } 472 out[o] = 47 as u8; o = o + 1 473 out[o] = 0 as u8 474 return out 475} 476 477// spawn the sovereign video downloader (double-fork). argv: elf, url, outpath, referer. 478func d_spawn_video(url: *u8, outpath: *u8, referer: *u8) -> i64 { 479 let wpath: *u8 = d_path(d_self_dir(), "/nx_video_get.sov.elf" as *u8, sys_mmap(640)) 480 let argv: *i64 = sys_mmap(64) as *i64 481 argv[0] = wpath as i64; argv[1] = url as i64; argv[2] = outpath as i64; argv[3] = referer as i64; argv[4] = 0 482 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0 483 let p1: i64 = sys_fork() 484 if p1 == 0 { let p2: i64 = sys_fork(); if p2 == 0 { d_close_inherited(); sys_execve(wpath, argv, envp); sys_exit(127) } sys_exit(0) } 485 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(p1, st, 0) 486 return 0 487} 488// spawn the sovereign HLS downloader (double-fork). argv: elf, m3u8-url, outpath(.ts), referer. 489func d_spawn_hls(url: *u8, outpath: *u8, referer: *u8) -> i64 { 490 let wpath: *u8 = d_path(d_self_dir(), "/nx_hls_get.sov.elf" as *u8, sys_mmap(640)) 491 let argv: *i64 = sys_mmap(64) as *i64 492 argv[0] = wpath as i64; argv[1] = url as i64; argv[2] = outpath as i64; argv[3] = referer as i64; argv[4] = 0 493 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0 494 let p1: i64 = sys_fork() 495 if p1 == 0 { let p2: i64 = sys_fork(); if p2 == 0 { d_close_inherited(); sys_execve(wpath, argv, envp); sys_exit(127) } sys_exit(0) } 496 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(p1, st, 0) 497 return 0 498} 499// is this URL an HLS playlist? (path ends with .m3u8, case-insensitive, before any '?') 500func d_is_hls(url: *u8) -> i64 { 501 var n: i64 = 0; while url[n] != (0 as u8) { n = n + 1 } 502 var e: i64 = n; var i: i64 = 0 503 while i < n { if url[i] == (63 as u8) { e = i; i = n } else { i = i + 1 } } // stop at '?' 504 if e < 5 { return 0 } 505 let s: i64 = e - 5 506 if url[s] != (46 as u8) { return 0 } // '.' 507 var c0: i64 = url[s+1] as i64; if c0 >= 65 { if c0 <= 90 { c0 = c0 + 32 } } 508 var c1: i64 = url[s+2] as i64; if c1 >= 65 { if c1 <= 90 { c1 = c1 + 32 } } 509 var c2: i64 = url[s+3] as i64; if c2 >= 65 { if c2 <= 90 { c2 = c2 + 32 } } 510 var c3: i64 = url[s+4] as i64; if c3 >= 65 { if c3 <= 90 { c3 = c3 + 32 } } 511 if c0 != 109 { return 0 } // m 512 if c1 != 51 { return 0 } // 3 513 if c2 != 117 { return 0 } // u 514 if c3 != 56 { return 0 } // 8 515 return 1 516} 517// HLS output name: basename (up to the first '.') sanitized, then a forced .ts (stitched MPEG-TS). 518func d_hls_outname(url: *u8, outdir: *u8, out: *u8) -> *u8 { 519 var ulen: i64 = 0; while url[ulen] != (0 as u8) { ulen = ulen + 1 } 520 var qend: i64 = ulen; var qi: i64 = 0; while qi < ulen { if url[qi] == (63 as u8) { qend = qi; qi = ulen } else { qi = qi + 1 } } 521 var seg: i64 = 0; var si: i64 = 0; while si < qend { if url[si] == (47 as u8) { seg = si + 1 } si = si + 1 } 522 var o: i64 = d_puts(out, 0, outdir); o = d_puts(out, o, "/" as *u8) 523 var nz: i64 = 0; var k: i64 = seg; var stop: i64 = 0 524 while k < qend { if stop == 0 { 525 let c: i64 = url[k] as i64 526 if c == 46 { stop = 1 } else { var ok: i64 = 0; if c>=48 { if c<=57 { ok=1 } } if c>=65 { if c<=90 { ok=1 } } if c>=97 { if c<=122 { ok=1 } } if c==45 { ok=1 } if c==95 { ok=1 } if ok==1 { out[o]=url[k]; o=o+1; nz=nz+1 } } 527 } k = k + 1 } 528 if nz == 0 { o = d_puts(out, o, "video" as *u8) } 529 o = d_puts(out, o, ".ts" as *u8) 530 out[o] = 0 as u8 531 return out 532} 533// derive outdir/<basename> from a video URL (last path segment before '?'; sanitized; fallback video.mp4). 534func d_vidname(url: *u8, outdir: *u8, out: *u8) -> *u8 { 535 var ulen: i64 = 0; while url[ulen] != (0 as u8) { ulen = ulen + 1 } 536 var qend: i64 = ulen; var qi: i64 = 0; while qi < ulen { if url[qi] == (63 as u8) { qend = qi; qi = ulen } else { qi = qi + 1 } } 537 var seg: i64 = 0; var si: i64 = 0; while si < qend { if url[si] == (47 as u8) { seg = si + 1 } si = si + 1 } 538 var o: i64 = d_puts(out, 0, outdir); o = d_puts(out, o, "/" as *u8) 539 var nz: i64 = 0; var k: i64 = seg 540 while k < qend { let c: i64 = url[k] as i64; var ok: i64 = 0; if c>=48 { if c<=57 { ok=1 } } if c>=65 { if c<=90 { ok=1 } } if c>=97 { if c<=122 { ok=1 } } if c==46 { ok=1 } if c==45 { ok=1 } if c==95 { ok=1 } if ok==1 { out[o]=url[k]; o=o+1; nz=nz+1 } k=k+1 } 541 if nz == 0 { o = d_puts(out, o, "video.mp4" as *u8) } 542 out[o] = 0 as u8 543 return out 544} 545 546func d_scan_to(buf: *u8, n: i64, from: i64, delim: i64) -> i64 { 547 var i: i64 = from 548 while i < n { if (buf[i] as i64) == delim { return i } i = i + 1 } 549 return n 550} 551func d_json_buf(dst: *u8, off: i64, src: *u8, from: i64, to: i64) -> i64 { 552 var i: i64 = from 553 while i < to { 554 let c: i64 = src[i] as i64 555 if c==34 { dst[off]=92 as u8; off=off+1; dst[off]=34 as u8; off=off+1 } 556 else { if c==92 { dst[off]=92 as u8; off=off+1; dst[off]=92 as u8; off=off+1 } 557 else { dst[off]=src[i]; off=off+1 } } 558 i = i + 1 559 } 560 return off 561} 562func d_raw_buf(dst: *u8, off: i64, src: *u8, from: i64, to: i64) -> i64 { 563 if from >= to { dst[off]=48 as u8; return off+1 } 564 var i: i64 = from; while i < to { dst[off]=src[i]; off=off+1; i=i+1 } 565 return off 566} 567// GET /discover_result -> read discover.tsv (+ .status) -> JSON 568func d_discover_result(cfd: i64, dpath: *u8) -> i64 { 569 let sp: *u8 = sys_mmap(TORRENT_MAGIC_1024); var i: i64 = 0; while dpath[i]!=(0 as u8){sp[i]=dpath[i];i=i+1} 570 let suf: *u8 = ".status" as *u8; var j: i64 = 0; while suf[j]!=(0 as u8){sp[i]=suf[j];i=i+1;j=j+1} sp[i]=0 as u8 571 let sbuf: *u8 = sys_mmap(TORRENT_MAGIC_1024); let sn: i64 = d_read_file(sp, sbuf, TORRENT_MAGIC_1024) 572 let stateb: *u8 = sys_mmap(64); stateb[0]=0 as u8 573 var scount: i64 = 0; var sbytes: i64 = 0 574 if sn > 0 { 575 d_copy_line(sbuf, sn, d_find_val(sbuf, sn, "state=" as *u8), stateb, 64) 576 scount = d_int_at(sbuf, sn, d_find_val(sbuf, sn, "count=" as *u8)) 577 sbytes = d_int_at(sbuf, sn, d_find_val(sbuf, sn, "bytes=" as *u8)) 578 } else { let df: *u8 = "fetching" as *u8; var z: i64 = 0; while df[z]!=(0 as u8){stateb[z]=df[z];z=z+1} stateb[z]=0 as u8 } 579 let tbuf: *u8 = sys_mmap(TORRENT_MAGIC_262144); let tn: i64 = d_read_file(dpath, tbuf, TORRENT_MAGIC_262144) 580 let out: *u8 = sys_mmap(TORRENT_MAGIC_262144); var o: i64 = 0 581 o = d_puts(out, o, "{\"state\":\"" as *u8); o = d_json_buf(out, o, stateb, 0, d_strlen(stateb)) 582 o = d_puts(out, o, "\",\"count\":" as *u8); o = d_putn(out, o, scount) 583 o = d_puts(out, o, ",\"bytes\":" as *u8); o = d_putn(out, o, sbytes) 584 o = d_puts(out, o, ",\"magnets\":[" as *u8) 585 if tn > 0 { 586 var p: i64 = 0; var first: i64 = 1; var guard: i64 = 0 587 while p < tn { 588 if guard > TORRENT_MAGIC_4000 { p = tn } else { 589 guard = guard + 1 590 let a: i64 = d_scan_to(tbuf, tn, p, 9) 591 let b: i64 = d_scan_to(tbuf, tn, a+1, 9) 592 let c: i64 = d_scan_to(tbuf, tn, b+1, 9) 593 let e: i64 = d_scan_to(tbuf, tn, c+1, 10) 594 if a < tn { 595 if first == 0 { o = d_puts(out, o, "," as *u8) } first = 0 596 o = d_puts(out, o, "{\"hash\":\"" as *u8); o = d_json_buf(out, o, tbuf, p, a) 597 o = d_puts(out, o, "\",\"name\":\"" as *u8); o = d_json_buf(out, o, tbuf, a+1, b) 598 o = d_puts(out, o, "\",\"trackers\":" as *u8); o = d_raw_buf(out, o, tbuf, b+1, c) 599 o = d_puts(out, o, ",\"magnet\":\"" as *u8); o = d_json_buf(out, o, tbuf, c+1, e) 600 o = d_puts(out, o, "\"}" as *u8) 601 } 602 p = e + 1 603 } 604 } 605 } 606 o = d_puts(out, o, "]}" as *u8) 607 d_send(cfd, "200 OK" as *u8, "application/json" as *u8, out, o) 608 return 0 609} 610 611// ============ MULTI-TORRENT: per-info-hash isolation + a registry ============ 612// Each torrent lives in outdir/<btih>/download.* (the worker already takes its outdir). 613// outdir/torrents.idx is a newline-list of active <btih> ids. No clobber between torrents. 614 615// extract the btih value from a magnet ("...btih:<id>&..." or end) -> the per-torrent id (hex/base32) 616func d_extract_btih(mag: *u8, n: i64, out: *u8, ocap: i64) -> i64 { 617 var i: i64 = 0; var start: i64 = 0 - 1 618 while i + 5 <= n { 619 if mag[i]==(98 as u8) { if mag[i+1]==(116 as u8) { if mag[i+2]==(105 as u8) { if mag[i+3]==(104 as u8) { if mag[i+4]==(58 as u8) { 620 start = i + 5; i = n + 100 621 }}}}} 622 i = i + 1 623 } 624 if start < 0 { return 0 } 625 var j: i64 = start; var k: i64 = 0; var go: i64 = 1 626 while go == 1 { 627 if j >= n { go = 0 } else { if mag[j]==(38 as u8) { go = 0 } else { 628 if k < ocap - 1 { out[k] = mag[j]; k = k + 1 } 629 j = j + 1 630 } } 631 } 632 out[k] = 0 as u8 633 return k 634} 635// hex char -> 0..15 (else -1), for %XX url-decode in dn= names 636func d_hexv(c: i64) -> i64 { 637 if c >= 48 { if c <= 57 { return c - 48 } } 638 if c >= 97 { if c <= 102 { return c - 97 + 10 } } 639 if c >= 65 { if c <= 70 { return c - 65 + 10 } } 640 return 0 - 1 641} 642// extract dn= (display name) from the magnet, url-decode (%XX, '+'), make filesystem+URL-safe -> out. 643// Keeps [A-Za-z0-9._-]; every run of other chars collapses to a single '_' (never leading). So the user 644// sees a recognizable folder ("javdb.com_326FCT-218") in Downloads instead of a raw info-hash. 0 = no dn=. 645func d_extract_name(mag: *u8, n: i64, out: *u8, ocap: i64) -> i64 { 646 var i: i64 = 0; var start: i64 = 0 - 1 647 while i + 3 <= n { 648 if mag[i]==(100 as u8) { if mag[i+1]==(110 as u8) { if mag[i+2]==(61 as u8) { start = i + 3; i = n + 100 } } } // 'd','n','=' 649 i = i + 1 650 } 651 if start < 0 { return 0 } 652 var j: i64 = start; var k: i64 = 0; var pendu: i64 = 0; var go: i64 = 1 653 while go == 1 { 654 if j >= n { go = 0 } else { if mag[j]==(38 as u8) { go = 0 } else { 655 var c: i64 = mag[j] as i64 656 if c == 37 { if j + 2 < n { let hi: i64 = d_hexv(mag[j+1] as i64); let lo: i64 = d_hexv(mag[j+2] as i64); if hi >= 0 { if lo >= 0 { c = hi*16+lo; j = j + 2 } } } } 657 if c == 43 { c = 32 } 658 var keep: i64 = 0 659 if c >= 48 { if c <= 57 { keep = 1 } } 660 if c >= 65 { if c <= 90 { keep = 1 } } 661 if c >= 97 { if c <= 122 { keep = 1 } } 662 if c == 46 { keep = 1 } 663 if c == 45 { keep = 1 } 664 if c == 95 { keep = 1 } 665 if keep == 1 { 666 if pendu == 1 { if k < ocap - 1 { out[k] = 95 as u8; k = k + 1 } pendu = 0 } 667 if k < ocap - 1 { out[k] = c as u8; k = k + 1 } 668 } else { if k > 0 { pendu = 1 } } 669 j = j + 1 670 } } 671 } 672 out[k] = 0 as u8 673 return k 674} 675// build outdir/<id> into dir (NUL-terminated); returns dir. 676func d_subdir(outdir: *u8, id: *u8, dir: *u8) -> *u8 { 677 var p: i64 = d_puts(dir, 0, outdir); p = d_puts(dir, p, "/" as *u8); p = d_puts(dir, p, id); dir[p] = 0 as u8 678 return dir 679} 680func d_ends(buf: *u8, off: i64, len: i64, suf: *u8) -> i64 { 681 let sl: i64 = d_strlen(suf) 682 if sl > len { return 0 } 683 var i: i64 = 0 684 while i < sl { if buf[off+len-sl+i] != suf[i] { return 0 } i = i + 1 } 685 return 1 686} 687// is id one of the newline-delimited lines in buf[0..n)? 688func d_idx_has(buf: *u8, n: i64, id: *u8) -> i64 { 689 let idl: i64 = d_strlen(id) 690 var i: i64 = 0 691 while i < n { 692 let e: i64 = d_scan_to(buf, n, i, 10) 693 if (e - i) == idl { var j: i64 = 0; var m: i64 = 1; while j < idl { if buf[i+j] != id[j] { m = 0; j = idl } else { j = j + 1 } } if m == 1 { return 1 } } 694 i = e + 1 695 } 696 return 0 697} 698// add id to the registry if absent (rewrite the small file; idempotent) 699func d_idx_add(idxpath: *u8, id: *u8) -> i64 { 700 // best-effort dedup, then ATOMIC O_APPEND -- not read-modify-write. Two simultaneous adds used to 701 // both rewrite the whole file, losing one entry (the gate's false-RED); O_APPEND can't clobber. 702 let nl: *i64 = sys_mmap(16) as *i64; let buf: *u8 = d_idx_load(idxpath, nl); let n: i64 = nl[0] 703 if n > 0 { if d_idx_has(buf, n, id) == 1 { d_idx_free(buf, n); return 0 } } 704 d_idx_free(buf, n) 705 let fd: i64 = __syscall(SYS_OPENAT, AT_FDCWD, idxpath as i64, O_WRONLY_CA, 0x1a4, 0, 0) 706 if fd < 0 { return 0 - 1 } 707 let line: *u8 = sys_mmap(160); var k: i64 = 0; while id[k] != (0 as u8) { line[k] = id[k]; k = k + 1 } line[k] = 10 as u8; k = k + 1 708 sys_write(fd, line, k) 709 sys_close(fd) 710 return 0 711} 712// drop id's line from the registry (rewrite without it) 713func d_idx_remove(idxpath: *u8, id: *u8) -> i64 { 714 let nl: *i64 = sys_mmap(16) as *i64; let buf: *u8 = d_idx_load(idxpath, nl); let n: i64 = nl[0]; if n <= 0 { return 0 } 715 let idl: i64 = d_strlen(id) 716 let out: *u8 = sys_mmap(n + 16); var o: i64 = 0 // a rewrite is never larger than the registry it came from 717 var i: i64 = 0 718 while i < n { 719 let e: i64 = d_scan_to(buf, n, i, 10) 720 let len: i64 = e - i 721 var keep: i64 = 1 722 if len == idl { var j: i64 = 0; var m: i64 = 1; while j < idl { if buf[i+j] != id[j] { m = 0; j = idl } else { j = j + 1 } } if m == 1 { keep = 0 } } 723 if keep == 1 { if len > 0 { var c: i64 = i; while c < e { out[o] = buf[c]; o = o + 1; c = c + 1 } out[o] = 10 as u8; o = o + 1 } } 724 i = e + 1 725 } 726 d_write_file(idxpath, out, o) 727 return 0 728} 729// append ONE Download JSON object (id + its dir's status, control-overridden) to out at o0; return new o. 730func d_status_one(out: *u8, o0: i64, id: *u8, dir: *u8) -> i64 { 731 var o: i64 = o0 732 let statpath: *u8 = d_path(dir, "/download.status" as *u8, sys_mmap(640)) 733 let ctl2: *u8 = d_path(dir, "/download.control" as *u8, sys_mmap(640)) 734 let cb: *u8 = sys_mmap(16); let cn: i64 = d_read_file(ctl2, cb, 15) 735 var paused: i64 = 0; if cn > 0 { if (cb[0] as i64) == 112 { paused = 1 } } 736 let buf: *u8 = sys_mmap(TORRENT_MAGIC_8192); let n: i64 = d_read_file(statpath, buf, TORRENT_MAGIC_8192) 737 let total: i64 = d_int_at(buf, n, d_find_val(buf, n, "total=" as *u8)) 738 let done: i64 = d_int_at(buf, n, d_find_val(buf, n, "done=" as *u8)) 739 let have: i64 = d_int_at(buf, n, d_find_val(buf, n, "have=" as *u8)) 740 let pieces: i64= d_int_at(buf, n, d_find_val(buf, n, "pieces=" as *u8)) 741 let peers: i64 = d_int_at(buf, n, d_find_val(buf, n, "peers=" as *u8)) 742 let stateb: *u8 = sys_mmap(64); d_copy_line(buf, n, d_find_val(buf, n, "state=" as *u8), stateb, 64) 743 if n <= 0 { var z: i64 = 0; let s0: *u8 = "started" as *u8; while s0[z] != (0 as u8) { stateb[z] = s0[z]; z = z + 1 } stateb[z] = 0 as u8 } 744 if paused == 1 { var z2: i64 = 0; let s1: *u8 = "paused" as *u8; while s1[z2] != (0 as u8) { stateb[z2] = s1[z2]; z2 = z2 + 1 } stateb[z2] = 0 as u8 } 745 let nameb: *u8 = sys_mmap(512); d_copy_line(buf, n, d_find_val(buf, n, "name=" as *u8), nameb, 512) 746 if n <= 0 { var z3: i64 = 0; let s2: *u8 = "resolving" as *u8; while s2[z3] != (0 as u8) { nameb[z3] = s2[z3]; z3 = z3 + 1 } nameb[z3] = 0 as u8 } 747 var vpct: i64 = 0; if pieces > 0 { vpct = have*100/pieces } 748 o = d_puts(out, o, "{\"id\":\"" as *u8); o = d_puts(out, o, id) 749 o = d_puts(out, o, "\",\"name\":\"" as *u8); o = d_json_buf(out, o, nameb, 0, d_strlen(nameb)) 750 o = d_puts(out, o, "\",\"size\":" as *u8); o = d_putn(out, o, total) 751 o = d_puts(out, o, ",\"done\":" as *u8); o = d_putn(out, o, done) 752 o = d_puts(out, o, ",\"peers\":" as *u8); o = d_putn(out, o, peers) 753 o = d_puts(out, o, ",\"havePieces\":" as *u8); o = d_putn(out, o, have) 754 o = d_puts(out, o, ",\"totalPieces\":" as *u8); o = d_putn(out, o, pieces) 755 o = d_puts(out, o, ",\"verified\":" as *u8); o = d_putn(out, o, vpct) 756 o = d_puts(out, o, ",\"status\":\"" as *u8); o = d_puts(out, o, stateb) 757 o = d_puts(out, o, "\"}" as *u8) 758 return o 759} 760// GET /status: aggregate every torrent in the registry into the downloads array. 761func d_status_all(cfd: i64, outdir: *u8, idxpath: *u8) -> i64 { 762 let nl: *i64 = sys_mmap(16) as *i64; let idx: *u8 = d_idx_load(idxpath, nl); let n: i64 = nl[0] 763 let out: *u8 = sys_mmap(TORRENT_MAGIC_262144); var o: i64 = 0 764 o = d_puts(out, o, "{\"downloads\":[" as *u8) 765 var first: i64 = 1 766 if n > 0 { 767 var i: i64 = 0; var guard: i64 = 0 768 while i < n { 769 if guard > TORRENT_MAGIC_2000 { i = n } else { 770 guard = guard + 1 771 let e: i64 = d_scan_to(idx, n, i, 10) 772 let len: i64 = e - i 773 if len > 0 { 774 let id: *u8 = sys_mmap(128); var k: i64 = 0; while k < len { if k < 127 { id[k] = idx[i+k]; k = k + 1 } else { k = len } } id[len] = 0 as u8 775 if d_idx_has(idx, i, id) == 0 { // dedup: O_APPEND may leave a rare dup line; render each id once 776 let dir: *u8 = d_subdir(outdir, id, sys_mmap(640)) 777 if first == 0 { o = d_puts(out, o, "," as *u8) } first = 0 778 o = d_status_one(out, o, id, dir) 779 } 780 } 781 i = e + 1 782 } 783 } 784 } 785 o = d_puts(out, o, "]}" as *u8) 786 d_send(cfd, "200 OK" as *u8, "application/json" as *u8, out, o) 787 return 0 788} 789 790// ---- stream-while-downloading: serve the VIDEO file's downloaded prefix from download.part (HTTP Range) ---- 791// The torrent's LARGEST file is the video; its bytes live at [voff, voff+vlen) inside download.part. We serve 792// the contiguous-downloaded intersection so a <video> plays what has arrived. Works fully for a completed 793// download (streams straight from download.part, no extraction) and progressively in sequential stream-mode. 794 795// largest file's (offset,length) within download.part, from download.meta. out[0]=offset, out[1]=length. 796func d_video_extent(meta: *u8, msize: i64, out: *i64) -> i64 { 797 out[0] = 0; out[1] = 0 798 let filo: i64 = nx_bc_dict_get(meta, 0, msize, "files" as *u8, 5) 799 if filo >= 0 { if (meta[filo] as i64) == 0x6C { // 'l' -> multi-file 800 var p: i64 = filo + 1; var off: i64 = 0; var bestoff: i64 = 0; var bestlen: i64 = 0; var run: i64 = 1 801 while run == 1 { 802 if p >= msize { run = 0 } else { if (meta[p] as i64) == 0x65 { run = 0 } else { 803 let lo: i64 = nx_bc_dict_get(meta, p, msize, "length" as *u8, 6); let lv: *i64 = sys_mmap(8) as *i64; lv[0]=0; if lo>=0 { nx_bc_int(meta, lo, msize, lv) } 804 if lv[0] > bestlen { bestlen = lv[0]; bestoff = off } 805 off = off + lv[0]; p = nx_bc_skip(meta, p, msize) 806 } } 807 } 808 out[0] = bestoff; out[1] = bestlen; return 1 809 } } 810 let lno: i64 = nx_bc_dict_get(meta, 0, msize, "length" as *u8, 6); let lv2: *i64 = sys_mmap(8) as *i64; lv2[0]=0; if lno>=0 { nx_bc_int(meta, lno, msize, lv2) } 811 out[0] = 0; out[1] = lv2[0]; return 1 // single-file -> whole part is the file 812} 813 814// contiguous downloaded bytes from offset 0 (full pieces only) = read download.done + download.npc. 815func d_contig_bytes(dir: *u8) -> i64 { 816 let donep: *u8 = d_path(dir, "/download.done" as *u8, sys_mmap(640)) 817 let db: *u8 = sys_mmap(TORRENT_MAGIC_262144); let dn: i64 = d_read_file(donep, db, TORRENT_MAGIC_262144) 818 if dn <= 0 { return 0 } 819 var contig: i64 = 0; var i: i64 = 0 820 while i < dn { if (db[i] as i64) == 1 { contig = contig + 1; i = i + 1 } else { i = dn } } 821 let npcp: *u8 = d_path(dir, "/download.npc" as *u8, sys_mmap(640)) 822 let nb: *u8 = sys_mmap(16); let nn: i64 = d_read_file(npcp, nb, 16) 823 var plen: i64 = 0 824 if nn >= 8 { plen = ((nb[4] as i64)<<24)|((nb[5] as i64)<<16)|((nb[6] as i64)<<8)|(nb[7] as i64) } 825 return contig * plen 826} 827 828// extract + url-decode the urlencoded "magnet=" form field (the browser UI sends 829// magnet=<encodeURIComponent(...)>&area=...). %XX -> byte, '+' -> space; the value runs to the field 830// separator '&' or end. The magnet's OWN '&' (between dn=/tr=) arrive as %26, so they do NOT terminate 831// early. Returns the decoded length, or 0 if there's no magnet= field / it isn't a magnet (caller then 832// falls back to the JSON/raw scan the gate uses). FIXES: browser "Add" returning 400 "no magnet". 833func d_form_magnet(body: *u8, blen: i64, out: *u8, ocap: i64) -> i64 { 834 var i: i64 = 0; var start: i64 = 0 - 1 835 while i + 7 <= blen { 836 if body[i]==(109 as u8){ if body[i+1]==(97 as u8){ if body[i+2]==(103 as u8){ if body[i+3]==(110 as u8){ if body[i+4]==(101 as u8){ if body[i+5]==(116 as u8){ if body[i+6]==(61 as u8){ 837 start = i + 7; i = blen + 100 838 }}}}}}} 839 i = i + 1 840 } 841 if start < 0 { return 0 } 842 var j: i64 = start; var k: i64 = 0; var go: i64 = 1 843 while go == 1 { 844 if j >= blen { go = 0 } else { 845 var c: i64 = body[j] as i64 846 if c == 38 { go = 0 } else { // '&' = end of this form field 847 if c == 37 { if j + 2 < blen { let hi: i64 = d_hexv(body[j+1] as i64); let lo: i64 = d_hexv(body[j+2] as i64); if hi >= 0 { if lo >= 0 { c = hi*16 + lo; j = j + 2 } } } } 848 if c == 43 { c = 32 } // '+' -> space 849 if k < ocap - 1 { out[k] = c as u8; k = k + 1 } 850 j = j + 1 851 } 852 } 853 } 854 out[k] = 0 as u8 855 if k < 7 { return 0 } // must be at least "magnet:" 856 if out[0]!=(109 as u8){ return 0 } if out[1]!=(97 as u8){ return 0 } if out[2]!=(103 as u8){ return 0 } if out[3]!=(110 as u8){ return 0 } if out[4]!=(101 as u8){ return 0 } if out[5]!=(116 as u8){ return 0 } if out[6]!=(58 as u8){ return 0 } 857 return k 858} 859 860// size in bytes of a file (st_size @ off 48 of the x86_64 struct stat; mirrors the mtime@88 read in 861// d_autoresume). 0 if it doesn't exist. Lets /stream serve a (near-)complete video's full extent. 862func d_filesize(path: *u8) -> i64 { 863 let stbuf: *u8 = sys_mmap(160) 864 if sys_fstatat(path, stbuf) == 0 { let szp: *i64 = (((stbuf as i64) + 48)) as *i64; return szp[0] } 865 return 0 866} 867 868// out[0] = pieces verified on disk (count of 1-bytes in download.done), out[1] = total pieces (download.npc). 869func d_have_npc(dir: *u8, out: *i64) -> i64 { 870 out[0] = 0; out[1] = 0 871 let donep: *u8 = d_path(dir, "/download.done" as *u8, sys_mmap(640)) 872 let db: *u8 = sys_mmap(TORRENT_MAGIC_262144); let dn: i64 = d_read_file(donep, db, TORRENT_MAGIC_262144) 873 var have: i64 = 0; var i: i64 = 0 874 while i < dn { if (db[i] as i64) == 1 { have = have + 1 } i = i + 1 } 875 out[0] = have 876 let npcp: *u8 = d_path(dir, "/download.npc" as *u8, sys_mmap(640)) 877 let nb: *u8 = sys_mmap(16); let nn: i64 = d_read_file(npcp, nb, 16) 878 if nn >= 4 { out[1] = ((nb[0] as i64)<<24)|((nb[1] as i64)<<16)|((nb[2] as i64)<<8)|(nb[3] as i64) } 879 return 0 880} 881 882// parse "Range: bytes=START-END" (END optional). out[0]=start, out[1]=end(-1 open). returns 1 if present. 883func d_parse_range(req: *u8, rn: i64, out: *i64) -> i64 { 884 out[0] = 0; out[1] = 0 - 1 885 let pat: *u8 = "Range: bytes=" as *u8; let pl: i64 = 13 886 var i: i64 = 0; var pos: i64 = 0 - 1 887 while i + pl <= rn { var j: i64 = 0; var m: i64 = 1; while j < pl { if req[i+j] != pat[j] { m = 0; j = pl } else { j = j + 1 } } if m == 1 { pos = i + pl; i = rn } else { i = i + 1 } } 888 if pos < 0 { return 0 } 889 var s: i64 = 0; var sgo: i64 = 1 890 while sgo == 1 { if pos >= rn { sgo = 0 } else { let c: i64 = req[pos] as i64; if c >= 48 { if c <= 57 { s = s*10 + (c-48); pos = pos + 1 } else { sgo = 0 } } else { sgo = 0 } } } 891 out[0] = s // pos now points at the first non-digit (the '-') or rn 892 if pos < rn { if (req[pos] as i64) == 45 { // '-' 893 pos = pos + 1; var e: i64 = 0; var he: i64 = 0 894 while pos < rn { let c2: i64 = req[pos] as i64; if c2 >= 48 { if c2 <= 57 { e = e*10 + (c2-48); he = 1; pos = pos + 1 } else { pos = rn } } else { pos = rn } } 895 if he == 1 { out[1] = e } 896 } } 897 return 1 898} 899 900func d_stream(cfd: i64, req: *u8, rn: i64, dir: *u8) -> i64 { 901 // a /stream request means the operator is watching -> switch this torrent to SEQUENTIAL download so 902 // the playable prefix grows in order (the worker reads download.stream each pick). Harmless if complete. 903 d_write_file(d_path(dir, "/download.stream" as *u8, sys_mmap(640)), "1" as *u8, 1) 904 let metap: *u8 = d_path(dir, "/download.meta" as *u8, sys_mmap(640)) 905 let meta: *u8 = sys_mmap(TORRENT_MAGIC_1048576); let msize: i64 = d_read_file(metap, meta, TORRENT_MAGIC_1048576) 906 if msize <= 0 { d_send_str(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "no metadata yet\n" as *u8); return 0 } 907 let ext: *i64 = sys_mmap(16) as *i64; d_video_extent(meta, msize, ext) 908 let voff: i64 = ext[0]; let vlen: i64 = ext[1] 909 if vlen <= 0 { d_send_str(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "no playable file\n" as *u8); return 0 } 910 // AVAIL = how much of the video we can serve now. Near-complete or complete (>=90% of pieces) -> serve 911 // the whole furthest-downloaded extent of download.part (any still-missing piece is a sparse-zero gap the 912 // player skips), so a 99%-done OR finished torrent IS watchable. Early (<90%) -> only the contiguous 913 // prefix from byte 0, so sequential stream-mode plays cleanly from the start. FIXES "can't watch 99% 914 // done" + lets you watch one torrent while others keep downloading. 915 let hn: *i64 = sys_mmap(16) as *i64; d_have_npc(dir, hn) 916 let have: i64 = hn[0]; let npc: i64 = hn[1] 917 // COMPLETE single-file MPEG-TS -> fly-remux to fMP4 so a plain <video> (Chrome/Firefox) plays it, 918 // mirroring the gallery's gs_vid_remux_ts (rung 8). In-progress / multi-file fall through to the partial Range 919 // serve below (watch-while-downloading + native mp4). The daemon forks per-connection, so execve here 920 // replaces only this child. 921 if npc > 0 { if have >= npc { if voff == 0 { 922 let partp0: *u8 = d_path(dir, "/download.part" as *u8, sys_mmap(640)) 923 let mb0: *u8 = sys_mmap(64); let pf0: i64 = sys_openat_rd(partp0); var g0: i64 = 0; if pf0 >= 0 { g0 = sys_read(pf0, mb0, 16) } 924 var is_ts: i64 = 0; if g0 >= 1 { if (mb0[0] as i64) == 0x47 { is_ts = 1 } } // 0x47 = MPEG-TS sync byte 925 var is_ftyp: i64 = 0; if g0 >= 16 { if mb0[4]==(102 as u8) { if mb0[5]==(116 as u8) { if mb0[6]==(121 as u8) { if mb0[7]==(112 as u8) { is_ftyp = 1 } } } } } // 'ftyp' = mp4 926 var is_fast: i64 = 0 927 if is_ftyp == 1 { // is the box AFTER ftyp already 'moov' (faststart)? then the Range serve below is seekable. 928 let ftsz: i64 = ((mb0[0] as i64)<<24)|((mb0[1] as i64)<<16)|((mb0[2] as i64)<<8)|(mb0[3] as i64) 929 let nb: *u8 = sys_mmap(16); var ng: i64 = 0; if pf0 >= 0 { sys_lseek(pf0, ftsz, 0); ng = sys_read(pf0, nb, 8) } 930 if ng >= 8 { if nb[4]==(109 as u8) { if nb[5]==(111 as u8) { if nb[6]==(111 as u8) { if nb[7]==(118 as u8) { is_fast = 1 } } } } } // 'moov' 931 } 932 if pf0 >= 0 { sys_close(pf0) } 933 // COMPLETE single-file MPEG-TS -> fly-remux to fMP4 so a plain <video> (Chrome/Firefox) plays it. 934 if is_ts == 1 { 935 let fh: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: video/mp4\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8 936 sys_write(cfd, fh, d_strlen(fh)); sys_dup3(cfd, 1, 0) 937 let wexe: *u8 = d_path(d_self_dir(), "/nx_ts2fmp4.sov.elf" as *u8, sys_mmap(640)) // sibling of the daemon (self-dir): /tmp on dev, /volume1/ai/torrent on the NAS 938 let av2: *i64 = sys_mmap(64) as *i64; av2[0] = wexe as i64; av2[1] = partp0 as i64; av2[2] = 0 939 let ev2: *i64 = sys_mmap(16) as *i64; ev2[0] = 0 940 sys_execve(wexe, av2, ev2); sys_exit(127) 941 } 942 // NON-FASTSTART mp4 ([ftyp][mdat..][moov]): a browser streaming via Range reads the front, finds no moov 943 // -> "no video, this format is not supported". Relocate moov to the FRONT on the fly (nx_mp4_faststart, 944 // lossless, gate-proven) so a plain <video> plays it. Faststart mp4s (moov already front) fall through 945 // to the seekable Range serve below. 946 if is_ftyp == 1 { if is_fast == 0 { 947 let fh2: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: video/mp4\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8 948 sys_write(cfd, fh2, d_strlen(fh2)); sys_dup3(cfd, 1, 0) 949 let wexe2: *u8 = d_path(d_self_dir(), "/nx_mp4_faststart.sov.elf" as *u8, sys_mmap(640)) 950 let av3: *i64 = sys_mmap(64) as *i64; av3[0] = wexe2 as i64; av3[1] = partp0 as i64; av3[2] = 0 951 let ev3: *i64 = sys_mmap(16) as *i64; ev3[0] = 0 952 sys_execve(wexe2, av3, ev3); sys_exit(127) 953 } } 954 } } } 955 var avail: i64 = 0 956 if npc > 0 { if have * 100 >= npc * 90 { 957 let partp0: *u8 = d_path(dir, "/download.part" as *u8, sys_mmap(640)) 958 avail = d_filesize(partp0) - voff 959 } else { avail = d_contig_bytes(dir) - voff } } 960 else { avail = d_contig_bytes(dir) - voff } 961 if avail < 0 { avail = 0 } if avail > vlen { avail = vlen } 962 if avail <= 0 { d_send_str(cfd, "425 Too Early" as *u8, "text/plain" as *u8, "not enough downloaded yet\n" as *u8); return 0 } 963 let rng: *i64 = sys_mmap(16) as *i64; d_parse_range(req, rn, rng) 964 var rs: i64 = rng[0]; var re: i64 = rng[1] 965 if re < 0 { re = vlen - 1 } 966 if rs < 0 { rs = 0 } 967 if rs >= avail { d_send_str(cfd, "416 Range Not Satisfiable" as *u8, "text/plain" as *u8, "beyond available\n" as *u8); return 0 } 968 if re > avail - 1 { re = avail - 1 } 969 let served: i64 = re - rs + 1 970 let h: *u8 = sys_mmap(512); var o: i64 = 0 971 o = d_puts(h, o, "HTTP/1.1 206 Partial Content\r\nContent-Type: video/mp4\r\nAccept-Ranges: bytes\r\nContent-Range: bytes " as *u8) 972 o = d_putn(h, o, rs); o = d_puts(h, o, "-" as *u8); o = d_putn(h, o, re); o = d_puts(h, o, "/" as *u8); o = d_putn(h, o, vlen) 973 o = d_puts(h, o, "\r\nContent-Length: " as *u8); o = d_putn(h, o, served) 974 o = d_puts(h, o, "\r\nAccess-Control-Allow-Origin: *\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8) 975 sys_write(cfd, h, o) 976 let partp: *u8 = d_path(dir, "/download.part" as *u8, sys_mmap(640)) 977 let pfd: i64 = sys_openat_rd(partp) 978 if pfd >= 0 { 979 sys_lseek(pfd, voff + rs, 0) 980 let chunk: *u8 = sys_mmap(TORRENT_MAGIC_1048576); var rem: i64 = served 981 while rem > 0 { var want: i64 = TORRENT_MAGIC_1048576; if rem < want { want = rem } let g: i64 = sys_read(pfd, chunk, want); if g <= 0 { rem = 0 } else { sys_write(cfd, chunk, g); rem = rem - g } } 982 sys_close(pfd) 983 } 984 sys_close(cfd) 985 return 0 986} 987 988// DEDUP BY INFO-HASH: scan existing torrents' download.magnet for one whose btih == `btih`. Returns 1 989// + copies that torrent's id into out_id (so an add of the same content under a different display name 990// RESUMES the one download instead of creating a duplicate subdir). Works for torrents added before this 991// fix too (it reads their magnet, not a sidecar). 992func d_find_by_btih(outdir: *u8, idxpath: *u8, btih: *u8, btn: i64, out_id: *u8, ocap: i64) -> i64 { 993 let nl: *i64 = sys_mmap(16) as *i64; let idx: *u8 = d_idx_load(idxpath, nl); let n: i64 = nl[0] 994 if n <= 0 { return 0 } 995 var i: i64 = 0; var guard: i64 = 0 996 while i < n { 997 if guard > TORRENT_MAGIC_2000 { i = n } else { 998 guard = guard + 1 999 let e: i64 = d_scan_to(idx, n, i, 10) 1000 let idlen: i64 = e - i 1001 if idlen > 0 { if idlen < 500 { 1002 let id: *u8 = sys_mmap(512); var k: i64 = 0; while k < idlen { id[k] = idx[i+k]; k = k + 1 } id[idlen] = 0 as u8 1003 let dir: *u8 = d_subdir(outdir, id, sys_mmap(640)) 1004 let magp: *u8 = d_path(dir, "/download.magnet" as *u8, sys_mmap(640)) 1005 let mb: *u8 = sys_mmap(TORRENT_MAGIC_4096); let mln: i64 = d_read_file(magp, mb, TORRENT_MAGIC_4095) 1006 if mln > 0 { 1007 mb[mln] = 0 as u8 1008 let eb: *u8 = sys_mmap(128); let ebn: i64 = d_extract_btih(mb, mln, eb, 128) 1009 if ebn == btn { 1010 var m: i64 = 1; var j: i64 = 0 1011 while j < btn { if eb[j] != btih[j] { m = 0; j = btn } else { j = j + 1 } } 1012 if m == 1 { var c: i64 = 0; while c < idlen { if c < ocap-1 { out_id[c] = id[c] } c = c + 1 } out_id[idlen] = 0 as u8; return 1 } 1013 } 1014 } 1015 } } 1016 i = e + 1 1017 } 1018 } 1019 return 0 1020} 1021 1022// STARTUP AUTO-RESUME (crash/reboot recovery): resume every torrent that is control=active AND has 1023// on-disk progress AND whose status file is STALE (not touched in 60s = its worker is dead). The 1024// staleness guard is load-bearing: a GRACEFUL daemon restart leaves the workers alive (they double-fork 1025// + reparent to init, so they survive), and their status keeps updating -> NOT stale -> NOT re-spawned 1026// (no duplicate worker). Only a crash/reboot (workers gone, status frozen) triggers a resume. Returns count. 1027// bud = the sweep's shared admission budget (d_admit_budget, ONE measurement per sweep so N resumes cannot 1028// each read the same pre-spawn headroom and all be admitted). rot = the sweep ordinal: the walk starts at 1029// registry entry (rot mod entries) and wraps, so under a small budget every torrent gets its turn instead of 1030// the head of the registry winning forever (round-robin by construction, no per-torrent bookkeeping). 1031func d_autoresume(outdir: *u8, idxpath: *u8, bud: *i64, rot: i64) -> i64 { 1032 let nl: *i64 = sys_mmap(16) as *i64; let idx: *u8 = d_idx_load(idxpath, nl); let n: i64 = nl[0] 1033 if n <= 0 { return 0 } 1034 let ts: *i64 = sys_mmap(16) as *i64; sys_clock_gettime_real(ts); let now: i64 = ts[0] 1035 let stbuf: *u8 = sys_mmap(160) 1036 // pass 1: registry line starts (bounded by the same entry guard the walk always had) 1037 let starts: *i64 = sys_mmap((TORRENT_MAGIC_2000 + 1) * 8) as *i64 1038 var ne: i64 = 0 1039 var sc: i64 = 0 1040 while sc < n { 1041 if ne > TORRENT_MAGIC_2000 { sc = n } else { 1042 starts[ne] = sc 1043 ne = ne + 1 1044 sc = d_scan_to(idx, n, sc, 10) + 1 1045 } 1046 } 1047 if ne <= 0 { d_idx_free(idx, n); return 0 } 1048 var rs: i64 = 0 1049 if rot > 0 { rs = rot % ne } 1050 var ki: i64 = 0; var resumed: i64 = 0 1051 while ki < ne { 1052 var ei: i64 = rs + ki 1053 if ei >= ne { ei = ei - ne } 1054 let i: i64 = starts[ei] 1055 ki = ki + 1 1056 let e: i64 = d_scan_to(idx, n, i, 10) 1057 let idlen: i64 = e - i 1058 if idlen > 0 { if idlen < 500 { 1059 let id: *u8 = sys_mmap(512); var k: i64 = 0; while k < idlen { id[k] = idx[i+k]; k = k + 1 } id[idlen] = 0 as u8 1060 let dir: *u8 = d_subdir(outdir, id, sys_mmap(640)) 1061 let ctlp: *u8 = d_path(dir, "/download.control" as *u8, sys_mmap(640)); let cb: *u8 = sys_mmap(16); let cn: i64 = d_read_file(ctlp, cb, 15) 1062 var active: i64 = 0; if cn > 0 { if (cb[0] as i64) == 97 { active = 1 } } // 'a'ctive 1063 if active == 1 { 1064 let hn: *i64 = sys_mmap(16) as *i64; d_have_npc(dir, hn) 1065 // RESUMABLE = on-disk progress OR metadata-not-yet-resolved (npc==0). A magnet still fetching 1066 // its info dict legitimately has NO progress yet -- the old `d_has_progress` gate skipped it 1067 // FOREVER, so a magnet that failed metadata once was never retried (the BANK-155/MGFX-142 1068 // "never resolved metadata" stuck bug). npc>0 && have>=npc = COMPLETE -> skip (no forever-respawn). 1069 // RESUME ANY active torrent -- the `notdone` gate below is the sole COMPLETE-skip. The old gate 1070 // (resumable only if d_has_progress OR npc==0) STRANDED FOREVER the torrents that resolved 1071 // metadata (npc>0) but downloaded ZERO pieces (download.done absent -> d_has_progress=0): exactly 1072 // BANK-155/ajvr-256/ajvr-166 (the "IGNORING" torrents). They got ONE worker at /add, and once it 1073 // died without completing a piece they were NEVER re-spawned -> stuck at have=0 permanently. That 1074 // was the REAL "IGNORING" root cause -- not the unchoke timeout, not reciprocity: they simply had 1075 // no worker. notdone (npc==0 OR have<npc) already excludes COMPLETE, so resume-all is correct. 1076 var resumable: i64 = 1 1077 if resumable == 1 { 1078 var notdone: i64 = 1; if hn[1] > 0 { if hn[0] >= hn[1] { notdone = 0 } } 1079 if notdone == 1 { 1080 let statp: *u8 = d_path(dir, "/download.status" as *u8, sys_mmap(640)) 1081 var stale: i64 = 1 1082 if sys_fstatat(statp, stbuf) == 0 { let mtp: *i64 = (((stbuf as i64) + 88)) as *i64; if now - mtp[0] < 60 { stale = 0 } } 1083 if stale == 1 { 1084 let mp: *u8 = d_path(dir, "/download.magnet" as *u8, sys_mmap(640)); let mb: *u8 = sys_mmap(TORRENT_MAGIC_4096); let mln: i64 = d_read_file(mp, mb, TORRENT_MAGIC_4095) 1085 if mln > 0 { mb[mln] = 0 as u8; if d_spawn_worker(mb, dir, 1, bud) == 0 { resumed = resumed + 1 } } 1086 } 1087 } 1088 } 1089 } 1090 } } 1091 } 1092 d_idx_free(idx, n) 1093 return resumed 1094} 1095 1096// ---- AREA routing + access enforcement (R2: two areas; R3 wires the real session->level) ---- 1097// scan buf[0..n) for the NUL-terminated literal pat; return 1 if present. 1098func d_buf_find(buf: *u8, n: i64, pat: *u8) -> i64 { 1099 let pln: i64 = d_strlen(pat) 1100 if pln <= 0 { return 0 } 1101 var i: i64 = 0 1102 while i + pln <= n { 1103 var j: i64 = 0; var m: i64 = 1 1104 while j < pln { if buf[i+j] != pat[j] { m = 0; j = pln } else { j = j + 1 } } 1105 if m == 1 { return 1 } 1106 i = i + 1 1107 } 1108 return 0 1109} 1110// which destination AREA does this request select? "area=gallery"/"area=nsfw" -> NSFW; default SFW ("media"). 1111// Default is the LOWER-privilege area so an unspecified/garbled flag can NEVER silently land in NSFW. 1112func d_parse_area(req: *u8, n: i64) -> i64 { 1113 if d_buf_find(req, n, "area=gallery" as *u8) == 1 { return NX_TAREA_NSFW } 1114 if d_buf_find(req, n, "area=nsfw" as *u8) == 1 { return NX_TAREA_NSFW } 1115 return NX_TAREA_SFW 1116} 1117// is this file present? (the local-operator marker check) 1118func d_marker_present(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 } 1119// parse a non-negative int from the TRUSTED internal header "X-Nishi-Level:" (set by the front gateway). -1 if absent. 1120func d_trusted_level(req: *u8, n: i64) -> i64 { 1121 let pat: *u8 = "X-Nishi-Level:" as *u8; let pl: i64 = 14 1122 var i: i64 = 0; var pos: i64 = 0 - 1 1123 while i + pl <= n { var j: i64 = 0; var m: i64 = 1; while j < pl { if req[i+j] != pat[j] { m = 0; j = pl } else { j = j + 1 } } if m == 1 { pos = i + pl; i = n } else { i = i + 1 } } 1124 if pos < 0 { return 0 - 1 } 1125 if pos < n { if req[pos]==(32 as u8) { pos = pos + 1 } } 1126 var v: i64 = 0; var got: i64 = 0 1127 while pos < n { let c: i64 = req[pos] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); got = 1; pos = pos + 1 } else { pos = n } } else { pos = n } } 1128 if got == 0 { return 0 - 1 } 1129 return v 1130} 1131// the requester's GRANTED level (R3 -- DENY-BY-DEFAULT). 1132// LOCAL mode (marker present = the operator's own box): OWNER -- today's single-operator behavior. 1133// HUB mode (no marker): the front gateway already authenticated (OPAQUE) + resolved the level via 1134// nx_torrent_authn and injected the trusted internal header X-Nishi-Level. Read it; absent/garbled -> 0 1135// (ANON), so nx_taccess_allow denies every gated area. The daemon MUST bind localhost behind the gateway in 1136// hub mode so an outside client can't spoof the header (Cardinal 12: trust internal, validate at the boundary). 1137func d_viewer_level(req: *u8, n: i64) -> i64 { 1138 if d_marker_present(TORRENT_LOCAL_MARKER) == 1 { return NX_TACCESS_OWNER } 1139 let lv: i64 = d_trusted_level(req, n) 1140 if lv < 0 { return 0 } 1141 return lv 1142} 1143// resolve the dir for torrent `id` in a root the viewer MAY access. SFW always; NSFW only for OWNER. 1144// returns 1 + fills out_dir + out_area[0]=area, else 0 (fail-closed: a family viewer cannot even ADDRESS 1145// an NSFW torrent, so /stream and the control verbs can't reach it by id-guessing). 1146func d_resolve_dir(id: *u8, level: i64, sfw_root: *u8, sfw_idx: *u8, nsfw_root: *u8, nsfw_idx: *u8, out_dir: *u8, out_area: *i64) -> i64 { 1147 let nl1: *i64 = sys_mmap(16) as *i64; let ib: *u8 = d_idx_load(sfw_idx, nl1); let n1: i64 = nl1[0] 1148 if n1 > 0 { if d_idx_has(ib, n1, id) == 1 { d_subdir(sfw_root, id, out_dir); out_area[0] = NX_TAREA_SFW; return 1 } } 1149 if level >= NX_TACCESS_OWNER { 1150 let nl2: *i64 = sys_mmap(16) as *i64; let ib2: *u8 = d_idx_load(nsfw_idx, nl2); let n2: i64 = nl2[0] 1151 if n2 > 0 { if d_idx_has(ib2, n2, id) == 1 { d_subdir(nsfw_root, id, out_dir); out_area[0] = NX_TAREA_NSFW; return 1 } } 1152 } 1153 return 0 1154} 1155// append all torrents in ONE root's registry to out (sharing offset + first-flag across roots); return new o. 1156func d_status_root(out: *u8, o0: i64, first_p: *i64, root: *u8, idxpath: *u8) -> i64 { 1157 var o: i64 = o0 1158 let nl: *i64 = sys_mmap(16) as *i64; let idx: *u8 = d_idx_load(idxpath, nl); let n: i64 = nl[0] 1159 if n > 0 { 1160 var i: i64 = 0; var guard: i64 = 0 1161 while i < n { 1162 if guard > TORRENT_MAGIC_2000 { i = n } else { 1163 guard = guard + 1 1164 let e: i64 = d_scan_to(idx, n, i, 10) 1165 let len: i64 = e - i 1166 if len > 0 { 1167 let id: *u8 = sys_mmap(128); var k: i64 = 0; while k < len { if k < 127 { id[k] = idx[i+k]; k = k + 1 } else { k = len } } id[len] = 0 as u8 1168 if d_idx_has(idx, i, id) == 0 { 1169 let dir: *u8 = d_subdir(root, id, sys_mmap(640)) 1170 if first_p[0] == 0 { o = d_puts(out, o, "," as *u8) } first_p[0] = 0 1171 o = d_status_one(out, o, id, dir) 1172 } 1173 } 1174 i = e + 1 1175 } 1176 } 1177 } 1178 return o 1179} 1180// GET /status: aggregate the torrents the viewer MAY see -- SFW always, NSFW only for OWNER. Isolation by 1181// construction: a family viewer never even scans the NSFW root, so an NSFW torrent cannot appear in their list. 1182func d_status_two(cfd: i64, level: i64, sfw_root: *u8, sfw_idx: *u8, nsfw_root: *u8, nsfw_idx: *u8) -> i64 { 1183 let out: *u8 = sys_mmap(TORRENT_MAGIC_262144); var o: i64 = 0 1184 o = d_puts(out, o, "{\"downloads\":[" as *u8) 1185 let fp: *i64 = sys_mmap(16) as *i64; fp[0] = 1 1186 o = d_status_root(out, o, fp, sfw_root, sfw_idx) 1187 if level >= NX_TACCESS_OWNER { o = d_status_root(out, o, fp, nsfw_root, nsfw_idx) } 1188 o = d_puts(out, o, "]}" as *u8) 1189 d_send(cfd, "200 OK" as *u8, "application/json" as *u8, out, o) 1190 return 0 1191} 1192 1193// ---- router ------------------------------------------------------ 1194func d_route(cfd: i64, req: *u8, method: i64, po: i64, pl: i64, cl_: i64, bo: i64, rn: i64, 1195 sfw_root: *u8, sfw_idx: *u8, nsfw_root: *u8, nsfw_idx: *u8, indexp: *u8, appjsp: *u8, discpath: *u8) -> i64 { 1196 let level: i64 = d_viewer_level(req, rn) 1197 if method == 1 { 1198 if d_streq(req,po,pl,"/torrent" as *u8)==1 { d_serve_file(cfd, indexp, "text/html; charset=utf-8" as *u8); return 0 } 1199 if d_streq(req,po,pl,"/torrent/" as *u8)==1 { d_serve_file(cfd, indexp, "text/html; charset=utf-8" as *u8); return 0 } 1200 if d_streq(req,po,pl,"/" as *u8)==1 { d_serve_file(cfd, indexp, "text/html; charset=utf-8" as *u8); return 0 } 1201 if d_streq(req,po,pl,"/torrent/app.js" as *u8)==1 { d_serve_file(cfd, appjsp, "application/javascript" as *u8); return 0 } 1202 if d_streq(req,po,pl,"/torrent/api/me" as *u8)==1 { d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, "{\"user\":\"local\"}" as *u8); return 0 } 1203 if d_streq(req,po,pl,"/torrent/api/status" as *u8)==1 { d_status_two(cfd, level, sfw_root, sfw_idx, nsfw_root, nsfw_idx); return 0 } 1204 // STREAM the video file's downloaded prefix (HTTP Range) so a <video> can play while/after download. 1205 if d_starts(req,po,pl,"/torrent/stream/" as *u8)==1 { 1206 var idlen: i64 = pl - 16 // strip "/torrent/stream/" (16) 1207 if idlen > 511 { idlen = 511 } 1208 if idlen > 0 { 1209 let id: *u8 = sys_mmap(512); var k: i64 = 0; while k < idlen { id[k] = req[po+16+k]; k = k + 1 } id[idlen] = 0 as u8 1210 let dir: *u8 = sys_mmap(640); let sa: *i64 = sys_mmap(16) as *i64 1211 if d_resolve_dir(id, level, sfw_root, sfw_idx, nsfw_root, nsfw_idx, dir, sa) == 1 { 1212 d_stream(cfd, req, rn, dir) 1213 } else { d_send_str(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "not found\n" as *u8) } 1214 } else { d_send_str(cfd, "400 Bad Request" as *u8, "text/plain" as *u8, "no id\n" as *u8) } 1215 return 0 1216 } 1217 if d_streq(req,po,pl,"/torrent/api/discover_result" as *u8)==1 { d_discover_result(cfd, discpath); return 0 } 1218 d_send_str(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "not found\n" as *u8); return 0 1219 } 1220 if method == 2 { 1221 if d_streq(req,po,pl,"/torrent/api/login" as *u8)==1 { d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, "{\"ok\":true}" as *u8); return 0 } 1222 if d_streq(req,po,pl,"/torrent/api/add" as *u8)==1 { 1223 // The POST body can trail the headers in a later TCP segment, and some clients 1224 // omit a parseable Content-Length. So: drain the socket (idle-bounded) into the 1225 // same buffer, then search the WHOLE request -- the "magnet:" run never occurs 1226 // in HTTP headers, so a full-buffer scan is unambiguous and Content-Length-free. 1227 sys_set_socket_timeout(cfd, 2) 1228 var tot: i64 = rn 1229 var go: i64 = 1 1230 while go == 1 { 1231 if tot >= TORRENT_MAGIC_16384 { go = 0 } else { 1232 let r: i64 = sys_read(cfd, req + tot, TORRENT_MAGIC_16384 - tot) 1233 if r <= 0 { go = 0 } else { tot = tot + r } 1234 } 1235 } 1236 let mag: *u8 = sys_mmap(TORRENT_MAGIC_4096) 1237 var mn: i64 = d_form_magnet(((req as i64) + bo) as *u8, tot - bo, mag, TORRENT_MAGIC_4096) // browser UI: urlencoded magnet=<%XX> -> decode 1238 if mn <= 0 { mn = d_extract_magnet(req, tot, mag, TORRENT_MAGIC_4096) } // JSON/raw fallback (the gate path) 1239 if mn <= 0 { d_send_str(cfd, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"no magnet\"}" as *u8); return 0 } 1240 // AREA + ACCESS (R2): which destination did the request flag, and may THIS viewer reach it? 1241 // Deny-by-default via nx_taccess_allow (SFW="media" needs FAMILY level; NSFW="gallery" needs OWNER). 1242 let area: i64 = d_parse_area(req, tot) 1243 if nx_taccess_allow(level, area) != NX_TACCESS_ALLOW { 1244 d_send_str(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"access denied for this area\"}" as *u8); return 0 1245 } 1246 var aroot: *u8 = sfw_root; var aidx: *u8 = sfw_idx 1247 if area == NX_TAREA_NSFW { aroot = nsfw_root; aidx = nsfw_idx } 1248 // MULTI-TORRENT: id = the magnet's btih; each torrent gets its OWN <area-root>/<id> + a registry 1249 // entry, so concurrent downloads never clobber each other (the BBB-clobber bug). 1250 let hbv: *u8 = sys_mmap(128); let hbn: i64 = d_extract_btih(mag, mn, hbv, 128); if hbn <= 0 { d_send_str(cfd, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"bad magnet (no btih)\"}" as *u8); return 0 } 1251 // DEDUP BY INFO-HASH (within the area): if this exact content is already here (any display name), 1252 // RESUME that one download instead of spawning a duplicate subdir (the 326FCT-218-added-twice bug). 1253 let exid: *u8 = sys_mmap(512) 1254 if d_find_by_btih(aroot, aidx, hbv, hbn, exid, 512) == 1 { 1255 let edir: *u8 = d_subdir(aroot, exid, sys_mmap(640)) 1256 d_write_file(d_path(edir, "/download.control" as *u8, sys_mmap(640)), "active" as *u8, 6) 1257 d_spawn_admitted(mag, edir, 1, nsfw_root, "dedup-resume" as *u8) 1258 let rb0: *u8 = sys_mmap(256); var r0: i64 = d_puts(rb0, 0, "{\"id\":\"" as *u8); r0 = d_puts(rb0, r0, exid); r0 = d_puts(rb0, r0, "\",\"dedup\":true}" as *u8); rb0[r0] = 0 as u8 1259 d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, rb0); return 0 1260 } 1261 // folder/id = the TITLE (dn=), sanitized -- so Downloads shows "javdb.com_326FCT-218/" not a raw 1262 // hash. Falls back to the info-hash only when the magnet carries no dn=. 1263 let id: *u8 = sys_mmap(512); var idl: i64 = d_extract_name(mag, mn, id, 512) 1264 if idl <= 0 { idl = d_extract_btih(mag, mn, id, 512) } 1265 let dir: *u8 = d_subdir(aroot, id, sys_mmap(640)) 1266 sys_mkdir(dir, 0x1ff) 1267 d_write_file(d_path(dir, "/download.magnet" as *u8, sys_mmap(640)), mag, mn) 1268 d_write_file(d_path(dir, "/download.control" as *u8, sys_mmap(640)), "active" as *u8, 6) 1269 d_idx_add(aidx, id) 1270 d_spawn_admitted(mag, dir, d_has_progress(dir), nsfw_root, "add" as *u8) // RESUME if a partial download already exists (idempotent re-add); DEFERRED when the I/O budget is spent (the sweep re-asks) 1271 let rb: *u8 = sys_mmap(256); var ro: i64 = d_puts(rb, 0, "{\"id\":\"" as *u8); ro = d_puts(rb, ro, id); ro = d_puts(rb, ro, "\"}" as *u8); rb[ro] = 0 as u8 1272 d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, rb); return 0 1273 } 1274 if d_streq(req,po,pl,"/torrent/api/discover" as *u8)==1 { 1275 sys_set_socket_timeout(cfd, 2) 1276 var tot: i64 = rn 1277 var go: i64 = 1 1278 while go == 1 { 1279 if tot >= TORRENT_MAGIC_16384 { go = 0 } else { 1280 let r: i64 = sys_read(cfd, req + tot, TORRENT_MAGIC_16384 - tot) 1281 if r <= 0 { go = 0 } else { tot = tot + r } 1282 } 1283 } 1284 let urlb: *u8 = sys_mmap(TORRENT_MAGIC_4096) 1285 let un: i64 = d_extract_url(req + bo, tot - bo, urlb, TORRENT_MAGIC_4096) 1286 if un <= 0 { d_send_str(cfd, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"no url\"}" as *u8); return 0 } 1287 d_spawn_discover(urlb, discpath) 1288 d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, "{\"ok\":true}" as *u8); return 0 1289 } 1290 if d_streq(req,po,pl,"/torrent/api/video" as *u8)==1 { 1291 sys_set_socket_timeout(cfd, 2) 1292 var tot: i64 = rn; var go: i64 = 1 1293 while go == 1 { if tot >= TORRENT_MAGIC_16384 { go = 0 } else { let r: i64 = sys_read(cfd, req + tot, TORRENT_MAGIC_16384 - tot); if r <= 0 { go = 0 } else { tot = tot + r } } } 1294 let urlb: *u8 = sys_mmap(TORRENT_MAGIC_4096); let un: i64 = d_extract_url(req + bo, tot - bo, urlb, TORRENT_MAGIC_4096) 1295 if un <= 0 { d_send_str(cfd, "400 Bad Request" as *u8, "application/json" as *u8, "{\"error\":\"no url\"}" as *u8); return 0 } 1296 let varea: i64 = d_parse_area(req, tot) 1297 if nx_taccess_allow(level, varea) != NX_TACCESS_ALLOW { d_send_str(cfd, "403 Forbidden" as *u8, "application/json" as *u8, "{\"error\":\"access denied for this area\"}" as *u8); return 0 } 1298 var vroot: *u8 = sfw_root; if varea == NX_TAREA_NSFW { vroot = nsfw_root } 1299 let vdir: *u8 = d_path(vroot, "/videos" as *u8, sys_mmap(640)); sys_mkdir(vdir, 0x1ff) 1300 let vref: *u8 = d_url_referer(urlb, sys_mmap(TORRENT_MAGIC_4200)) // same-origin Referer so CDNs that gate direct links serve the bytes (empty = #1 cause of 403) 1301 if d_is_hls(urlb) == 1 { // HLS .m3u8 -> fetch+stitch (+AES decrypt) all segments via nx_hls_get 1302 let houtp: *u8 = d_hls_outname(urlb, vdir, sys_mmap(TORRENT_MAGIC_1024)) 1303 d_spawn_hls(urlb, houtp, vref) 1304 } else { 1305 let outp: *u8 = d_vidname(urlb, vdir, sys_mmap(TORRENT_MAGIC_1024)) 1306 d_spawn_video(urlb, outp, vref) 1307 } 1308 d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, "{\"ok\":true}" as *u8); return 0 1309 } 1310 // MULTI-TORRENT control: /torrent/api/<id>/{pause,resume,remove}, id = the torrent's btih. 1311 if d_starts(req,po,pl,"/torrent/api/" as *u8)==1 { 1312 var act: i64 = 0; var sl: i64 = 0 1313 if d_ends(req,po,pl,"/pause" as *u8)==1 { act = 1; sl = 6 } 1314 if d_ends(req,po,pl,"/resume" as *u8)==1 { act = 2; sl = 7 } 1315 if d_ends(req,po,pl,"/remove" as *u8)==1 { act = 3; sl = 7 } 1316 if d_ends(req,po,pl,"/reannounce" as *u8)==1 { act = 4; sl = 11 } // the UNSTICK lever 1317 if d_ends(req,po,pl,"/recheck" as *u8)==1 { act = 5; sl = 8 } 1318 if act > 0 { 1319 var idlen: i64 = pl - 13 - sl // strip "/torrent/api/" (13) + the action suffix 1320 if idlen > 127 { idlen = 127 } // defensive: id buffer is 128 1321 if idlen > 0 { 1322 let id: *u8 = sys_mmap(128); var k: i64 = 0; while k < idlen { if k < 127 { id[k] = req[po+13+k]; k = k + 1 } else { k = idlen } } id[idlen] = 0 as u8 1323 let dir: *u8 = sys_mmap(640); let ca: *i64 = sys_mmap(16) as *i64 1324 if d_resolve_dir(id, level, sfw_root, sfw_idx, nsfw_root, nsfw_idx, dir, ca) == 1 { 1325 if act == 1 { d_write_file(d_path(dir, "/download.control" as *u8, sys_mmap(640)), "paused" as *u8, 6) } 1326 if act == 2 { 1327 d_write_file(d_path(dir, "/download.control" as *u8, sys_mmap(640)), "active" as *u8, 6) 1328 let mp: *u8 = d_path(dir, "/download.magnet" as *u8, sys_mmap(640)); let mb2: *u8 = sys_mmap(TORRENT_MAGIC_4096); let mln: i64 = d_read_file(mp, mb2, TORRENT_MAGIC_4095) 1329 if mln > 0 { mb2[mln] = 0 as u8; d_spawn_admitted(mb2, dir, 1, nsfw_root, "resume" as *u8) } 1330 } 1331 if act == 3 { 1332 d_write_file(d_path(dir, "/download.control" as *u8, sys_mmap(640)), "removed" as *u8, 7) 1333 if ca[0] == NX_TAREA_NSFW { d_idx_remove(nsfw_idx, id) } else { d_idx_remove(sfw_idx, id) } 1334 } 1335 if act == 4 { // force-reannounce: respawn the worker -> re-hits trackers/DHT + re-discovers peers (the UNSTICK for IGNORING torrents) 1336 d_write_file(d_path(dir, "/download.control" as *u8, sys_mmap(640)), "active" as *u8, 6) 1337 let mp4: *u8 = d_path(dir, "/download.magnet" as *u8, sys_mmap(640)); let mb4: *u8 = sys_mmap(TORRENT_MAGIC_4096); let ml4: i64 = d_read_file(mp4, mb4, TORRENT_MAGIC_4095) 1338 if ml4 > 0 { mb4[ml4] = 0 as u8; d_spawn_admitted(mb4, dir, 1, nsfw_root, "reannounce" as *u8) } 1339 } 1340 if act == 5 { // force-recheck: signal the worker to re-verify on-disk pieces 1341 d_write_file(d_path(dir, "/download.control" as *u8, sys_mmap(640)), "recheck" as *u8, 7) 1342 } 1343 } 1344 } 1345 } 1346 d_send_str(cfd, "200 OK" as *u8, "application/json" as *u8, "{\"ok\":true}" as *u8); return 0 1347 } 1348 d_send_str(cfd, "404 Not Found" as *u8, "text/plain" as *u8, "not found\n" as *u8); return 0 1349 } 1350 d_send_str(cfd, "405 Method Not Allowed" as *u8, "text/plain" as *u8, "method\n" as *u8) 1351 return 0 1352} 1353 1354func main(argc: i64, argv: *i64) -> i64 { 1355 // TWO download AREAS (operator 2026-06-20): a lower-privilege "media" area (family+) and an owner-only area. 1356 // Each area = its OWN root + registry, so a member's /status never even scans the owner root (isolation by 1357 // construction). DATA-DRIVEN ROOTS (CLAUDE.md #17): argv overrides the workstation defaults so the SAME binary 1358 // runs NAS-native -- argv[1]=media/SFW root (e.g. /volume1/movies), argv[2]=owner/NSFW root (e.g. /volume1/logging), 1359 // argv[3]=the UI shell file. idx/discover are derived from the roots. The worker stays /tmp (deployed at launch). 1360 var sfw_root: *u8 = "/mnt/c/Users/elder/Downloads/nishi-media" as *u8 1361 var nsfw_root: *u8 = "/mnt/c/Users/elder/Downloads/nishi-torrents" as *u8 1362 var indexp: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/sites/nishifamily/torrent/index.html" as *u8 1363 if argc > 1 { sfw_root = argv[1] as *u8 } 1364 if argc > 2 { nsfw_root = argv[2] as *u8 } 1365 if argc > 3 { indexp = argv[3] as *u8 } 1366 let sfw_idx: *u8 = d_path(sfw_root, "/torrents.idx" as *u8, sys_mmap(700)) 1367 let nsfw_idx: *u8 = d_path(nsfw_root, "/torrents.idx" as *u8, sys_mmap(700)) 1368 let discpath: *u8 = d_path(nsfw_root, "/discover.tsv" as *u8, sys_mmap(700)) 1369 let appjsp: *u8 = indexp // the emitted shell is self-contained; /torrent/app.js serves it too (harmless) 1370 sys_mkdir(sfw_root, 0x1ff); sys_mkdir(nsfw_root, 0x1ff) // ensure both areas exist 1371 1372 let addr: *u8 = sys_mmap(16) 1373 nx_http_server_addr_any(addr, TORRENT_MAGIC_8097) // 0.0.0.0:TORRENT_MAGIC_8097 base; tightened to loopback below unless bind_mode="any" 1374 // SECURITY (s-class): behind the OPAQUE gateway the daemon TRUSTS the injected X-Nishi-Level header, so a 1375 // LAN-direct client reaching 0.0.0.0:8097 could FORGE that header and bypass auth. Default + the NAS "hub" 1376 // profile therefore bind LOOPBACK (only the loopback gateway on :18793 reaches :8097). Only an explicit 1377 // bind_mode="any" (argv[4]) keeps 0.0.0.0 -- the dev laptop needs it for WSL2 localhost-forwarding. 1378 var bind_any: i64 = 0 1379 if argc > 4 { if d_streq(argv[4] as *u8, 0, 3, "any" as *u8) == 1 { bind_any = 1 } } 1380 if bind_any == 0 { 1381 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8 1382 let bl: *u8 = "TORRENTD bind=LOOPBACK 127.0.0.1:8097 (gateway-only)\n" as *u8; sys_write(1, bl, d_strlen(bl)) 1383 } else { 1384 let ba: *u8 = "TORRENTD bind=ANY 0.0.0.0:8097 (dev/WSL2)\n" as *u8; sys_write(1, ba, d_strlen(ba)) 1385 } 1386 let vp: *i64 = sys_mmap(16) as *i64 1387 let lfd: i64 = nx_http_server_listen(addr, 16, vp) 1388 if lfd < 0 { 1389 sys_write(1, "TORRENTD bind FAILED (port 8097 in use?)\n" as *u8, 41); sys_exit(1); return 1 1390 } 1391 // FD_CLOEXEC on the listen socket: the download WORKERS are fork+execve'd from request-handling 1392 // children and would otherwise INHERIT this :8097 fd, holding the port alive after the daemon dies 1393 // and blocking every restart (the bug that wedged the redeploy). CLOEXEC closes it across execve. 1394 __syscall(72, lfd, 2, 1, 0, 0, 0) // fcntl(lfd, F_SETFD=2, FD_CLOEXEC=1) -> x86_64 nr 72 1395 // the length is DERIVED, never hand-counted: this line was written as 48 for a 49-byte literal and dropped 1396 // its own newline, gluing the next log line onto it (rule 11: a hand-counted length beside a literal drifts). 1397 let ll: *u8 = "TORRENTD listening http://127.0.0.1:8097/torrent\n" as *u8; sys_write(1, ll, d_strlen(ll)) 1398 // crash/reboot recovery: resume active downloads in BOTH areas whose worker died -- under ONE I/O budget 1399 // for the whole pass (measured once), so recovery cannot re-create the storm it is recovering from. 1400 let bud0: *i64 = sys_mmap(D_BUD_BYTES) as *i64 1401 d_admit_budget(nsfw_root, bud0) 1402 let rsm: i64 = d_autoresume(nsfw_root, nsfw_idx, bud0, 0) + d_autoresume(sfw_root, sfw_idx, bud0, 0) 1403 d_admit_log("startup" as *u8, bud0) 1404 let rb1: *u8 = sys_mmap(64); var ro1: i64 = d_puts(rb1, 0, "TORRENTD autoresume resumed=" as *u8); ro1 = d_putn(rb1, ro1, rsm); rb1[ro1] = 10 as u8; sys_write(1, rb1, ro1 + 1) 1405 1406 // ---- PERSISTENT RETRY SWEEP (rung #2: the poorly-seeded "stuck at 99%" fix) ---------------------- 1407 // A detached child re-runs the resume sweep every D_RETRY_INTERVAL_MS so a torrent whose worker gave 1408 // up (~20min of no new pieces) keeps getting re-spawned -> re-announces (re-triggering the wide 1409 // tracker scan, rung #1) -> it finishes whenever a seed with the missing tail piece appears. The sweep 1410 // REUSES d_autoresume, which already skips COMPLETE + paused + live-worker torrents, so only genuinely 1411 // stuck ones are re-spawned. SINGLE-INSTANCE via a generation file (.sweeper.gen): on a daemon restart 1412 // the new sweeper claims a fresh gen, so the old (reparented-to-init) one retires on its next wake -> 1413 // no sweeper pile-up, no tracker-ban spam. fds 3..255 closed so it never holds the :8097 listen socket. 1414 let genp: *u8 = d_path(nsfw_root, "/.sweeper.gen" as *u8, sys_mmap(700)) 1415 let swp1: i64 = sys_fork() 1416 if swp1 == 0 { 1417 let swp2: i64 = sys_fork() 1418 if swp2 == 0 { 1419 var sfdx: i64 = 3; while sfdx < 256 { sys_close(sfdx); sfdx = sfdx + 1 } 1420 let gts: *i64 = sys_mmap(16) as *i64; sys_clock_gettime_real(gts); let mygen: i64 = gts[0]*TORRENT_MAGIC_1000000000 + gts[1] 1421 let gbuf: *u8 = sys_mmap(32); let gl: i64 = d_putn(gbuf, 0, mygen); d_write_file(genp, gbuf, gl) 1422 var run: i64 = 1 1423 var sweep_no: i64 = 0 // the sweep ordinal = the registry rotation index (round-robin under a budget) 1424 let budS: *i64 = sys_mmap(D_BUD_BYTES) as *i64 1425 while run == 1 { 1426 sys_sleep_ms(D_RETRY_INTERVAL_MS) 1427 let rbuf: *u8 = sys_mmap(32); let rgn: i64 = d_read_file(genp, rbuf, 31) 1428 var cur: i64 = 0; if rgn > 0 { cur = d_int_at(rbuf, rgn, 0) } 1429 if cur != mygen { run = 0 } else { 1430 sweep_no = sweep_no + 1 1431 d_admit_budget(nsfw_root, budS) 1432 let got: i64 = d_autoresume(nsfw_root, nsfw_idx, budS, sweep_no) + d_autoresume(sfw_root, sfw_idx, budS, sweep_no) 1433 d_admit_log("retry-sweep" as *u8, budS) 1434 let lb: *u8 = sys_mmap(64); var lo: i64 = d_puts(lb, 0, "TORRENTD retry-sweep re-spawned=" as *u8); lo = d_putn(lb, lo, got); lb[lo] = 10 as u8; sys_write(1, lb, lo + 1) 1435 } 1436 } 1437 sys_exit(0) 1438 } 1439 sys_exit(0) 1440 } 1441 let swst: *i64 = sys_mmap(16) as *i64; sys_wait4(swp1, swst, 0) 1442 1443 let req: *u8 = sys_mmap(TORRENT_MAGIC_16384) 1444 let m: *i64 = sys_mmap(16) as *i64; let po: *i64 = sys_mmap(16) as *i64; let pl: *i64 = sys_mmap(16) as *i64 1445 let cl_: *i64= sys_mmap(16) as *i64; let bo: *i64 = sys_mmap(16) as *i64; let rn: *i64 = sys_mmap(16) as *i64 1446 1447 // fork-per-connection: a browser opens several sockets (page, app.js, /me, then 1448 // /status every second). A single blocking accept-loop would stall if any one client 1449 // is slow/half-open. Each connection is handled in a disposable child; the parent 1450 // just accepts + reaps. Children are bounded by a socket read timeout (no-hang law). 1451 let st: *i64 = sys_mmap(16) as *i64 1452 var serving: i64 = 1 1453 while serving == 1 { 1454 let cfd: i64 = nx_http_server_accept_one(lfd, vp) 1455 if cfd >= 0 { 1456 let pid: i64 = sys_fork() 1457 if pid == 0 { 1458 sys_set_socket_timeout(cfd, 10) 1459 let rc: i64 = nx_http_server_read_request(cfd, req, TORRENT_MAGIC_16384, m, po, pl, cl_, bo, rn) 1460 if rc == 0 { 1461 d_route(cfd, req, m[0], po[0], pl[0], cl_[0], bo[0], rn[0], sfw_root, sfw_idx, nsfw_root, nsfw_idx, indexp, appjsp, discpath) 1462 } else { 1463 d_send_str(cfd, "400 Bad Request" as *u8, "text/plain" as *u8, "bad request\n" as *u8) 1464 } 1465 sys_exit(0) 1466 } 1467 sys_close(cfd) 1468 var reap: i64 = 1 1469 while reap == 1 { let w: i64 = sys_wait4(0 - 1, st, WNOHANG); if w <= 0 { reap = 0 } } 1470 } 1471 } 1472 return 0 1473}