1objects: std.ArrayList(Object) = .empty,
2
3pub fn deinit(self: *Archive, allocator: Allocator) void {
4 self.objects.deinit(allocator);
5}
6
7pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {
8 const comp = macho_file.base.comp;
9 const io = comp.io;
10 const gpa = comp.gpa;
11 const diags = &comp.link_diags;
12
13 var arena = std.heap.ArenaAllocator.init(gpa);
14 defer arena.deinit();
15
16 const handle = macho_file.getFileHandle(handle_index);
17 const offset = if (fat_arch) |ar| ar.offset else 0;
18 const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size;
19
20 var pos: usize = offset + SARMAG;
21 while (true) {
22 if (pos >= end_pos) break;
23 if (!mem.isAligned(pos, 2)) pos += 1;
24
25 var hdr_buffer: [@sizeOf(ar_hdr)]u8 = undefined;
26 {
27 const amt = try handle.readPositionalAll(io, &hdr_buffer, pos);
28 if (amt != @sizeOf(ar_hdr)) return error.InputOutput;
29 }
30 const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*;
31 pos += @sizeOf(ar_hdr);
32
33 if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
34 return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{
35 std.ascii.hexEscape(ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower),
36 });
37 }
38
39 var hdr_size = try hdr.size();
40 const name = name: {
41 if (hdr.name()) |n| break :name n;
42 if (try hdr.nameLength()) |len| {
43 hdr_size -= len;
44 const buf = try arena.allocator().alloc(u8, len);
45 const amt = try handle.readPositionalAll(io, buf, pos);
46 if (amt != len) return error.InputOutput;
47 pos += len;
48 const actual_len = mem.findScalar(u8, buf, @as(u8, 0)) orelse len;
49 break :name buf[0..actual_len];
50 }
51 unreachable;
52 };
53 defer pos += hdr_size;
54
55 if (mem.eql(u8, name, SYMDEF) or
56 mem.eql(u8, name, SYMDEF64) or
57 mem.eql(u8, name, SYMDEF_SORTED) or
58 mem.eql(u8, name, SYMDEF64_SORTED)) continue;
59
60 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
61 comp.dirs.cwd,
62 path.root_dir.path orelse ".",
63 path.sub_path,
64 });
65 errdefer gpa.free(abs_path);
66
67 const o_basename = try gpa.dupe(u8, name);
68 errdefer gpa.free(o_basename);
69
70 const object: Object = .{
71 .offset = pos,
72 .in_archive = .{
73 .path = abs_path,
74 .size = hdr_size,
75 },
76 .path = o_basename,
77 .file_handle = handle_index,
78 .index = undefined,
79 .alive = false,
80 .mtime = hdr.date() catch 0,
81 };
82
83 log.debug("extracting object '{s}' from archive '{f}'", .{ o_basename, path });
84
85 try self.objects.append(gpa, object);
86 }
87}
88
89pub fn writeHeader(
90 object_name: []const u8,
91 object_size: usize,
92 writer: *Writer,
93) !void {
94 var hdr: ar_hdr = .{};
95
96 const object_name_start = writer.end + @sizeOf(ar_hdr);
97 const object_start = mem.alignForward(usize, object_name_start + object_name.len + 1, 8);
98 const object_name_len = object_start - object_name_start;
99 const total_object_size = object_size + object_name_len;
100
101 {
102 var stream: Writer = .fixed(&hdr.ar_name);
103 stream.print("#1/{d}", .{object_name_len}) catch unreachable;
104 }
105 {
106 var stream: Writer = .fixed(&hdr.ar_size);
107 stream.print("{d}", .{total_object_size}) catch unreachable;
108 }
109
110 try writer.writeAll(mem.asBytes(&hdr));
111 try writer.print("{s}\x00", .{object_name});
112
113 const padding = object_name_len - object_name.len - 1;
114 if (padding > 0) {
115 try writer.splatByteAll(0, padding);
116 }
117}
118
119// Archive files start with the ARMAG identifying string. Then follows a
120// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
121// member indicates, for each member file.
122/// String that begins an archive file.
123pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
124/// Size of that string.
125pub const SARMAG: u4 = 8;
126
127/// String in ar_fmag at the end of each header.
128const ARFMAG: *const [2:0]u8 = "`\n";
129
130pub const SYMDEF = "__.SYMDEF";
131pub const SYMDEF64 = "__.SYMDEF_64";
132pub const SYMDEF_SORTED = "__.SYMDEF SORTED";
133pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED";
134
135pub const ar_hdr = extern struct {
136 /// Member file name, sometimes / terminated.
137 ar_name: [16]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
138 /// File date, decimal seconds since Epoch.
139 ar_date: [12]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
140 /// User ID, in ASCII format.
141 ar_uid: [6]u8 = "0\x20\x20\x20\x20\x20".*,
142 /// Group ID, in ASCII format.
143 ar_gid: [6]u8 = "0\x20\x20\x20\x20\x20".*,
144 /// File mode, in ASCII octal.
145 ar_mode: [8]u8 = "0\x20\x20\x20\x20\x20\x20\x20".*,
146 /// File size, in ASCII decimal.
147 ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*,
148 /// Always contains ARFMAG.
149 ar_fmag: [2]u8 = ARFMAG.*,
150
151 fn date(self: ar_hdr) !u64 {
152 const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)});
153 return std.fmt.parseInt(u64, value, 10);
154 }
155
156 fn size(self: ar_hdr) !u32 {
157 const value = mem.trimEnd(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)});
158 return std.fmt.parseInt(u32, value, 10);
159 }
160
161 fn name(self: *const ar_hdr) ?[]const u8 {
162 const value = &self.ar_name;
163 if (mem.startsWith(u8, value, "#1/")) return null;
164 const sentinel = mem.findScalar(u8, value, '/') orelse value.len;
165 return value[0..sentinel];
166 }
167
168 fn nameLength(self: ar_hdr) !?u32 {
169 const value = &self.ar_name;
170 if (!mem.startsWith(u8, value, "#1/")) return null;
171 const trimmed = mem.trimEnd(u8, self.ar_name["#1/".len..], &[_]u8{0x20});
172 return try std.fmt.parseInt(u32, trimmed, 10);
173 }
174};
175
176pub const ArSymtab = struct {
177 entries: std.ArrayList(Entry) = .empty,
178 strtab: StringTable = .{},
179
180 pub fn deinit(ar: *ArSymtab, allocator: Allocator) void {
181 ar.entries.deinit(allocator);
182 ar.strtab.deinit(allocator);
183 }
184
185 pub fn sort(ar: *ArSymtab) void {
186 mem.sort(Entry, ar.entries.items, {}, Entry.lessThan);
187 }
188
189 pub fn size(ar: ArSymtab, format: Format) usize {
190 const ptr_width = ptrWidth(format);
191 return ptr_width + ar.entries.items.len * 2 * ptr_width + ptr_width + mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width);
192 }
193
194 pub fn write(ar: ArSymtab, format: Format, macho_file: *MachO, writer: *Writer) !void {
195 const ptr_width = ptrWidth(format);
196 // Header
197 try writeHeader(SYMDEF, ar.size(format), writer);
198 // Symtab size
199 try writeInt(format, ar.entries.items.len * 2 * ptr_width, writer);
200 // Symtab entries
201 for (ar.entries.items) |entry| {
202 const file_off = switch (macho_file.getFile(entry.file).?) {
203 .zig_object => |x| x.output_ar_state.file_off,
204 .object => |x| x.output_ar_state.file_off,
205 else => unreachable,
206 };
207 // Name offset
208 try writeInt(format, entry.off, writer);
209 // File offset
210 try writeInt(format, file_off, writer);
211 }
212 // Strtab size
213 const strtab_size = mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width);
214 const padding = strtab_size - ar.strtab.buffer.items.len;
215 try writeInt(format, strtab_size, writer);
216 // Strtab
217 try writer.writeAll(ar.strtab.buffer.items);
218 if (padding > 0) {
219 try writer.splatByteAll(0, padding);
220 }
221 }
222
223 const PrintFormat = struct {
224 ar: ArSymtab,
225 macho_file: *MachO,
226
227 fn default(f: PrintFormat, bw: *Writer) Writer.Error!void {
228 const ar = f.ar;
229 const macho_file = f.macho_file;
230 for (ar.entries.items, 0..) |entry, i| {
231 const name = ar.strtab.getAssumeExists(entry.off);
232 const file = macho_file.getFile(entry.file).?;
233 try bw.print(" {d}: {s} in file({d})({f})\n", .{ i, name, entry.file, file.fmtPath() });
234 }
235 }
236 };
237
238 pub fn fmt(ar: ArSymtab, macho_file: *MachO) std.fmt.Alt(PrintFormat, PrintFormat.default) {
239 return .{ .data = .{ .ar = ar, .macho_file = macho_file } };
240 }
241
242 const Entry = struct {
243 /// Symbol name offset
244 off: u32,
245 /// Exporting file
246 file: File.Index,
247
248 pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool {
249 _ = ctx;
250 if (lhs.off == rhs.off) return lhs.file < rhs.file;
251 return lhs.off < rhs.off;
252 }
253 };
254};
255
256pub const Format = enum {
257 p32,
258 p64,
259};
260
261pub fn ptrWidth(format: Format) usize {
262 return switch (format) {
263 .p32 => @as(usize, 4),
264 .p64 => 8,
265 };
266}
267
268pub fn writeInt(format: Format, value: u64, writer: *Writer) !void {
269 switch (format) {
270 .p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little),
271 .p64 => try writer.writeInt(u64, value, .little),
272 }
273}
274
275pub const ArState = struct {
276 /// File offset of the ar_hdr describing the contributing
277 /// object in the archive.
278 file_off: u64 = 0,
279
280 /// Total size of the contributing object (excludes ar_hdr and long name with padding).
281 size: u64 = 0,
282};
283
284const fat = @import("fat.zig");
285const link = @import("../../link.zig");
286const log = std.log.scoped(.link);
287const macho = std.macho;
288const mem = std.mem;
289const std = @import("std");
290const Allocator = std.mem.Allocator;
291const Path = std.Build.Cache.Path;
292const Writer = std.Io.Writer;
293
294const Archive = @This();
295const File = @import("file.zig").File;
296const MachO = @import("../MachO.zig");
297const Object = @import("Object.zig");
298const StringTable = @import("../StringTable.zig");