code wiki / (root) / nx_tar.nx

nx_tar.nx source

↩ module page · 241 lines · 8601 B

1// nx_tar.nx -- USTAR archive: a COMPLETE format, reader and writer. 2// 3// The tree had nx_tar_header.nx -- a header STRUCT and nothing that could 4// read or write an archive. This finishes it: build a multi-file archive, 5// walk it back, and recover every name, size and byte. Symmetric, so it is 6// provable by exact round-trip rather than against hand-picked constants. 7// 8// tar is unchanged since 1988 and is still the packaging substrate for 9// everything Unix -- and, with the existing gzip, it is the .tar.gz path. 10// 11// THE CHECKSUM'S SELF-REFERENCE. The header checksum is the sum of all 512 12// header bytes WITH THE CHECKSUM FIELD ITSELF TREATED AS EIGHT SPACES. Both 13// writer and reader must substitute spaces; a reader that sums the stored 14// digits instead rejects every valid archive, and a writer that sums zeros 15// produces archives GNU tar refuses. 16// 17// OCTAL, NOT BINARY. Every numeric field is ASCII octal, right-aligned and 18// zero-padded, terminated by NUL. Writing them as binary integers produces a 19// header that looks fine in a hex dump and is unreadable by every tar on 20// earth. 21// 22// THE TWO-BLOCK TERMINATOR. An archive ends with 1024 zero bytes. One block 23// is not enough -- readers scan for two consecutive zero blocks, and a 24// single-block ending is treated as a truncated archive. 25// 26// genealogy_id: posix_ustar_1988 27// lineage_id: nx_tar_v1 28// license_tier: ORIGINAL 29 30import "nx_syscalls.nx" 31 32const NX_TAR_BLOCK: i64 = 512 33const NX_TAR_NAME_OFF: i64 = 0 34const NX_TAR_NAME_LEN: i64 = 100 35const NX_TAR_MODE_OFF: i64 = 100 36const NX_TAR_UID_OFF: i64 = 108 37const NX_TAR_GID_OFF: i64 = 116 38const NX_TAR_SIZE_OFF: i64 = 124 39const NX_TAR_MTIME_OFF: i64 = 136 40const NX_TAR_CHK_OFF: i64 = 148 41const NX_TAR_CHK_LEN: i64 = 8 42const NX_TAR_TYPE_OFF: i64 = 156 43const NX_TAR_MAGIC_OFF: i64 = 257 44 45const NX_TAR_TYPE_FILE: i64 = 0x30 46const NX_TAR_TYPE_DIR: i64 = 0x35 47 48// parsed field slots 49const NX_TAR_FLD_SIZE: i64 = 0 50const NX_TAR_FLD_MODE: i64 = 1 51const NX_TAR_FLD_MTIME: i64 = 2 52const NX_TAR_FLD_TYPE: i64 = 3 53const NX_TAR_FLD_DATAOFF: i64 = 4 54const NX_TAR_FLD_NEXT: i64 = 5 55const NX_TAR_FLD_NAMELEN: i64 = 6 56 57// ===== octal fields =============================================== 58// 59// width includes the terminating NUL, so a 12-byte size field carries 11 60// octal digits. Values that do not fit are REFUSED rather than truncated -- 61// a silently truncated size produces an archive that unpacks the wrong 62// number of bytes and blames the next header. 63 64func nxtar_octal_w(out: *u8, off: i64, width: i64, v: i64) -> i64 { 65 if v < 0 { return 0 } 66 let digits: i64 = width - 1 67 var x: i64 = v 68 var i: i64 = digits - 1 69 while i >= 0 { 70 out[off + i] = ((x % 8) + 0x30) as u8 71 x = x / 8 72 i = i - 1 73 } 74 if x != 0 { return 0 } 75 out[off + digits] = 0 as u8 76 return 1 77} 78 79func nxtar_octal_r(b: *u8, off: i64, width: i64) -> i64 { 80 var v: i64 = 0 81 var i: i64 = 0 82 while i < width { 83 let c: i64 = (b[off + i] as i64) & 255 84 if c == 0 { i = width } else { 85 if c == 0x20 { i = width } else { 86 if c < 0x30 { return 0 - 1 } 87 if c > 0x37 { return 0 - 1 } 88 v = v * 8 + (c - 0x30) 89 i = i + 1 90 } } 91 } 92 return v 93} 94 95// ===== the self-referential checksum ============================== 96// 97// Sums all 512 bytes with the checksum field read as eight spaces. 98 99func nxtar_checksum(h: *u8) -> i64 { 100 var sum: i64 = 0 101 var i: i64 = 0 102 while i < NX_TAR_BLOCK { 103 if i >= NX_TAR_CHK_OFF { 104 if i < NX_TAR_CHK_OFF + NX_TAR_CHK_LEN { 105 sum = sum + 0x20 106 } else { 107 sum = sum + ((h[i] as i64) & 255) 108 } 109 } else { 110 sum = sum + ((h[i] as i64) & 255) 111 } 112 i = i + 1 113 } 114 return sum 115} 116 117func nxtar_strlen(s: *u8) -> i64 { 118 var i: i64 = 0 119 while s[i] != (0 as u8) { i = i + 1 } 120 return i 121} 122 123// ===== write one header =========================================== 124 125func nxtar_header_build(name: *u8, size: i64, mode: i64, mtime: i64, 126 typeflag: i64, out: *u8) -> i64 { 127 let nlen: i64 = nxtar_strlen(name) 128 if nlen <= 0 { return 0 } 129 if nlen >= NX_TAR_NAME_LEN { return 0 } 130 if size < 0 { return 0 } 131 132 var i: i64 = 0 133 while i < NX_TAR_BLOCK { out[i] = 0 as u8; i = i + 1 } 134 135 i = 0 136 while i < nlen { out[NX_TAR_NAME_OFF + i] = name[i]; i = i + 1 } 137 138 if nxtar_octal_w(out, NX_TAR_MODE_OFF, 8, mode) == 0 { return 0 } 139 if nxtar_octal_w(out, NX_TAR_UID_OFF, 8, 0) == 0 { return 0 } 140 if nxtar_octal_w(out, NX_TAR_GID_OFF, 8, 0) == 0 { return 0 } 141 if nxtar_octal_w(out, NX_TAR_SIZE_OFF, 12, size) == 0 { return 0 } 142 if nxtar_octal_w(out, NX_TAR_MTIME_OFF, 12, mtime) == 0 { return 0 } 143 out[NX_TAR_TYPE_OFF] = (typeflag & 255) as u8 144 145 // "ustar\0" then "00" 146 out[NX_TAR_MAGIC_OFF] = 0x75 as u8 147 out[NX_TAR_MAGIC_OFF+1] = 0x73 as u8 148 out[NX_TAR_MAGIC_OFF+2] = 0x74 as u8 149 out[NX_TAR_MAGIC_OFF+3] = 0x61 as u8 150 out[NX_TAR_MAGIC_OFF+4] = 0x72 as u8 151 out[NX_TAR_MAGIC_OFF+5] = 0 as u8 152 out[NX_TAR_MAGIC_OFF+6] = 0x30 as u8 153 out[NX_TAR_MAGIC_OFF+7] = 0x30 as u8 154 155 // checksum last: six octal digits, NUL, space 156 let sum: i64 = nxtar_checksum(out) 157 nxtar_octal_w(out, NX_TAR_CHK_OFF, 7, sum) 158 out[NX_TAR_CHK_OFF + 7] = 0x20 as u8 159 return 1 160} 161 162// ===== append a file ============================================== 163// 164// Writes header + data + zero padding to the next 512 boundary. 165// Returns the new offset, or 0 on refusal. 166 167func nxtar_append(out: *u8, cap: i64, off: i64, name: *u8, 168 data: *u8, len: i64, mode: i64, mtime: i64) -> i64 { 169 let padded: i64 = ((len + NX_TAR_BLOCK - 1) / NX_TAR_BLOCK) * NX_TAR_BLOCK 170 if off + NX_TAR_BLOCK + padded > cap { return 0 } 171 if nxtar_header_build(name, len, mode, mtime, NX_TAR_TYPE_FILE, out + off) == 0 { return 0 } 172 var p: i64 = off + NX_TAR_BLOCK 173 var i: i64 = 0 174 while i < len { out[p + i] = data[i]; i = i + 1 } 175 while i < padded { out[p + i] = 0 as u8; i = i + 1 } 176 return p + padded 177} 178 179// ===== finalise =================================================== 180// 181// Two zero blocks. One is not enough -- readers scan for two. 182 183func nxtar_finalize(out: *u8, cap: i64, off: i64) -> i64 { 184 if off + NX_TAR_BLOCK * 2 > cap { return 0 } 185 var i: i64 = 0 186 while i < NX_TAR_BLOCK * 2 { out[off + i] = 0 as u8; i = i + 1 } 187 return off + NX_TAR_BLOCK * 2 188} 189 190// ===== read one entry ============================================= 191// 192// Validates the ustar magic and the checksum before reporting anything. 193// Returns 1 with fld filled, 0 at end-of-archive, -1 on a malformed header. 194 195func nxtar_read(data: *u8, n: i64, off: i64, fld: *i64) -> i64 { 196 if off + NX_TAR_BLOCK > n { return 0 - 1 } 197 198 // an all-zero block is the terminator 199 var allzero: i64 = 1 200 var i: i64 = 0 201 while i < NX_TAR_BLOCK { 202 if (data[off + i] as i64 & 255) != 0 { allzero = 0; i = NX_TAR_BLOCK } 203 i = i + 1 204 } 205 if allzero == 1 { return 0 } 206 207 // ustar magic 208 if (data[off + NX_TAR_MAGIC_OFF] as i64 & 255) != 0x75 { return 0 - 1 } 209 if (data[off + NX_TAR_MAGIC_OFF+1] as i64 & 255) != 0x73 { return 0 - 1 } 210 if (data[off + NX_TAR_MAGIC_OFF+2] as i64 & 255) != 0x74 { return 0 - 1 } 211 if (data[off + NX_TAR_MAGIC_OFF+3] as i64 & 255) != 0x61 { return 0 - 1 } 212 if (data[off + NX_TAR_MAGIC_OFF+4] as i64 & 255) != 0x72 { return 0 - 1 } 213 214 let stored: i64 = nxtar_octal_r(data, off + NX_TAR_CHK_OFF, NX_TAR_CHK_LEN) 215 if stored < 0 { return 0 - 1 } 216 let calc: i64 = nxtar_checksum(data + off) 217 if calc != stored { return 0 - 1 } 218 219 let size: i64 = nxtar_octal_r(data, off + NX_TAR_SIZE_OFF, 12) 220 if size < 0 { return 0 - 1 } 221 let padded: i64 = ((size + NX_TAR_BLOCK - 1) / NX_TAR_BLOCK) * NX_TAR_BLOCK 222 if off + NX_TAR_BLOCK + padded > n { return 0 - 1 } 223 224 var nlen: i64 = 0 225 var go: i64 = 1 226 while go == 1 { 227 if nlen >= NX_TAR_NAME_LEN { go = 0 } else { 228 if (data[off + nlen] as i64 & 255) == 0 { go = 0 } else { 229 nlen = nlen + 1 230 } } 231 } 232 233 fld[NX_TAR_FLD_SIZE] = size 234 fld[NX_TAR_FLD_MODE] = nxtar_octal_r(data, off + NX_TAR_MODE_OFF, 8) 235 fld[NX_TAR_FLD_MTIME] = nxtar_octal_r(data, off + NX_TAR_MTIME_OFF, 12) 236 fld[NX_TAR_FLD_TYPE] = (data[off + NX_TAR_TYPE_OFF] as i64) & 255 237 fld[NX_TAR_FLD_DATAOFF] = off + NX_TAR_BLOCK 238 fld[NX_TAR_FLD_NEXT] = off + NX_TAR_BLOCK + padded 239 fld[NX_TAR_FLD_NAMELEN] = nlen 240 return 1 241}