Re-added tests

Fixed compile errors from tests
This commit is contained in:
Caleb J. Gardner
2026-06-16 22:01:44 -05:00
parent 9ecab7f2a4
commit 42a92853a4
16 changed files with 253 additions and 103 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, location: []cons
); );
defer root_inode.deinit(alloc); defer root_inode.deinit(alloc);
root_inode.extract(alloc, io, self.super, self.map.memory, self.decomp, location, options); return root_inode.extract(alloc, io, self.super, self.map.memory, self.decomp, location, options);
} }
// Superblock // Superblock
+10 -4
View File
@@ -7,9 +7,9 @@ const Error = @import("decomp.zig").Error;
pub fn zlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize { pub fn zlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.z_stream = .{ var stream: c.z_stream = .{
.next_in = in.ptr, .next_in = in.ptr,
.avail_in = in.len, .avail_in = @truncate(in.len),
.next_out = out.ptr, .next_out = out.ptr,
.avail_out = out.len, .avail_out = @truncate(out.len),
}; };
var res = c.inflateInit(&stream); var res = c.inflateInit(&stream);
if (res != c.Z_OK) if (res != c.Z_OK)
@@ -30,7 +30,7 @@ pub fn lzma(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (res != c.LZMA_OK) if (res != c.LZMA_OK)
return Error.DecompressionFailed; return Error.DecompressionFailed;
while (res == c.LZMA_OK) while (res == c.LZMA_OK)
res = c.lzma_code(&stream); res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_STREAM_END) if (res != c.LZMA_STREAM_END)
return Error.DecompressionFailed; return Error.DecompressionFailed;
return stream.total_out; return stream.total_out;
@@ -40,9 +40,15 @@ pub fn lz4(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
in.ptr, in.ptr,
out.ptr, out.ptr,
@intCast(in.len), @intCast(in.len),
out.len, @intCast(out.len),
); );
if (res < 0) if (res < 0)
return Error.DecompressionFailed; return Error.DecompressionFailed;
return @abs(res); return @abs(res);
} }
pub fn zstd(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
if (c.ZSTD_isError(res) != 0)
return Error.DecompressionFailed;
return res;
}
+6 -6
View File
@@ -19,7 +19,7 @@ size: u64,
frag_data: ?[]u8 = null, frag_data: ?[]u8 = null,
frag_offset: u32 = 0, frag_offset: u32 = 0,
cache: ?Cache = null, cache: ?*Cache = null,
pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, blocks: []DataBlock, start: u64, size: u64) Extractor { pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, blocks: []DataBlock, start: u64, size: u64) Extractor {
return .{ return .{
@@ -37,7 +37,7 @@ pub fn addFrag(self: *Extractor, frag_data: []u8, frag_offset: u32) void {
self.frag_data = frag_data; self.frag_data = frag_data;
self.frag_offset = frag_offset; self.frag_offset = frag_offset;
} }
pub fn addCache(self: *Extractor, cache: Cache) void { pub fn addCache(self: *Extractor, cache: *Cache) void {
self.cache = cache; self.cache = cache;
} }
@@ -57,7 +57,7 @@ pub fn extractAsync(self: Extractor, alloc: std.mem.Allocator, io: Io, file: Io.
var read_offset: u64 = self.start; var read_offset: u64 = self.start;
for (0.., self.blocks) |i, block| { for (0.., self.blocks) |i, block| {
group.async(io, blockThread, .{ self, alloc, map.memory, read_offset, i, &err }); group.async(io, blockThread, .{ self, alloc, io, map.memory, read_offset, @truncate(i), &err });
read_offset += block.size; read_offset += block.size;
} }
if (self.frag_data != null) if (self.frag_data != null)
@@ -66,12 +66,12 @@ pub fn extractAsync(self: Extractor, alloc: std.mem.Allocator, io: Io, file: Io.
try group.await(io); try group.await(io);
if (err != null) if (err != null)
return err; return err.?;
try map.write(io); try map.write(io);
} }
fn blockThread(self: Extractor, alloc: std.mem.Allocator, map_data: []u8, read_offset: u64, block_idx: u32, err: *?Error) error{Canceled}!void { fn blockThread(self: Extractor, alloc: std.mem.Allocator, io: Io, map_data: []u8, read_offset: u64, block_idx: u32, err: *?Error) error{Canceled}!void {
const size = if (self.frag_data == null and block_idx == (self.size - 1 / self.block_size)) const size = if (self.frag_data == null and block_idx == (self.size - 1 / self.block_size))
self.size % self.block_size self.size % self.block_size
else else
@@ -93,7 +93,7 @@ fn blockThread(self: Extractor, alloc: std.mem.Allocator, map_data: []u8, read_o
} }
if (self.cache != null) { if (self.cache != null) {
const decomp_block = self.cache.?.get(read_offset, block.size) catch |inner_err| { const decomp_block = self.cache.?.get(io, read_offset, block.size) catch |inner_err| {
switch (inner_err) { switch (inner_err) {
error.Canceled => return error.Canceled, error.Canceled => return error.Canceled,
else => |e| err.* = e, else => |e| err.* = e,
+8 -6
View File
@@ -24,7 +24,8 @@ sparse_block: bool = false,
frag_data: ?[]u8 = null, frag_data: ?[]u8 = null,
frag_offset: u32 = 0, frag_offset: u32 = 0,
cache: ?Cache = null, io: ?Io = null,
cache: ?*Cache = null,
block: [1024 * 1024]u8 = undefined, block: [1024 * 1024]u8 = undefined,
@@ -58,7 +59,8 @@ pub fn addFrag(self: *Reader, frag_data: []u8, frag_offset: u32) void {
self.frag_data = frag_data; self.frag_data = frag_data;
self.frag_offset = frag_offset; self.frag_offset = frag_offset;
} }
pub fn addCache(self: *Reader, cache: Cache) void { pub fn addCache(self: *Reader, io: Io, cache: *Cache) void {
self.io = io;
self.cache = cache; self.cache = cache;
} }
@@ -92,7 +94,7 @@ fn advance(self: *Reader) Io.Reader.Error!void {
if (block.size == 0) { if (block.size == 0) {
self.sparse_block = true; self.sparse_block = true;
self.end = size; self.interface.end = size;
return; return;
} else { } else {
self.sparse_block = false; self.sparse_block = false;
@@ -109,7 +111,7 @@ fn advance(self: *Reader) Io.Reader.Error!void {
self.interface.buffer = self.block[0..size]; self.interface.buffer = self.block[0..size];
self.interface.end = size; self.interface.end = size;
} else { } else {
self.interface.buffer = self.cache.?.get(self.io, self.offset, block.size) catch return error.ReadFailed; self.interface.buffer = self.cache.?.get(self.io.?, self.offset, block.size) catch return error.ReadFailed;
self.interface.end = self.interface.buffer.len; self.interface.end = self.interface.buffer.len;
} }
} }
@@ -119,7 +121,7 @@ fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!u
if (r.seek >= r.end) if (r.seek >= r.end)
try self.advance(); try self.advance();
if (limit == .nothing) return; if (limit == .nothing) return 0;
const to_write = @min(@intFromEnum(limit), r.end - r.seek); const to_write = @min(@intFromEnum(limit), r.end - r.seek);
@@ -136,7 +138,7 @@ fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
const self: *Reader = @fieldParentPtr("interface", r); const self: *Reader = @fieldParentPtr("interface", r);
try self.advance(); try self.advance();
} }
if (limit == .nothing) return; if (limit == .nothing) return 0;
const to_discard = @min(@intFromEnum(limit), r.end - r.seek); const to_discard = @min(@intFromEnum(limit), r.end - r.seek);
+3 -3
View File
@@ -6,10 +6,10 @@ const zig = @import("zig_decomp.zig");
pub fn getFn(e: Enum) !Fn { pub fn getFn(e: Enum) !Fn {
return switch (e) { return switch (e) {
.gzip => if (build.use_zig_decomp) zig.gzip else c.gzip, .gzip => if (build.use_zig_decomp) zig.zlib else c.zlib,
.lzma => if (build.use_zig_decomp) zig.lzma else c.lzma, .lzma => if (build.use_zig_decomp) zig.lzma else c.lzma,
.lzo => if (build.use_zig_decomp or !build.allow_lzo) error.LzoUnsupported else c.lzo, .lzo => if (build.use_zig_decomp or !build.allow_lzo) error.LzoUnsupported else c.lzo,
.xz => if (build.use_zig_decomp) zig.xz else c.xz, .xz => if (build.use_zig_decomp) zig.xz else c.lzma,
.lz4 => if (build.use_zig_decomp) error.Lz4Unsupported else c.lz4, .lz4 => if (build.use_zig_decomp) error.Lz4Unsupported else c.lz4,
.zstd => if (build.use_zig_decomp) zig.zstd else c.zstd, .zstd => if (build.use_zig_decomp) zig.zstd else c.zstd,
}; };
@@ -17,7 +17,7 @@ pub fn getFn(e: Enum) !Fn {
// Types // Types
pub const Fn = *fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize; pub const Fn = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize;
pub const Enum = enum(u16) { pub const Enum = enum(u16) {
gzip = 1, gzip = 1,
+5 -5
View File
@@ -15,7 +15,7 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u64) !Directory {
var read: u64 = 3; var read: u64 = 3;
var out: std.ArrayList(Entry) = .initCapacity(alloc, 50); var out: std.ArrayList(Entry) = try .initCapacity(alloc, 50);
errdefer { errdefer {
for (out.items) |entry| for (out.items) |entry|
entry.deinit(alloc); entry.deinit(alloc);
@@ -29,13 +29,13 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u64) !Directory {
try out.ensureUnusedCapacity(alloc, hdr.count + 1); try out.ensureUnusedCapacity(alloc, hdr.count + 1);
for (0..hdr.count + 1) |_| { for (0..hdr.count + 1) |_| {
try rdr.readSliceEndian(Entry, @ptrCast(&raw), .little); try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
const name = try alloc.alloc(u8, raw.name_size + 1); const name = try alloc.alloc(u8, raw.name_size + 1);
try rdr.readSliceEndian(u8, name, .little); try rdr.readSliceEndian(u8, name, .little);
const entry = out.addOneAssumeCapacity(alloc); const entry = out.addOneAssumeCapacity();
entry.* = .{ entry.* = .{
.name = name, .name = name,
@@ -48,7 +48,7 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u64) !Directory {
} }
} }
return out.toOwnedSlice(alloc); return .{ .entries = try out.toOwnedSlice(alloc) };
} }
pub fn deinit(self: Directory, alloc: std.mem.Allocator) void { pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
for (self.entries) |entry| for (self.entries) |entry|
@@ -64,7 +64,7 @@ pub const Entry = struct {
name: []const u8, name: []const u8,
block_start: u32, block_start: u32,
block_offset: u16, block_offset: u16,
type: Inode.type, type: Inode.Type,
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void { pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
alloc.free(self.name); alloc.free(self.name);
+66 -27
View File
@@ -22,7 +22,7 @@ pub fn extract(
ext_loc: []const u8, ext_loc: []const u8,
options: ExtractionOptions, options: ExtractionOptions,
) !void { ) !void {
const path = std.mem.trim(ext_loc, "/"); const path = std.mem.trim(u8, ext_loc, "/");
var id_table: Lookup.Table(u16) = .init(alloc, data, decomp, super.id_start, super.id_count); var id_table: Lookup.Table(u16) = .init(alloc, data, decomp, super.id_start, super.id_count);
defer id_table.deinit(); defer id_table.deinit();
@@ -42,7 +42,7 @@ pub fn extract(
else => {}, else => {},
}; };
var loop = io.async(io, finishLoop, .{ alloc, io, &sel, &id_table, &xattr_table, inode.hdr.num, options }); var loop = io.async(finishLoop, .{ alloc, io, &sel, &id_table, &xattr_table, inode.hdr.num, options });
var cache: Cache = .init(alloc, data, decomp); var cache: Cache = .init(alloc, data, decomp);
defer cache.deinit(); defer cache.deinit();
@@ -51,22 +51,22 @@ pub fn extract(
defer frag_table.deinit(); defer frag_table.deinit();
switch (inode.hdr.type) { switch (inode.hdr.type) {
.file, .ext_file => try sel.async( .file, .ext_file => sel.async(
.path, .path,
extractFile, extractFile,
.{ alloc, io, data, decomp, super.block_size, &cache, &frag_table, inode, path, true }, .{ alloc, io, data, decomp, super.block_size, &cache, &frag_table, inode, path, true },
), ),
.dir, .ext_dir => try sel.async( .dir, .ext_dir => sel.async(
.path, .path,
extractDir, extractDir,
.{ alloc, io, super, data, decomp, &sel, &cache, &frag_table, inode, path, true }, .{ alloc, io, super, data, decomp, &sel, &cache, &frag_table, inode, path, true },
), ),
.symlink, .ext_symlink => try sel.async( .symlink, .ext_symlink => sel.async(
.void, .void,
extractSymlink, extractSymlink,
.{ alloc, io, inode, path, true }, .{ alloc, io, inode, path, true },
), ),
else => try sel.async( else => sel.async(
.path, .path,
extractNod, extractNod,
.{ alloc, inode, path, true }, .{ alloc, inode, path, true },
@@ -76,7 +76,7 @@ pub fn extract(
try loop.await(io); try loop.await(io);
} }
fn dirOrder(a: PathReturn, b: PathReturn) std.math.Order { fn dirOrder(_: void, a: PathReturn, b: PathReturn) std.math.Order {
return std.math.order(std.mem.count(u8, a.path, "/"), std.mem.count(u8, b.path, "/")); return std.math.order(std.mem.count(u8, a.path, "/"), std.mem.count(u8, b.path, "/"));
} }
fn finishLoop(alloc: std.mem.Allocator, io: Io, sel: *Io.Select(ReturnUnion), id_table: *Lookup.Table(u16), xattr_table: *XattrTable, start_num: u32, options: ExtractionOptions) !void { fn finishLoop(alloc: std.mem.Allocator, io: Io, sel: *Io.Select(ReturnUnion), id_table: *Lookup.Table(u16), xattr_table: *XattrTable, start_num: u32, options: ExtractionOptions) !void {
@@ -88,12 +88,19 @@ fn finishLoop(alloc: std.mem.Allocator, io: Io, sel: *Io.Select(ReturnUnion), id
while (true) { while (true) {
const value: ReturnUnion = try sel.await(); const value: ReturnUnion = try sel.await();
switch (value) { const path_ret = switch (value) {
.void => continue, .void => {
else => {}, _ = sel.group.token.load(.unordered) orelse break;
} continue;
},
.path => |p| try p,
};
const path_ret: PathReturn = value.path; if (options.ignore_permissions and (options.ignore_xattr or path_ret.xattr_idx == null)) {
if (path_ret.hdr.num != start_num)
alloc.free(path_ret.path);
continue;
}
if (path_ret.hdr.type == .dir or path_ret.hdr.type == .ext_dir) { if (path_ret.hdr.type == .dir or path_ret.hdr.type == .ext_dir) {
dirs.push(alloc, path_ret) catch |err| { dirs.push(alloc, path_ret) catch |err| {
@@ -106,17 +113,43 @@ fn finishLoop(alloc: std.mem.Allocator, io: Io, sel: *Io.Select(ReturnUnion), id
defer if (path_ret.hdr.num != start_num) defer if (path_ret.hdr.num != start_num)
alloc.free(path_ret.path); alloc.free(path_ret.path);
if (options.ignore_permissions and (options.ignore_xattr or path_ret.xattr_idx == null)) continue;
var file = try Io.Dir.cwd().openFile(io, path_ret.path, .{}); var file = try Io.Dir.cwd().openFile(io, path_ret.path, .{});
defer file.close(); defer file.close(io);
if (!options.ignore_xattr and path_ret.xattr_idx != null) { if (!options.ignore_xattr and path_ret.xattr_idx != null) {
const xattr = try xattr_table.get(alloc, io, path_ret.xattr_idx.?); const xattr = try xattr_table.get(alloc, io, path_ret.xattr_idx.?);
defer xattr.deinit(alloc); defer xattr.deinit(alloc);
for (xattr.kvs) |kv| { for (xattr.kvs) |kv| {
const res = std.os.linux.fsetxattr(file.handle, kv.key, kv.value, kv.value.len, 0); const res = std.os.linux.fsetxattr(file.handle, kv.key, kv.value.ptr, kv.value.len, 0);
if (res != 0)
return error.SetXattrError;
}
}
if (!options.ignore_permissions) {
try file.setTimestamps(io, .{
.modify_timestamp = .init(Io.Timestamp.fromNanoseconds(@as(i96, @intCast(path_ret.hdr.mod_time)) * std.time.ns_per_s)),
});
try file.setPermissions(io, @enumFromInt(path_ret.hdr.permissions));
try file.setOwner(io, try id_table.get(io, path_ret.hdr.uid_idx), try id_table.get(io, path_ret.hdr.gid_idx));
}
_ = sel.group.token.load(.unordered) orelse break;
}
while (dirs.popMax()) |path_ret| {
if (path_ret.hdr.num != start_num)
alloc.free(path_ret.path);
var file = try Io.Dir.cwd().openFile(io, path_ret.path, .{});
defer file.close(io);
if (!options.ignore_xattr and path_ret.xattr_idx != null) {
const xattr = try xattr_table.get(alloc, io, path_ret.xattr_idx.?);
defer xattr.deinit(alloc);
for (xattr.kvs) |kv| {
const res = std.os.linux.fsetxattr(file.handle, kv.key, kv.value.ptr, kv.value.len, 0);
if (res != 0) if (res != 0)
return error.SetXattrError; return error.SetXattrError;
} }
@@ -159,7 +192,7 @@ fn extractDir(
var meta: MetadataReader = .init(alloc, data, decomp, d.block_start); var meta: MetadataReader = .init(alloc, data, decomp, d.block_start);
try meta.interface.discardAll(d.block_offset); try meta.interface.discardAll(d.block_offset);
break :blk Directory.init(alloc, &meta.interface, d.size); break :blk try Directory.init(alloc, &meta.interface, d.size);
}, },
.ext_dir => |d| blk: { .ext_dir => |d| blk: {
if (d.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = d.xattr_idx; if (d.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = d.xattr_idx;
@@ -167,7 +200,7 @@ fn extractDir(
var meta: MetadataReader = .init(alloc, data, decomp, d.block_start); var meta: MetadataReader = .init(alloc, data, decomp, d.block_start);
try meta.interface.discardAll(d.block_offset); try meta.interface.discardAll(d.block_offset);
break :blk Directory.init(alloc, &meta.interface, d.size); break :blk try Directory.init(alloc, &meta.interface, d.size);
}, },
else => unreachable, else => unreachable,
}; };
@@ -176,7 +209,7 @@ fn extractDir(
for (dir.entries) |entry| { for (dir.entries) |entry| {
var new_inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry); var new_inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry);
const new_path = std.mem.concat(alloc, &.{ path, "/", entry.name }) catch |err| { const new_path = std.mem.concat(alloc, u8, &.{ path, "/", entry.name }) catch |err| {
new_inode.deinit(alloc); new_inode.deinit(alloc);
return err; return err;
}; };
@@ -184,10 +217,12 @@ fn extractDir(
switch (entry.type) { switch (entry.type) {
.dir => sel.async(.path, extractDir, .{ alloc, io, super, data, decomp, sel, cache, frag_table, new_inode, new_path, false }), .dir => sel.async(.path, extractDir, .{ alloc, io, super, data, decomp, sel, cache, frag_table, new_inode, new_path, false }),
.file => sel.async(.path, extractFile, .{ alloc, io, data, decomp, super.block_size, cache, frag_table, new_inode, new_path, false }), .file => sel.async(.path, extractFile, .{ alloc, io, data, decomp, super.block_size, cache, frag_table, new_inode, new_path, false }),
.symlink => sel.async(.void, extractSymlink, .{ alloc, io, data, decomp, new_inode, new_path, false }), .symlink => sel.async(.void, extractSymlink, .{ alloc, io, new_inode, new_path, false }),
else => sel.async(.path, extractNod, .{ alloc, data, decomp, new_inode, new_path, false }), else => sel.async(.path, extractNod, .{ alloc, new_inode, new_path, false }),
} }
} }
return ret;
} }
fn extractFile( fn extractFile(
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
@@ -220,10 +255,12 @@ fn extractFile(
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
if (entry.size.uncompressed) { if (entry.size.uncompressed) {
rdr.addFrag(data[entry.block_start..][0..entry.size.size]); rdr.addFrag(data[entry.block_start..][0..entry.size.size], f.frag_offset);
} else { } else {
rdr.addFrag(try cache.get(io, entry.block_start, entry.size)); rdr.addFrag(try cache.get(io, entry.block_start, entry.size.size), f.frag_offset);
} }
break :blk rdr;
}, },
.ext_file => |f| blk: { .ext_file => |f| blk: {
if (f.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = f.xattr_idx; if (f.xattr_idx != 0xFFFFFFFF) ret.xattr_idx = f.xattr_idx;
@@ -235,10 +272,12 @@ fn extractFile(
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
if (entry.size.uncompressed) { if (entry.size.uncompressed) {
rdr.addFrag(data[entry.block_start..][0..entry.size.size]); rdr.addFrag(data[entry.block_start..][0..entry.size.size], f.frag_offset);
} else { } else {
rdr.addFrag(try cache.get(io, entry.block_start, entry.size)); rdr.addFrag(try cache.get(io, entry.block_start, entry.size.size), f.frag_offset);
} }
break :blk rdr;
}, },
else => unreachable, else => unreachable,
}; };
@@ -329,11 +368,11 @@ fn extractNod(alloc: std.mem.Allocator, inode: Inode, path: []const u8, origin:
const ReturnUnion = union(enum) { const ReturnUnion = union(enum) {
path: Error!PathReturn, path: Error!PathReturn,
dir: Error!PathReturn,
void: Error!void, void: Error!void,
}; };
const Error = error{} || Decomp.Error || Directory.Error || DataExtractor.Error; const Error = error{MknodError} || Decomp.Error || Directory.Error || DataExtractor.Error || Io.Dir.CreateDirPathError ||
Io.Dir.SymLinkError || Io.File.Atomic.LinkError;
const PathReturn = struct { const PathReturn = struct {
hdr: Inode.Header, hdr: Inode.Header,
+15 -11
View File
@@ -22,7 +22,7 @@ pub fn extract(
ext_loc: []const u8, ext_loc: []const u8,
options: ExtractionOptions, options: ExtractionOptions,
) !void { ) !void {
const path = std.mem.trim(ext_loc, "/"); const path = std.mem.trim(u8, ext_loc, "/");
var cache: Cache = .init(alloc, data, decomp); var cache: Cache = .init(alloc, data, decomp);
defer cache.deinit(); defer cache.deinit();
@@ -84,7 +84,7 @@ pub fn extractReal(
var meta: MetadaReader = .init(alloc, data, decomp, d.block_start); var meta: MetadaReader = .init(alloc, data, decomp, d.block_start);
try meta.interface.discardAll(d.block_offset); try meta.interface.discardAll(d.block_offset);
break :blk Directory.init(alloc, &meta.interface, d.size); break :blk try Directory.init(alloc, &meta.interface, d.size);
}, },
.ext_dir => |d| blk: { .ext_dir => |d| blk: {
if (d.xattr_idx != 0xFFFFFFFF) xattr_idx = d.xattr_idx; if (d.xattr_idx != 0xFFFFFFFF) xattr_idx = d.xattr_idx;
@@ -92,7 +92,7 @@ pub fn extractReal(
var meta: MetadaReader = .init(alloc, data, decomp, d.block_start); var meta: MetadaReader = .init(alloc, data, decomp, d.block_start);
try meta.interface.discardAll(d.block_offset); try meta.interface.discardAll(d.block_offset);
break :blk Directory.init(alloc, &meta.interface, d.size); break :blk try Directory.init(alloc, &meta.interface, d.size);
}, },
else => unreachable, else => unreachable,
}; };
@@ -101,7 +101,7 @@ pub fn extractReal(
for (dir.entries) |entry| { for (dir.entries) |entry| {
var new_inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry); var new_inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry);
const new_path = std.mem.concat(alloc, &.{ path, "/", entry.name }) catch |err| { const new_path = std.mem.concat(alloc, u8, &.{ path, "/", entry.name }) catch |err| {
new_inode.deinit(alloc); new_inode.deinit(alloc);
return err; return err;
}; };
@@ -113,31 +113,35 @@ pub fn extractReal(
var rdr: DataReader = switch (inode.data) { var rdr: DataReader = switch (inode.data) {
.file => |f| blk: { .file => |f| blk: {
var rdr: DataReader = .init(alloc, data, decomp, super.block_size, f.blocks, f.size, f.block_start); var rdr: DataReader = .init(alloc, data, decomp, super.block_size, f.blocks, f.size, f.block_start);
rdr.addCache(cache); rdr.addCache(io, cache);
if (f.frag_idx == 0xFFFFFFFF) break :blk rdr; if (f.frag_idx == 0xFFFFFFFF) break :blk rdr;
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
if (entry.size.uncompressed) { if (entry.size.uncompressed) {
rdr.addFrag(data[entry.block_start..][0..entry.size.size]); rdr.addFrag(data[entry.block_start..][0..entry.size.size], f.frag_offset);
} else { } else {
rdr.addFrag(try cache.get(io, entry.block_start, entry.size)); rdr.addFrag(try cache.get(io, entry.block_start, entry.size.size), f.frag_offset);
} }
break :blk rdr;
}, },
.ext_file => |f| blk: { .ext_file => |f| blk: {
if (f.xattr_idx != 0xFFFFFFFF) xattr_idx = f.xattr_idx; if (f.xattr_idx != 0xFFFFFFFF) xattr_idx = f.xattr_idx;
var rdr: DataReader = .init(alloc, data, decomp, super.block_size, f.blocks, f.size, f.block_start); var rdr: DataReader = .init(alloc, data, decomp, super.block_size, f.blocks, f.size, f.block_start);
rdr.addCache(cache); rdr.addCache(io, cache);
if (f.frag_idx == 0xFFFFFFFF) break :blk rdr; if (f.frag_idx == 0xFFFFFFFF) break :blk rdr;
const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx);
if (entry.size.uncompressed) { if (entry.size.uncompressed) {
rdr.addFrag(data[entry.block_start..][0..entry.size.size]); rdr.addFrag(data[entry.block_start..][0..entry.size.size], f.frag_offset);
} else { } else {
rdr.addFrag(try cache.get(io, entry.block_start, entry.size)); rdr.addFrag(try cache.get(io, entry.block_start, entry.size.size), f.frag_offset);
} }
break :blk rdr;
}, },
else => unreachable, else => unreachable,
}; };
@@ -221,7 +225,7 @@ pub fn extractReal(
defer xattr.deinit(alloc); defer xattr.deinit(alloc);
for (xattr.kvs) |kv| { for (xattr.kvs) |kv| {
const res = std.os.linux.fsetxattr(fil.handle, kv.key, kv.value, kv.value.len, 0); const res = std.os.linux.fsetxattr(fil.handle, kv.key, kv.value.ptr, kv.value.len, 0);
if (res != 0) if (res != 0)
return error.SetXattrError; return error.SetXattrError;
} }
+10 -10
View File
@@ -40,7 +40,7 @@ pub fn initEntry(alloc: std.mem.Allocator, super: Superblock, data: []u8, decomp
const inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry); const inode: Inode = try .initEntry(alloc, data, decomp, super.inode_start, super.block_size, entry);
errdefer inode.deinit(alloc); errdefer inode.deinit(alloc);
const new_name = try alloc.dupe(entry.name); const new_name = try alloc.dupe(u8, entry.name);
return init(alloc, super, data, decomp, new_name, inode); return init(alloc, super, data, decomp, new_name, inode);
} }
@@ -49,22 +49,22 @@ pub fn initRef(alloc: std.mem.Allocator, super: Superblock, data: []u8, decomp:
const inode: Inode = try .initRef(alloc, data, decomp, super.inode_start, super.block_size, ref); const inode: Inode = try .initRef(alloc, data, decomp, super.inode_start, super.block_size, ref);
errdefer inode.deinit(alloc); errdefer inode.deinit(alloc);
const new_name = try alloc.dupe(name); const new_name = try alloc.dupe(u8, name);
return init(alloc, super, data, decomp, new_name, inode); return init(alloc, super, data, decomp, new_name, inode);
} }
pub fn copy(self: File, alloc: std.mem.Allocator) !File { pub fn copy(self: File, alloc: std.mem.Allocator) !File {
const inode = self.inode; var inode = self.inode;
switch (inode.data) { switch (inode.data) {
.file => |*f| f.blocks = try alloc.dupe(self.inode.data.file.blocks), .file => |*f| f.blocks = try alloc.dupe(Inode.DataBlock, self.inode.data.file.blocks),
.ext_file => |*f| f.blocks = try alloc.dupe(self.inode.data.file.blocks), .ext_file => |*f| f.blocks = try alloc.dupe(Inode.DataBlock, self.inode.data.file.blocks),
.symlink => |*s| s.target = try alloc.dupe(self.inode.data.symlink.target), .symlink => |*s| s.target = try alloc.dupe(u8, self.inode.data.symlink.target),
.ext_symlink => |*s| s.target = try alloc.dupe(self.inode.data.symlink.target), .ext_symlink => |*s| s.target = try alloc.dupe(u8, self.inode.data.symlink.target),
else => {}, else => {},
} }
errdefer inode.deinit(alloc); errdefer inode.deinit(alloc);
const new_name = try alloc.dupe(self.name); const new_name = try alloc.dupe(u8, self.name);
return init(alloc, self.super, self.data, self.decomp, new_name, inode); return init(alloc, self.super, self.data, self.decomp, new_name, inode);
} }
@@ -92,7 +92,7 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
try meta.interface.discardAll(d.block_offset); try meta.interface.discardAll(d.block_offset);
break :blk meta; break :blk meta;
}, },
else => error.NotDirectory, else => return error.NotDirectory,
}; };
const path = std.mem.trim(u8, filepath, "/"); const path = std.mem.trim(u8, filepath, "/");
@@ -100,7 +100,7 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
if (path.len == 0 or (path.len == 1 and path[0] == '.')) if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return self.copy(alloc); return self.copy(alloc);
const first_element: []u8 = std.mem.sliceTo(path, '/'); const first_element: []const u8 = std.mem.sliceTo(path, '/');
const file: File = blk: { const file: File = blk: {
var directory: Directory = try .init(alloc, &meta.interface, size); var directory: Directory = try .init(alloc, &meta.interface, size);
+5 -5
View File
@@ -40,13 +40,13 @@ pub fn initRef(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, inode_st
var meta: MetadataReader = .init(alloc, data, decomp, inode_start + ref.block_start); var meta: MetadataReader = .init(alloc, data, decomp, inode_start + ref.block_start);
try meta.interface.discardAll(ref.block_offset); try meta.interface.discardAll(ref.block_offset);
return .init(alloc, &meta.interface, block_size); return .init(alloc, block_size, &meta.interface);
} }
pub fn initEntry(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, inode_start: u64, block_size: u32, entry: Directory.Entry) !Inode { pub fn initEntry(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, inode_start: u64, block_size: u32, entry: Directory.Entry) !Inode {
var meta: MetadataReader = .init(alloc, data, decomp, inode_start + entry.block_start); var meta: MetadataReader = .init(alloc, data, decomp, inode_start + entry.block_start);
try meta.interface.discardAll(entry.block_offset); try meta.interface.discardAll(entry.block_offset);
return .init(alloc, &meta.interface, block_size); return .init(alloc, block_size, &meta.interface);
} }
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) { switch (self.data) {
@@ -64,7 +64,7 @@ pub fn extract(self: Inode, alloc: std.mem.Allocator, io: Io, super: Superblock,
// Types // Types
pub const Ref = packed struct { pub const Ref = packed struct(u64) {
block_offset: u16, block_offset: u16,
block_start: u32, block_start: u32,
_: u16, _: u16,
@@ -191,7 +191,7 @@ pub const ExtFile = struct {
var data: [40]u8 = undefined; var data: [40]u8 = undefined;
try rdr.readSliceAll(&data); try rdr.readSliceAll(&data);
const frag_idx = std.mem.readInt(u32, data[28..], .little); const frag_idx = std.mem.readInt(u32, data[28..32], .little);
const size = std.mem.readInt(u64, data[8..16], .little); const size = std.mem.readInt(u64, data[8..16], .little);
var blocks_num = size / block_size; var blocks_num = size / block_size;
@@ -238,7 +238,7 @@ pub const ExtSymlink = struct {
xattr_idx: u32, xattr_idx: u32,
fn init(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink { fn init(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
const sym: Symlink = .init(alloc, rdr); const sym: Symlink = try .init(alloc, rdr);
var xattr_idx: u32 = undefined; var xattr_idx: u32 = undefined;
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little); try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
+24 -9
View File
@@ -4,7 +4,7 @@ const Io = std.Io;
const DataBlock = @import("inode.zig").DataBlock; const DataBlock = @import("inode.zig").DataBlock;
const Decomp = @import("decomp.zig"); const Decomp = @import("decomp.zig");
const MetadataReader = @import("meta_rdr.zig"); const MetadataReader = @import("meta_rdr.zig");
const ProtectedMap = @import("util/protected_map.zig"); const ProtectedMap = @import("util/protected_map.zig").ProtectedMap;
pub fn Table(comptime T: anytype) type { pub fn Table(comptime T: anytype) type {
return struct { return struct {
@@ -21,7 +21,15 @@ pub fn Table(comptime T: anytype) type {
table: ProtectedMap(u32, []T, getBlock), table: ProtectedMap(u32, []T, getBlock),
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64, table_num: u32) Self { pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64, table_num: u32) Self {
return .{ .data = data, .decomp = decomp, .table_start = table_start, .table_num = table_num, .table = .init(alloc) }; return .{
.data = data,
.decomp = decomp,
.table_start = table_start,
.table_num = table_num,
.table = .init(alloc),
};
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
var iter = self.table.map.valueIterator(); var iter = self.table.map.valueIterator();
@@ -38,20 +46,27 @@ pub fn Table(comptime T: anytype) type {
const block = idx / VALUES_PER_BLOCK; const block = idx / VALUES_PER_BLOCK;
const block_idx = idx % VALUES_PER_BLOCK; const block_idx = idx % VALUES_PER_BLOCK;
const values = try self.table.getOrPut(io, block, .{ self.*, block }); const values = try self.table.getOrPut(io, block, .{
self.table.alloc,
self.data,
self.decomp,
self.table_start,
self.table_num,
block,
});
return values[block_idx]; return values.*[block_idx];
} }
pub fn getBlock(self: Self, block_idx: u32) ![]T { pub fn getBlock(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64, table_num: u32, block_idx: u32) ![]T {
const offset: u64 = std.mem.readInt(u64, self.data[self.table_start + (block_idx * 8) ..][0..8], .little); const offset: u64 = std.mem.readInt(u64, data[table_start + (block_idx * 8) ..][0..8], .little);
const block = try self.table.alloc(T, if (block_idx == (self.table_num - 1 / VALUES_PER_BLOCK)) const block = try alloc.alloc(T, if (block_idx == (table_num - 1 / VALUES_PER_BLOCK))
self.table_num % VALUES_PER_BLOCK table_num % VALUES_PER_BLOCK
else else
VALUES_PER_BLOCK); VALUES_PER_BLOCK);
var meta: MetadataReader = .init(self.table.alloc, self.data, self.decomp, offset); var meta: MetadataReader = .init(alloc, data, decomp, offset);
try meta.interface.readSliceEndian(T, block, .little); try meta.interface.readSliceEndian(T, block, .little);
return block; return block;
+1 -1
View File
@@ -43,7 +43,7 @@ fn advance(self: *MetadataReader) Reader.Error!void {
self.interface.seek = 0; self.interface.seek = 0;
errdefer self.interface.end = 0; errdefer self.interface.end = 0;
const hdr: Header = @intCast(std.mem.readInt(u16, self.data[self.cur_offset..][0..2], .little)); const hdr: Header = @bitCast(std.mem.readInt(u16, self.data[self.cur_offset..][0..2], .little));
defer self.cur_offset += hdr.size; defer self.cur_offset += hdr.size;
const block = self.data[self.cur_offset + 2 ..][0..hdr.size]; const block = self.data[self.cur_offset + 2 ..][0..hdr.size];
+81 -4
View File
@@ -6,16 +6,93 @@ const Archive = @import("archive.zig");
const TestArchive = "testing/LinuxPATest.sfs"; const TestArchive = "testing/LinuxPATest.sfs";
test "Basics" {} test "Basics" {
const io = testing.io;
const alloc = testing.allocator;
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io);
var archive: Archive = try .init(io, archive_file, 0);
defer archive.deinit(io);
try testing.expectEqualDeep(archive.super, LinuxPATestCorrectSuperblock);
var root = try archive.root(alloc);
defer root.deinit();
}
const TestFile = "Start.exe"; const TestFile = "Start.exe";
const TestFileExtractLocation = "testing/Start.exe"; const TestFileExtractLocation = "testing/Start.exe";
test "ExtractSingleFile" {} test "ExtractSingleFile" {
const io = testing.io;
const alloc = testing.allocator;
const TestFullExtractLocation = "testing/TestExtract"; Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {};
test "ExtractCompleteArchive" {} var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io);
var archive: Archive = try .init(io, archive_file, 0);
defer archive.deinit(io);
var start_exe = try archive.open(alloc, TestFile);
defer start_exe.deinit();
try start_exe.extract(alloc, io, TestFileExtractLocation, .default);
}
const TestFullExtractLocationMT = "testing/TestExtractMT";
test "ExtractCompleteArchiveMultiThreaded" {
const io = testing.io;
const alloc = testing.allocator;
Io.Dir.cwd().deleteFile(io, TestFullExtractLocationMT) catch {};
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io);
var archive: Archive = try .init(io, archive_file, 0);
defer archive.deinit(io);
try archive.extract(alloc, io, TestFullExtractLocationMT, .default);
}
const TestFullExtractLocationSTOption = "testing/TestExtractSTOption";
test "ExtractCompleteArchiveSingleThreadedOption" {
const io = testing.io;
const alloc = testing.allocator;
Io.Dir.cwd().deleteFile(io, TestFullExtractLocationSTOption) catch {};
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io);
var archive: Archive = try .init(io, archive_file, 0);
defer archive.deinit(io);
try archive.extract(alloc, io, TestFullExtractLocationSTOption, .single_threaded_default);
}
const TestFullExtractLocationSTIo = "testing/TestExtractSTIo";
test "ExtractCompleteArchiveSingleThreadedIo" {
const io = Io.Threaded.global_single_threaded.io();
const alloc = testing.allocator;
Io.Dir.cwd().deleteFile(io, TestFullExtractLocationSTIo) catch {};
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
defer archive_file.close(io);
var archive: Archive = try .init(io, archive_file, 0);
defer archive.deinit(io);
try archive.extract(alloc, io, TestFullExtractLocationSTIo, .default);
}
const LinuxPATestCorrectSuperblock: Archive.Superblock = .{ const LinuxPATestCorrectSuperblock: Archive.Superblock = .{
.magic = std.mem.readInt(u32, "hsqs", .little), .magic = std.mem.readInt(u32, "hsqs", .little),
+1 -1
View File
@@ -29,7 +29,7 @@ pub fn deinit(self: *Cache) void {
} }
pub fn get(self: *Cache, io: Io, offset: u64, compressed_size: u32) Error![]u8 { pub fn get(self: *Cache, io: Io, offset: u64, compressed_size: u32) Error![]u8 {
const res: CachedBlock = try self.cache.getOrPut(io, offset, .{ self.alloc, self.data, self.decomp, offset, compressed_size }); const res: *CachedBlock = try self.cache.getOrPut(io, offset, .{ self.alloc, self.data, self.decomp, offset, compressed_size });
return res.block[0..res.size]; return res.block[0..res.size];
} }
+14 -9
View File
@@ -2,12 +2,14 @@ const std = @import("std");
const Io = std.Io; const Io = std.Io;
pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn: anytype) type { pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn: anytype) type {
std.debug.assert(std.meta.activeTag(@typeInfo(create_fn)) == .@"fn"); const fn_info = @typeInfo(@TypeOf(create_fn));
std.debug.assert(std.meta.activeTag(fn_info) == .@"fn");
const ret_info = @typeInfo(create_fn).@"fn".return_type; const ret_info = fn_info.@"fn".return_type;
std.debug.assert(ret_info != null);
std.debug.assert(ret_info == T or std.debug.assert(ret_info == T or
(std.meta.activeTag(@typeInfo(ret_info)) == .error_union and @typeInfo(ret_info).error_union.payload == T)); (std.meta.activeTag(@typeInfo(ret_info.?)) == .error_union and @typeInfo(ret_info.?).error_union.payload == T));
return struct { return struct {
const Map = @This(); const Map = @This();
@@ -28,7 +30,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
self.map.deinit(); self.map.deinit();
} }
pub fn getOrPut(self: *Map, io: Io, key: K, create_fn_args: std.meta.ArgsTuple(create_fn)) Error!*T { pub fn getOrPut(self: *Map, io: Io, key: K, create_fn_args: std.meta.ArgsTuple(@TypeOf(create_fn))) Error!*T {
{ {
try self.mut.lockShared(io); try self.mut.lockShared(io);
defer self.mut.unlockShared(io); defer self.mut.unlockShared(io);
@@ -36,9 +38,9 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
const value = self.map.getPtr(key); const value = self.map.getPtr(key);
if (value != null) { if (value != null) {
if (!value.?.filled.isSet()) if (!value.?.filled.isSet())
value.?.filled.wait(io); try value.?.filled.wait(io);
if (value.?.err != null) return value.?.err != null; if (value.?.err != null) return value.?.err.?;
return value.?.value; return &value.?.value;
} }
} }
try self.mut.lock(io); try self.mut.lock(io);
@@ -54,7 +56,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
res.value_ptr.* = .{}; res.value_ptr.* = .{};
defer res.value_ptr.filled.set(io); defer res.value_ptr.filled.set(io);
res.mut.unlock(io); self.mut.unlock(io);
self.mut.lockSharedUncancelable(io); self.mut.lockSharedUncancelable(io);
defer self.mut.unlockShared(io); defer self.mut.unlockShared(io);
@@ -64,8 +66,11 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
} else { } else {
res.value_ptr.value = @call(.auto, create_fn, create_fn_args) catch |err| { res.value_ptr.value = @call(.auto, create_fn, create_fn_args) catch |err| {
res.value_ptr.err = err; res.value_ptr.err = err;
return err;
}; };
} }
return &res.value_ptr.value;
} }
// Map Types // Map Types
@@ -73,7 +78,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn
pub const Error = error{ Canceled, OutOfMemory } || pub const Error = error{ Canceled, OutOfMemory } ||
if (@TypeOf(CreateError) == void) error{} else CreateError; if (@TypeOf(CreateError) == void) error{} else CreateError;
const CreateError: type = switch (@typeInfo(@typeInfo(create_fn).@"fn".return_type)) { const CreateError: type = switch (@typeInfo(ret_info.?)) {
.error_union => |e| e.error_set, .error_union => |e| e.error_set,
else => void, else => void,
}; };
+3 -1
View File
@@ -28,7 +28,7 @@ pub fn deinit(self: *XattrTable) void {
pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !Xattr { pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !Xattr {
const entry: Entry = try self.table.get(io, idx); const entry: Entry = try self.table.get(io, idx);
const out: std.ArrayList(KeyValue) = try .initCapacity(alloc, entry.count); var out: std.ArrayList(KeyValue) = try .initCapacity(alloc, entry.count);
errdefer { errdefer {
for (out.items) |kv| for (out.items) |kv|
kv.deinit(alloc); kv.deinit(alloc);
@@ -75,6 +75,8 @@ pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !Xattr
.value = value, .value = value,
}; };
} }
return .{ .kvs = try out.toOwnedSlice(alloc) };
} }
fn readValue(alloc: std.mem.Allocator, rdr: *Io.Reader) ![]u8 { fn readValue(alloc: std.mem.Allocator, rdr: *Io.Reader) ![]u8 {