diff --git a/src/archive.zig b/src/archive.zig index c4e9934..8ece980 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -32,6 +32,7 @@ const MEM_SIZE = 4 * 1024 * 1024 * 1024; parent_alloc: std.mem.Allocator, alloc: std.heap.FixedBufferAllocator, fixed_buf: []u8, +thread_count: usize, fil: OffsetFile, @@ -59,7 +60,6 @@ pub fn init(alloc: std.mem.Allocator, fil: File) !Archive { /// If trying to extract a full archive, a large memory size & thread count could help. /// If you're planning on only interacting with a small number of files, it should be fine to use few threads and a small memory size. pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: usize, mem: usize) !Archive { - _ = threads; var super: Superblock = undefined; const red = try fil.pread(@ptrCast(&super), offset); std.debug.assert(red == @sizeOf(Superblock)); @@ -69,6 +69,7 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u .parent_alloc = alloc, .alloc = .init(fixed_buf), .fixed_buf = fixed_buf, + .thread_count = threads, .fil = .init(fil, offset), .super = super, @@ -88,21 +89,31 @@ pub fn allocator(self: *Archive) std.mem.Allocator { return self.alloc.threadSafeAllocator(); } +fn setupValues(self: *Archive) !void { + self.decomp = try .init(self.allocator(), self.super.compression, self.super.block_size, self.thread_count); + self.frag_table = try .init(self.allocator(), self.fil, &self.decomp, self.super.frag_start, self.super.frag_count); + self.id_table = try .init(self.allocator(), self.fil, &self.decomp, self.super.id_start, self.super.id_count); + self.export_table = try .init(self.allocator(), self.fil, &self.decomp, self.super.export_start, self.super.inode_count); +} + pub fn root(self: *Archive) !SfsFile { + if (!self.setup) try self.setupValues(); var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{}); - var meta: MetadataReader = .init(self.allocator(), &rdr, &self.decomp); + var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp); try meta.interface.discardAll(self.super.root_ref.block_offset); const inode: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size); return .init(self, inode, ""); } pub fn open(self: *Archive, path: []const u8) !SfsFile { + if (!self.setup) try self.setupValues(); var root_fil = try self.root(); defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit(); return root_fil.open(path); } pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void { + if (!self.setup) try self.setupValues(); var root_fil = try self.root(); defer root_fil.deinit(); return root_fil.extract(path, options); diff --git a/src/decomp.zig b/src/decomp.zig index 8bf800f..5968a5b 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -16,7 +16,7 @@ const DecompError = error{ }; pub const CompressionType = enum(u16) { - gzig = 1, + gzip = 1, lzma, lzo, xz, @@ -44,7 +44,7 @@ pub const DecompThread = struct { .mgr = mgr, .buf = switch (mgr.comp_type) { .gzip => try mgr.alloc.alloc(u8, compress.flate.max_window_len), - .zstd => try mgr.alloc.alloc(u8, compress.zstd.default_window_len), + .zstd => try mgr.alloc.alloc(u8, compress.zstd.default_window_len + compress.zstd.block_size_max), .lzma, .xz => &[0]u8{}, else => unreachable, }, @@ -62,24 +62,24 @@ pub const DecompThread = struct { pub fn submitData(self: *DecompThread, dat: []u8, res: []u8) anyerror!usize { if (self.status.raw == 3) return DecompError.ThreadClosed; if (self.status.raw == 0) { - self.status.raw = 1; self.thr = try .spawn(.{}, thread, .{self}); } self.dat = dat; defer self.dat = &[0]u8{}; self.res = res; + self.status.raw = 2; while (self.status.raw == 2) Futex.wait(&self.status, 2); return self.res_size; } pub fn submitReader(self: *DecompThread, rdr: *Reader, res: []u8) anyerror!usize { if (self.status.raw == 3) return DecompError.ThreadClosed; if (self.status.raw == 0) { - self.status.raw = 1; self.thr = try .spawn(.{}, thread, .{self}); } self.rdr = rdr; defer self.rdr = null; self.res = res; + self.status.raw = 2; while (self.status.raw == 2) Futex.wait(&self.status, 2); return self.res_size; } @@ -89,11 +89,12 @@ pub const DecompThread = struct { while (self.status.raw != 3) { while (self.status.raw == 1) Futex.wait(&self.status, 1); if (self.status.raw == 3) return; - var rdr: *Reader = if (self.rdr != null) self.rdr.? else &Reader.fixed(self.dat); + var dat_rdr: Reader = .fixed(self.dat); + var rdr: *Reader = if (self.rdr != null) self.rdr.? else &dat_rdr; self.res_size = blk: switch (comp_type) { .gzip => { var decomp_rdr = compress.flate.Decompress.init(rdr, .zlib, self.buf); - break :blk decomp_rdr.reader.readSliceEndian(u8, self.res, .little); + break :blk decomp_rdr.reader.readSliceShort(self.res); }, .lzma => { var decomp_rdr = compress.lzma.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| { @@ -109,12 +110,12 @@ pub const DecompThread = struct { }, .zstd => { var decomp_rdr = compress.zstd.Decompress.init(rdr, self.buf, .{}); - break :blk decomp_rdr.reader.readSliceEndian(u8, self.res, .little); + break :blk decomp_rdr.reader.readSliceShort(self.res); }, else => unreachable, }; const orig = self.status.swap(1, .release); - Futex.wake(&self.status); + Futex.wake(&self.status, 1); if (orig == 3) return; } } @@ -124,6 +125,7 @@ const DecompMgr = @This(); alloc: std.mem.Allocator, comp_type: CompressionType, +block_size: u32, threads: []DecompThread, queue: std.DoublyLinkedList = .{}, @@ -131,14 +133,15 @@ mut: Mutex = .{}, cond: Condition = .{}, to_start: usize, -pub fn init(alloc: std.mem.Allocator, comp_type: CompressionType, threads: usize) !DecompMgr { +pub fn init(alloc: std.mem.Allocator, comp_type: CompressionType, block_size: u32, threads: usize) !DecompMgr { return switch (comp_type) { .lzo => DecompError.LzoUnsupported, .lz4 => DecompError.Lz4Unsupported, else => .{ .alloc = alloc, .comp_type = comp_type, - .threads = try alloc.alloc(threads), + .block_size = block_size, + .threads = try alloc.alloc(DecompThread, threads), .to_start = threads, }, }; @@ -179,13 +182,13 @@ pub fn decompReader(self: *DecompMgr, rdr: *Reader, res: []u8) !usize { self.mut.lock(); var thr: *DecompThread = undefined; var node = self.queue.popFirst(); - if (self.node != null) { + if (node != null) { self.mut.unlock(); thr = @fieldParentPtr("node", node.?); } else blk: { defer self.mut.unlock(); if (self.to_start > 0) { - self.threads[self.to_start - 1] = .init(self); + self.threads[self.to_start - 1] = try .init(self); thr = &self.threads[self.to_start - 1]; self.to_start -= 1; break :blk; diff --git a/src/dir_entry.zig b/src/dir_entry.zig index 8147815..0eee76a 100644 --- a/src/dir_entry.zig +++ b/src/dir_entry.zig @@ -28,7 +28,7 @@ pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry { var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size. var hdr: Header = undefined; var raw: RawEntry = undefined; - var out: std.ArrayList(Entry) = .initCapacity(alloc, 0); + var out: std.ArrayList(Entry) = .empty; errdefer { for (out.items) |i| i.deinit(alloc); @@ -42,13 +42,14 @@ pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry { try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little); const name = try alloc.alloc(u8, raw.name_size + 1); try rdr.readSliceEndian(u8, name, .little); - _ = out.addOneAssumeCapacity(.{ + const val = out.addOneAssumeCapacity(); + val.* = .{ .block_start = hdr.block_start, .block_offset = raw.offset, .num = @abs(hdr.num + raw.offset), .inode_type = raw.inode_type, .name = name, - }); + }; cur_red += @sizeOf(RawEntry) + raw.name_size + 1; } } diff --git a/src/file.zig b/src/file.zig index b19df9e..90dff2b 100644 --- a/src/file.zig +++ b/src/file.zig @@ -24,7 +24,7 @@ name: []const u8, /// Initialize a new File. /// name is copied to the File so can be safely freed afterwards. pub fn init(archive: *Archive, inode: Inode, name: []const u8) !File { - const new_name = try archive.allocator().alloc(name.len); + const new_name = try archive.allocator().alloc(u8, name.len); @memcpy(new_name, name); return .{ .archive = archive, @@ -34,11 +34,11 @@ pub fn init(archive: *Archive, inode: Inode, name: []const u8) !File { } pub fn fromEntry(archive: *Archive, entry: DirEntry) !File { var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{}); - var meta: MetadataReader = .init(archive.allocator(), &rdr, &archive.decomp); + var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, &archive.decomp); try meta.interface.discardAll(entry.block_offset); const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size); - errdefer inode.deinit(); - const new_name = try archive.allocator().alloc(entry.name.len); + errdefer inode.deinit(archive.allocator()); + const new_name = try archive.allocator().alloc(u8, entry.name.len); @memcpy(new_name, entry.name); return .init(archive, inode, new_name); } @@ -49,7 +49,7 @@ pub fn deinit(self: File) void { self.inode.deinit(alloc); } -fn getEntries(self: *File) ![]DirEntry { +fn getEntries(self: File) ![]DirEntry { if (!self.isDir()) return FileError.NotDirectory; var block_start: u32 = undefined; var block_offset: u16 = undefined; @@ -67,25 +67,32 @@ fn getEntries(self: *File) ![]DirEntry { }, else => unreachable, } - var rdr = self.archive.fil.readerAt(self.archive.super.dir_start + block_start, &[0]u8{}); + var rdr = try self.archive.fil.readerAt(self.archive.super.dir_start + block_start, &[0]u8{}); const alloc = self.archive.allocator(); var meta: MetadataReader = .init(alloc, &rdr.interface, &self.archive.decomp); try meta.interface.discardAll(block_offset); return DirEntry.readDir(alloc, &rdr.interface, size); } +pub fn isDir(self: File) bool { + return switch (self.inode.hdr.inode_type) { + .dir, .ext_dir => true, + else => false, + }; +} + /// Open a file/folder within a directory at the given path. /// If path is ".", "/", or "./", this File is returned. -pub fn open(self: *File, path: []const u8) !File { +pub fn open(self: File, path: []const u8) !File { if (!self.isDir()) return FileError.NotDirectory; if (pathIsSelf(path)) return self; // Recursively stip ending & leading path separators. // TODO: potentially do this more efficiently or have stricter requirements. if (path[0] == '/') return self.open(path[1..]); if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]); - const idx = std.mem.indexOf(u8, path, '/') orelse path.len; + const idx = std.mem.indexOf(u8, path, "/") orelse path.len; const first_element = path[0..idx]; - if (std.mem.eql(first_element, ".")) return self; + if (std.mem.eql(u8, first_element, ".")) return self; const entries = try self.getEntries(); var cur_slice = entries; var split = cur_slice.len / 2; diff --git a/src/inode.zig b/src/inode.zig index 929881b..cd5407e 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -65,20 +65,20 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode { return .{ .hdr = hdr, .data = switch (hdr.inode_type) { - .dir => .{ .dir = .read(rdr) }, - .file => .{ .file = .read(alloc, rdr, block_size) }, - .symlink => .{ .symlink = .read(alloc, rdr) }, - .block_dev => .{ .block_dev = .read(rdr) }, - .char_dev => .{ .char_dev = .read(rdr) }, - .fifo => .{ .fifo = .read(rdr) }, - .socket => .{ .socket = .read(rdr) }, - .ext_dir => .{ .ext_dir = .read(rdr) }, - .ext_file => .{ .ext_file = .read(alloc, rdr, block_size) }, - .ext_symlink => .{ .ext_symlink = .read(alloc, rdr) }, - .ext_block_dev => .{ .ext_block_dev = .read(rdr) }, - .ext_char_dev => .{ .ext_char_dev = .read(rdr) }, - .ext_fifo => .{ .ext_fifo = .read(rdr) }, - .ext_socket => .{ .ext_socket = .read(rdr) }, + .dir => .{ .dir = try .read(rdr) }, + .file => .{ .file = try .read(alloc, rdr, block_size) }, + .symlink => .{ .symlink = try .read(alloc, rdr) }, + .block_dev => .{ .block_dev = try .read(rdr) }, + .char_dev => .{ .char_dev = try .read(rdr) }, + .fifo => .{ .fifo = try .read(rdr) }, + .socket => .{ .socket = try .read(rdr) }, + .ext_dir => .{ .ext_dir = try .read(rdr) }, + .ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) }, + .ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) }, + .ext_block_dev => .{ .ext_block_dev = try .read(rdr) }, + .ext_char_dev => .{ .ext_char_dev = try .read(rdr) }, + .ext_fifo => .{ .ext_fifo = try .read(rdr) }, + .ext_socket => .{ .ext_socket = try .read(rdr) }, }, }; } diff --git a/src/inode_data/dir.zig b/src/inode_data/dir.zig index c5c64cd..aa0216e 100644 --- a/src/inode_data/dir.zig +++ b/src/inode_data/dir.zig @@ -4,7 +4,7 @@ pub const Dir = packed struct { block_start: u32, hard_links: u32, size: u16, - block_offset: u32, + block_offset: u16, parent_num: u32, pub fn read(rdr: *Reader) !Dir { diff --git a/src/inode_data/file.zig b/src/inode_data/file.zig index 94c4404..12240f8 100644 --- a/src/inode_data/file.zig +++ b/src/inode_data/file.zig @@ -20,7 +20,7 @@ pub const File = struct { const size: u32 = std.mem.readInt(u32, start[12..16], .little); var num_blocks: u32 = size / block_size; if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1; - const sizes = try alloc.alloc(u32, num_blocks); + const sizes = try alloc.alloc(BlockSize, num_blocks); errdefer alloc.free(sizes); try rdr.readSliceEndian(BlockSize, sizes, .little); return .{ @@ -52,9 +52,9 @@ pub const ExtFile = struct { try rdr.readSliceEndian(u8, &start, .little); const frag_idx: u32 = std.mem.readInt(u32, start[28..32], .little); const size: u64 = std.mem.readInt(u64, start[8..16], .little); - var num_blocks: u32 = size / block_size; + var num_blocks: u32 = @truncate(size / block_size); if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1; - const sizes = try alloc.alloc(u32, num_blocks); + const sizes = try alloc.alloc(BlockSize, num_blocks); errdefer alloc.free(sizes); try rdr.readSliceEndian(BlockSize, sizes, .little); return .{ diff --git a/src/test.zig b/src/test.zig index 5f4336f..40f16b0 100644 --- a/src/test.zig +++ b/src/test.zig @@ -62,7 +62,7 @@ const LinuxPATestCorrectSuperblock: Superblock = .{ .ver_min = 0, .root_ref = .{ .block_offset = 1363, - .table_offset = 29237, + .block_start = 29237, ._ = 0, }, .size = 106841744, diff --git a/src/util/metadata.zig b/src/util/metadata.zig index c6992a4..1c8b7c8 100644 --- a/src/util/metadata.zig +++ b/src/util/metadata.zig @@ -20,7 +20,7 @@ decomp: *DecompMgr, buf: [8192]u8 = undefined, interface: Reader, -err: anyerror = 0, +err: ?anyerror = null, pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *DecompMgr) This { return .{ @@ -45,12 +45,12 @@ fn advance(self: *This) !void { var hdr: BlockHeader = undefined; try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little); if (hdr.uncompressed) { - try self.rdr.readSliceEndian(u8, &self.buf[0..hdr.size], .little); + try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little); self.interface.end = hdr.size; self.interface.buffer = self.buf[0..hdr.size]; return; } - var limit_rdr = self.rdr.limited(@enumFromInt(hdr.size), &[0]u8); + var limit_rdr = self.rdr.limited(@enumFromInt(hdr.size), &[0]u8{}); self.interface.end = try self.decomp.decompReader(&limit_rdr.interface, &self.buf); self.interface.buffer = self.buf[0..self.interface.end]; }