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