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}