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,...@@ -15,6 +15,12 @@ name: []const u8,
1515
16header: ar_hdr = undefined,16header: 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
18/// Parsed table of contents.24/// Parsed table of contents.
19/// Each symbol name points to a list of all definition25/// Each symbol name points to a list of all definition
20/// sites within the current static archive.26/// sites within the current static archive.
...@@ -53,32 +59,33 @@ const ar_hdr = extern struct {...@@ -53,32 +59,33 @@ const ar_hdr = extern struct {
53 /// Always contains ARFMAG.59 /// Always contains ARFMAG.
54 ar_fmag: [2]u8,60 ar_fmag: [2]u8,
5561
56 const NameOrLength = union(enum) {62 const NameOrIndex = union(enum) {
57 Name: []const u8,63 name: []const u8,
58 Length: u32,64 index: u32,
59 };65 };
60 fn nameOrLength(self: ar_hdr) !NameOrLength {66
61 const value = getValue(&self.ar_name);67 fn nameOrIndex(archive: ar_hdr) !NameOrIndex {
68 const value = getValue(&archive.ar_name);
62 const slash_index = mem.indexOfScalar(u8, value, '/') orelse return error.MalformedArchive;69 const slash_index = mem.indexOfScalar(u8, value, '/') orelse return error.MalformedArchive;
63 const len = value.len;70 const len = value.len;
64 if (slash_index == len - 1) {71 if (slash_index == len - 1) {
65 // Name stored directly72 // Name stored directly
66 return NameOrLength{ .Name = value };73 return NameOrIndex{ .name = value };
67 } else {74 } else {
68 // Name follows the header directly and its length is encoded in75 // Name follows the header directly and its length is encoded in
69 // the name field.76 // the name field.
70 const length = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);77 const index = try std.fmt.parseInt(u32, value[slash_index + 1 ..], 10);
71 return NameOrLength{ .Length = length };78 return NameOrIndex{ .index = index };
72 }79 }
73 }80 }
7481
75 fn date(self: ar_hdr) !u64 {82 fn date(archive: ar_hdr) !u64 {
76 const value = getValue(&self.ar_date);83 const value = getValue(&archive.ar_date);
77 return std.fmt.parseInt(u64, value, 10);84 return std.fmt.parseInt(u64, value, 10);
78 }85 }
7986
80 fn size(self: ar_hdr) !u32 {87 fn size(archive: ar_hdr) !u32 {
81 const value = getValue(&self.ar_size);88 const value = getValue(&archive.ar_size);
82 return std.fmt.parseInt(u32, value, 10);89 return std.fmt.parseInt(u32, value, 10);
83 }90 }
8491
...@@ -87,18 +94,19 @@ const ar_hdr = extern struct {...@@ -87,18 +94,19 @@ const ar_hdr = extern struct {
87 }94 }
88};95};
8996
90pub fn deinit(self: *Archive, allocator: Allocator) void {97pub fn deinit(archive: *Archive, allocator: Allocator) void {
91 for (self.toc.keys()) |*key| {98 for (archive.toc.keys()) |*key| {
92 allocator.free(key.*);99 allocator.free(key.*);
93 }100 }
94 for (self.toc.values()) |*value| {101 for (archive.toc.values()) |*value| {
95 value.deinit(allocator);102 value.deinit(allocator);
96 }103 }
97 self.toc.deinit(allocator);104 archive.toc.deinit(allocator);
105 allocator.free(archive.long_file_names);
98}106}
99107
100pub fn parse(self: *Archive, allocator: Allocator) !void {108pub fn parse(archive: *Archive, allocator: Allocator) !void {
101 const reader = self.file.reader();109 const reader = archive.file.reader();
102110
103 const magic = try reader.readBytesNoEof(SARMAG);111 const magic = try reader.readBytesNoEof(SARMAG);
104 if (!mem.eql(u8, &magic, ARMAG)) {112 if (!mem.eql(u8, &magic, ARMAG)) {
...@@ -106,38 +114,31 @@ pub fn parse(self: *Archive, allocator: Allocator) !void {...@@ -106,38 +114,31 @@ pub fn parse(self: *Archive, allocator: Allocator) !void {
106 return error.NotArchive;114 return error.NotArchive;
107 }115 }
108116
109 self.header = try reader.readStruct(ar_hdr);117 archive.header = try reader.readStruct(ar_hdr);
110 if (!mem.eql(u8, &self.header.ar_fmag, ARFMAG)) {118 if (!mem.eql(u8, &archive.header.ar_fmag, ARFMAG)) {
111 log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.ar_fmag });119 log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, archive.header.ar_fmag });
112 return error.NotArchive;120 return error.NotArchive;
113 }121 }
114122
115 try self.parseTableOfContents(allocator, reader);123 try archive.parseTableOfContents(allocator, reader);
124 try archive.parseNameTable(allocator, reader);
116}125}
117126
118fn parseName(allocator: Allocator, header: ar_hdr, reader: anytype) ![]u8 {127fn parseName(archive: *const Archive, header: ar_hdr) ![]const u8 {
119 const name_or_length = try header.nameOrLength();128 const name_or_index = try header.nameOrIndex();
120 var name: []u8 = undefined;129 switch (name_or_index) {
121 switch (name_or_length) {130 .name => |name| return name,
122 .Name => |n| {131 .index => |index| {
123 name = try allocator.dupe(u8, n);132 const name = mem.sliceTo(archive.long_file_names[index..], 0x0a);
124 },133 return mem.trimRight(u8, name, "/");
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]);
131 },134 },
132 }135 }
133 return name;
134}136}
135137
136fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !void {138fn parseTableOfContents(archive: *Archive, allocator: Allocator, reader: anytype) !void {
137 log.debug("parsing table of contents for archive file '{s}'", .{self.name});
138 // size field can have extra spaces padded in front as well as the end,139 // size field can have extra spaces padded in front as well as the end,
139 // so we trim those first before parsing the ASCII value.140 // 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, " ");
141 const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);142 const sym_tab_size = try std.fmt.parseInt(u32, size_trimmed, 10);
142143
143 const num_symbols = try reader.readIntBig(u32);144 const num_symbols = try reader.readIntBig(u32);
...@@ -157,7 +158,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !...@@ -157,7 +158,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
157158
158 var i: usize = 0;159 var i: usize = 0;
159 while (i < sym_tab.len) {160 while (i < sym_tab.len) {
160 const string = std.mem.sliceTo(sym_tab[i..], 0);161 const string = mem.sliceTo(sym_tab[i..], 0);
161 if (string.len == 0) {162 if (string.len == 0) {
162 i += 1;163 i += 1;
163 continue;164 continue;
...@@ -165,7 +166,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !...@@ -165,7 +166,7 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
165 i += string.len;166 i += string.len;
166 const name = try allocator.dupe(u8, string);167 const name = try allocator.dupe(u8, string);
167 errdefer allocator.free(name);168 errdefer allocator.free(name);
168 const gop = try self.toc.getOrPut(allocator, name);169 const gop = try archive.toc.getOrPut(allocator, name);
169 if (gop.found_existing) {170 if (gop.found_existing) {
170 allocator.free(name);171 allocator.free(name);
171 } else {172 } else {
...@@ -175,31 +176,46 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !...@@ -175,31 +176,46 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) !
175 }176 }
176}177}
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
178/// From a given file offset, starts reading for a file header.196/// From a given file offset, starts reading for a file header.
179/// When found, parses the object file into an `Object` and returns it.197/// When found, parses the object file into an `Object` and returns it.
180pub fn parseObject(self: Archive, allocator: Allocator, file_offset: u32) !Object {198pub fn parseObject(archive: Archive, allocator: Allocator, file_offset: u32) !Object {
181 try self.file.seekTo(file_offset);199 try archive.file.seekTo(file_offset);
182 const reader = self.file.reader();200 const reader = archive.file.reader();
183 const header = try reader.readStruct(ar_hdr);201 const header = try reader.readStruct(ar_hdr);
184 const current_offset = try self.file.getPos();202 const current_offset = try archive.file.getPos();
185 try self.file.seekTo(0);203 try archive.file.seekTo(0);
186204
187 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {205 if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) {
188 log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag });206 log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, header.ar_fmag });
189 return error.MalformedArchive;207 return error.MalformedArchive;
190 }208 }
191209
192 const object_name = try parseName(allocator, header, reader);210 const object_name = try archive.parseName(header);
193 defer allocator.free(object_name);
194
195 const name = name: {211 const name = name: {
196 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;212 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);
198 break :name try std.fmt.allocPrint(allocator, "{s}({s})", .{ path, object_name });214 break :name try std.fmt.allocPrint(allocator, "{s}({s})", .{ path, object_name });
199 };215 };
200 defer allocator.free(name);216 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, .{});
203 errdefer object_file.close();219 errdefer object_file.close();
204220
205 try object_file.seekTo(current_offset);221 try object_file.seekTo(current_offset);