Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a710b98cf |
@@ -15,17 +15,17 @@ jobs:
|
||||
- name: Install deps
|
||||
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
||||
- 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
|
||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64
|
||||
run: mv zig-out/bin/unsquashfs ./
|
||||
- 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
|
||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: true
|
||||
files: |
|
||||
unsquashfs-x86_64
|
||||
unsquashfs-x86_64-c-libs
|
||||
unsquashfs
|
||||
unsquashfs-c-libs
|
||||
|
||||
@@ -6,23 +6,19 @@ A library and application to decompress or view squashfs archives.
|
||||
|
||||
## 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
|
||||
|
||||
> `-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.
|
||||
|
||||
> `-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`.
|
||||
|
||||
> `-Dvalgrind=true`
|
||||
|
||||
Just sets the valgrind build option.
|
||||
|
||||
> `-Dversion=0.0.0`
|
||||
> `-Dversion`
|
||||
|
||||
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:
|
||||
|
||||
* mod_time is not set 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 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
|
||||
|
||||
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
|
||||
## Building 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.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ const std = @import("std");
|
||||
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 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 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);
|
||||
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||
const mod = b.addModule("zig_squashfs", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = use_c_libs_option,
|
||||
.valgrind = valgrind,
|
||||
.link_libc = if (use_c_libs_option == true) true else false,
|
||||
});
|
||||
mod.addOptions("config", zig_squashfs_options);
|
||||
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"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = use_c_libs_option,
|
||||
.link_libc = if (use_c_libs_option == true) true else false,
|
||||
.imports = &.{
|
||||
.{ .name = "zig_squashfs", .module = mod },
|
||||
},
|
||||
.valgrind = valgrind,
|
||||
});
|
||||
exe_mod.addOptions("config", unsquashfs_options);
|
||||
const exe = b.addExecutable(.{
|
||||
@@ -62,6 +59,13 @@ pub fn build(b: *std.Build) !void {
|
||||
b.installArtifact(lib);
|
||||
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(.{
|
||||
.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_exe_tests.step);
|
||||
|
||||
// zls build check steps
|
||||
const lib_check = b.addLibrary(.{
|
||||
.name = "squashfs",
|
||||
.root_module = mod,
|
||||
});
|
||||
// Wanted by ZLS for better detection.
|
||||
const exe_check = b.addExecutable(.{
|
||||
.name = "unsquashfs",
|
||||
.root_module = exe_mod,
|
||||
.root_module = mod,
|
||||
});
|
||||
const check = b.step("check", "Check if unsquashfs compiles");
|
||||
check.dependOn(&lib_check.step);
|
||||
check.dependOn(&exe_check.step);
|
||||
}
|
||||
|
||||
+41
-35
@@ -31,24 +31,21 @@ pub const FragEntry = packed struct {
|
||||
|
||||
const Archive = @This();
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
fil: OffsetFile,
|
||||
decomp: Decomp.DecompFn,
|
||||
|
||||
super: Superblock,
|
||||
|
||||
frag_table: Table(FragEntry) = undefined,
|
||||
id_table: Table(u16) = undefined,
|
||||
export_table: Table(InodeRef) = undefined,
|
||||
decomp: Decomp.DecompFn,
|
||||
|
||||
/// 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, offset: u64) !Archive {
|
||||
frag_table: Table(FragEntry),
|
||||
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;
|
||||
const red = try fil.pread(@ptrCast(&super), offset);
|
||||
std.debug.assert(red == @sizeOf(Superblock));
|
||||
_ = try fil.pread(@ptrCast(&super), offset);
|
||||
try super.validate();
|
||||
const off_fil: OffsetFile = .init(fil, offset);
|
||||
const decomp: Decomp.DecompFn = switch (super.compression) {
|
||||
.gzip => Decomp.gzipDecompress,
|
||||
.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,
|
||||
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
||||
};
|
||||
const offset_fil: OffsetFile = .init(fil, offset);
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
|
||||
.fil = off_fil,
|
||||
.decomp = decomp,
|
||||
|
||||
.fil = offset_fil,
|
||||
.super = super,
|
||||
|
||||
.frag_table = try .init(alloc, off_fil, decomp, super.frag_start, super.frag_count),
|
||||
.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),
|
||||
.decomp = decomp,
|
||||
|
||||
.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);
|
||||
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);
|
||||
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 meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||
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);
|
||||
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 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);
|
||||
const in: Inode = try .read(self.alloc, &meta.interface, self.super.block_size);
|
||||
try in.extractTo(alloc, self, path, options);
|
||||
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
|
||||
try in.extractTo(alloc, self, ext_path, options);
|
||||
}
|
||||
|
||||
@@ -44,14 +44,8 @@ pub fn main() !void {
|
||||
}
|
||||
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
||||
defer fil.close();
|
||||
var arc: squashfs.Archive = try .init(alloc, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||
defer arc.deinit();
|
||||
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.
|
||||
var arc: squashfs.Archive = try .init(fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||
try arc.extract(alloc, extLoc, if (verbose) try .VerboseDefault(&out.interface) else try .Default()); //TODO: Handle error gracefully.
|
||||
}
|
||||
|
||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
||||
|
||||
@@ -29,6 +29,7 @@ pub const CompressionType = enum(u16) {
|
||||
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 gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
||||
|
||||
+26
-24
@@ -24,39 +24,40 @@ const FileError = error{
|
||||
|
||||
const SfsFile = @This();
|
||||
|
||||
archive: *Archive,
|
||||
alloc: std.mem.Allocator,
|
||||
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) !SfsFile {
|
||||
const new_name = try archive.allocator().alloc(u8, name.len);
|
||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, inode: Inode, name: []const u8) !SfsFile {
|
||||
const new_name = try alloc.alloc(u8, name.len);
|
||||
@memcpy(new_name, name);
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.archive = archive,
|
||||
.inode = inode,
|
||||
.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 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);
|
||||
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
||||
errdefer inode.deinit(archive.allocator());
|
||||
return .init(archive, inode, entry.name);
|
||||
const inode: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
|
||||
errdefer inode.deinit(alloc);
|
||||
return .init(alloc, archive, inode, entry.name);
|
||||
}
|
||||
|
||||
pub fn deinit(self: SfsFile) void {
|
||||
var alloc = self.archive.allocator();
|
||||
alloc.free(self.name);
|
||||
self.inode.deinit(alloc);
|
||||
self.alloc.free(self.name);
|
||||
self.inode.deinit(self.alloc);
|
||||
}
|
||||
|
||||
fn getEntries(self: SfsFile) ![]DirEntry {
|
||||
return self.inode.dirEntries(self.archive.allocator(), self.archive.*);
|
||||
fn getEntries(self: SfsFile, alloc: std.mem.Allocator) ![]DirEntry {
|
||||
return self.inode.dirEntries(alloc, self.archive);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Recursively stip ending & leading path separators.
|
||||
if (path[0] == '/') return self.open(path[1..]);
|
||||
if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]);
|
||||
if (path[0] == '/') return self.open(alloc, path[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 first_element = path[0..idx];
|
||||
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]);
|
||||
const entries = try self.getEntries();
|
||||
if (std.mem.eql(u8, first_element, ".")) return self.open(alloc, path[idx + 1 ..]);
|
||||
const entries = try self.getEntries(alloc);
|
||||
defer {
|
||||
for (entries) |e| {
|
||||
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);
|
||||
switch (comp) {
|
||||
.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) {
|
||||
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.
|
||||
/// 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 {
|
||||
return self.inode.extractToThreaded(alloc, self.archive, path, options);
|
||||
pub fn extract(self: SfsFile, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||
return self.inode.extractTo(alloc, self.archive, path, options);
|
||||
}
|
||||
|
||||
/// Utility function.
|
||||
@@ -182,8 +183,10 @@ pub fn pathIsSelf(path: []const u8) bool {
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
entries: []DirEntry,
|
||||
archive: *Archive,
|
||||
archive: Archive,
|
||||
|
||||
idx: u32 = 0,
|
||||
|
||||
@@ -193,10 +196,9 @@ pub const Iterator = struct {
|
||||
return try SfsFile.fromEntry(self.archive, self.entries[self.idx]);
|
||||
}
|
||||
pub fn deinit(self: Iterator) void {
|
||||
var alloc = self.archive.allocator();
|
||||
for (self.entries) |e| {
|
||||
e.deinit(alloc);
|
||||
e.deinit(self.alloc);
|
||||
}
|
||||
alloc.free(self.entries);
|
||||
self.alloc.free(self.entries);
|
||||
}
|
||||
};
|
||||
|
||||
+60
-56
@@ -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 meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||
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.
|
||||
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) {
|
||||
.file => readerFromData(alloc, archive, self.data.file),
|
||||
.ext_file => readerFromData(alloc, archive, self.data.ext_file),
|
||||
else => error.NotRegularFile,
|
||||
};
|
||||
}
|
||||
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);
|
||||
if (data.frag_idx != 0xFFFFFFFF)
|
||||
out.addFragment(try archive.frag_table.get(data.frag_idx), data.frag_block_offset);
|
||||
return out;
|
||||
fn readerFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) !DataReader {
|
||||
return .init(
|
||||
alloc,
|
||||
archive,
|
||||
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.
|
||||
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) {
|
||||
.file => threadedReaderFromData(alloc, archive, self.data.file),
|
||||
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
|
||||
else => error.NotRegularFile,
|
||||
};
|
||||
}
|
||||
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);
|
||||
if (data.frag_idx != 0xFFFFFFFF)
|
||||
out.addFragment(try archive.frag_table.get(data.frag_idx), data.frag_block_offset);
|
||||
return out;
|
||||
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) !ThreadedDataReader {
|
||||
return .init(
|
||||
alloc,
|
||||
archive,
|
||||
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.
|
||||
@@ -154,9 +164,9 @@ fn entriesFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) ![
|
||||
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
||||
}
|
||||
|
||||
/// Extract the inode to the given path. Single threaded.
|
||||
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);
|
||||
/// Extract the inode to the given path.
|
||||
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); // We go to a dedicated function for mutli-threaded
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
// 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| {
|
||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
|
||||
};
|
||||
const entries = try self.dirEntries(alloc, archive.*);
|
||||
const entries = try self.dirEntries(alloc, archive);
|
||||
defer {
|
||||
for (entries) |entry| entry.deinit(alloc);
|
||||
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),
|
||||
.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_xattr: bool,
|
||||
|
||||
wg: WaitGroup = .{},
|
||||
wg: WaitGroup,
|
||||
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);
|
||||
errdefer alloc.destroy(out);
|
||||
out.* = .{
|
||||
.alloc = alloc,
|
||||
|
||||
.path = path,
|
||||
.uid = try archive.id_table.get(hdr.uid_idx),
|
||||
.gid = try archive.id_table.get(hdr.gid_idx),
|
||||
.uid = try archive.id_table.get(alloc, hdr.uid_idx),
|
||||
.gid = try archive.id_table.get(alloc, hdr.gid_idx),
|
||||
.perm = hdr.permissions,
|
||||
.mod_time = hdr.mod_time,
|
||||
|
||||
.ignore_permissions = options.ignore_permissions,
|
||||
.ignore_xattr = options.ignore_xattr,
|
||||
|
||||
.wg = .{},
|
||||
};
|
||||
out.wg.startMany(dir_size);
|
||||
return out;
|
||||
@@ -242,35 +254,26 @@ const Parent = struct {
|
||||
|
||||
/// Extract the inode to the given path. Multi-threaded.
|
||||
/// Functions identically to extractTo on all but regular files and directories.
|
||||
///
|
||||
/// If threads <= 1, then this just calls extractTo.
|
||||
fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
fn extractToThreaded(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
// 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);
|
||||
|
||||
// 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();
|
||||
if (path[path.len - 1] == '/') return self.extractToThreaded(alloc, archive, path[0 .. path.len - 1], options);
|
||||
|
||||
// Arena Allocator
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(allocator);
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(alloc);
|
||||
defer arena_alloc.deinit();
|
||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
||||
const alloc = thread_alloc.allocator();
|
||||
|
||||
var wg: WaitGroup = .{};
|
||||
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
|
||||
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();
|
||||
var out_err: ?anyerror = null;
|
||||
|
||||
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);
|
||||
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);
|
||||
if (options.ignore_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 => {
|
||||
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();
|
||||
|
||||
var arena_alloc: std.heap.ArenaAllocator = .init(allocator);
|
||||
defer arena_alloc.deinit();
|
||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
||||
const alloc = thread_alloc.allocator();
|
||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = alloc };
|
||||
|
||||
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, .{});
|
||||
defer fil.close();
|
||||
@@ -301,18 +301,18 @@ fn extractToThreaded(self: Inode, allocator: std.mem.Allocator, archive: *Archiv
|
||||
try fil.updateTimes(time, time);
|
||||
if (!options.ignore_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),
|
||||
else => try self.extractDevice(archive, path, options),
|
||||
else => try self.extractDevice(alloc, archive, path, options),
|
||||
}
|
||||
}
|
||||
|
||||
fn extractThreadEntry(
|
||||
entry: DirEntry,
|
||||
alloc: std.mem.Allocator,
|
||||
archive: *Archive,
|
||||
archive: Archive,
|
||||
path: []const u8,
|
||||
options: ExtractionOptions,
|
||||
wg: *WaitGroup,
|
||||
@@ -340,7 +340,7 @@ fn extractThreadEntry(
|
||||
fn extractThread(
|
||||
self: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
archive: *Archive,
|
||||
archive: Archive,
|
||||
path: []const u8,
|
||||
options: ExtractionOptions,
|
||||
wg: *WaitGroup,
|
||||
@@ -353,7 +353,7 @@ fn extractThread(
|
||||
defer {
|
||||
if (parent != null) parent.?.finish() catch |err| {
|
||||
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;
|
||||
};
|
||||
wg.finish();
|
||||
@@ -368,7 +368,7 @@ fn extractThread(
|
||||
return;
|
||||
};
|
||||
|
||||
const entries = self.dirEntries(alloc, archive.*) catch |err| {
|
||||
const entries = self.dirEntries(alloc, archive) catch |err| {
|
||||
if (options.verbose)
|
||||
options.verbose_writer.?.print("Error getting directory entries for inode #{} (extracting to {s}): {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||
out_err.* = err;
|
||||
@@ -400,6 +400,10 @@ fn extractThread(
|
||||
},
|
||||
) catch |err| {
|
||||
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)
|
||||
options.verbose_writer.?.print("Error starting extraction thread: {}\n", .{err}) catch {};
|
||||
out_err.* = err;
|
||||
@@ -422,7 +426,7 @@ fn extractThread(
|
||||
};
|
||||
},
|
||||
else => {
|
||||
self.extractDevice(archive, path, options) catch |err| {
|
||||
self.extractDevice(alloc, archive, path, options) catch |err| {
|
||||
if (options.verbose)
|
||||
options.verbose_writer.?.print("Error extracting device/IPC inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||
out_err.* = err;
|
||||
@@ -434,7 +438,7 @@ fn extractThread(
|
||||
/// Optionally set owner & permissions.
|
||||
///
|
||||
/// 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 });
|
||||
defer fil.close();
|
||||
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);
|
||||
if (!options.ignore_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.
|
||||
/// pool is used to spawn threads.
|
||||
///
|
||||
/// 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, .{});
|
||||
defer fil.close();
|
||||
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);
|
||||
if (!options.ignore_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.
|
||||
@@ -482,7 +486,7 @@ fn extractSymlink(self: Inode, path: []const u8) !void {
|
||||
///
|
||||
/// Optionally set owner & permissions.
|
||||
/// 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 dev: u32 = 0;
|
||||
switch (self.data) {
|
||||
@@ -531,7 +535,7 @@ fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: Extr
|
||||
try fil.updateTimes(time, time);
|
||||
if (!options.ignore_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) {
|
||||
// TODO
|
||||
|
||||
+5
-4
@@ -5,8 +5,8 @@ const Writer = std.Io.Writer;
|
||||
|
||||
const ExtractionOptions = @This();
|
||||
|
||||
/// The number of threads used for extraction. 0 implies single threaded.
|
||||
threads: usize = 1,
|
||||
/// The amount of threads to use for extraction.
|
||||
threads: usize,
|
||||
/// Don't set the file's owner & permissions after extraction
|
||||
ignore_permissions: bool = false,
|
||||
/// Don't set xattr values. Currently xattrs are never set anyway.
|
||||
@@ -18,7 +18,8 @@ verbose: bool = false,
|
||||
/// Where to print verbose log.
|
||||
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 {
|
||||
return .{
|
||||
.threads = try std.Thread.getCpuCount(),
|
||||
@@ -26,8 +27,8 @@ pub fn Default() !ExtractionOptions {
|
||||
}
|
||||
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||
return .{
|
||||
.threads = try std.Thread.getCpuCount(),
|
||||
.verbose = true,
|
||||
.verbose_writer = wrt,
|
||||
.threads = try std.Thread.getCpuCount(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub const Archive = @import("archive.zig");
|
||||
pub const ExtractionOptions = @import("options.zig");
|
||||
|
||||
+7
-34
@@ -16,61 +16,34 @@ pub fn Table(T: anytype) type {
|
||||
|
||||
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||
|
||||
alloc: std.mem.Allocator,
|
||||
fil: OffsetFile,
|
||||
decomp: DecompFn,
|
||||
tab_start: u64,
|
||||
|
||||
tab: std.AutoHashMap(u32, []T),
|
||||
tab_start: u64,
|
||||
values: u32,
|
||||
|
||||
mut: Mutex = .{},
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
|
||||
pub fn init(fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) Self {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.fil = fil,
|
||||
.decomp = decomp,
|
||||
.tab_start = tab_start,
|
||||
|
||||
.tab = .init(alloc),
|
||||
.tab_start = tab_start,
|
||||
.values = values,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
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;
|
||||
pub fn get(self: Self, alloc: std.mem.Allocator, idx: u32) !T {
|
||||
const block_num = idx / 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 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 offset: u64 = 0;
|
||||
try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little);
|
||||
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
||||
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
||||
try meta.interface.readSliceEndian(T, @ptrCast(slice), .little);
|
||||
try self.tab.put(block_num, slice);
|
||||
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||
try meta.interface.readSliceEndian(T, slice[0..slice_size], .little);
|
||||
return slice[idx_offset];
|
||||
}
|
||||
};
|
||||
|
||||
+6
-9
@@ -9,8 +9,7 @@ const TestArchive = "testing/LinuxPATest.sfs";
|
||||
test "Basics" {
|
||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||
defer fil.close();
|
||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||
defer sfs.deinit();
|
||||
const sfs: Archive = try .init(fil, 0);
|
||||
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
||||
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
||||
return error.BadSuperblock;
|
||||
@@ -24,11 +23,10 @@ test "ExtractSingleFile" {
|
||||
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
||||
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);
|
||||
var sfs: Archive = try .init(fil, 0);
|
||||
var test_fil = try sfs.open(std.testing.allocator, TestFile);
|
||||
defer test_fil.deinit();
|
||||
try test_fil.extract(TestFileExtractLocation, .Default);
|
||||
try test_fil.extract(std.testing.allocator, TestFileExtractLocation, try .Default());
|
||||
//TODO: validate extracted file.
|
||||
}
|
||||
|
||||
@@ -38,9 +36,8 @@ test "ExtractCompleteArchive" {
|
||||
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||
defer fil.close();
|
||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
||||
defer sfs.deinit();
|
||||
try sfs.extract(TestFullExtractLocation, .Default);
|
||||
var sfs: Archive = try .init(fil, 0);
|
||||
try sfs.extract(std.testing.allocator, TestFullExtractLocation, try .Default());
|
||||
}
|
||||
|
||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||
|
||||
+4
-6
@@ -29,13 +29,16 @@ interface: Reader,
|
||||
cur_offset: u64,
|
||||
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 .{
|
||||
.alloc = alloc,
|
||||
.fil = archive.fil,
|
||||
.decomp = archive.decomp,
|
||||
.block_size = archive.super.block_size,
|
||||
.blocks = blocks,
|
||||
|
||||
.frag = frag_entry,
|
||||
.frag_offset = frag_offset,
|
||||
.size = size,
|
||||
.cur_offset = start,
|
||||
.interface = .{
|
||||
@@ -56,11 +59,6 @@ pub fn deinit(self: *DataReader) void {
|
||||
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 {
|
||||
var res = self.blocks.len;
|
||||
if (self.frag != null) res += 1;
|
||||
|
||||
@@ -28,7 +28,7 @@ size: 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 .{
|
||||
.alloc = alloc,
|
||||
.fil = archive.fil,
|
||||
@@ -37,12 +37,10 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, sta
|
||||
.blocks = blocks,
|
||||
.size = size,
|
||||
.start_offset = start,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void {
|
||||
self.frag = entry;
|
||||
self.frag_offset = frag_offset;
|
||||
.frag_offset = frag_offset,
|
||||
.frag = frag_entry,
|
||||
};
|
||||
}
|
||||
|
||||
fn numBlocks(self: ThreadedDataReader) usize {
|
||||
|
||||
Reference in New Issue
Block a user