From f3fb8a128f3be7e3d8929e0508a3a8e79c0b15ba Mon Sep 17 00:00:00 2001 From: "Caleb J. Gardner" Date: Fri, 16 Jan 2026 06:53:10 -0600 Subject: [PATCH] Inodes! ExtractionOptions! Files! Directories! --- src/archive.zig | 64 +++++++++++++++++-- src/decomp.zig | 4 +- src/dir_entry.zig | 60 ++++++++++++++++++ src/file.zig | 133 ++++++++++++++++++++++++++++++++++++++++ src/inode.zig | 93 +++++++++++++++++++++++++++- src/inode_data/dir.zig | 32 ++++++++++ src/inode_data/file.zig | 75 ++++++++++++++++++++++ src/inode_data/misc.zig | 94 ++++++++++++++++++++++++++++ src/options.zig | 17 +++++ src/super.zig | 7 ++- src/table.zig | 21 ++++--- src/test.zig | 58 ++++++++++++++++-- src/util/metadata.zig | 8 +-- 13 files changed, 636 insertions(+), 30 deletions(-) create mode 100644 src/dir_entry.zig create mode 100644 src/file.zig create mode 100644 src/inode_data/dir.zig create mode 100644 src/inode_data/file.zig create mode 100644 src/inode_data/misc.zig create mode 100644 src/options.zig diff --git a/src/archive.zig b/src/archive.zig index 8926e8d..c4e9934 100644 --- a/src/archive.zig +++ b/src/archive.zig @@ -4,21 +4,47 @@ const std = @import("std"); const File = std.fs.File; +const DecompMgr = @import("decomp.zig"); +const ExtractionOptions = @import("options.zig"); +const Inode = @import("inode.zig"); +const InodeRef = Inode.Ref; +const SfsFile = @import("file.zig"); const Superblock = @import("super.zig").Superblock; +const Table = @import("table.zig").Table; +const MetadataReader = @import("util/metadata.zig"); const OffsetFile = @import("util/offset_file.zig"); +const FragEntry = packed struct { + start: u64, + size: packed struct { + size: u24, + uncompressed: bool, + _: u7, + }, + _: u32, +}; + const Archive = @This(); // 4 Gigs -const MIN_MEM_SIZE = 4 * 1024 * 1024 * 1024; +const MEM_SIZE = 4 * 1024 * 1024 * 1024; parent_alloc: std.mem.Allocator, alloc: std.heap.FixedBufferAllocator, fixed_buf: []u8, + fil: OffsetFile, super: Superblock, +setup: bool = false, + +decomp: DecompMgr = undefined, + +frag_table: Table(FragEntry) = undefined, +id_table: Table(u16) = undefined, +export_table: Table(InodeRef) = undefined, + /// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage. pub fn init(alloc: std.mem.Allocator, fil: File) !Archive { return initAdvanced( @@ -26,7 +52,7 @@ pub fn init(alloc: std.mem.Allocator, fil: File) !Archive { fil, 0, try std.Thread.getCpuCount(), - @min(MIN_MEM_SIZE, std.process.totalSystemMemory() / 2), + @min(MEM_SIZE, try std.process.totalSystemMemory() / 2), ); } /// Create the Archive dictating the amount of threads & memory used. @@ -37,7 +63,8 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u var super: Superblock = undefined; const red = try fil.pread(@ptrCast(&super), offset); std.debug.assert(red == @sizeOf(Superblock)); - const fixed_buf = alloc.alloc(u8, mem); + try super.validate(); + const fixed_buf = try alloc.alloc(u8, mem); return .{ .parent_alloc = alloc, .alloc = .init(fixed_buf), @@ -47,7 +74,36 @@ pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: u .super = super, }; } - pub fn deinit(self: *Archive) void { self.parent_alloc.free(self.fixed_buf); + if (self.setup) { + self.decomp.deinit(); + self.frag_table.deinit(); + self.export_table.deinit(); + self.id_table.deinit(); + } +} + +pub fn allocator(self: *Archive) std.mem.Allocator { + return self.alloc.threadSafeAllocator(); +} + +pub fn root(self: *Archive) !SfsFile { + 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); + 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 { + 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 { + 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 07d0f77..8bf800f 100644 --- a/src/decomp.zig +++ b/src/decomp.zig @@ -93,7 +93,7 @@ pub const DecompThread = struct { self.res_size = blk: switch (comp_type) { .gzip => { var decomp_rdr = compress.flate.Decompress.init(rdr, .zlib, self.buf); - break :blk decomp_rdr.reader.readSliceAll(self.res); + break :blk decomp_rdr.reader.readSliceEndian(u8, self.res, .little); }, .lzma => { var decomp_rdr = compress.lzma.decompress(self.mgr.alloc, rdr.adaptToOldInterface()) catch |err| { @@ -109,7 +109,7 @@ pub const DecompThread = struct { }, .zstd => { var decomp_rdr = compress.zstd.Decompress.init(rdr, self.buf, .{}); - break :blk decomp_rdr.reader.readSliceAll(self.res); + break :blk decomp_rdr.reader.readSliceEndian(u8, self.res, .little); }, else => unreachable, }; diff --git a/src/dir_entry.zig b/src/dir_entry.zig new file mode 100644 index 0000000..8147815 --- /dev/null +++ b/src/dir_entry.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +const InodeType = @import("inode.zig").InodeType; + +const Entry = @This(); + +const Header = extern struct { // use extern due to bad alignment with packed. + count: u32, + block_start: u32, + num: u32, +}; + +const RawEntry = packed struct { + offset: u16, + inode_offset: i16, + inode_type: InodeType, + name_size: u16, +}; + +block_start: u32, +block_offset: u16, +num: u32, +inode_type: InodeType, +name: []const u8, + +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); + errdefer { + for (out.items) |i| + i.deinit(alloc); + out.deinit(alloc); + } + while (cur_red < size) { + try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + cur_red += @sizeOf(Header); + try out.ensureUnusedCapacity(alloc, hdr.num + 1); + for (0..hdr.num + 1) |_| { + 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(.{ + .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; + } + } + return out.toOwnedSlice(alloc); +} + +pub fn deinit(self: Entry, alloc: std.mem.Allocator) void { + alloc.free(self.name); +} diff --git a/src/file.zig b/src/file.zig new file mode 100644 index 0000000..b19df9e --- /dev/null +++ b/src/file.zig @@ -0,0 +1,133 @@ +const std = @import("std"); +const WaitGroup = std.Thread.WaitGroup; +const Mutex = std.Thread.Mutex; + +const Archive = @import("archive.zig"); +const DirEntry = @import("dir_entry.zig"); +const ExtractionOptions = @import("options.zig"); +const Inode = @import("inode.zig"); +const MetadataReader = @import("util/metadata.zig"); + +const FileError = error{ + NotDirectory, + NotRegularFile, + NotFound, +}; + +const File = @This(); + +archive: *Archive, + +inode: Inode, +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); + @memcpy(new_name, name); + return .{ + .archive = archive, + .inode = inode, + .name = new_name, + }; +} +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); + 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); + @memcpy(new_name, entry.name); + return .init(archive, inode, new_name); +} + +pub fn deinit(self: File) void { + var alloc = self.archive.allocator(); + alloc.free(self.name); + self.inode.deinit(alloc); +} + +fn getEntries(self: *File) ![]DirEntry { + if (!self.isDir()) return FileError.NotDirectory; + var block_start: u32 = undefined; + var block_offset: u16 = undefined; + var size: u32 = undefined; + switch (self.inode.data) { + .dir => |d| { + block_start = d.block_start; + block_offset = d.block_offset; + size = d.size; + }, + .ext_dir => |d| { + block_start = d.block_start; + block_offset = d.block_offset; + size = d.size; + }, + else => unreachable, + } + var rdr = 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); +} + +/// 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 { + 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 first_element = path[0..idx]; + if (std.mem.eql(first_element, ".")) return self; + const entries = try self.getEntries(); + var cur_slice = entries; + var split = cur_slice.len / 2; + while (cur_slice.len == 0) { + split = cur_slice.len / 2; + const comp = std.mem.order(u8, entries[split].name, first_element); + switch (comp) { + .eq => { + var fil: File = try .fromEntry(self.archive, cur_slice[split]); + if (idx == path.len) { + return fil; + } + defer fil.deinit(); + return fil.open(path[idx + 1 ..]); + }, + .lt => cur_slice = cur_slice[0..split], + .gt => cur_slice = cur_slice[split..], + } + } + return FileError.NotFound; +} + +pub fn extract(self: *File, path: []const u8, options: ExtractionOptions) !void { + _ = self; + _ = path; + _ = options; + return error.TODO; +} + +const ParentInfo = struct { + fil: *File, +}; + +fn extractReal(self: *File, path: []const u8, options: ExtractionOptions) void { + _ = self; + _ = path; + _ = options; +} + +pub fn pathIsSelf(path: []const u8) bool { + if (path.len == 0) return true; + if (path.len == 1 and (path[0] == '/' or path[0] == '.')) return true; + if (path.len == 2 and (path[0] == '.' and path[1] == '/')) return true; + return false; +} diff --git a/src/inode.zig b/src/inode.zig index 1e92a2d..929881b 100644 --- a/src/inode.zig +++ b/src/inode.zig @@ -1,5 +1,94 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +const dir = @import("inode_data/dir.zig"); +const file = @import("inode_data/file.zig"); +const misc = @import("inode_data/misc.zig"); + pub const Ref = packed struct { - _: u16, - table_offset: u32, block_offset: u16, + block_start: u32, + _: u16, }; + +pub const InodeType = enum(u16) { + dir = 1, + file, + symlink, + block_dev, + char_dev, + fifo, + socket, + ext_dir, + ext_file, + ext_symlink, + ext_block_dev, + ext_char_dev, + ext_fifo, + ext_socket, +}; + +pub const InodeData = union(InodeType) { + dir: dir.Dir, + file: file.File, + symlink: misc.Symlink, + block_dev: misc.Dev, + char_dev: misc.Dev, + fifo: misc.IPC, + socket: misc.IPC, + ext_dir: dir.ExtDir, + ext_file: file.ExtFile, + ext_symlink: misc.ExtSymlink, + ext_block_dev: misc.ExtDev, + ext_char_dev: misc.ExtDev, + ext_fifo: misc.ExtIPC, + ext_socket: misc.ExtIPC, +}; + +pub const Header = packed struct { + inode_type: InodeType, + permissions: u16, + uid_idx: u16, + gid_idx: u16, + mod_time: u32, + num: u32, +}; + +const Inode = @This(); + +hdr: Header, +data: InodeData, + +pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode { + var hdr: Header = undefined; + try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); + 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) }, + }, + }; +} + +pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { + switch (self.data) { + .file => |f| alloc.free(f.block_sizes), + .ext_file => |f| alloc.free(f.block_sizes), + .symlink => |s| alloc.free(s.target), + .ext_symlink => |s| alloc.free(s.target), + else => {}, + } +} diff --git a/src/inode_data/dir.zig b/src/inode_data/dir.zig new file mode 100644 index 0000000..c5c64cd --- /dev/null +++ b/src/inode_data/dir.zig @@ -0,0 +1,32 @@ +const Reader = @import("std").Io.Reader; + +pub const Dir = packed struct { + block_start: u32, + hard_links: u32, + size: u16, + block_offset: u32, + parent_num: u32, + + pub fn read(rdr: *Reader) !Dir { + var d: Dir = undefined; + try rdr.readSliceEndian(Dir, @ptrCast(&d), .little); + return d; + } +}; + +pub const ExtDir = packed struct { + hard_links: u32, + size: u32, + block_start: u32, + parent_num: u32, + idx_count: u16, + block_offset: u16, + xattr_id: u32, + // index: []DirIndex + + pub fn read(rdr: *Reader) !ExtDir { + var d: ExtDir = undefined; + try rdr.readSliceEndian(Dir, @ptrCast(&d), .little); + return d; + } +}; diff --git a/src/inode_data/file.zig b/src/inode_data/file.zig new file mode 100644 index 0000000..94c4404 --- /dev/null +++ b/src/inode_data/file.zig @@ -0,0 +1,75 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +pub const BlockSize = packed struct { + size: u31, + uncompressed: bool, +}; + +pub const File = struct { + block_start: u32, // bytes 0-3 + frag_idx: u32, // bytes 4-7 + frag_block_offset: u32, // bytes 8-11 + size: u32, // bytes 12-15 + block_sizes: []BlockSize, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File { + var start: [16]u8 = undefined; + try rdr.readSliceEndian(u8, &start, .little); + const frag_idx: u32 = std.mem.readInt(u32, start[4..8], .little); + 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); + errdefer alloc.free(sizes); + try rdr.readSliceEndian(BlockSize, sizes, .little); + return .{ + .block_start = std.mem.readInt(u32, start[0..4], .little), + .frag_idx = frag_idx, + .frag_block_offset = std.mem.readInt(u32, start[8..12], .little), + .size = size, + .block_sizes = sizes, + }; + } + + pub fn deinit(self: File, alloc: std.mem.Allocator) void { + alloc.free(self.block_sizes); + } +}; + +pub const ExtFile = struct { + block_start: u64, // bytes 0-7 + size: u64, // bytes 8-15 + sparse: u64, // bytes 16-23 + hard_links: u32, // bytes 24-27 + frag_idx: u32, // bytes 28-31 + frag_block_offset: u32, // bytes 32-35 + xattr_idx: u32, // bytes 36-39 + block_sizes: []BlockSize, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile { + var start: [40]u8 = undefined; + 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; + if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1; + const sizes = try alloc.alloc(u32, num_blocks); + errdefer alloc.free(sizes); + try rdr.readSliceEndian(BlockSize, sizes, .little); + return .{ + .block_start = std.mem.readInt(u64, start[0..8], .little), + .size = size, + .sparse = std.mem.readInt(u64, start[16..24], .little), + .hard_links = std.mem.readInt(u32, start[24..28], .little), + .frag_idx = frag_idx, + .frag_block_offset = std.mem.readInt(u32, start[32..36], .little), + .xattr_idx = std.mem.readInt(u32, start[36..40], .little), + .block_sizes = sizes, + }; + } + + pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void { + alloc.free(self.block_sizes); + } +}; diff --git a/src/inode_data/misc.zig b/src/inode_data/misc.zig new file mode 100644 index 0000000..2bed10e --- /dev/null +++ b/src/inode_data/misc.zig @@ -0,0 +1,94 @@ +const std = @import("std"); +const Reader = std.Io.Reader; + +pub const Symlink = struct { + hard_links: u32, + target: []const u8, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink { + var start: [8]u8 = undefined; + try rdr.readSliceEndian(u8, &start, .little); + const target_size = std.mem.readInt(u32, start[4..8], .little); + const target = try alloc.alloc(u8, target_size + 1); + errdefer alloc.free(target); + try rdr.readSliceEndian(u8, target, .little); + return .{ + .hard_links = std.mem.readInt(u32, start[0..4], .little), + .target = target, + }; + } + + pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void { + alloc.free(self.target); + } +}; + +pub const ExtSymlink = struct { + hard_links: u32, + target: []const u8, + xattr_idx: u32, + + pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink { + var start: [8]u8 = undefined; + try rdr.readSliceEndian(u8, &start, .little); + const target_size = std.mem.readInt(u32, start[4..8], .little); + const target = try alloc.alloc(u8, target_size + 1); + errdefer alloc.free(target); + try rdr.readSliceEndian(u8, target, .little); + var xattr_idx: u32 = undefined; + try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little); + return .{ + .hard_links = std.mem.readInt(u32, start[0..4], .little), + .target = target, + .xattr_idx = xattr_idx, + }; + } + + pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void { + alloc.free(self.target); + } +}; + +pub const Dev = packed struct { + hard_links: u32, + dev: u32, + + pub fn read(rdr: *Reader) !Dev { + var d: Dev = undefined; + try rdr.readSliceEndian(Dev, @ptrCast(&d), .little); + return d; + } +}; + +pub const ExtDev = packed struct { + hard_links: u32, + dev: u32, + xattr_idx: u32, + + pub fn read(rdr: *Reader) !ExtDev { + var d: ExtDev = undefined; + try rdr.readSliceEndian(ExtDev, @ptrCast(&d), .little); + return d; + } +}; + +pub const IPC = packed struct { + hard_links: u32, + + pub fn read(rdr: *Reader) !IPC { + var d: IPC = undefined; + try rdr.readSliceEndian(IPC, @ptrCast(&d), .little); + return d; + } +}; + +pub const ExtIPC = packed struct { + hard_links: u32, + xattr_idx: u32, + + pub fn read(rdr: *Reader) !ExtIPC { + var d: ExtIPC = undefined; + try rdr.readSliceEndian(ExtIPC, @ptrCast(&d), .little); + return d; + } +}; diff --git a/src/options.zig b/src/options.zig new file mode 100644 index 0000000..e681331 --- /dev/null +++ b/src/options.zig @@ -0,0 +1,17 @@ +const Writer = @import("std").Io.Writer; + +const ExtractionOptions = @This(); + +/// Don't set the file's permissions after extraction +ignorePermissions: bool = false, +/// Don't set the file's owner after extraction. +ignoreOwner: bool = false, +/// Replace symlinks with their target. +dereferenceSymlinks: bool = false, + +verbose: bool = false, +/// If options verbose and verboseWriter not set, logs are printed to stdout. +verboseWriter: ?Writer = null, + +pub const Default: ExtractionOptions = .{}; +pub const VerboseDefault: ExtractionOptions = .{ .verbose = true }; diff --git a/src/super.zig b/src/super.zig index fc28f5c..c1b6c65 100644 --- a/src/super.zig +++ b/src/super.zig @@ -1,9 +1,10 @@ -const math = @import("std").math; +const std = @import("std"); +const math = std.math; const CompressionType = @import("decomp.zig").CompressionType; const InodeRef = @import("inode.zig").Ref; -const SQUASHFS_MAGIC: u32 = "hsqs"; +const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little); const SuperblockError = error{ InvalidMagic, @@ -52,7 +53,7 @@ pub const Superblock = packed struct { pub fn validate(self: Superblock) !void { if (self.magic != SQUASHFS_MAGIC) return SuperblockError.InvalidMagic; - if (self.magic.flags.check) + if (self.flags.check) return SuperblockError.InvalidCheck; if (self.ver_maj != 4 or self.ver_min != 0) return SuperblockError.InvalidVersion; diff --git a/src/table.zig b/src/table.zig index 8ad3092..6114904 100644 --- a/src/table.zig +++ b/src/table.zig @@ -11,7 +11,7 @@ const TableError = error{ pub fn Table(T: anytype) type { return struct { - const This = @This(); + const Self = @This(); const VALS_PER_BLOCK = 8192 / @sizeOf(T); @@ -20,12 +20,12 @@ pub fn Table(T: anytype) type { decomp: *DecompMgr, tab_start: u64, - mut: Mutex = .{}, - tab: std.AutoHashMap(u32, []T), values: u32, - pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *DecompMgr, tab_start: u64, values: u32) !This { + mut: Mutex = .{}, + + pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *DecompMgr, tab_start: u64, values: u32) !Self { return .{ .alloc = alloc, .fil = fil, @@ -37,15 +37,15 @@ pub fn Table(T: anytype) type { }; } - pub fn deinit(self: *This) void { + pub fn deinit(self: *Self) void { var iter = self.tab.valueIterator(); - for (iter.next()) |s| { - self.alloc.free(s); + while (iter.next()) |s| { + self.alloc.free(s.*); } self.tab.deinit(); } - pub fn get(self: *This, idx: u32) !T { + pub fn get(self: *Self, idx: u32) !T { if (idx >= self.values) return TableError.InvalidIndex; const block_num = idx / VALS_PER_BLOCK; const idx_offset = idx - (block_num * VALS_PER_BLOCK); @@ -65,11 +65,12 @@ pub fn Table(T: anytype) type { const slice = try self.alloc.alloc(slice_size); var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{}); const offset: u64 = 0; - try rdr.interface.readSliceAll(@ptrCast(&idx_offset)); + try rdr.interface.readSliceEndian(u64, @ptrCast(&idx_offset), .little); rdr = try self.fil.readerAt(offset, &[0]u8{}); var meta: MetadataReader = .init(&rdr.interface, self.decomp); - try meta.interface.readSliceAll(@ptrCast(slice)); + try meta.interface.readSliceEndian(T, @ptrCast(slice), .little); try self.tab.put(block_num, slice); + return slice[idx_offset]; } }; } diff --git a/src/test.zig b/src/test.zig index 31dba94..5f4336f 100644 --- a/src/test.zig +++ b/src/test.zig @@ -2,26 +2,74 @@ const std = @import("std"); const stuff = @import("builtin"); const Archive = @import("archive.zig"); -const Superblock = @import("super.zig"); +const Superblock = @import("super.zig").Superblock; const TestArchive = "testing/LinuxPATest.sfs"; test "Basics" { - var fil = try std.fs.cwd().openFile(TestArchive); + var fil = try std.fs.cwd().openFile(TestArchive, .{}); defer fil.close(); var sfs: Archive = try .init(std.testing.allocator, fil); defer sfs.deinit(); + if (sfs.super != LinuxPATestCorrectSuperblock) { + std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super }); + return error.BadSuperblock; + } } const TestFile = "Start.exe"; const TestFileExtractLocation = "testing/Start.exe"; -test "ExtractSingleFile" {} +test "ExtractSingleFile" { + var fil = try std.fs.cwd().openFile(TestArchive, .{}); + defer fil.close(); + var sfs: Archive = try .init(std.testing.allocator, fil); + defer sfs.deinit(); + var test_fil = try sfs.open(TestFile); + try test_fil.extract(TestFileExtractLocation, .VerboseDefault); + //TODO: validate extracted file. +} const TestFullExtractLocation = "testing/TestExtract"; test "ExtractCompleteArchive" {} -const CorrectSuperblock = Superblock{ - .magic = "hsqs", +const LinuxPATestCorrectSuperblock: Superblock = .{ + .magic = std.mem.readInt(u32, "hsqs", .little), + .inode_count = 2974, + .mod_time = 1632696724, + .block_size = 131072, + .frag_count = 264, + .compression = .zstd, + .block_log = 17, + .flags = .{ + .inode_uncompressed = false, + .data_uncompressed = false, + .check = false, + .frag_uncompressed = false, + .fragment_never = false, + .fragment_always = false, + .duplicates = true, + .exportable = true, + .xattr_uncompressed = false, + .xattr_never = false, + .compression_options = false, + .ids_uncompressed = false, + ._ = 0, + }, + .id_count = 1, + .ver_maj = 4, + .ver_min = 0, + .root_ref = .{ + .block_offset = 1363, + .table_offset = 29237, + ._ = 0, + }, + .size = 106841744, + .id_start = 106841632, + .xattr_start = 106841720, + .inode_start = 106778274, + .dir_start = 106807998, + .frag_start = 106837747, + .export_start = 106841602, }; diff --git a/src/util/metadata.zig b/src/util/metadata.zig index ac10431..c6992a4 100644 --- a/src/util/metadata.zig +++ b/src/util/metadata.zig @@ -14,7 +14,7 @@ const BlockHeader = packed struct { const This = @This(); alloc: std.mem.Allocator, -rdr: Reader, +rdr: *Reader, decomp: *DecompMgr, buf: [8192]u8 = undefined, @@ -22,7 +22,7 @@ buf: [8192]u8 = undefined, interface: Reader, err: anyerror = 0, -pub fn init(alloc: std.mem.Allocator, rdr: Reader, decomp: *DecompMgr) This { +pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *DecompMgr) This { return .{ .alloc = alloc, .rdr = rdr, @@ -43,9 +43,9 @@ pub fn init(alloc: std.mem.Allocator, rdr: Reader, decomp: *DecompMgr) This { fn advance(self: *This) !void { self.interface.seek = 0; var hdr: BlockHeader = undefined; - try self.rdr.readSliceAll(@ptrCast(&hdr)); + try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little); if (hdr.uncompressed) { - try self.rdr.readSliceAll(&self.buf[0..hdr.size]); + 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;