code wiki / _hdl_build / nx_dr_archive.nx

nx_dr_archive.nx source

↩ module page · 114 lines · 7240 B

1// nx_dr_archive.nx -- CAP-DR-ARCHIVE: sovereign disaster-recovery archiving. Copies critical NAS state to a SEPARATE 2// device (a local drive that is NOT the NAS) + verifies BYTE-IDENTITY + records a sha256 receipt -- killing the DATA 3// SPOF without a failover host. Data-driven: a manifest (source-path <TAB> archive-name) is the SSOT of what to 4// protect. Composes rep_in_sync (byte-identity proof), nx_sha256 (integrity fingerprint), slk_* (manifest parse). 5// Idempotent (each run = a fresh, verified snapshot). Additive-safe: READS the NAS, WRITES only the archive dir. 6// argv: [1]=manifest-path [2]=archive-dir (on a separate drive, e.g. /mnt/d/nishi-archive) 7// license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 10import "nx_site_lock_lib.nx" 11import "nx_sha256.nx" 12import "nx_seg_store.nx" 13const AR_MAGIC_262144: i64 = 262144 14const AR_MAGIC_1024: i64 = 1024 15const AR_MAGIC_1536: i64 = 1536 16 17const AR_CAP: i64 = 67108864 // 64 MiB/file cap (crown jewels are small; large media is a later tier) 18 19func ar_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 20// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 21// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 22// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 23// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 24func ar_wn(v: i64) -> i64 { nxi_out(v); return 0 } 25func ar_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 26 27// read a file fully; returns bytes, or -1 if it cannot be opened (missing). 28func ar_read(path: *u8, out: *u8, cap: i64) -> i64 { 29 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 - 1 } 30 var total: i64 = 0; var go: i64 = 1 31 while go == 1 { let nr: i64 = sys_read(fd, (out as i64 + total) as *u8, cap - total); if nr <= 0 { go = 0 } if nr > 0 { total = total + nr } if total >= cap { go = 0 } } 32 sys_close(fd); return total 33} 34// write buf -> path fresh (truncate). Returns bytes, or -1 on open fail. 35func ar_write(path: *u8, buf: *u8, n: i64) -> i64 { 36 let fd: i64 = sys_openat_wr(path, 0x1a4); if fd < 0 { return 0 - 1 } 37 sys_write(fd, buf, n); sys_close(fd); return n 38} 39// dir + "/" + name -> out (NUL-terminated). 40func ar_join(out: *u8, dir: *u8, name: *u8) -> i64 { 41 var o: i64=0; var i: i64=0 42 while dir[i]!=(0 as u8){out[o]=dir[i];o=o+1;i=i+1} 43 out[o]=47 as u8; o=o+1 44 i=0; while name[i]!=(0 as u8){out[o]=name[i];o=o+1;i=i+1} 45 out[o]=0 as u8; return o 46} 47func ar_field(reg: *u8, ls: i64, le: i64, f: i64, out: *u8, cap: i64) -> i64 { 48 let fs: *i64=sys_mmap(8); let fe: *i64=sys_mmap(8) 49 if slk_field(reg, ls, le, f, fs, fe) == 1 { var o: i64=0; let l: i64=fe[0]-fs[0]; while o<l { if o<cap-1 { out[o]=reg[fs[0]+o] } o=o+1 } out[o]=0 as u8; return l } 50 out[0]=0 as u8; return 0 51} 52// bytes-equal a[0..n) vs b[0..n) 53func ar_eq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 } 54 55func main(argc: i64, argv: *i64) -> i64 { 56 if argc < 3 { ar_w("usage: nx_dr_archive <manifest-path> <archive-dir>\n" as *u8); sys_exit(2); return 2 } 57 let manifest: *u8 = argv[1] as *u8 58 let adir: *u8 = argv[2] as *u8 59 ar_w("=== nx_dr_archive: sovereign DR copy NAS -> separate drive (verified) ===\n" as *u8) 60 ar_w(" archive dir: " as *u8); ar_w(adir); ar_w("\n" as *u8) 61 sys_mkdir(adir, 0x1ed) // ensure the archive dir exists (0755); harmless if already present 62 63 let reg: *u8 = sys_mmap(AR_MAGIC_262144); let rn: i64 = ar_read(manifest, reg, AR_MAGIC_262144) 64 if rn <= 0 { ar_w(" FATAL: cannot read manifest\n" as *u8); sys_exit(3); return 3 } 65 66 let src: *u8 = sys_mmap(AR_MAGIC_1024); let name: *u8 = sys_mmap(512); let dest: *u8 = sys_mmap(AR_MAGIC_1536) 67 let fbuf: *u8 = sys_mmap(AR_CAP); let vbuf: *u8 = sys_mmap(AR_CAP) 68 let dig: *u8 = sys_mmap(64); let hexb: *u8 = sys_mmap(80) 69 let hx: *u8 = "0123456789abcdef" as *u8 70 // SOVEREIGN receipt: append-only content-addressed .docs + binary-search .keys (nx_seg_store) -- NOT ad-hoc TSV 71 let rprefix: *u8 = sys_mmap(AR_MAGIC_1536); ar_join(rprefix, adir, "receipt" as *u8) 72 // ROOT FIX (count-as-segid, id 1785519700): ss_manifest RETURNS THE ROW COUNT, not an id. Past 73 // SS_MANIFEST_LEGACY_CAP (256) the count PINS at 256, so every later write reuses id 257 and OVERWRITES 74 // the previous segment -- data loss by CLOBBER (distinct from the seq1730 pointer poison, which loses 75 // data by SHADOWING). ss_next_segid parses the real ids and is what the lib header prescribes. 76 // The ss_segs[] buffer was filled and never read, so it goes with the call. 77 let ss_segid: i64 = ss_next_segid(rprefix) 78 let w: *i64 = ss_begin() 79 var ls: i64 = 0 80 var archived: i64 = 0; var skipped: i64 = 0; var failed: i64 = 0; var total_bytes: i64 = 0 81 82 while ls < rn { 83 let le: i64 = slk_line_end(reg, rn, ls) 84 if le > ls { if reg[ls] != (35 as u8) { 85 if ar_field(reg, ls, le, 0, src, AR_MAGIC_1024) > 0 { 86 ar_field(reg, ls, le, 1, name, 512) 87 ar_join(dest, adir, name) 88 let n: i64 = ar_read(src, fbuf, AR_CAP) 89 if n < 0 { ar_w(" SKIP (absent): " as *u8); ar_w(src); ar_w("\n" as *u8); skipped = skipped + 1 } 90 else { 91 let wr: i64 = ar_write(dest, fbuf, n) 92 if wr < 0 { ar_w(" FAIL (write): " as *u8); ar_w(dest); ar_w("\n" as *u8); failed = failed + 1 } 93 else { 94 let vn: i64 = ar_read(dest, vbuf, AR_CAP) 95 var ok: i64 = 0; if vn == n { if ar_eq(fbuf, vbuf, n) == 1 { ok = 1 } } 96 sha256_digest(fbuf, n, dig) 97 var h: i64 = 0; while h < 32 { let by: i64 = (dig[h] as i64) & 0xff; hexb[h*2]=hx[(by>>4)&15]; hexb[h*2+1]=hx[by&15]; h=h+1 } hexb[64]=0 as u8 98 ar_w(" OK " as *u8); ar_w(name); ar_w(" bytes="); ar_wn(n); ar_w(" sha256="); sys_write(1, hexb, 16); ar_w(".."); if ok==1 { ar_w(" VERIFIED\n" as *u8) } else { ar_w(" VERIFY-FAIL\n" as *u8) } 99 if ok == 1 { archived = archived + 1; total_bytes = total_bytes + n 100 ss_add(w, 1, name, hexb, 64) // sovereign record: key=name -> val=sha256hex (64 bytes) 101 } else { failed = failed + 1 } 102 } 103 } 104 } 105 } } 106 ls = le + 1 107 } 108 // commit the SOVEREIGN receipt -> <archive-dir>/receipt.docs + receipt.keys (crash-safe, content-addressed, binary-search; NO TSV) 109 ss_commit(rprefix, w, ss_segid) 110 111 ar_w(" ---- archived=" as *u8); ar_wn(archived); ar_w(" skipped=" as *u8); ar_wn(skipped); ar_w(" failed=" as *u8); ar_wn(failed); ar_w(" bytes=" as *u8); ar_wn(total_bytes); ar_w("\n" as *u8) 112 if failed == 0 { ar_w("=== DR ARCHIVE OK (crown jewels on a separate device, byte-verified + receipted) ===\n" as *u8); sys_exit(0); return 0 } 113 ar_w("=== DR ARCHIVE had FAILURES ===\n" as *u8); sys_exit(1); return 1 114}