From 42a92853a47570329955495ed4f0da223a65381a Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Tue, 16 Jun 2026 22:01:44 -0500 Subject: [PATCH] Re-added tests Fixed compile errors from tests --- src/archive.zig | 2 +- src/c_decomp.zig | 14 ++++-- src/data/extractor.zig | 12 ++--- src/data/reader.zig | 14 +++--- src/decomp.zig | 6 +-- src/directory.zig | 10 ++-- src/extract-multi.zig | 93 +++++++++++++++++++++++++++----------- src/extract-single.zig | 26 ++++++----- src/file.zig | 20 ++++---- src/inode.zig | 10 ++-- src/lookup.zig | 33 ++++++++++---- src/meta_rdr.zig | 2 +- src/test.zig | 85 ++++++++++++++++++++++++++++++++-- src/util/cache.zig | 2 +- src/util/protected_map.zig | 23 ++++++---- src/xattr.zig | 4 +- 16 files changed, 253 insertions(+), 103 deletions(-) diff --git a/src/archive.zig b/src/archive.zig index f24e337..fbc76d9 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -67,7 +67,7 @@ pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, location: []cons ); 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 diff --git a/src/c_decomp.zig b/src/c_decomp.zig index c9e59bf..c4832fb 100644 --- a/src/c_decomp.zig +++ b/src/c_decomp.zig @@ -7,9 +7,9 @@ const Error = @import("decomp.zig").Error; pub fn zlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize { var stream: c.z_stream = .{ .next_in = in.ptr, - .avail_in = in.len, + .avail_in = @truncate(in.len), .next_out = out.ptr, - .avail_out = out.len, + .avail_out = @truncate(out.len), }; var res = c.inflateInit(&stream); 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) return Error.DecompressionFailed; while (res == c.LZMA_OK) - res = c.lzma_code(&stream); + res = c.lzma_code(&stream, c.LZMA_RUN); if (res != c.LZMA_STREAM_END) return Error.DecompressionFailed; return stream.total_out; @@ -40,9 +40,15 @@ pub fn lz4(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize { in.ptr, out.ptr, @intCast(in.len), - out.len, + @intCast(out.len), ); if (res < 0) return Error.DecompressionFailed; 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; +} diff --git a/src/data/extractor.zig b/src/data/extractor.zig index a635b67..8a2e5a8 100644 --- a/src/data/extractor.zig +++ b/src/data/extractor.zig @@ -19,7 +19,7 @@ size: u64, frag_data: ?[]u8 = null, 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 { return .{ @@ -37,7 +37,7 @@ pub fn addFrag(self: *Extractor, frag_data: []u8, frag_offset: u32) void { self.frag_data = frag_data; self.frag_offset = frag_offset; } -pub fn addCache(self: *Extractor, cache: Cache) void { +pub fn addCache(self: *Extractor, cache: *Cache) void { 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; 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; } 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); if (err != null) - return err; + return err.?; 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)) self.size % self.block_size else @@ -93,7 +93,7 @@ fn blockThread(self: Extractor, alloc: std.mem.Allocator, map_data: []u8, read_o } 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) { error.Canceled => return error.Canceled, else => |e| err.* = e, diff --git a/src/data/reader.zig b/src/data/reader.zig index f2592f8..d3b4d55 100644 --- a/src/data/reader.zig +++ b/src/data/reader.zig @@ -24,7 +24,8 @@ sparse_block: bool = false, frag_data: ?[]u8 = null, frag_offset: u32 = 0, -cache: ?Cache = null, +io: ?Io = null, +cache: ?*Cache = null, 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_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; } @@ -92,7 +94,7 @@ fn advance(self: *Reader) Io.Reader.Error!void { if (block.size == 0) { self.sparse_block = true; - self.end = size; + self.interface.end = size; return; } else { 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.end = size; } 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; } } @@ -119,7 +121,7 @@ fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!u if (r.seek >= r.end) try self.advance(); - if (limit == .nothing) return; + if (limit == .nothing) return 0; 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); try self.advance(); } - if (limit == .nothing) return; + if (limit == .nothing) return 0; const to_discard = @min(@intFromEnum(limit), r.end - r.seek); diff --git a/src/decomp.zig b/src/decomp.zig index 9614d28..7fbfdb6 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -6,10 +6,10 @@ const zig = @import("zig_decomp.zig"); pub fn getFn(e: Enum) !Fn { 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, .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, .zstd => if (build.use_zig_decomp) zig.zstd else c.zstd, }; @@ -17,7 +17,7 @@ pub fn getFn(e: Enum) !Fn { // 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) { gzip = 1, diff --git a/src/directory.zig b/src/directory.zig index b527452..2924587 100644 --- a/src/directory.zig +++ b/src/directory.zig @@ -15,7 +15,7 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u64) !Directory { var read: u64 = 3; - var out: std.ArrayList(Entry) = .initCapacity(alloc, 50); + var out: std.ArrayList(Entry) = try .initCapacity(alloc, 50); errdefer { for (out.items) |entry| 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); 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); try rdr.readSliceEndian(u8, name, .little); - const entry = out.addOneAssumeCapacity(alloc); + const entry = out.addOneAssumeCapacity(); entry.* = .{ .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 { for (self.entries) |entry| @@ -64,7 +64,7 @@ pub const Entry = struct { name: []const u8, block_start: u32, block_offset: u16, - type: Inode.type, + type: Inode.Type, pub fn deinit(self: Entry, alloc: std.mem.Allocator) void { alloc.free(self.name); diff --git a/src/extract-multi.zig b/src/extract-multi.zig index 9b328a9..6bb87bb 100644 --- a/src/extract-multi.zig +++ b/src/extract-multi.zig @@ -22,7 +22,7 @@ pub fn extract( ext_loc: []const u8, options: ExtractionOptions, ) !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); defer id_table.deinit(); @@ -42,7 +42,7 @@ pub fn extract( 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); defer cache.deinit(); @@ -51,22 +51,22 @@ pub fn extract( defer frag_table.deinit(); switch (inode.hdr.type) { - .file, .ext_file => try sel.async( + .file, .ext_file => sel.async( .path, extractFile, .{ 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, extractDir, .{ alloc, io, super, data, decomp, &sel, &cache, &frag_table, inode, path, true }, ), - .symlink, .ext_symlink => try sel.async( + .symlink, .ext_symlink => sel.async( .void, extractSymlink, .{ alloc, io, inode, path, true }, ), - else => try sel.async( + else => sel.async( .path, extractNod, .{ alloc, inode, path, true }, @@ -76,7 +76,7 @@ pub fn extract( 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, "/")); } 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) { const value: ReturnUnion = try sel.await(); - switch (value) { - .void => continue, - else => {}, - } + const path_ret = switch (value) { + .void => { + _ = 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) { 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) 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, .{}); - defer file.close(); + 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, 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) return error.SetXattrError; } @@ -159,7 +192,7 @@ fn extractDir( var meta: MetadataReader = .init(alloc, data, decomp, d.block_start); 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: { 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); 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, }; @@ -176,7 +209,7 @@ fn extractDir( for (dir.entries) |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); return err; }; @@ -184,10 +217,12 @@ fn extractDir( switch (entry.type) { .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 }), - .symlink => sel.async(.void, extractSymlink, .{ alloc, io, data, decomp, new_inode, new_path, false }), - else => sel.async(.path, extractNod, .{ alloc, 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, new_inode, new_path, false }), } } + + return ret; } fn extractFile( alloc: std.mem.Allocator, @@ -220,10 +255,12 @@ fn extractFile( const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); 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 { - 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: { 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); 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 { - 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, }; @@ -329,11 +368,11 @@ fn extractNod(alloc: std.mem.Allocator, inode: Inode, path: []const u8, origin: const ReturnUnion = union(enum) { path: Error!PathReturn, - dir: Error!PathReturn, 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 { hdr: Inode.Header, diff --git a/src/extract-single.zig b/src/extract-single.zig index c5071d9..3a413de 100644 --- a/src/extract-single.zig +++ b/src/extract-single.zig @@ -22,7 +22,7 @@ pub fn extract( ext_loc: []const u8, options: ExtractionOptions, ) !void { - const path = std.mem.trim(ext_loc, "/"); + const path = std.mem.trim(u8, ext_loc, "/"); var cache: Cache = .init(alloc, data, decomp); defer cache.deinit(); @@ -84,7 +84,7 @@ pub fn extractReal( var meta: MetadaReader = .init(alloc, data, decomp, d.block_start); 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: { 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); 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, }; @@ -101,7 +101,7 @@ pub fn extractReal( for (dir.entries) |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); return err; }; @@ -113,31 +113,35 @@ pub fn extractReal( var rdr: DataReader = switch (inode.data) { .file => |f| blk: { 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; const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); 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 { - 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: { 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); - rdr.addCache(cache); + rdr.addCache(io, cache); if (f.frag_idx == 0xFFFFFFFF) break :blk rdr; const entry: Lookup.FragEntry = try frag_table.get(io, f.frag_idx); 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 { - 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, }; @@ -221,7 +225,7 @@ pub fn extractReal( defer xattr.deinit(alloc); 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) return error.SetXattrError; } diff --git a/src/file.zig b/src/file.zig index c8dc89b..e9557fa 100644 --- a/src/file.zig +++ b/src/file.zig @@ -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); 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); } @@ -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); 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); } pub fn copy(self: File, alloc: std.mem.Allocator) !File { - const inode = self.inode; + var inode = self.inode; switch (inode.data) { - .file => |*f| f.blocks = try alloc.dupe(self.inode.data.file.blocks), - .ext_file => |*f| f.blocks = try alloc.dupe(self.inode.data.file.blocks), - .symlink => |*s| s.target = try alloc.dupe(self.inode.data.symlink.target), - .ext_symlink => |*s| s.target = try alloc.dupe(self.inode.data.symlink.target), + .file => |*f| f.blocks = try alloc.dupe(Inode.DataBlock, 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(u8, self.inode.data.symlink.target), + .ext_symlink => |*s| s.target = try alloc.dupe(u8, self.inode.data.symlink.target), else => {}, } 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); } @@ -92,7 +92,7 @@ pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File { try meta.interface.discardAll(d.block_offset); break :blk meta; }, - else => error.NotDirectory, + else => return error.NotDirectory, }; 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] == '.')) 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: { var directory: Directory = try .init(alloc, &meta.interface, size); diff --git a/src/inode.zig b/src/inode.zig index 6d8f4d2..725be28 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -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); 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 { var meta: MetadataReader = .init(alloc, data, decomp, inode_start + entry.block_start); 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 { switch (self.data) { @@ -64,7 +64,7 @@ pub fn extract(self: Inode, alloc: std.mem.Allocator, io: Io, super: Superblock, // Types -pub const Ref = packed struct { +pub const Ref = packed struct(u64) { block_offset: u16, block_start: u32, _: u16, @@ -191,7 +191,7 @@ pub const ExtFile = struct { var data: [40]u8 = undefined; 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); var blocks_num = size / block_size; @@ -238,7 +238,7 @@ pub const ExtSymlink = struct { xattr_idx: u32, 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; try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little); diff --git a/src/lookup.zig b/src/lookup.zig index a0bfbf8..c763ef7 100644 --- a/src/lookup.zig +++ b/src/lookup.zig @@ -4,7 +4,7 @@ const Io = std.Io; const DataBlock = @import("inode.zig").DataBlock; const Decomp = @import("decomp.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 { return struct { @@ -21,7 +21,15 @@ pub fn Table(comptime T: anytype) type { table: ProtectedMap(u32, []T, getBlock), 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 { 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 = 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 { - const offset: u64 = std.mem.readInt(u64, self.data[self.table_start + (block_idx * 8) ..][0..8], .little); + 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, 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)) - self.table_num % VALUES_PER_BLOCK + const block = try alloc.alloc(T, if (block_idx == (table_num - 1 / VALUES_PER_BLOCK)) + table_num % VALUES_PER_BLOCK else 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); return block; diff --git a/src/meta_rdr.zig b/src/meta_rdr.zig index 25acc12..9dc4a8c 100644 --- a/src/meta_rdr.zig +++ b/src/meta_rdr.zig @@ -43,7 +43,7 @@ fn advance(self: *MetadataReader) Reader.Error!void { self.interface.seek = 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; const block = self.data[self.cur_offset + 2 ..][0..hdr.size]; diff --git a/src/test.zig b/src/test.zig index 0f21b9e..98a153e 100644 --- a/src/test.zig +++ b/src/test.zig @@ -6,16 +6,93 @@ const Archive = @import("archive.zig"); 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 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 = .{ .magic = std.mem.readInt(u32, "hsqs", .little), diff --git a/src/util/cache.zig b/src/util/cache.zig index bbfb0ac..3c1e561 100644 --- a/src/util/cache.zig +++ b/src/util/cache.zig @@ -29,7 +29,7 @@ pub fn deinit(self: *Cache) void { } 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]; } diff --git a/src/util/protected_map.zig b/src/util/protected_map.zig index bf700ef..a7a4fe0 100644 --- a/src/util/protected_map.zig +++ b/src/util/protected_map.zig @@ -2,12 +2,14 @@ const std = @import("std"); const Io = std.Io; 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.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 { const Map = @This(); @@ -28,7 +30,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn 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); 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); if (value != null) { if (!value.?.filled.isSet()) - value.?.filled.wait(io); - if (value.?.err != null) return value.?.err != null; - return value.?.value; + try value.?.filled.wait(io); + if (value.?.err != null) return value.?.err.?; + return &value.?.value; } } try self.mut.lock(io); @@ -54,7 +56,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn res.value_ptr.* = .{}; defer res.value_ptr.filled.set(io); - res.mut.unlock(io); + self.mut.unlock(io); self.mut.lockSharedUncancelable(io); defer self.mut.unlockShared(io); @@ -64,8 +66,11 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn } else { res.value_ptr.value = @call(.auto, create_fn, create_fn_args) catch |err| { res.value_ptr.err = err; + return err; }; } + + return &res.value_ptr.value; } // Map Types @@ -73,7 +78,7 @@ pub fn ProtectedMap(comptime K: anytype, comptime T: anytype, comptime create_fn pub const Error = error{ Canceled, OutOfMemory } || 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, else => void, }; diff --git a/src/xattr.zig b/src/xattr.zig index ffafa19..fa4c9f3 100644 --- a/src/xattr.zig +++ b/src/xattr.zig @@ -28,7 +28,7 @@ pub fn deinit(self: *XattrTable) void { pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !Xattr { 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 { for (out.items) |kv| kv.deinit(alloc); @@ -75,6 +75,8 @@ pub fn get(self: *XattrTable, alloc: std.mem.Allocator, io: Io, idx: u32) !Xattr .value = value, }; } + + return .{ .kvs = try out.toOwnedSlice(alloc) }; } fn readValue(alloc: std.mem.Allocator, rdr: *Io.Reader) ![]u8 {