nx_archive.nx source
↩ module page · 228 lines · 7814 B
1// nx_archive.nx -- BSD-format ar(1) static library builder.
2//
3// Replaces `ar rcs libfoo.a *.o`. Useful for shipping reusable
4// NishiLang code as a single linkable artifact.
5//
6// AR format (System V variant; also BSD-compatible):
7//
8// "!<arch>\n" 8 bytes -- magic
9// one or more entries:
10// [name (16 bytes, space-padded, ends with '/')]
11// [date (12 bytes ASCII decimal seconds-since-epoch)]
12// [uid (6 bytes ASCII decimal)]
13// [gid (6 bytes ASCII decimal)]
14// [mode (8 bytes ASCII octal)]
15// [size (10 bytes ASCII decimal)]
16// [magic (2 bytes "`\n")]
17// [data (size bytes)]
18// [pad (1 byte if size is odd -- "\n")]
19//
20// Total per entry: 60-byte header + data + optional pad. Names
21// longer than 15 chars use the "//" extended-name table (BSD/SysV
22// convention), which we DEFER to v0.0.2 -- v0.0.1 names must be
23// ≤ 15 chars + a single trailing '/'.
24//
25// Pairs with nx_elf_writer (produces objects), nx_elf_read +
26// nx_objdump (inspect), nxld (linker that consumes archives).
27
28// nx_safety_envelope:
29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
30// sil_target: SIL1
31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
32// verdict: NOT_YET_EVALUATED
33
34import "syscalls.nx"
35const NX_MAGIC_4096: i64 = 4096
36
37// NX_AR_MAGIC bytes: "!<arch>\n" -- written byte-by-byte in nx_ar_new
38// since NishiLang const slots accept only integer literals.
39const NX_AR_HEADER_BYTES: i64 = 60
40const NX_AR_NAME_BYTES: i64 = 16
41const NX_AR_DATE_BYTES: i64 = 12
42const NX_AR_UID_BYTES: i64 = 6
43const NX_AR_GID_BYTES: i64 = 6
44const NX_AR_MODE_BYTES: i64 = 8
45const NX_AR_SIZE_BYTES: i64 = 10
46
47struct NxAr {
48 buf: *u8,
49 cap: i64,
50 used: i64,
51 n: i64, // number of entries
52}
53
54const NX_AR_BYTES: i64 = 32
55
56// ---- decimal / ASCII pack helpers --------------------------------
57
58// Write `v` as ASCII decimal, left-justified, space-padded to width.
59// Returns 0 OK / -1 if value won't fit.
60func nx_ar_pad_dec(dst: *u8, off: i64, width: i64, v: i64) -> i64 {
61 let tmp: *u8 = sys_mmap(32)
62 var n: i64 = v
63 var k: i64 = 0
64 if n == 0 {
65 tmp[0] = 0x30
66 k = 1
67 }
68 while n > 0 {
69 tmp[k] = 0x30 + (n - (n / 10) * 10)
70 n = n / 10
71 k = k + 1
72 }
73 if k > width { return -1 }
74 // Write digits left-to-right (reversed from tmp).
75 var di: i64 = 0
76 while di < k {
77 dst[off + di] = tmp[k - 1 - di]
78 di = di + 1
79 }
80 // Space-pad the remainder.
81 while di < width {
82 dst[off + di] = 0x20
83 di = di + 1
84 }
85 return 0
86}
87
88// Pack `name` into the 16-byte name field, append '/', space-pad.
89func nx_ar_pad_name(dst: *u8, off: i64, name: *u8) -> i64 {
90 var i: i64 = 0
91 while i < 15 {
92 if name[i] == 0 { i = 15 }
93 if i < 15 {
94 dst[off + i] = name[i]
95 i = i + 1
96 }
97 }
98 // Compute actual length to know where to put the '/'.
99 var nlen: i64 = 0
100 while nlen < 15 {
101 if name[nlen] == 0 { nlen = 15 }
102 if nlen < 15 { nlen = nlen + 1 }
103 }
104 if nlen > 15 { return -1 }
105 dst[off + nlen] = 0x2F // '/'
106 var pi: i64 = nlen + 1
107 while pi < NX_AR_NAME_BYTES {
108 dst[off + pi] = 0x20
109 pi = pi + 1
110 }
111 return 0
112}
113
114// ---- construction -------------------------------------------------
115
116func nx_ar_new(cap: i64) -> *NxAr {
117 let raw: *u8 = sys_mmap(NX_AR_BYTES)
118 let a: *NxAr = raw as *NxAr
119 a.buf = sys_mmap(cap)
120 a.cap = cap
121 a.used = 0
122 a.n = 0
123 // Write magic "!<arch>\n" at offset 0.
124 a.buf[0] = 0x21 // '!'
125 a.buf[1] = 0x3C // '<'
126 a.buf[2] = 0x61 // 'a'
127 a.buf[3] = 0x72 // 'r'
128 a.buf[4] = 0x63 // 'c'
129 a.buf[5] = 0x68 // 'h'
130 a.buf[6] = 0x3E // '>'
131 a.buf[7] = 0x0A // '\n'
132 a.used = 8
133 return a
134}
135
136// Append a member. `name` ≤ 15 chars + NUL. Returns 0 / -1.
137func nx_ar_add(a: *NxAr, name: *u8, data: *u8, size: i64) -> i64 {
138 let entry_total: i64 = NX_AR_HEADER_BYTES + size + (size & 1)
139 if a.used + entry_total > a.cap { return -1 }
140
141 let h_off: i64 = a.used
142 if nx_ar_pad_name(a.buf, h_off + 0, name) < 0 { return -1 }
143
144 // date / uid / gid / mode / size — fill with sensible defaults
145 // (date=0 keeps F6 manifests deterministic across machines).
146 nx_ar_pad_dec(a.buf, h_off + 16, NX_AR_DATE_BYTES, 0)
147 nx_ar_pad_dec(a.buf, h_off + 28, NX_AR_UID_BYTES, 0)
148 nx_ar_pad_dec(a.buf, h_off + 34, NX_AR_GID_BYTES, 0)
149 nx_ar_pad_dec(a.buf, h_off + 40, NX_AR_MODE_BYTES, 644)
150 nx_ar_pad_dec(a.buf, h_off + 48, NX_AR_SIZE_BYTES, size)
151
152 // Trailing 2-byte magic "`\n"
153 a.buf[h_off + 58] = 0x60 // '`'
154 a.buf[h_off + 59] = 0x0A // '\n'
155
156 // Data.
157 var i: i64 = 0
158 while i < size {
159 a.buf[h_off + NX_AR_HEADER_BYTES + i] = data[i]
160 i = i + 1
161 }
162
163 // Odd-size pad newline.
164 if (size & 1) == 1 {
165 a.buf[h_off + NX_AR_HEADER_BYTES + size] = 0x0A
166 }
167
168 a.used = a.used + entry_total
169 a.n = a.n + 1
170 return 0
171}
172
173func nx_ar_size(a: *NxAr) -> i64 { return a.used }
174func nx_ar_count(a: *NxAr) -> i64 { return a.n }
175func nx_ar_bytes(a: *NxAr) -> *u8 { return a.buf }
176
177// ---- self-test ----------------------------------------------------
178
179func main() -> i64 {
180 let a: *NxAr = nx_ar_new(NX_MAGIC_4096)
181
182 // Magic at offset 0.
183 if a.buf[0] != 0x21 { return __syscall(93, 10, 0, 0, 0, 0, 0) } // '!'
184 if a.buf[7] != 0x0A { return __syscall(93, 11, 0, 0, 0, 0, 0) }
185 if nx_ar_size(a) != 8 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
186 if nx_ar_count(a) != 0 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
187
188 // Add a 4-byte member named "foo.o".
189 let foo_data: *u8 = sys_mmap(16)
190 foo_data[0] = 0x41; foo_data[1] = 0x42; foo_data[2] = 0x43; foo_data[3] = 0x44
191 let foo_name: *u8 = sys_mmap(16)
192 foo_name[0] = 0x66; foo_name[1] = 0x6F; foo_name[2] = 0x6F
193 foo_name[3] = 0x2E; foo_name[4] = 0x6F; foo_name[5] = 0
194 if nx_ar_add(a, foo_name, foo_data, 4) != 0 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
195
196 // Header at offset 8 starts with "foo.o/" then spaces.
197 if a.buf[8] != 0x66 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
198 if a.buf[9] != 0x6F { return __syscall(93, 31, 0, 0, 0, 0, 0) }
199 if a.buf[10] != 0x6F { return __syscall(93, 32, 0, 0, 0, 0, 0) }
200 if a.buf[11] != 0x2E { return __syscall(93, 33, 0, 0, 0, 0, 0) }
201 if a.buf[12] != 0x6F { return __syscall(93, 34, 0, 0, 0, 0, 0) }
202 if a.buf[13] != 0x2F { return __syscall(93, 35, 0, 0, 0, 0, 0) } // '/'
203 if a.buf[14] != 0x20 { return __syscall(93, 36, 0, 0, 0, 0, 0) } // ' '
204
205 // Trailing magic at offset 8+58.
206 if a.buf[8 + 58] != 0x60 { return __syscall(93, 40, 0, 0, 0, 0, 0) }
207 if a.buf[8 + 59] != 0x0A { return __syscall(93, 41, 0, 0, 0, 0, 0) }
208
209 // Data follows the header.
210 if a.buf[8 + 60] != 0x41 { return __syscall(93, 50, 0, 0, 0, 0, 0) } // 'A'
211 if a.buf[8 + 63] != 0x44 { return __syscall(93, 51, 0, 0, 0, 0, 0) } // 'D'
212
213 // Even-size data: no pad. Total used = 8 + 60 + 4 = 72.
214 if nx_ar_size(a) != 72 { return __syscall(93, 52, 0, 0, 0, 0, 0) }
215
216 // Add an odd-size member: 5 bytes named "bar.o" -> +1 byte pad.
217 let bar_data: *u8 = sys_mmap(16)
218 bar_data[0] = 0x57; bar_data[1] = 0x58; bar_data[2] = 0x59; bar_data[3] = 0x5A; bar_data[4] = 0x5B
219 let bar_name: *u8 = sys_mmap(16)
220 bar_name[0] = 0x62; bar_name[1] = 0x61; bar_name[2] = 0x72
221 bar_name[3] = 0x2E; bar_name[4] = 0x6F; bar_name[5] = 0
222 nx_ar_add(a, bar_name, bar_data, 5)
223 // 72 + 60 + 5 + 1 = 138.
224 if nx_ar_size(a) != 138 { return __syscall(93, 60, 0, 0, 0, 0, 0) }
225 if nx_ar_count(a) != 2 { return __syscall(93, 61, 0, 0, 0, 0, 0) }
226
227 return 0
228}