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}