code wiki / (root) / nx_untar.nx

nx_untar.nx source

↩ module page · 187 lines · 9902 B

1// nx_untar.nx -- extract files from a USTAR archive (.tar or .tar.gz) into a directory INSIDE the sovereign 2// write boundary. The missing verb between nx_research_fetch (which mirrors a tarball into 3// knowledge/fetched/ with a pinned sha) and every organ that needs one of the files inside it. 4// 5// nx_untar <archive.tar|archive.tar.gz> <outdir> [name-substring] 6// 7// Composes the incumbents, never re-implements them: nx_inflate (inf_gunzip) for the .gz layer and 8// nx_tar (nxtar_read) for the entries. The decompressed size is DERIVED from the gzip ISIZE trailer -- 9// the format's own declaration -- so there is no reserve to guess and no cap to raise. Entries are 10// written FLAT (basename only, 0644) into <outdir>, which must begin with knowledge/ or /tmp/ (the same 11// boundary nx_mkdirp enforces); anything else is refused before a byte is written. A substring filter 12// selects entries by name; with none, every regular file is written. 13// Every written file is announced with its byte count so absence is visible in normal output, and the 14// summary partition (entries = files + dirs + other) is printed and must sum. Exit: 0 written>0 | 15// 2 usage/boundary | 3 archive unreadable | 4 nothing matched (EMPTY is not a pass). 16// First use 2026-09-02: landing LiberationSans-Regular.ttf on the NAS for the browser face (BR26). 17// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 18import "nx_syscalls.nx" 19import "nx_inflate.nx" 20import "nx_tar.nx" 21 22const UT_MODE_644: i64 = 420 23const UT_MODE_DIR: i64 = 493 24const UT_GZ_TRAILER: i64 = 8 // gzip: CRC32 + ISIZE, little-endian, at the very end 25const UT_PATH_CAP: i64 = 4096 26const UT_FLD_SLOTS: i64 = 8 27const UT_EXIT_USAGE: i64 = 2 28const UT_EXIT_UNREADABLE: i64 = 3 29const UT_EXIT_EMPTY: i64 = 4 30 31func ut_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 32func ut_e(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(2, s, n); return 0 } 33func ut_num(v: i64) -> i64 { 34 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 35 var m: i64 = v 36 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 37 let t: *u8 = sys_mmap(32) 38 var k: i64 = 0 39 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 40 let o: *u8 = sys_mmap(32) 41 var i: i64 = 0 42 while i < k { o[i] = t[k-1-i]; i = i + 1 } 43 sys_write(1, o, k) 44 return 0 45} 46func ut_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 47func ut_starts(s: *u8, p: *u8) -> i64 { 48 var i: i64 = 0 49 while p[i] != (0 as u8) { if s[i] != p[i] { return 0 } i = i + 1 } 50 return 1 51} 52func ut_ends_gz(s: *u8) -> i64 { 53 let n: i64 = ut_slen(s) 54 if n < 3 { return 0 } 55 if (s[n-3] & 0xff) != 46 { return 0 } 56 if (s[n-2] & 0xff) != 103 { return 0 } 57 if (s[n-1] & 0xff) != 122 { return 0 } 58 return 1 59} 60// does the entry name (data+off, len) contain the substring? 61func ut_contains(d: *u8, off: i64, len: i64, sub: *u8) -> i64 { 62 let sl: i64 = ut_slen(sub) 63 if sl == 0 { return 1 } 64 var i: i64 = 0 65 while i + sl <= len { 66 var k: i64 = 0 67 var ok: i64 = 1 68 while k < sl { if (d[off+i+k] & 0xff) != (sub[k] & 0xff) { ok = 0; k = sl } else { k = k + 1 } } 69 if ok == 1 { return 1 } 70 i = i + 1 71 } 72 return 0 73} 74// little-endian u32 (the gzip trailer) 75func ut_rd32le(b: *u8, o: i64) -> i64 { return (b[o] as i64) + (b[o+1] as i64)*256 + (b[o+2] as i64)*65536 + (b[o+3] as i64)*16777216 } 76 77func main(argc: i64, argv: *i64) -> i64 { 78 if argc < 3 { 79 ut_e("usage: nx_untar <archive.tar|archive.tar.gz> <outdir under knowledge/ or /tmp/> [name-substring]\n\x00" as *u8) 80 return UT_EXIT_USAGE 81 } 82 let apath: *u8 = argv[1] as *u8 83 let outdir: *u8 = argv[2] as *u8 84 var sub: *u8 = "\x00" as *u8 85 if argc >= 4 { sub = argv[3] as *u8 } 86 var inside: i64 = 0 87 if ut_starts(outdir, "knowledge/\x00" as *u8) == 1 { inside = 1 } 88 if ut_starts(outdir, "/tmp/\x00" as *u8) == 1 { inside = 1 } 89 if inside == 0 { 90 ut_e("nx_untar: REFUSED outdir outside the sovereign write boundary (knowledge/ or /tmp/): \x00" as *u8); ut_e(outdir); ut_e("\n\x00" as *u8) 91 return UT_EXIT_USAGE 92 } 93 let lp: *i64 = sys_mmap(16) as *i64 94 lp[0] = 0 - 1 95 let raw: *u8 = sys_read_file(apath, lp) 96 let rawlen: i64 = lp[0] 97 if rawlen <= 0 { ut_e("nx_untar: cannot read \x00" as *u8); ut_e(apath); ut_e("\n\x00" as *u8); return UT_EXIT_UNREADABLE } 98 var data: *u8 = raw 99 var n: i64 = rawlen 100 // DETECT BY MAGIC, NEVER BY NAME (learned on first use 2026-09-02): the sovereign fetcher had mirrored a 101 // GitHub tarball under its .tar.gz name after its transport decoder inflated the gzip payload, so the 102 // file on disk was a plain ustar archive -- a name-based test asked the gzip layer for an ISIZE that 103 // was really the last 4 bytes of a tar block and refused a perfectly readable archive. A .gz name with 104 // no 1f 8b magic is announced and read as tar. 105 var isgz: i64 = 0 106 if rawlen >= 2 { if (raw[0] & 0xff) == 31 { if (raw[1] & 0xff) == 139 { isgz = 1 } } } 107 if isgz == 0 { if ut_ends_gz(apath) == 1 { ut_w("nx_untar: name ends in .gz but the bytes carry no gzip magic -- reading as a plain ustar archive\n\x00" as *u8) } } 108 if isgz == 1 { 109 if rawlen < UT_GZ_TRAILER { ut_e("nx_untar: gzip too short for a trailer\n\x00" as *u8); return UT_EXIT_UNREADABLE } 110 // DERIVED, not guessed: the gzip ISIZE trailer declares the decompressed length (mod 2^32; an 111 // archive that large is not this organ's subject and reads as a short buffer, refused below) 112 let isize: i64 = ut_rd32le(raw, rawlen - 4) 113 if isize <= 0 { ut_e("nx_untar: gzip ISIZE trailer declares 0 bytes\n\x00" as *u8); return UT_EXIT_UNREADABLE } 114 let out: *u8 = sys_mmap(isize + 16) 115 let got: i64 = inf_gunzip(raw, rawlen, out, isize + 16) 116 if got <= 0 { ut_e("nx_untar: gunzip failed rc=\x00" as *u8); ut_num(got); ut_e("\n\x00" as *u8); return UT_EXIT_UNREADABLE } 117 if got != isize { ut_e("nx_untar: gunzip produced a length the ISIZE trailer did not declare (truncated or corrupt)\n\x00" as *u8); return UT_EXIT_UNREADABLE } 118 data = out 119 n = got 120 ut_w("gunzip: \x00" as *u8); ut_num(rawlen); ut_w(" -> \x00" as *u8); ut_num(n); ut_w(" bytes (ISIZE-derived)\n\x00" as *u8) 121 } 122 sys_mkdir(outdir, UT_MODE_DIR) // EEXIST is fine: the directory is the precondition, not the product 123 let fld: *i64 = sys_mmap(8*UT_FLD_SLOTS) as *i64 124 let path: *u8 = sys_mmap(UT_PATH_CAP) 125 var off: i64 = 0 126 var entries: i64 = 0 127 var files: i64 = 0 128 var dirs: i64 = 0 129 var other: i64 = 0 130 var matched: i64 = 0 131 var written: i64 = 0 132 var go: i64 = 1 133 while go == 1 { 134 let r: i64 = nxtar_read(data, n, off, fld) 135 if r <= 0 { 136 if r < 0 { ut_e("nx_untar: archive unreadable at offset \x00" as *u8); ut_num(off); ut_e(" (bad header or checksum)\n\x00" as *u8); return UT_EXIT_UNREADABLE } 137 go = 0 138 } else { 139 entries = entries + 1 140 let ty: i64 = fld[NX_TAR_FLD_TYPE] 141 let nlen: i64 = fld[NX_TAR_FLD_NAMELEN] 142 let size: i64 = fld[NX_TAR_FLD_SIZE] 143 let doff: i64 = fld[NX_TAR_FLD_DATAOFF] 144 var isfile: i64 = 0 145 if ty == NX_TAR_TYPE_FILE { isfile = 1 } 146 if ty == 0 { isfile = 1 } 147 if isfile == 1 { files = files + 1 } else { if ty == NX_TAR_TYPE_DIR { dirs = dirs + 1 } else { other = other + 1 } } 148 if isfile == 1 { if ut_contains(data, off, nlen, sub) == 1 { 149 matched = matched + 1 150 // basename of the entry 151 var bs: i64 = 0 152 var q: i64 = 0 153 while q < nlen { if (data[off+q] & 0xff) == 47 { bs = q + 1 } q = q + 1 } 154 var pl: i64 = 0 155 let ol: i64 = ut_slen(outdir) 156 var c: i64 = 0 157 while c < ol { path[pl] = outdir[c]; pl = pl + 1; c = c + 1 } 158 if pl > 0 { if (path[pl-1] & 0xff) != 47 { path[pl] = 47 as u8; pl = pl + 1 } } 159 var q2: i64 = bs 160 while q2 < nlen { if pl < UT_PATH_CAP - 1 { path[pl] = data[off+q2]; pl = pl + 1 } q2 = q2 + 1 } 161 path[pl] = 0 as u8 162 let fd: i64 = sys_openat_wr(path, UT_MODE_644) 163 if fd < 0 { ut_e("nx_untar: cannot open for write \x00" as *u8); ut_e(path); ut_e("\n\x00" as *u8) } 164 else { 165 var wrote: i64 = 0 166 while wrote < size { 167 let wn: i64 = sys_write(fd, (data as i64 + doff + wrote) as *u8, size - wrote) 168 if wn <= 0 { wrote = size + 1 } else { wrote = wrote + wn } 169 } 170 sys_close(fd) 171 if wrote == size { 172 written = written + 1 173 ut_w("UNTAR \x00" as *u8); sys_write(1, (data as i64 + off) as *u8, nlen); ut_w(" bytes=\x00" as *u8); ut_num(size); ut_w(" -> \x00" as *u8); ut_w(path); ut_w("\n\x00" as *u8) 174 } else { 175 ut_e("nx_untar: SHORT WRITE on \x00" as *u8); ut_e(path); ut_e("\n\x00" as *u8) 176 } 177 } 178 } } 179 off = fld[NX_TAR_FLD_NEXT] 180 } 181 } 182 ut_w("entries=\x00" as *u8); ut_num(entries); ut_w(" files=\x00" as *u8); ut_num(files); ut_w(" dirs=\x00" as *u8); ut_num(dirs); ut_w(" other=\x00" as *u8); ut_num(other) 183 ut_w(" sum=\x00" as *u8); ut_num(files + dirs + other); ut_w(" matched=\x00" as *u8); ut_num(matched); ut_w(" written=\x00" as *u8); ut_num(written) 184 if written > 0 { ut_w(" verdict=WRITTEN\n\x00" as *u8); return 0 } 185 ut_w(" verdict=EMPTY-not-a-pass\n\x00" as *u8) 186 return UT_EXIT_EMPTY 187}