1 Commits

Author SHA1 Message Date
Caleb J. Gardner 9a710b98cf Moved a lot of things to have explicit alloc arguments
Changed Table to be stateless
2026-02-15 10:31:42 -06:00
14 changed files with 180 additions and 228 deletions
+6 -6
View File
@@ -15,17 +15,17 @@ jobs:
- name: Install deps - name: Install deps
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
- name: Build normal version - name: Build normal version
run: zig build --release=fast -Dversion=${{ github.ref_name }} run: zig build -Drelease=true -Dversion=${{ github.ref_name }}
- name: Move normal build out - name: Move normal build out
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64 run: mv zig-out/bin/unsquashfs ./
- name: Rebuild with C libraries - name: Rebuild with C libraries
run: zig build --release=fast -Duse_c_libs=true -Dversion="${{ github.ref_name }}" run: zig build -Drelease=true -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
- name: Move C build out - name: Move C build out
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
prerelease: true prerelease: true
files: | files: |
unsquashfs-x86_64 unsquashfs
unsquashfs-x86_64-c-libs unsquashfs-c-libs
+6 -26
View File
@@ -6,23 +6,19 @@ A library and application to decompress or view squashfs archives.
## Current State ## Current State
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)). Overall works, but currently is missing some features (see below). Extraction is a bit slow compared to the normal `unsquashfs` (from my _very_ basic testing it's about ~3x slower). Only properly work on Linux, any other OSes probably won't work fully and are untested.
## Build options ## Build options
> `-Duse_c_libs=true` > `-Duse_c_libs`
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression. Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
> `-Dallow_lzo=true` > `-Dallow_lzo`
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`. Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
> `-Dvalgrind=true` > `-Dversion`
Just sets the valgrind build option.
> `-Dversion=0.0.0`
Sets the version of `unsquashfs` shown when `--version` is passed. Sets the version of `unsquashfs` shown when `--version` is passed.
@@ -30,27 +26,11 @@ Sets the version of `unsquashfs` shown when `--version` is passed.
Most features are present except for the following: Most features are present except for the following:
* mod_time is not set on extraction
* xattrs are not applied on extraction * xattrs are not applied on extraction
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular. * When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
## Performance ## Building considerations
This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) and with `--release=fast`.
* Under ideal circumstances, my library is ~70% slower (.11s vs .18s)
* Mutli-threading on small archives noticably increases extraction times (when using C libraries) (.18s vs .57s). This should theoretically reverse on larger archives with many inodes, but I haven't tested that yet.
* Using Zig libraries *significantly* increases decompression time by ~600% under ideal circumstances.
Times:
* *unsquashfs*: .11s
* *C-libs, single-threaded*: .18s
* *C-libs, multi-threaded*: .57s
* *Zig-libs, single-threaded*: 5.87s
* *Zig-libs, multi-threaded*: 1.10s
## Build considerations
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building. Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
+12 -13
View File
@@ -3,7 +3,6 @@ const std = @import("std");
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones"); const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support"); const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support");
const valgrind = b.option(bool, "valgrind", "Compile with valgrind integration");
const version_string_option = b.option([]const u8, "version", "Version of the library/binary"); const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
const zig_squashfs_options = b.addOptions(); const zig_squashfs_options = b.addOptions();
@@ -11,13 +10,12 @@ pub fn build(b: *std.Build) !void {
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false); zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false);
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
const mod = b.addModule("zig_squashfs", .{ const mod = b.addModule("zig_squashfs", .{
.root_source_file = b.path("src/root.zig"), .root_source_file = b.path("src/root.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = use_c_libs_option, .link_libc = if (use_c_libs_option == true) true else false,
.valgrind = valgrind,
}); });
mod.addOptions("config", zig_squashfs_options); mod.addOptions("config", zig_squashfs_options);
if (use_c_libs_option == true) { if (use_c_libs_option == true) {
@@ -42,11 +40,10 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/bin/unsquashfs.zig"), .root_source_file = b.path("src/bin/unsquashfs.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = use_c_libs_option, .link_libc = if (use_c_libs_option == true) true else false,
.imports = &.{ .imports = &.{
.{ .name = "zig_squashfs", .module = mod }, .{ .name = "zig_squashfs", .module = mod },
}, },
.valgrind = valgrind,
}); });
exe_mod.addOptions("config", unsquashfs_options); exe_mod.addOptions("config", unsquashfs_options);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
@@ -62,6 +59,13 @@ pub fn build(b: *std.Build) !void {
b.installArtifact(lib); b.installArtifact(lib);
b.installArtifact(exe); b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const mod_tests = b.addTest(.{ const mod_tests = b.addTest(.{
.root_module = mod, .root_module = mod,
}); });
@@ -74,16 +78,11 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&run_mod_tests.step); test_step.dependOn(&run_mod_tests.step);
test_step.dependOn(&run_exe_tests.step); test_step.dependOn(&run_exe_tests.step);
// zls build check steps // Wanted by ZLS for better detection.
const lib_check = b.addLibrary(.{
.name = "squashfs",
.root_module = mod,
});
const exe_check = b.addExecutable(.{ const exe_check = b.addExecutable(.{
.name = "unsquashfs", .name = "unsquashfs",
.root_module = exe_mod, .root_module = mod,
}); });
const check = b.step("check", "Check if unsquashfs compiles"); const check = b.step("check", "Check if unsquashfs compiles");
check.dependOn(&lib_check.step);
check.dependOn(&exe_check.step); check.dependOn(&exe_check.step);
} }
+41 -35
View File
@@ -31,24 +31,21 @@ pub const FragEntry = packed struct {
const Archive = @This(); const Archive = @This();
alloc: std.mem.Allocator,
fil: OffsetFile, fil: OffsetFile,
decomp: Decomp.DecompFn,
super: Superblock, super: Superblock,
frag_table: Table(FragEntry) = undefined, decomp: Decomp.DecompFn,
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. frag_table: Table(FragEntry),
pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive { id_table: Table(u16),
export_table: Table(InodeRef),
/// Begin reading a squashfs archive from the given File at the given offset.
pub fn init(fil: File, offset: u64) !Archive {
var super: Superblock = undefined; var super: Superblock = undefined;
const red = try fil.pread(@ptrCast(&super), offset); _ = try fil.pread(@ptrCast(&super), offset);
std.debug.assert(red == @sizeOf(Superblock));
try super.validate(); try super.validate();
const off_fil: OffsetFile = .init(fil, offset);
const decomp: Decomp.DecompFn = switch (super.compression) { const decomp: Decomp.DecompFn = switch (super.compression) {
.gzip => Decomp.gzipDecompress, .gzip => Decomp.gzipDecompress,
.lzma => Decomp.lzmaDecompress, .lzma => Decomp.lzmaDecompress,
@@ -57,51 +54,60 @@ pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive {
.lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported, .lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported,
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported, .lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
}; };
const offset_fil: OffsetFile = .init(fil, offset);
return .{ return .{
.alloc = alloc, .fil = offset_fil,
.fil = off_fil,
.decomp = decomp,
.super = super, .super = super,
.frag_table = try .init(alloc, off_fil, decomp, super.frag_start, super.frag_count), .decomp = decomp,
.id_table = try .init(alloc, off_fil, decomp, super.id_start, super.id_count),
.export_table = try .init(alloc, off_fil, decomp, super.export_start, super.inode_count), .frag_table = .init(offset_fil, decomp, super.frag_start, super.frag_count),
.id_table = .init(offset_fil, decomp, super.id_start, super.id_count),
.export_table = .init(offset_fil, decomp, super.export_start, super.inode_count),
}; };
} }
pub fn deinit(self: *Archive) void {
self.frag_table.deinit();
self.export_table.deinit();
self.id_table.deinit();
}
pub fn inode(self: *Archive, alloc: std.mem.Allocator, num: u32) !Inode { pub fn inode(self: Archive, num: u32) !Inode {
if (!self.setup) try self.setupValues();
const ref = try self.export_table.get(num - 1); const ref = try self.export_table.get(num - 1);
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{}); var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
var meta: MetadataReader = .init(alloc, &rdr.interface, &self.decomp); var meta: MetadataReader = .init(self.alloc, &rdr.interface, &self.decomp);
try meta.interface.discardAll(ref.block_offset); try meta.interface.discardAll(ref.block_offset);
return try .read(alloc, &meta.interface, self.super.block_size); return try .read(self.alloc, &meta.interface, self.super.block_size);
} }
pub fn root(self: *Archive, alloc: std.mem.Allocator) !SfsFile { pub fn root(self: Archive, alloc: std.mem.Allocator) !SfsFile {
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{}); var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp); var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
try meta.interface.discardAll(self.super.root_ref.block_offset); try meta.interface.discardAll(self.super.root_ref.block_offset);
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size); const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
return .init(self, in, ""); return .init(alloc, self, in, "");
} }
pub fn open(self: *Archive, alloc: std.mem.Allocator, path: []const u8) !SfsFile { pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !SfsFile {
var root_fil = try self.root(alloc); var root_fil = try self.root(alloc);
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit(); defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
return root_fil.open(path); return root_fil.open(alloc, path);
} }
pub fn extract(self: *Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void { pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
var ext_path: []u8 = undefined;
if (std.fs.cwd().statFile(path)) |stat| {
if (stat.kind == .directory) {
ext_path = @constCast(path);
} else return error.ExtractionPathExists;
} else |err| {
if (err == error.FileNotFound) {
ext_path = @constCast(path);
} else {
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
return err;
}
}
defer if (ext_path.len > path.len) alloc.free(ext_path);
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{}); var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp); var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
try meta.interface.discardAll(self.super.root_ref.block_offset); try meta.interface.discardAll(self.super.root_ref.block_offset);
const in: Inode = try .read(self.alloc, &meta.interface, self.super.block_size); const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
try in.extractTo(alloc, self, path, options); try in.extractTo(alloc, self, ext_path, options);
} }
+2 -8
View File
@@ -44,14 +44,8 @@ pub fn main() !void {
} }
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully. var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
defer fil.close(); defer fil.close();
var arc: squashfs.Archive = try .init(alloc, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully. var arc: squashfs.Archive = try .init(fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
defer arc.deinit(); try arc.extract(alloc, extLoc, if (verbose) try .VerboseDefault(&out.interface) else try .Default()); //TODO: Handle error gracefully.
const options: squashfs.ExtractionOptions = .{
.threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
.verbose = verbose,
.verbose_writer = if (verbose) &out.interface else null,
};
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
} }
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void { fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
+1
View File
@@ -29,6 +29,7 @@ pub const CompressionType = enum(u16) {
zstd, zstd,
}; };
/// A generic decompression function. alloc is only used for internal use and any allocations made will be freed.
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types. pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip; pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
+26 -24
View File
@@ -24,39 +24,40 @@ const FileError = error{
const SfsFile = @This(); const SfsFile = @This();
archive: *Archive, alloc: std.mem.Allocator,
archive: Archive,
inode: Inode, inode: Inode,
name: []const u8, name: []const u8,
/// Initialize a new File. /// Initialize a new File.
/// name is copied to the File so can be safely freed afterwards. /// name is copied to the File so can be safely freed afterwards.
pub fn init(archive: *Archive, inode: Inode, name: []const u8) !SfsFile { pub fn init(alloc: std.mem.Allocator, archive: Archive, inode: Inode, name: []const u8) !SfsFile {
const new_name = try archive.allocator().alloc(u8, name.len); const new_name = try alloc.alloc(u8, name.len);
@memcpy(new_name, name); @memcpy(new_name, name);
return .{ return .{
.alloc = alloc,
.archive = archive, .archive = archive,
.inode = inode, .inode = inode,
.name = new_name, .name = new_name,
}; };
} }
pub fn fromEntry(archive: *Archive, entry: DirEntry) !SfsFile { pub fn fromEntry(alloc: std.mem.Allocator, archive: Archive, entry: DirEntry) !SfsFile {
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{}); var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, archive.decomp); var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
try meta.interface.discardAll(entry.block_offset); try meta.interface.discardAll(entry.block_offset);
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size); const inode: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
errdefer inode.deinit(archive.allocator()); errdefer inode.deinit(alloc);
return .init(archive, inode, entry.name); return .init(alloc, archive, inode, entry.name);
} }
pub fn deinit(self: SfsFile) void { pub fn deinit(self: SfsFile) void {
var alloc = self.archive.allocator(); self.alloc.free(self.name);
alloc.free(self.name); self.inode.deinit(self.alloc);
self.inode.deinit(alloc);
} }
fn getEntries(self: SfsFile) ![]DirEntry { fn getEntries(self: SfsFile, alloc: std.mem.Allocator) ![]DirEntry {
return self.inode.dirEntries(self.archive.allocator(), self.archive.*); return self.inode.dirEntries(alloc, self.archive);
} }
pub fn ownerUid(self: SfsFile) !u16 { pub fn ownerUid(self: SfsFile) !u16 {
@@ -101,13 +102,13 @@ pub fn open(self: SfsFile, alloc: std.mem.Allocator, path: []const u8) !SfsFile
if (pathIsSelf(path)) return self; if (pathIsSelf(path)) return self;
// Recursively stip ending & leading path separators. // Recursively stip ending & leading path separators.
if (path[0] == '/') return self.open(path[1..]); if (path[0] == '/') return self.open(alloc, path[1..]);
if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]); if (path[path.len - 1] == '/') return self.open(alloc, 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]; const first_element = path[0..idx];
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]); if (std.mem.eql(u8, first_element, ".")) return self.open(alloc, path[idx + 1 ..]);
const entries = try self.getEntries(); const entries = try self.getEntries(alloc);
defer { defer {
for (entries) |e| { for (entries) |e| {
e.deinit(alloc); e.deinit(alloc);
@@ -121,7 +122,7 @@ pub fn open(self: SfsFile, alloc: std.mem.Allocator, path: []const u8) !SfsFile
const comp = std.mem.order(u8, first_element, cur_slice[split].name); const comp = std.mem.order(u8, first_element, cur_slice[split].name);
switch (comp) { switch (comp) {
.eq => { .eq => {
var fil: SfsFile = try .fromEntry(self.archive, cur_slice[split]); var fil: SfsFile = try .fromEntry(alloc, self.archive, cur_slice[split]);
if (idx == path.len) { if (idx == path.len) {
return fil; return fil;
} }
@@ -169,8 +170,8 @@ pub fn devNum(self: SfsFile) !u32 {
/// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist. /// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist.
/// If the gievn path is a folder, the File's contents will be extracted within. /// If the gievn path is a folder, the File's contents will be extracted within.
pub fn extract(self: *SfsFile, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void { pub fn extract(self: SfsFile, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
return self.inode.extractToThreaded(alloc, self.archive, path, options); return self.inode.extractTo(alloc, self.archive, path, options);
} }
/// Utility function. /// Utility function.
@@ -182,8 +183,10 @@ pub fn pathIsSelf(path: []const u8) bool {
} }
pub const Iterator = struct { pub const Iterator = struct {
alloc: std.mem.Allocator,
entries: []DirEntry, entries: []DirEntry,
archive: *Archive, archive: Archive,
idx: u32 = 0, idx: u32 = 0,
@@ -193,10 +196,9 @@ pub const Iterator = struct {
return try SfsFile.fromEntry(self.archive, self.entries[self.idx]); return try SfsFile.fromEntry(self.archive, self.entries[self.idx]);
} }
pub fn deinit(self: Iterator) void { pub fn deinit(self: Iterator) void {
var alloc = self.archive.allocator();
for (self.entries) |e| { for (self.entries) |e| {
e.deinit(alloc); e.deinit(self.alloc);
} }
alloc.free(self.entries); self.alloc.free(self.entries);
} }
}; };
+60 -56
View File
@@ -93,7 +93,7 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
}, },
}; };
} }
pub fn readFromEntry(alloc: std.mem.Allocator, archive: *Archive, entry: DirEntry) !Inode { pub fn readFromEntry(alloc: std.mem.Allocator, archive: Archive, entry: DirEntry) !Inode {
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{}); var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp); var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
try meta.interface.discardAll(entry.block_offset); try meta.interface.discardAll(entry.block_offset);
@@ -111,32 +111,42 @@ pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
} }
/// Get the data reader for a file inode. /// Get the data reader for a file inode.
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !DataReader { pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive) !DataReader {
return switch (self.hdr.inode_type) { return switch (self.hdr.inode_type) {
.file => readerFromData(alloc, archive, self.data.file), .file => readerFromData(alloc, archive, self.data.file),
.ext_file => readerFromData(alloc, archive, self.data.ext_file), .ext_file => readerFromData(alloc, archive, self.data.ext_file),
else => error.NotRegularFile, else => error.NotRegularFile,
}; };
} }
fn readerFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !DataReader { fn readerFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) !DataReader {
var out: DataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size); return .init(
if (data.frag_idx != 0xFFFFFFFF) alloc,
out.addFragment(try archive.frag_table.get(data.frag_idx), data.frag_block_offset); archive,
return out; data.block_sizes,
data.block_start,
data.size,
data.frag_block_offset,
if (data.frag_idx == 0xFFFFFFFF) null else try archive.frag_table.get(alloc, data.frag_idx),
);
} }
/// Get a threaded data reader for a file inode. /// Get a threaded data reader for a file inode.
pub fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !ThreadedDataReader { pub fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive) !ThreadedDataReader {
return switch (self.hdr.inode_type) { return switch (self.hdr.inode_type) {
.file => threadedReaderFromData(alloc, archive, self.data.file), .file => threadedReaderFromData(alloc, archive, self.data.file),
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file), .ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
else => error.NotRegularFile, else => error.NotRegularFile,
}; };
} }
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !ThreadedDataReader { fn threadedReaderFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) !ThreadedDataReader {
var out: ThreadedDataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size); return .init(
if (data.frag_idx != 0xFFFFFFFF) alloc,
out.addFragment(try archive.frag_table.get(data.frag_idx), data.frag_block_offset); archive,
return out; data.block_sizes,
data.block_start,
data.size,
data.frag_block_offset,
if (data.frag_idx == 0xFFFFFFFF) null else try archive.frag_table.get(alloc, data.frag_idx),
);
} }
/// Get the directory entries for a directory inode. /// Get the directory entries for a directory inode.
@@ -154,9 +164,9 @@ fn entriesFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) ![
return DirEntry.readDir(alloc, &meta.interface, data.size); return DirEntry.readDir(alloc, &meta.interface, data.size);
} }
/// Extract the inode to the given path. Single threaded. /// Extract the inode to the given path.
pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void { pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
if (options.threads > 1) return self.extractToThreaded(alloc, archive, path, options); if (options.threads > 1) return self.extractToThreaded(alloc, archive, path, options); // We go to a dedicated function for mutli-threaded
switch (self.hdr.inode_type) { switch (self.hdr.inode_type) {
.dir, .ext_dir => { .dir, .ext_dir => {
// Removing any trailing separators since that's the easiest path forward. // Removing any trailing separators since that's the easiest path forward.
@@ -164,7 +174,7 @@ pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path:
std.fs.cwd().makeDir(path) catch |err| { std.fs.cwd().makeDir(path) catch |err| {
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err; if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
}; };
const entries = try self.dirEntries(alloc, archive.*); const entries = try self.dirEntries(alloc, archive);
defer { defer {
for (entries) |entry| entry.deinit(alloc); for (entries) |entry| entry.deinit(alloc);
alloc.free(entries); alloc.free(entries);
@@ -183,7 +193,7 @@ pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path:
}, },
.file, .ext_file => try self.extractRegFile(alloc, archive, path, options), .file, .ext_file => try self.extractRegFile(alloc, archive, path, options),
.symlink, .ext_symlink => try self.extractSymlink(path), .symlink, .ext_symlink => try self.extractSymlink(path),
else => try self.extractDevice(archive, path, options), else => try self.extractDevice(alloc, archive, path, options),
} }
} }
@@ -199,23 +209,25 @@ const Parent = struct {
ignore_permissions: bool, ignore_permissions: bool,
ignore_xattr: bool, ignore_xattr: bool,
wg: WaitGroup = .{}, wg: WaitGroup,
mut: Mutex = .{}, mut: Mutex = .{},
fn create(alloc: std.mem.Allocator, hdr: Header, archive: *Archive, path: []const u8, options: ExtractionOptions, dir_size: usize) !*Parent { fn create(alloc: std.mem.Allocator, hdr: Header, archive: Archive, path: []const u8, options: ExtractionOptions, dir_size: usize) !*Parent {
const out = try alloc.create(Parent); const out = try alloc.create(Parent);
errdefer alloc.destroy(out); errdefer alloc.destroy(out);
out.* = .{ out.* = .{
.alloc = alloc, .alloc = alloc,
.path = path, .path = path,
.uid = try archive.id_table.get(hdr.uid_idx), .uid = try archive.id_table.get(alloc, hdr.uid_idx),
.gid = try archive.id_table.get(hdr.gid_idx), .gid = try archive.id_table.get(alloc, hdr.gid_idx),
.perm = hdr.permissions, .perm = hdr.permissions,
.mod_time = hdr.mod_time, .mod_time = hdr.mod_time,
.ignore_permissions = options.ignore_permissions, .ignore_permissions = options.ignore_permissions,
.ignore_xattr = options.ignore_xattr, .ignore_xattr = options.ignore_xattr,
.wg = .{},
}; };
out.wg.startMany(dir_size); out.wg.startMany(dir_size);
return out; return out;
@@ -242,35 +254,26 @@ const Parent = struct {
/// Extract the inode to the given path. Multi-threaded. /// Extract the inode to the given path. Multi-threaded.
/// Functions identically to extractTo on all but regular files and directories. /// Functions identically to extractTo on all but regular files and directories.
/// fn extractToThreaded(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
/// If threads <= 1, then this just calls extractTo.
fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
switch (self.hdr.inode_type) { switch (self.hdr.inode_type) {
.dir, .ext_dir => { .dir, .ext_dir => {
// Removing any trailing separators since that's the easiest path forward. // Removing any trailing separators since that's the easiest path forward.
if (path[path.len - 1] == '/') return self.extractToThreaded(allocator, archive, path[0 .. path.len - 1], options); if (path[path.len - 1] == '/') return self.extractToThreaded(alloc, archive, path[0 .. path.len - 1], options);
// Fixed Allocator
// const mem_buf = archive.allocator().alloc(u8, 2 * 1024 * 1024 * 1024);
// defer archive.allocator().free(mem_buf);
// var fixed_alloc: std.heap.FixedBufferAllocator = .init(mem_buf);
// const alloc = fixed_alloc.threadSafeAllocator();
// Arena Allocator // Arena Allocator
var arena_alloc: std.heap.ArenaAllocator = .init(allocator); var arena_alloc: std.heap.ArenaAllocator = .init(alloc);
defer arena_alloc.deinit(); defer arena_alloc.deinit();
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() }; var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
const alloc = thread_alloc.allocator();
var wg: WaitGroup = .{}; var wg: WaitGroup = .{};
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator // defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
var pool: Pool = undefined; var pool: Pool = undefined;
try pool.init(.{ .allocator = allocator, .n_jobs = options.threads - 1 }); try pool.init(.{ .allocator = alloc, .n_jobs = options.threads - 1 });
defer pool.deinit(); defer pool.deinit();
var out_err: ?anyerror = null; var out_err: ?anyerror = null;
wg.start(); wg.start();
self.extractThread(alloc, archive, path, options, &wg, &pool, &out_err, null); self.extractThread(thread_alloc.allocator(), archive, path, options, &wg, &pool, &out_err, null);
pool.waitAndWork(&wg); pool.waitAndWork(&wg);
if (out_err != null) return out_err.?; if (out_err != null) return out_err.?;
@@ -280,20 +283,17 @@ fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archiv
try fil.updateTimes(time, time); try fil.updateTimes(time, time);
if (options.ignore_permissions) { if (options.ignore_permissions) {
try fil.chmod(self.hdr.permissions); try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id_table.get(self.hdr.uid_idx), try archive.id_table.get(self.hdr.gid_idx)); try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
} }
}, },
.file, .ext_file => { .file, .ext_file => {
var pool: Pool = undefined; var pool: Pool = undefined;
try pool.init(.{ .allocator = allocator, .n_jobs = options.threads - 1 }); try pool.init(.{ .allocator = alloc, .n_jobs = options.threads - 1 });
defer pool.deinit(); defer pool.deinit();
var arena_alloc: std.heap.ArenaAllocator = .init(allocator); var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = alloc };
defer arena_alloc.deinit();
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
const alloc = thread_alloc.allocator();
try self.extractRegFileThreaded(alloc, archive, path, options, &pool); try self.extractRegFileThreaded(thread_alloc.allocator(), archive, path, options, &pool);
var fil = try std.fs.cwd().openFile(path, .{}); var fil = try std.fs.cwd().openFile(path, .{});
defer fil.close(); defer fil.close();
@@ -301,18 +301,18 @@ fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archiv
try fil.updateTimes(time, time); try fil.updateTimes(time, time);
if (!options.ignore_permissions) { if (!options.ignore_permissions) {
try fil.chmod(self.hdr.permissions); try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id_table.get(self.hdr.uid_idx), try archive.id_table.get(self.hdr.gid_idx)); try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
} }
}, },
.symlink, .ext_symlink => try self.extractSymlink(path), .symlink, .ext_symlink => try self.extractSymlink(path),
else => try self.extractDevice(archive, path, options), else => try self.extractDevice(alloc, archive, path, options),
} }
} }
fn extractThreadEntry( fn extractThreadEntry(
entry: DirEntry, entry: DirEntry,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
archive: *Archive, archive: Archive,
path: []const u8, path: []const u8,
options: ExtractionOptions, options: ExtractionOptions,
wg: *WaitGroup, wg: *WaitGroup,
@@ -340,7 +340,7 @@ fn extractThreadEntry(
fn extractThread( fn extractThread(
self: Inode, self: Inode,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
archive: *Archive, archive: Archive,
path: []const u8, path: []const u8,
options: ExtractionOptions, options: ExtractionOptions,
wg: *WaitGroup, wg: *WaitGroup,
@@ -353,7 +353,7 @@ fn extractThread(
defer { defer {
if (parent != null) parent.?.finish() catch |err| { if (parent != null) parent.?.finish() catch |err| {
if (options.verbose) if (options.verbose)
options.verbose_writer.?.print("Error setting folder permission to {s}: {}\n", .{ path, err }) catch {}; options.verbose_writer.?.print("Error setting folder permission to {s}: {}\n", .{ parent.?.path, err }) catch {};
out_err.* = err; out_err.* = err;
}; };
wg.finish(); wg.finish();
@@ -368,7 +368,7 @@ fn extractThread(
return; return;
}; };
const entries = self.dirEntries(alloc, archive.*) catch |err| { const entries = self.dirEntries(alloc, archive) catch |err| {
if (options.verbose) if (options.verbose)
options.verbose_writer.?.print("Error getting directory entries for inode #{} (extracting to {s}): {}\n", .{ self.hdr.num, path, err }) catch {}; options.verbose_writer.?.print("Error getting directory entries for inode #{} (extracting to {s}): {}\n", .{ self.hdr.num, path, err }) catch {};
out_err.* = err; out_err.* = err;
@@ -400,6 +400,10 @@ fn extractThread(
}, },
) catch |err| { ) catch |err| {
wg.finish(); wg.finish();
p.finish() catch |e| {
if (options.verbose)
options.verbose_writer.?.print("Error setting folder permission to {s}: {}\n", .{ p.path, e }) catch {};
};
if (options.verbose) if (options.verbose)
options.verbose_writer.?.print("Error starting extraction thread: {}\n", .{err}) catch {}; options.verbose_writer.?.print("Error starting extraction thread: {}\n", .{err}) catch {};
out_err.* = err; out_err.* = err;
@@ -422,7 +426,7 @@ fn extractThread(
}; };
}, },
else => { else => {
self.extractDevice(archive, path, options) catch |err| { self.extractDevice(alloc, archive, path, options) catch |err| {
if (options.verbose) if (options.verbose)
options.verbose_writer.?.print("Error extracting device/IPC inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {}; options.verbose_writer.?.print("Error extracting device/IPC inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {};
out_err.* = err; out_err.* = err;
@@ -434,7 +438,7 @@ fn extractThread(
/// Optionally set owner & permissions. /// Optionally set owner & permissions.
/// ///
/// Assumes the inode is a file or ext_file type. /// Assumes the inode is a file or ext_file type.
fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void { fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true }); var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true });
defer fil.close(); defer fil.close();
var wrt = fil.writer(&[0]u8{}); var wrt = fil.writer(&[0]u8{});
@@ -447,14 +451,14 @@ fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path
try fil.updateTimes(time, time); try fil.updateTimes(time, time);
if (!options.ignore_permissions) { if (!options.ignore_permissions) {
try fil.chmod(self.hdr.permissions); try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id_table.get(self.hdr.uid_idx), try archive.id_table.get(self.hdr.gid_idx)); try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
} }
} }
/// Extract the inode file contents to the given path threadedly. /// Extract the inode file contents to the given path threadedly.
/// pool is used to spawn threads. /// pool is used to spawn threads.
/// ///
/// Assumes the inode is a file or ext_file type. /// Assumes the inode is a file or ext_file type.
fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions, pool: *Pool) !void { fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions, pool: *Pool) !void {
var fil = try std.fs.cwd().createFile(path, .{}); var fil = try std.fs.cwd().createFile(path, .{});
defer fil.close(); defer fil.close();
var data = try self.threadedDataReader(alloc, archive); var data = try self.threadedDataReader(alloc, archive);
@@ -464,7 +468,7 @@ fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: *Archi
try fil.updateTimes(time, time); try fil.updateTimes(time, time);
if (!options.ignore_permissions) { if (!options.ignore_permissions) {
try fil.chmod(self.hdr.permissions); try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id_table.get(self.hdr.uid_idx), try archive.id_table.get(self.hdr.gid_idx)); try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
} }
} }
/// Creates the symlink described by the inode. /// Creates the symlink described by the inode.
@@ -482,7 +486,7 @@ fn extractSymlink(self: Inode, path: []const u8) !void {
/// ///
/// Optionally set owner & permissions. /// Optionally set owner & permissions.
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts. /// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void { fn extractDevice(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
var mode: u32 = undefined; var mode: u32 = undefined;
var dev: u32 = 0; var dev: u32 = 0;
switch (self.data) { switch (self.data) {
@@ -531,7 +535,7 @@ fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: Extr
try fil.updateTimes(time, time); try fil.updateTimes(time, time);
if (!options.ignore_permissions) { if (!options.ignore_permissions) {
try fil.chmod(self.hdr.permissions); try fil.chmod(self.hdr.permissions);
try fil.chown(try archive.id_table.get(self.hdr.uid_idx), try archive.id_table.get(self.hdr.gid_idx)); try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
} }
if (!options.ignore_xattr) { if (!options.ignore_xattr) {
// TODO // TODO
+5 -4
View File
@@ -5,8 +5,8 @@ const Writer = std.Io.Writer;
const ExtractionOptions = @This(); const ExtractionOptions = @This();
/// The number of threads used for extraction. 0 implies single threaded. /// The amount of threads to use for extraction.
threads: usize = 1, threads: usize,
/// Don't set the file's owner & permissions after extraction /// Don't set the file's owner & permissions after extraction
ignore_permissions: bool = false, ignore_permissions: bool = false,
/// Don't set xattr values. Currently xattrs are never set anyway. /// Don't set xattr values. Currently xattrs are never set anyway.
@@ -18,7 +18,8 @@ verbose: bool = false,
/// Where to print verbose log. /// Where to print verbose log.
verbose_writer: ?*Writer = null, verbose_writer: ?*Writer = null,
pub const SingleThreadedDefault: ExtractionOptions = .{}; pub const SingleThreaded: ExtractionOptions = .{ .threads = 1 };
/// Use std.Thread.getCpuCount threads for extraction.
pub fn Default() !ExtractionOptions { pub fn Default() !ExtractionOptions {
return .{ return .{
.threads = try std.Thread.getCpuCount(), .threads = try std.Thread.getCpuCount(),
@@ -26,8 +27,8 @@ pub fn Default() !ExtractionOptions {
} }
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions { pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
return .{ return .{
.threads = try std.Thread.getCpuCount(),
.verbose = true, .verbose = true,
.verbose_writer = wrt, .verbose_writer = wrt,
.threads = try std.Thread.getCpuCount(),
}; };
} }
-1
View File
@@ -1,2 +1 @@
pub const Archive = @import("archive.zig"); pub const Archive = @import("archive.zig");
pub const ExtractionOptions = @import("options.zig");
+7 -34
View File
@@ -16,61 +16,34 @@ pub fn Table(T: anytype) type {
const VALS_PER_BLOCK = 8192 / @sizeOf(T); const VALS_PER_BLOCK = 8192 / @sizeOf(T);
alloc: std.mem.Allocator,
fil: OffsetFile, fil: OffsetFile,
decomp: DecompFn, decomp: DecompFn,
tab_start: u64,
tab: std.AutoHashMap(u32, []T), tab_start: u64,
values: u32, values: u32,
mut: Mutex = .{}, pub fn init(fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) Self {
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
return .{ return .{
.alloc = alloc,
.fil = fil, .fil = fil,
.decomp = decomp, .decomp = decomp,
.tab_start = tab_start,
.tab = .init(alloc), .tab_start = tab_start,
.values = values, .values = values,
}; };
} }
pub fn deinit(self: *Self) void { pub fn get(self: Self, alloc: std.mem.Allocator, idx: u32) !T {
var iter = self.tab.valueIterator();
while (iter.next()) |s| {
self.alloc.free(s.*);
}
self.tab.deinit();
}
pub fn get(self: *Self, idx: u32) !T {
if (idx >= self.values) return TableError.InvalidIndex;
const block_num = idx / VALS_PER_BLOCK; const block_num = idx / VALS_PER_BLOCK;
const idx_offset = idx - (block_num * VALS_PER_BLOCK); const idx_offset = idx - (block_num * VALS_PER_BLOCK);
if (self.tab.contains(block_num)) {
const block = self.tab.get(block_num).?;
return block[idx_offset];
}
self.mut.lock();
defer self.mut.unlock();
// Double check in case of race condition..
if (self.tab.contains(block_num)) {
const block = self.tab.get(block_num).?;
return block[idx_offset];
}
const is_last = (self.values - 1) / VALS_PER_BLOCK == block_num; const is_last = (self.values - 1) / VALS_PER_BLOCK == block_num;
const slice_size = if (is_last) self.values - (block_num * VALS_PER_BLOCK) else VALS_PER_BLOCK; const slice_size = if (is_last) self.values - (block_num * VALS_PER_BLOCK) else VALS_PER_BLOCK;
const slice = try self.alloc.alloc(T, slice_size); var slice: [VALS_PER_BLOCK]T = undefined;
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{}); var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
var offset: u64 = 0; var offset: u64 = 0;
try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little); try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little);
rdr = try self.fil.readerAt(offset, &[0]u8{}); rdr = try self.fil.readerAt(offset, &[0]u8{});
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp); var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
try meta.interface.readSliceEndian(T, @ptrCast(slice), .little); try meta.interface.readSliceEndian(T, slice[0..slice_size], .little);
try self.tab.put(block_num, slice);
return slice[idx_offset]; return slice[idx_offset];
} }
}; };
+6 -9
View File
@@ -9,8 +9,7 @@ const TestArchive = "testing/LinuxPATest.sfs";
test "Basics" { test "Basics" {
var fil = try std.fs.cwd().openFile(TestArchive, .{}); var fil = try std.fs.cwd().openFile(TestArchive, .{});
defer fil.close(); defer fil.close();
var sfs: Archive = try .init(std.testing.allocator, fil); const sfs: Archive = try .init(fil, 0);
defer sfs.deinit();
if (sfs.super != LinuxPATestCorrectSuperblock) { if (sfs.super != LinuxPATestCorrectSuperblock) {
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super }); std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
return error.BadSuperblock; return error.BadSuperblock;
@@ -24,11 +23,10 @@ test "ExtractSingleFile" {
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {}; std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
var fil = try std.fs.cwd().openFile(TestArchive, .{}); var fil = try std.fs.cwd().openFile(TestArchive, .{});
defer fil.close(); defer fil.close();
var sfs: Archive = try .init(std.testing.allocator, fil); var sfs: Archive = try .init(fil, 0);
defer sfs.deinit(); var test_fil = try sfs.open(std.testing.allocator, TestFile);
var test_fil = try sfs.open(TestFile);
defer test_fil.deinit(); defer test_fil.deinit();
try test_fil.extract(TestFileExtractLocation, .Default); try test_fil.extract(std.testing.allocator, TestFileExtractLocation, try .Default());
//TODO: validate extracted file. //TODO: validate extracted file.
} }
@@ -38,9 +36,8 @@ test "ExtractCompleteArchive" {
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {}; std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
var fil = try std.fs.cwd().openFile(TestArchive, .{}); var fil = try std.fs.cwd().openFile(TestArchive, .{});
defer fil.close(); defer fil.close();
var sfs: Archive = try .init(std.testing.allocator, fil); var sfs: Archive = try .init(fil, 0);
defer sfs.deinit(); try sfs.extract(std.testing.allocator, TestFullExtractLocation, try .Default());
try sfs.extract(TestFullExtractLocation, .Default);
} }
const LinuxPATestCorrectSuperblock: Superblock = .{ const LinuxPATestCorrectSuperblock: Superblock = .{
+4 -6
View File
@@ -29,13 +29,16 @@ interface: Reader,
cur_offset: u64, cur_offset: u64,
block_idx: u32 = 0, block_idx: u32 = 0,
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) DataReader { pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64, frag_offset: u32, frag_entry: ?FragEntry) DataReader {
return .{ return .{
.alloc = alloc, .alloc = alloc,
.fil = archive.fil, .fil = archive.fil,
.decomp = archive.decomp, .decomp = archive.decomp,
.block_size = archive.super.block_size, .block_size = archive.super.block_size,
.blocks = blocks, .blocks = blocks,
.frag = frag_entry,
.frag_offset = frag_offset,
.size = size, .size = size,
.cur_offset = start, .cur_offset = start,
.interface = .{ .interface = .{
@@ -56,11 +59,6 @@ pub fn deinit(self: *DataReader) void {
self.interface.seek = 0; self.interface.seek = 0;
} }
pub fn addFragment(self: *DataReader, entry: FragEntry, frag_offset: u32) void {
self.frag = entry;
self.frag_offset = frag_offset;
}
fn numBlocks(self: DataReader) usize { fn numBlocks(self: DataReader) usize {
var res = self.blocks.len; var res = self.blocks.len;
if (self.frag != null) res += 1; if (self.frag != null) res += 1;
+4 -6
View File
@@ -28,7 +28,7 @@ size: u64,
start_offset: u64, start_offset: u64,
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) ThreadedDataReader { pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64, frag_offset: u32, frag_entry: ?FragEntry) ThreadedDataReader {
return .{ return .{
.alloc = alloc, .alloc = alloc,
.fil = archive.fil, .fil = archive.fil,
@@ -37,12 +37,10 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, sta
.blocks = blocks, .blocks = blocks,
.size = size, .size = size,
.start_offset = start, .start_offset = start,
};
}
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void { .frag_offset = frag_offset,
self.frag = entry; .frag = frag_entry,
self.frag_offset = frag_offset; };
} }
fn numBlocks(self: ThreadedDataReader) usize { fn numBlocks(self: ThreadedDataReader) usize {