authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-08-19 19:26:44+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-08-20 14:50:08+02:00
logaca911ca18d43fddcc3d36f44d3750d07b53fb56
treea5b7eca81ae4e2a2b91ea3c3df0e797ce03c93a4
parente5e6eb983159df0a089e7d1c8efcea9006e253a9
signaturelock-open Commit is signed but in an unrecognized format.

wasm/archive: correctly parse long file names

Wasm archive files are encoded the same way as GNU. This means that the header notates the character index within the long file name list rather than the length of the name. The entire name is then delimited by an LF character (0x0a). This also makes a cosmetic update to remove the `self` name, and rather label it as `archive` instead.

1 files changed, 67 insertions(+), 51 deletions(-)

src/link/Wasm/Archive.zig+67-51
......@@ -15,6 +15,12 @@ name: []const u8,
1515
1616header: ar_hdr = undefined,
1717
18/// A list of long file names, delimited by a LF character (0x0a).
19/// This is stored as a single slice of bytes, as the header-names
20/// point to the character index of a file name, rather than the index
21/// in the list.
22long_file_names: []const u8 = undefined,
23
1824/// Parsed table of contents.
1925/// Each symbol name points to a list of all definition
2026/// sites within the current static archive.
......@@ -53,32 +59,33 @@ const ar_hdr = extern struct {
5359 /// Always contains ARFMAG.
5460 ar_fmag: [2]u8,
5561
56 const NameOrLength = union(enum) {
57 Name: []const u8,
58 Length: u32,
62 const NameOrIndex = union(enum) {
63 name: []const u8,
64 index: u32,
5965 };
60 fn nameOrLength(self: ar_hdr) !NameOrLength {
61 const value = getValue(&self.ar_name);
66
67 fn nameOrIndex(archive: ar_hdr) !NameOrIndex {
68 const value = getValue(&archive.ar_name);
6269 const slash_index = mem.indexOfScalar(u8, value, '/') orelse return error.MalformedArchive;
6370 const len = value.len;
6471 if (slash_index == len - 1) {
6572 // Name stored directly
66 return NameOrLength{ .Name = value };
73 return NameOrIndex{ .name = value };
6774 } else {
6875 // Name follows the header directly and its length is encoded in
6976 // the name field.
70 const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
71 return NameOrLength{ .Length = length };
77 const index = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
78 return NameOrIndex{ .index = index };
7279 }
7380 }
7481
75 fn date(self: ar_hdr) !u64 {
76 const value = getValue(&self.ar_date);
82 fn date(archive: ar_hdr) !u64 {
83 const value = getValue(&archive.ar_date);
7784 return std.fmt.parseInt(u64, value, 10);
7885 }
7986
80 fn size(self: ar_hdr) !u32 {
81 const value = getValue(&self.ar_size);
87 fn size(archive: ar_hdr) !u32 {
88 const value = getValue(&archive.ar_size);
8289 return std.fmt.parseInt(u32, value, 10);
8390 }
8491
......@@ -87,18 +94,19 @@ const ar_hdr = extern struct {
8794 }
8895};
8996
90pub fn deinit(self: *Archive, allocator: Allocator) void {
91 for (self.toc.keys()) |*key| {
97pub fn deinit(archive: *Archive, allocator: Allocator) void {
98 for (archive.toc.keys()) |*key| {
9299 allocator.free(key.*);
93100 }
94 for (self.toc.values()) |*value| {
101 for (archive.toc.values()) |*value| {
95102 value.deinit(allocator);
96103 }
97 self.toc.deinit(allocator);
104 archive.toc.deinit(allocator);
105 allocator.free(archive.long_file_names);
98106}
99107
100pub fn parse(self: *Archive, allocator: Allocator) !void {
101 const reader = self.file.reader();
108pub fn parse(archive: *Archive, allocator: Allocator) !void {
109 const reader = archive.file.reader();
102110
103111 const magic = try reader.readBytesNoEof(SARMAG);
104112 if (!mem.eql(u8, &magic, ARMAG)) {
......@@ -106,38 +114,31 @@ pub fn parse(self: *Archive, allocator: Allocator) !void {
106114 return error.NotArchive;
107115 }
108116
109 self.header = try reader.readStruct(ar_hdr);
110 if (!mem.eql(u8, &self.header.ar_fmag, ARFMAG)) {
111 log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.ar_fmag });
117 archive.header = try reader.readStruct(ar_hdr);
118 if (!mem.eql(u8, &archive.header.ar_fmag, ARFMAG)) {
119 log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, archive.header.ar_fmag });
112120 return error.NotArchive;
113121 }
114122
115 try self.parseTableOfContents(allocator, reader);
123 try archive.parseTableOfContents(allocator, reader);
124 try archive.parseNameTable(allocator, reader);
116125}
117126
118fn parseName(allocator: Allocator, header: ar_hdr, reader: anytype) ![]u8 {
119 const name_or_length = try header.nameOrLength();
120 var name: []u8 = undefined;
121 switch (name_or_length) {
122 .Name => |n| {
123 name = try allocator.dupe(u8, n);
124 },
125 .Length => |len| {
126 var n = try allocator.alloc(u8, len);
127 defer allocator.free(n);
128 try reader.readNoEof(n);
129 const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len;
130 name = try allocator.dupe(u8, n[0..actual_len]);
127fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {
128 const name_or_index = try header.nameOrIndex();
129 switch (name_or_index) {
130 .name => |name| return name,
131 .index => |index| {
132 const name = mem.sliceTo(archive.long_file_names[index..], 0x0a);
133 return mem.trimRight(u8, name, "/");
131134 },
132135 }
133 return name;
134136}
135137
136fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void {
137 log.debug("parsing table of contents for archive file '{s}'", .{self.name});
138fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype) !void {
138139 // size field can have extra spaces padded in front as well as the end,
139140 // so we trim those first before parsing the ASCII value.
140 const size_trimmed = std.mem.trim(u8, &self.header.ar_size, " ");
141 const size_trimmed = mem.trim(u8, &archive.header.ar_size, " ");
141142 const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);
142143
143144 const num_symbols = try reader.readIntBig(u32);
......@@ -157,7 +158,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
157158
158159 var i: usize = 0;
159160 while (i < sym_tab.len) {
160 const string = std.mem.sliceTo(sym_tab[i..], 0);
161 const string = mem.sliceTo(sym_tab[i..], 0);
161162 if (string.len == 0) {
162163 i += 1;
163164 continue;
......@@ -165,7 +166,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
165166 i += string.len;
166167 const name = try allocator.dupe(u8, string);
167168 errdefer allocator.free(name);
168 const gop = try self.toc.getOrPut(allocator, name);
169 const gop = try archive.toc.getOrPut(allocator, name);
169170 if (gop.found_existing) {
170171 allocator.free(name);
171172 } else {
......@@ -175,31 +176,46 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
175176 }
176177}
177178
179fn parseNameTable(archive: *Archive, allocator: Allocator, reader: anytype) !void {
180 const header: ar_hdr = try reader.readStruct(ar_hdr);
181 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
182 log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag });
183 return error.MalformedArchive;
184 }
185 if (!mem.eql(u8, header.ar_name[0..2], "//")) {
186 log.err("invalid archive. Long name table missing", .{});
187 return error.MalformedArchive;
188 }
189 const table_size = try header.size();
190 const long_file_names = try allocator.alloc(u8, table_size);
191 errdefer allocator.free(long_file_names);
192 try reader.readNoEof(long_file_names);
193 archive.long_file_names = long_file_names;
194}
195
178196/// From a given file offset, starts reading for a file header.
179197/// When found, parses the object file into an `Object` and returns it.
180pub fn parseObject(self: Archive, allocator: Allocator, file_offset: u32) !Object {
181 try self.file.seekTo(file_offset);
182 const reader = self.file.reader();
198pub fn parseObject(archive: Archive, allocator: Allocator, file_offset: u32) !Object {
199 try archive.file.seekTo(file_offset);
200 const reader = archive.file.reader();
183201 const header = try reader.readStruct(ar_hdr);
184 const current_offset = try self.file.getPos();
185 try self.file.seekTo(0);
202 const current_offset = try archive.file.getPos();
203 try archive.file.seekTo(0);
186204
187205 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
188206 log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag });
189207 return error.MalformedArchive;
190208 }
191209
192 const object_name = try parseName(allocator, header, reader);
193 defer allocator.free(object_name);
194
210 const object_name = try archive.parseName(header);
195211 const name = name: {
196212 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
197 const path = try std.os.realpath(self.name, &buffer);
213 const path = try std.os.realpath(archive.name, &buffer);
198214 break :name try std.fmt.allocPrint(allocator, "{s}({s})", .{ path, object_name });
199215 };
200216 defer allocator.free(name);
201217
202 const object_file = try std.fs.cwd().openFile(self.name, .{});
218 const object_file = try std.fs.cwd().openFile(archive.name, .{});
203219 errdefer object_file.close();
204220
205221 try object_file.seekTo(current_offset);