Compare commits
3 Commits
3ea3d8e9a0
...
a9e50a0ff5
| Author | SHA1 | Date | |
|---|---|---|---|
| a9e50a0ff5 | |||
| 712c4d0a19 | |||
| 5975bbb4a2 |
@@ -8,13 +8,15 @@ A library and application to decompress or view squashfs archives.
|
||||
|
||||
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
||||
|
||||
Currently things are still in flux after Zig 0.16's Io changes and the documentation below *might* not be up to date.
|
||||
|
||||
## Build options
|
||||
|
||||
> `-Duse_c_libs=true`
|
||||
> `-Duse_zig_decomp=true`
|
||||
|
||||
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 C libraries for decompression, use Zig's standard library for decompression. If using this option LZO and LZ4 decomrpession types are unsupported and decompression times will be significantly longer.
|
||||
|
||||
> `-Ddynamic=true`
|
||||
|
||||
Dynamicly link C libraries (if they're used) instead of statically linking them.
|
||||
|
||||
> `-Dallow_lzo=true`
|
||||
|
||||
@@ -37,20 +39,20 @@ Most features are present except for the following:
|
||||
|
||||
## 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 `-Doptimize=ReleaseFast`.
|
||||
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 with `-Doptimize=ReleaseFast`.
|
||||
|
||||
Currently, my only performance checks are checking execution time, nothing deeper.
|
||||
|
||||
* Currently, using my test archive, performance matches `unsquashfs`.
|
||||
* Currently, using my test archive, performance aproximately matches `unsquashfs` when multi-threaded, but significantly slower when single-threaded.
|
||||
* Using Zig decompression libraries *significantly* increases decompression time by 5x. Under ideal circumstances.
|
||||
* Performance improvements/regressions will be common. I'm still learning Zig.
|
||||
|
||||
Example Times:
|
||||
|
||||
* *unsquashfs, multi-threaded*: .12s
|
||||
* *unsquashfs, single-threaded*: .13s
|
||||
* *C-libs, single-threaded*: CURRENTLY UNTESTED
|
||||
* *C-libs, multi-threaded*: .16s
|
||||
* *unsquashfs, multi-threaded*: .15s
|
||||
* *unsquashfs, single-threaded*: .16s
|
||||
* *C-libs, single-threaded*: .36s
|
||||
* *C-libs, multi-threaded*: .14s
|
||||
* *Zig-libs, single-threaded*: CURRENTLY UNTESTED
|
||||
* *Zig-libs, multi-threaded*: .76s
|
||||
|
||||
|
||||
@@ -5,4 +5,13 @@ ARCHIVE="testing/LinuxPATest.sfs"
|
||||
REF_EXT_LOC="testing/LinuxPAReference"
|
||||
PROG_EXT_LOC="testing/LinuxPABinTest"
|
||||
|
||||
echo "Testing Multi-threaded Performance"
|
||||
echo ""
|
||||
|
||||
hyperfine --warmup 5 --prepare "rm -rf $REF_EXT_LOC && rm -rf $PROG_EXT_LOC" "unsquashfs -d $REF_EXT_LOC $ARCHIVE" "zig-out/bin/unsquashfs -d $PROG_EXT_LOC $ARCHIVE"
|
||||
|
||||
echo ""
|
||||
echo "Testing Single-threaded Performance"
|
||||
echo ""
|
||||
|
||||
hyperfine --warmup 5 --prepare "rm -rf $REF_EXT_LOC && rm -rf $PROG_EXT_LOC" "unsquashfs -p 1 -d $REF_EXT_LOC $ARCHIVE" "zig-out/bin/unsquashfs -p 1 -d $PROG_EXT_LOC $ARCHIVE"
|
||||
|
||||
@@ -3,6 +3,10 @@ const std = @import("std");
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse false;
|
||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false;
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
const dynamic = b.option(bool, "dynamic", "Dynamicly link C decompression libraries") orelse false;
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
var debug = b.option(bool, "debug", "Enable options to make debugging easier.");
|
||||
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
||||
|
||||
@@ -11,9 +15,12 @@ pub fn build(b: *std.Build) !void {
|
||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
|
||||
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
var optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
if (optimize == .Debug) debug = true;
|
||||
if (debug == true)
|
||||
optimize = .Debug;
|
||||
if (optimize == .Debug)
|
||||
debug = true;
|
||||
|
||||
const c = b.addTranslateC(.{
|
||||
.optimize = optimize,
|
||||
@@ -28,18 +35,24 @@ pub fn build(b: *std.Build) !void {
|
||||
.target = target,
|
||||
.valgrind = debug,
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
<<<<<<< HEAD
|
||||
// .link_libc = true,
|
||||
.imports = &.{
|
||||
.{ .name = "options", .module = zig_squashfs_options.createModule() },
|
||||
.{ .name = "c", .module = c.createModule() },
|
||||
=======
|
||||
.imports = &.{
|
||||
.{ .name = "options", .module = zig_squashfs_options.createModule() },
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
},
|
||||
}),
|
||||
.use_llvm = debug,
|
||||
});
|
||||
|
||||
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
|
||||
lib.root_module.linkLibrary(zstd.artifact("zstd"));
|
||||
const deps = try dependencies(b, optimize, target, use_zig_decomp, allow_lzo, dynamic);
|
||||
defer b.allocator.free(deps);
|
||||
|
||||
<<<<<<< HEAD
|
||||
const zng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target });
|
||||
lib.root_module.linkLibrary(zng.artifact("zng"));
|
||||
|
||||
@@ -51,6 +64,23 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
|
||||
lib.root_module.linkLibrary(lz4.artifact("lz4"));
|
||||
=======
|
||||
for (deps) |d|
|
||||
lib.root_module.linkLibrary(d);
|
||||
|
||||
if (!use_zig_decomp) {
|
||||
const c = b.addTranslateC(.{
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/c.h"),
|
||||
});
|
||||
if (allow_lzo) c.defineCMacro("ALLOW_LZO", null);
|
||||
lib.root_module.addImport("c", c.createModule());
|
||||
|
||||
if (dynamic)
|
||||
dynamicLinkLibraries(c, allow_lzo);
|
||||
}
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
|
||||
var version = version_string_option orelse "0.0.0-testing";
|
||||
if (version[0] == 'v') version = version[1..];
|
||||
@@ -80,17 +110,32 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
const mod_tests = b.addTest(.{
|
||||
.root_module = b.createModule(.{
|
||||
.optimize = .Debug,
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/test.zig"),
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "c", .module = c.createModule() },
|
||||
.{ .name = "options", .module = zig_squashfs_options.createModule() },
|
||||
},
|
||||
.valgrind = true,
|
||||
.valgrind = debug,
|
||||
}),
|
||||
.use_llvm = true,
|
||||
.use_llvm = debug,
|
||||
});
|
||||
mod_tests.root_module.linkLibrary(zstd.artifact("zstd"));
|
||||
|
||||
for (deps) |d|
|
||||
mod_tests.root_module.linkLibrary(d);
|
||||
|
||||
if (!use_zig_decomp) {
|
||||
const c = b.addTranslateC(.{
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/c.h"),
|
||||
});
|
||||
mod_tests.root_module.addImport("c", c.createModule());
|
||||
if (allow_lzo) c.defineCMacro("ALLOW_LZO", null);
|
||||
|
||||
if (dynamic)
|
||||
dynamicLinkLibraries(c, allow_lzo);
|
||||
}
|
||||
|
||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||
const test_step = b.step("test", "Run tests");
|
||||
@@ -109,3 +154,42 @@ pub fn build(b: *std.Build) !void {
|
||||
check.dependOn(&lib_check.step);
|
||||
check.dependOn(&exe_check.step);
|
||||
}
|
||||
|
||||
pub fn dynamicLinkLibraries(mod: *std.Build.Step.TranslateC, allow_lzo: bool) void {
|
||||
mod.linkSystemLibrary("zstd", .{});
|
||||
mod.linkSystemLibrary("zlib-ng", .{});
|
||||
mod.linkSystemLibrary("lzma", .{});
|
||||
mod.linkSystemLibrary("lz4", .{});
|
||||
if (allow_lzo)
|
||||
mod.linkSystemLibrary("minilzo", .{});
|
||||
}
|
||||
fn dependencies(
|
||||
b: *std.Build,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
target: std.Build.ResolvedTarget,
|
||||
use_zig_decomp: bool,
|
||||
allow_lzo: bool,
|
||||
dynamic: bool,
|
||||
) ![]*std.Build.Step.Compile {
|
||||
if (use_zig_decomp or dynamic) return &.{};
|
||||
|
||||
var list: std.ArrayList(*std.Build.Step.Compile) = .empty;
|
||||
|
||||
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
|
||||
try list.append(b.allocator, zstd.artifact("zstd"));
|
||||
|
||||
const zng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target });
|
||||
try list.append(b.allocator, zng.artifact("zng"));
|
||||
|
||||
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
|
||||
try list.append(b.allocator, xz.artifact("lzma"));
|
||||
|
||||
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
|
||||
try list.append(b.allocator, lz4.artifact("lz4"));
|
||||
|
||||
if (allow_lzo) {
|
||||
const minilzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
|
||||
try list.append(b.allocator, minilzo.artifact("minilzo"));
|
||||
}
|
||||
return list.toOwnedSlice(b.allocator);
|
||||
}
|
||||
|
||||
+133
-16
@@ -23,6 +23,7 @@ pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
|
||||
try rdr.seekTo(offset);
|
||||
var super: Superblock = undefined;
|
||||
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
|
||||
try super.validate();
|
||||
|
||||
return .{
|
||||
.file = try .init(io, file, super.size, offset),
|
||||
@@ -31,15 +32,14 @@ pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
|
||||
.stateless_decomp = try Decomp.StatelessDecomp(super.compression),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: Archive, io: Io) void {
|
||||
pub fn deinit(self: *Archive, io: Io) void {
|
||||
self.file.deinit(io);
|
||||
}
|
||||
|
||||
/// The root folder of the Archive. Used to open other Files.
|
||||
pub fn root(self: Archive, alloc: std.mem.Allocator, io: Io) !File {
|
||||
pub fn root(self: Archive, alloc: std.mem.Allocator) !File {
|
||||
const root_inode = try Utils.inodeFromRef(
|
||||
alloc,
|
||||
io,
|
||||
self.file,
|
||||
self.stateless_decomp,
|
||||
self.super.inode_start,
|
||||
@@ -50,7 +50,7 @@ pub fn root(self: Archive, alloc: std.mem.Allocator, io: Io) !File {
|
||||
}
|
||||
/// Opens a File within the archive.
|
||||
pub fn open(self: Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||
const root_file = try self.root(alloc, io);
|
||||
const root_file = try self.root(alloc);
|
||||
const path = std.mem.trim(u8, filepath, "/");
|
||||
if (Utils.pathIsSelf(path))
|
||||
return root_file;
|
||||
@@ -95,6 +95,19 @@ pub fn idTable(self: Archive, alloc: std.mem.Allocator, io: Io, idx: u32) !u16 {
|
||||
);
|
||||
}
|
||||
|
||||
/// Extract the entire archive contents to the given directory.
|
||||
pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void {
|
||||
const root_inode = try Utils.inodeFromRef(
|
||||
alloc,
|
||||
self.file,
|
||||
self.stateless_decomp,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
self.super.root_ref,
|
||||
);
|
||||
return root_inode.extract(alloc, io, self.file, self.super, extract_dir, options);
|
||||
}
|
||||
|
||||
// Superblock
|
||||
|
||||
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
||||
@@ -155,17 +168,121 @@ pub const Superblock = extern struct {
|
||||
}
|
||||
};
|
||||
|
||||
// Extraction
|
||||
// Tests
|
||||
|
||||
/// Extract the entire archive contents to the given directory.
|
||||
pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void {
|
||||
const root_inode = try Utils.inodeFromRef(
|
||||
alloc,
|
||||
self.file,
|
||||
self.stateless_decomp,
|
||||
self.super.inode_start,
|
||||
self.super.block_size,
|
||||
self.super.root_ref,
|
||||
);
|
||||
return root_inode.extract(alloc, io, self.file, self.super, extract_dir, options);
|
||||
const TestArchive = "testing/LinuxPATest.sfs";
|
||||
|
||||
test "Basics" {
|
||||
std.debug.print("Starting test: Basics...\n", .{});
|
||||
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock);
|
||||
const root_file = try sfs.root(alloc);
|
||||
defer root_file.deinit();
|
||||
}
|
||||
|
||||
const TestFile = "Start.exe";
|
||||
const TestFileExtractLocation = "testing/Start.exe";
|
||||
|
||||
test "ExtractSingleFile" {
|
||||
std.debug.print("Starting test: ExtractSingleFile...\n", .{});
|
||||
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {};
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
var test_fil = try sfs.open(alloc, io, TestFile);
|
||||
defer test_fil.deinit();
|
||||
try test_fil.extract(alloc, io, TestFileExtractLocation, .default);
|
||||
//TODO: validate extracted file.
|
||||
}
|
||||
|
||||
const TestFullExtractLocation = "testing/TestExtract";
|
||||
|
||||
test "ExtractCompleteArchive" {
|
||||
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
|
||||
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
try sfs.extract(alloc, io, TestFullExtractLocation, .default);
|
||||
}
|
||||
|
||||
test "ExtractCompleteArchiveSingleThreaded" {
|
||||
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
|
||||
|
||||
const alloc = std.testing.allocator;
|
||||
const io = std.testing.io;
|
||||
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
{
|
||||
std.debug.print("First testing using Threaded.global_single_threaded...\n", .{});
|
||||
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||
var sfs: Archive = try .init(Io.Threaded.global_single_threaded.io(), fil, 0);
|
||||
defer sfs.deinit(Io.Threaded.global_single_threaded.io());
|
||||
try sfs.extract(alloc, Io.Threaded.global_single_threaded.io(), TestFullExtractLocation, .default);
|
||||
}
|
||||
{
|
||||
std.debug.print("Next testing using ExtractionOptions.single_threaded...\n", .{});
|
||||
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
try sfs.extract(alloc, io, TestFullExtractLocation, .default_single_threaded);
|
||||
}
|
||||
}
|
||||
|
||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||
.magic = std.mem.readInt(u32, "hsqs", .little),
|
||||
.inode_count = 2974,
|
||||
.mod_time = 1632696724,
|
||||
.block_size = 131072,
|
||||
.frag_count = 264,
|
||||
.compression = .zstd,
|
||||
.block_log = 17,
|
||||
.flags = .{
|
||||
.inode_uncompressed = false,
|
||||
.data_uncompressed = false,
|
||||
.check = false,
|
||||
.frag_uncompressed = false,
|
||||
.fragment_never = false,
|
||||
.fragment_always = false,
|
||||
.duplicates = true,
|
||||
.exportable = true,
|
||||
.xattr_uncompressed = false,
|
||||
.xattr_never = false,
|
||||
.compression_options = false,
|
||||
.ids_uncompressed = false,
|
||||
._ = 0,
|
||||
},
|
||||
.id_count = 1,
|
||||
.ver_maj = 4,
|
||||
.ver_min = 0,
|
||||
.root_ref = .{
|
||||
.block_offset = 1363,
|
||||
.block_start = 29237,
|
||||
._ = 0,
|
||||
},
|
||||
.size = 106841744,
|
||||
.id_start = 106841632,
|
||||
.xattr_start = 106841720,
|
||||
.inode_start = 106778274,
|
||||
.dir_start = 106807998,
|
||||
.frag_start = 106837747,
|
||||
.export_start = 106841602,
|
||||
};
|
||||
|
||||
+11
-1
@@ -60,6 +60,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
|
||||
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully.
|
||||
const options: squashfs.ExtractionOptions = .{
|
||||
.single_threaded = threads == 1,
|
||||
.verbose = verbose,
|
||||
.verbose_writer = if (verbose) &out.interface else null,
|
||||
.ignore_xattr = ignore_xattrs,
|
||||
@@ -68,7 +69,16 @@ pub fn main(init: std.process.Init) !void {
|
||||
|
||||
if (force)
|
||||
try Io.Dir.cwd().deleteTree(io, extLoc);
|
||||
try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
||||
if (threads != 0) {
|
||||
var limited_io = Io.Threaded.init(alloc, .{
|
||||
.async_limit = .limited(threads - 1),
|
||||
.concurrent_limit = .limited(threads - 1),
|
||||
.argv0 = .init(init.minimal.args),
|
||||
.environ = init.minimal.environ,
|
||||
});
|
||||
return arc.extract(alloc, limited_io.io(), extLoc, options); //TODO: Handle error gracefully.
|
||||
}
|
||||
return arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
||||
}
|
||||
|
||||
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
||||
|
||||
+31
-3
@@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
|
||||
const options = @import("options");
|
||||
|
||||
@@ -7,7 +8,13 @@ const xz = @import("decomp/zig_xz.zig");
|
||||
const Decompressor = @import("util/decompressor.zig");
|
||||
|
||||
const zlib = if (options.use_zig_decomp) @import("decomp/zig_zlib.zig") else @import("decomp/c_zlib.zig");
|
||||
<<<<<<< HEAD
|
||||
const lzo = if (options.use_zig_decomp or !options.allow_lzo) void else @import("decomp/c_lzo.zig");
|
||||
=======
|
||||
const lzma = if (options.use_zig_decomp) @import("decomp/zig_lzma.zig") else @import("decomp/c_lzma.zig");
|
||||
const lzo = if (options.use_zig_decomp or options.allow_lzo) void else @import("decomp/c_lzo.zig");
|
||||
const xz = if (options.use_zig_decomp) @import("decomp/zig_xz.zig") else @import("decomp/c_xz.zig");
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
const lz4 = if (options.use_zig_decomp) void else @import("decomp/c_lz4.zig");
|
||||
const zstd = if (options.use_zig_decomp) @import("decomp/zig_zstd.zig") else @import("decomp/c_zstd.zig");
|
||||
|
||||
@@ -45,6 +52,7 @@ pub const Decomp = union(enum) {
|
||||
lz4: lz4,
|
||||
zstd: zstd,
|
||||
|
||||
<<<<<<< HEAD
|
||||
pub fn init(val: Enum, alloc: std.mem.Allocator, io: std.Io, block_size: u32) !Decomp {
|
||||
return switch (val) {
|
||||
.gzip => .{ .gzip = zlib.init(alloc, io, block_size) },
|
||||
@@ -53,23 +61,43 @@ pub const Decomp = union(enum) {
|
||||
.xz => .{ .xz = .{} },
|
||||
.lz4 => .{ .lz4 = .{} },
|
||||
.zstd => .{ .zstd = zstd.init(alloc, io, block_size) },
|
||||
=======
|
||||
pub fn init(val: Enum, alloc: std.mem.Allocator, io: Io, block_size: u32) !Decomp {
|
||||
return switch (val) {
|
||||
.gzip => .{ .gzip = if (options.use_zig_decomp) try zlib.init(alloc, io, block_size) else try zlib.init(alloc, io) },
|
||||
.lzma => .{ .lzma = if (options.use_zig_decomp) try lzma.init(alloc, io, block_size) else .{} },
|
||||
.lzo => if (options.use_zig_decomp or !options.allow_lzo) error.LzoUnsupported else .{ .lzo = .{} },
|
||||
.xz => .{ .xz = if (options.use_zig_decomp) try xz.init(alloc, io, block_size) else .{} },
|
||||
.lz4 => if (options.use_zig_decomp) error.Lz4Unsupported else .{ .lz4 = .{} },
|
||||
.zstd => .{ .zstd = if (options.use_zig_decomp) try zstd.init(alloc, io, block_size) else try zstd.init(alloc, io) },
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: *Decomp) void {
|
||||
pub fn deinit(self: *Decomp, alloc: std.mem.Allocator) void {
|
||||
if (options.use_zig_decomp) {
|
||||
switch (self.*) {
|
||||
.gzip => self.gzip.deinit(),
|
||||
.lzma => self.lzma.deinit(),
|
||||
.xz => self.xz.deinit(),
|
||||
.zstd => self.zstd.deinit(),
|
||||
else => {},
|
||||
}
|
||||
} else {
|
||||
switch (self.*) {
|
||||
.gzip => self.gzip.deinit(alloc),
|
||||
.zstd => self.zstd.deinit(alloc),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decompressor(self: *Decomp) *const Decompressor {
|
||||
return switch (self.*) {
|
||||
.gzip => &self.gzip.interface,
|
||||
.lzma => &lzma.stateless_decompressor,
|
||||
.lzo => &lzo.stateless_decompressor,
|
||||
.lzo => if (options.use_zig_decomp or !options.allow_lzo) unreachable else &lzo.stateless_decompressor,
|
||||
.xz => &xz.stateless_decompressor,
|
||||
.lz4 => &lz4.stateless_decompressor,
|
||||
.lz4 => if (options.use_zig_decomp) unreachable else &lz4.stateless_decompressor,
|
||||
.zstd => &self.zstd.interface,
|
||||
};
|
||||
}
|
||||
|
||||
+5
-11
@@ -8,18 +8,12 @@ const Error = Decompressor.Error;
|
||||
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||
|
||||
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
<<<<<<< HEAD
|
||||
const res = c.LZ4_decompress_fast(in.ptr, out.ptr, @truncate(out.len));
|
||||
=======
|
||||
const out_len: c_int = @bitCast(@as(u32, @truncate(out.len)));
|
||||
const res = c.LZ4_decompress_fast(in.ptr, out.ptr, out_len);
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
if (res < 0) return Error.ReadFailed;
|
||||
return @abs(res);
|
||||
}
|
||||
|
||||
// lzma_allocator
|
||||
|
||||
fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
return alloc.rawAlloc(size, .@"1", 0);
|
||||
}
|
||||
fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
alloc.rawFree(@ptrCast(mem_ptr), .@"1", 0);
|
||||
}
|
||||
|
||||
+10
-14
@@ -15,13 +15,8 @@ const Self = @This();
|
||||
|
||||
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||
|
||||
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var stream: c.lzma_stream = .{
|
||||
.allocator = &.{
|
||||
.alloc = lzmaAlloc,
|
||||
.free = lzmaFree,
|
||||
.@"opaque" = @constCast(&alloc),
|
||||
},
|
||||
.next_in = in.ptr,
|
||||
.avail_in = in.len,
|
||||
.next_out = out.ptr,
|
||||
@@ -38,11 +33,12 @@ fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8,
|
||||
|
||||
// lzma_allocator
|
||||
|
||||
fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
return alloc.rawAlloc(size, .@"1", 0);
|
||||
}
|
||||
fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
alloc.rawFree(@ptrCast(mem_ptr), .@"1", 0);
|
||||
}
|
||||
// fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
|
||||
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||
// return alloc.rawAlloc(size, .@"1", 0);
|
||||
// }
|
||||
// fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
// if (mem_ptr == null) return;
|
||||
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||
// alloc.free(@as([*]u8, @ptrCast(mem_ptr.?)));
|
||||
// }
|
||||
|
||||
@@ -22,14 +22,3 @@ fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out:
|
||||
if (res != c.LZO_E_OK) return Error.ReadFailed;
|
||||
return out_len;
|
||||
}
|
||||
|
||||
// lzma_allocator
|
||||
|
||||
fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
return alloc.rawAlloc(size, .@"1", 0);
|
||||
}
|
||||
fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
alloc.rawFree(@ptrCast(mem_ptr), .@"1", 0);
|
||||
}
|
||||
|
||||
+11
-14
@@ -15,13 +15,8 @@ const Self = @This();
|
||||
|
||||
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||
|
||||
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var stream: c.lzma_stream = .{
|
||||
.allocator = &.{
|
||||
.alloc = lzmaAlloc,
|
||||
.free = lzmaFree,
|
||||
.@"opaque" = @constCast(&alloc),
|
||||
},
|
||||
.next_in = in.ptr,
|
||||
.avail_in = in.len,
|
||||
.next_out = out.ptr,
|
||||
@@ -38,11 +33,13 @@ fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8,
|
||||
|
||||
// lzma_allocator
|
||||
|
||||
fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
return alloc.rawAlloc(size, .@"1", 0);
|
||||
}
|
||||
fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(@constCast(ptr)));
|
||||
alloc.rawFree(@ptrCast(mem_ptr), .@"1", 0);
|
||||
}
|
||||
// fn lzmaAlloc(ptr: ?*anyopaque, size: usize, _: usize) callconv(.c) ?*anyopaque {
|
||||
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||
// const mem = alloc.alloc(u8, size) catch return null;
|
||||
// return mem.ptr;
|
||||
// }
|
||||
// fn lzmaFree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
// if (mem_ptr == null) return;
|
||||
// var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||
// alloc.free(@as([*]u8, @ptrCast(mem_ptr.?)));
|
||||
// }
|
||||
|
||||
+10
-11
@@ -20,20 +20,15 @@ io: Io,
|
||||
ctx: []c.zng_stream,
|
||||
ctx_queue: Queue,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
|
||||
pub fn init(alloc: std.mem.Allocator, io: Io) !Self {
|
||||
const buf = try alloc.alloc(c.zng_stream, 20); // TODO: Choose a better number instead of a random one.
|
||||
var queue: Queue = .init(buf);
|
||||
for (0..20) |_|
|
||||
try queue.putOne(io, .{
|
||||
.zalloc = zalloc,
|
||||
.zfree = zfree,
|
||||
});
|
||||
try queue.putOne(io, .{});
|
||||
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.io = io,
|
||||
|
||||
.block_size = block_size,
|
||||
.ctx = buf,
|
||||
.ctx_queue = queue,
|
||||
};
|
||||
@@ -52,13 +47,12 @@ fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8
|
||||
var stream = self.ctx_queue.getOne(self.io) catch return Error.ReadFailed;
|
||||
defer self.ctx_queue.putOne(self.io, stream) catch {};
|
||||
|
||||
stream.@"opaque" = @constCast(&alloc);
|
||||
stream.next_in = in.ptr;
|
||||
stream.avail_in = @truncate(in.len);
|
||||
stream.next_out = out.ptr;
|
||||
stream.avail_out = @truncate(out.len);
|
||||
|
||||
try zlibDecomp(&stream, in, out);
|
||||
try zlibDecomp(&stream);
|
||||
|
||||
return stream.total_out;
|
||||
}
|
||||
@@ -74,9 +68,8 @@ inline fn zlibDecomp(stream: *c.zng_stream) !void {
|
||||
|
||||
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
|
||||
|
||||
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
var stream: c.zng_stream = .{
|
||||
.@"opaque" = @constCast(&alloc),
|
||||
.next_in = in.ptr,
|
||||
.avail_in = @truncate(in.len),
|
||||
.next_out = out.ptr,
|
||||
@@ -93,6 +86,12 @@ fn zalloc(ptr: ?*anyopaque, size: c_uint, len: c_uint) callconv(.c) ?*anyopaque
|
||||
return alloc.rawAlloc(size * len, .@"1", 0);
|
||||
}
|
||||
fn zfree(ptr: ?*anyopaque, mem_ptr: ?*anyopaque) callconv(.c) void {
|
||||
<<<<<<< HEAD
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||
alloc.rawFree(@ptrCast(mem_ptr), .@"1", 0);
|
||||
=======
|
||||
if (mem_ptr == null) return;
|
||||
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||
alloc.free(@as([*]u8, @ptrCast(mem_ptr.?)));
|
||||
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
|
||||
}
|
||||
|
||||
+14
-14
@@ -20,17 +20,15 @@ io: Io,
|
||||
ctx: []?*c.ZSTD_DCtx,
|
||||
ctx_queue: Queue,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
|
||||
pub fn init(alloc: std.mem.Allocator, io: Io) !Self {
|
||||
const buf = try alloc.alloc(?*c.ZSTD_DCtx, 20); // TODO: Choose a better number instead of a random one.
|
||||
var queue: Queue = .init(buf);
|
||||
for (0..20) |_|
|
||||
try queue.putOne(io, c.ZSTD_createDCtx());
|
||||
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.io = io,
|
||||
|
||||
.block_size = block_size,
|
||||
.ctx = buf,
|
||||
.ctx_queue = queue,
|
||||
};
|
||||
@@ -38,25 +36,27 @@ pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
|
||||
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
|
||||
self.ctx_queue.close(self.io);
|
||||
for (self.ctx) |ctx|
|
||||
c.ZSTD_freeDCtx(ctx);
|
||||
_ = c.ZSTD_freeDCtx(ctx);
|
||||
alloc.free(self.ctx);
|
||||
}
|
||||
|
||||
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||
if (d == null) {
|
||||
// TODO: Fix
|
||||
//
|
||||
// if (d == null) {
|
||||
return statelessDecomp(d, alloc, in, out);
|
||||
}
|
||||
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
|
||||
// }
|
||||
// var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
|
||||
|
||||
const ctx = self.ctx_queue.getOne(self.io) catch return Error.ReadFailed;
|
||||
defer self.ctx_queue.putOne(self.io, ctx) catch {};
|
||||
// const ctx = self.ctx_queue.getOne(self.io) catch return Error.ReadFailed;
|
||||
// defer self.ctx_queue.putOne(self.io, ctx) catch {};
|
||||
|
||||
_ = c.ZSTD_DCtx_reset(ctx, c.ZSTD_reset_session_only);
|
||||
// _ = c.ZSTD_DCtx_reset(ctx, c.ZSTD_reset_session_only);
|
||||
|
||||
const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len);
|
||||
if (c.ZSTD_isError(res) != 0)
|
||||
return Error.ReadFailed;
|
||||
return res;
|
||||
// const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len);
|
||||
// if (c.ZSTD_isError(res) != 0)
|
||||
// return Error.ReadFailed;
|
||||
// return res;
|
||||
}
|
||||
|
||||
// Stateless
|
||||
|
||||
@@ -56,7 +56,7 @@ fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8
|
||||
const buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
|
||||
defer self.buf_queue.putOne(self.io, buf) catch {};
|
||||
|
||||
return zlibDecomp(buf.buf, in, out);
|
||||
return zlibDecomp(buf, in, out);
|
||||
}
|
||||
|
||||
inline fn zlibDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
|
||||
|
||||
@@ -20,9 +20,9 @@ buf: [][]u8,
|
||||
buf_queue: Queue,
|
||||
|
||||
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
|
||||
const buf = try alloc.alloc([]u8, 20); // TODO: Choose a better number instead of a random one.
|
||||
const buf = try alloc.alloc([]u8, 5); // TODO: Choose a better number instead of a random one.
|
||||
var queue: Queue = .init(buf);
|
||||
for (0..20) |_|
|
||||
for (buf) |_|
|
||||
try queue.putOne(io, try alloc.alloc(u8, block_size + zstd.block_size_max));
|
||||
|
||||
return .{
|
||||
|
||||
+2
-3
@@ -37,7 +37,7 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, in: Inode, name: []const
|
||||
}
|
||||
pub fn fromDirEntry(alloc: std.mem.Allocator, archive: Archive, ent: DirEntry) !File {
|
||||
var rdr = archive.file.readerAt(archive.super.inode_start + ent.block_start);
|
||||
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.stateless_decomp);
|
||||
var meta: MetadataReader = .init(alloc, &rdr, archive.stateless_decomp);
|
||||
try meta.interface.discardAll(ent.block_offset);
|
||||
|
||||
var in: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
|
||||
@@ -52,7 +52,6 @@ pub fn deinit(self: File) void {
|
||||
pub fn open(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||
const entries = try self.inode.readDirectory(
|
||||
alloc,
|
||||
io,
|
||||
self.archive.file,
|
||||
self.archive.stateless_decomp,
|
||||
self.archive.super.dir_start,
|
||||
@@ -77,7 +76,7 @@ pub fn open(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8)
|
||||
}
|
||||
} else return Error.FileNotFound;
|
||||
|
||||
const first_elem_file = try fromDirEntry(alloc, io, self.archive, search_slice[idx]);
|
||||
const first_elem_file = try fromDirEntry(alloc, self.archive, search_slice[idx]);
|
||||
if (first_element.len == path.len)
|
||||
return first_elem_file;
|
||||
defer first_elem_file.deinit();
|
||||
|
||||
+235
-56
@@ -276,73 +276,26 @@ pub fn extract(
|
||||
const path = std.mem.trimEnd(u8, filepath, "/");
|
||||
|
||||
var decomp_base: Decomp = try .init(super.compression, alloc, io, super.block_size);
|
||||
decomp_base.deinit(alloc);
|
||||
const decomp = decomp_base.decompressor();
|
||||
|
||||
var frag_mgr: FragManager = try .init(alloc, fil, decomp, super.frag_start, super.frag_count, super.block_size);
|
||||
defer frag_mgr.deinit(io);
|
||||
|
||||
if (options.single_threaded)
|
||||
return self.extractSinglethreaded(alloc, io, fil, super, path, options, decomp, &frag_mgr);
|
||||
|
||||
var sel_buf: [10]ExtractReturnUnion = undefined;
|
||||
var sel: Io.Select(ExtractReturnUnion) = .init(io, &sel_buf);
|
||||
defer sel.cancelDiscard();
|
||||
|
||||
sel.async(.path_ret, extractReal, .{ self, alloc, io, fil, super, decomp, &sel, &frag_mgr, path, true });
|
||||
var loop = io.async(finishLoop, .{ alloc, io, fil, decomp, super, options, &sel });
|
||||
|
||||
var id_table: CachedTable(u16) = .init(alloc, fil, decomp, super.id_start, super.id_count);
|
||||
defer id_table.deinit(io);
|
||||
sel.async(.path_ret, extractRealAsync, .{ self, alloc, io, fil, super, decomp, &sel, &frag_mgr, path, true });
|
||||
|
||||
var xattr_table: ?XattrTable = if (super.flags.xattr_never or options.ignore_xattr or !@hasField(std.os, "linux"))
|
||||
null
|
||||
else
|
||||
try .init(alloc, fil, decomp, super.xattr_start);
|
||||
defer if (xattr_table != null) xattr_table.?.deinit(io);
|
||||
|
||||
var dir_queue: std.PriorityDequeue(PathRet, void, DirCompare) = .empty;
|
||||
defer dir_queue.deinit(alloc);
|
||||
|
||||
while (true) {
|
||||
if (sel.group.token.load(.unordered) == null) break;
|
||||
|
||||
const ret = try sel.await();
|
||||
const path_ret = try ret.path_ret;
|
||||
|
||||
if (options.ignore_permissions and xattr_table == null) {
|
||||
path_ret.deinit(alloc);
|
||||
continue;
|
||||
try loop.await(io);
|
||||
}
|
||||
|
||||
if (path_ret.inode.hdr.inode_type == .dir or path_ret.inode.hdr.inode_type == .ext_dir) {
|
||||
try dir_queue.push(alloc, path_ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
defer path_ret.deinit(alloc);
|
||||
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options);
|
||||
}
|
||||
|
||||
while (sel.cancel()) |ret| {
|
||||
const path_ret = try ret.path_ret;
|
||||
|
||||
if (options.ignore_permissions and xattr_table == null) {
|
||||
path_ret.deinit(alloc);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path_ret.inode.hdr.inode_type == .dir or path_ret.inode.hdr.inode_type == .ext_dir) {
|
||||
try dir_queue.push(alloc, path_ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
defer path_ret.deinit(alloc);
|
||||
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options);
|
||||
}
|
||||
|
||||
var iter = dir_queue.iterator();
|
||||
while (iter.next()) |path_ret| {
|
||||
defer path_ret.deinit(alloc);
|
||||
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options);
|
||||
}
|
||||
}
|
||||
pub fn extractReal(
|
||||
fn extractRealAsync(
|
||||
self: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
io: Io,
|
||||
@@ -385,7 +338,7 @@ pub fn extractReal(
|
||||
const new_inode = try read(alloc, &meta.interface, super.block_size);
|
||||
errdefer new_inode.deinit(alloc);
|
||||
|
||||
sel.async(.path_ret, extractReal, .{ new_inode, alloc, io, fil, super, decomp, sel, frag_mgr, new_path, false });
|
||||
sel.async(.path_ret, extractRealAsync, .{ new_inode, alloc, io, fil, super, decomp, sel, frag_mgr, new_path, false });
|
||||
}
|
||||
},
|
||||
.file, .ext_file => {
|
||||
@@ -454,3 +407,229 @@ pub fn extractReal(
|
||||
.origin = origin,
|
||||
};
|
||||
}
|
||||
fn finishLoop(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, super: Archive.Superblock, options: ExtractionOptions, sel: *Io.Select(ExtractReturnUnion)) !void {
|
||||
var id_table: CachedTable(u16) = .init(alloc, fil, decomp, super.id_start, super.id_count);
|
||||
defer id_table.deinit(io);
|
||||
|
||||
var xattr_table: ?XattrTable = if (super.flags.xattr_never or options.ignore_xattr or !@hasField(std.os, "linux"))
|
||||
null
|
||||
else
|
||||
try .init(alloc, fil, decomp, super.xattr_start);
|
||||
defer if (xattr_table != null) xattr_table.?.deinit(io);
|
||||
|
||||
var dir_queue: std.PriorityDequeue(PathRet, void, DirCompare) = .empty;
|
||||
defer dir_queue.deinit(alloc);
|
||||
|
||||
while (true) {
|
||||
if (sel.group.token.load(.unordered) == null) break;
|
||||
|
||||
const ret = try sel.await();
|
||||
const path_ret = try ret.path_ret;
|
||||
|
||||
if (options.ignore_permissions and xattr_table == null) {
|
||||
path_ret.deinit(alloc);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path_ret.inode.hdr.inode_type == .dir or path_ret.inode.hdr.inode_type == .ext_dir) {
|
||||
try dir_queue.push(alloc, path_ret);
|
||||
continue;
|
||||
}
|
||||
defer path_ret.deinit(alloc);
|
||||
|
||||
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options);
|
||||
}
|
||||
|
||||
while (sel.cancel()) |ret| {
|
||||
const path_ret = try ret.path_ret;
|
||||
|
||||
if (options.ignore_permissions and xattr_table == null) {
|
||||
path_ret.deinit(alloc);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path_ret.inode.hdr.inode_type == .dir or path_ret.inode.hdr.inode_type == .ext_dir) {
|
||||
try dir_queue.push(alloc, path_ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
defer path_ret.deinit(alloc);
|
||||
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options);
|
||||
}
|
||||
|
||||
var iter = dir_queue.iterator();
|
||||
while (iter.next()) |path_ret| {
|
||||
defer path_ret.deinit(alloc);
|
||||
try path_ret.setMetadata(alloc, io, &id_table, if (xattr_table == null) null else &xattr_table.?, options);
|
||||
}
|
||||
}
|
||||
/// Extracts the given inode to the given path. If the inode not a directory, the given path must not exist.
|
||||
/// If the inode is a directory the path must not exist or be a directory.
|
||||
fn extractSinglethreaded(
|
||||
self: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
io: Io,
|
||||
fil: OffsetFile,
|
||||
super: Archive.Superblock,
|
||||
path: []const u8,
|
||||
options: ExtractionOptions,
|
||||
decomp: *const Decompressor,
|
||||
frag: *FragManager,
|
||||
) !void {
|
||||
var id_table: CachedTable(u16) = .init(alloc, fil, decomp, super.id_start, super.id_count);
|
||||
defer id_table.deinit(io);
|
||||
|
||||
var xattr_table: ?XattrTable = if (super.flags.xattr_never or options.ignore_xattr or !@hasField(std.os, "linux"))
|
||||
null
|
||||
else
|
||||
try .init(alloc, fil, decomp, super.xattr_start);
|
||||
defer if (xattr_table != null) xattr_table.?.deinit(io);
|
||||
|
||||
return self.extractReal(
|
||||
alloc,
|
||||
io,
|
||||
fil,
|
||||
super,
|
||||
decomp,
|
||||
frag,
|
||||
&id_table,
|
||||
if (xattr_table == null) null else &xattr_table.?,
|
||||
path,
|
||||
options,
|
||||
);
|
||||
}
|
||||
fn extractReal(
|
||||
self: Inode,
|
||||
alloc: std.mem.Allocator,
|
||||
io: Io,
|
||||
fil: OffsetFile,
|
||||
super: Archive.Superblock,
|
||||
decomp: *const Decompressor,
|
||||
frag_mgr: *FragManager,
|
||||
id_table: *CachedTable(u16),
|
||||
xattr_table: ?*XattrTable,
|
||||
path: []const u8,
|
||||
options: ExtractionOptions,
|
||||
) !void {
|
||||
switch (self.hdr.inode_type) {
|
||||
.dir, .ext_dir => {
|
||||
try Io.Dir.cwd().createDir(io, path, @enumFromInt(0o777));
|
||||
|
||||
const entries = self.readDirectory(alloc, fil, decomp, super.dir_start) catch |err| switch (err) {
|
||||
Error.NotDirectory, Error.NotExtended, Error.NotRegularFile, Error.NotSymlink => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer {
|
||||
for (entries) |e|
|
||||
e.deinit(alloc);
|
||||
alloc.free(entries);
|
||||
}
|
||||
|
||||
for (entries) |e| {
|
||||
const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", e.name });
|
||||
defer alloc.free(new_path);
|
||||
|
||||
var rdr = fil.readerAt(super.inode_start + e.block_start);
|
||||
var meta: MetadataReader = .init(alloc, &rdr, decomp);
|
||||
try meta.interface.discardAll(e.block_offset);
|
||||
|
||||
const new_inode = try read(alloc, &meta.interface, super.block_size);
|
||||
defer new_inode.deinit(alloc);
|
||||
|
||||
try new_inode.extractReal(alloc, io, fil, super, decomp, frag_mgr, id_table, xattr_table, new_path, options);
|
||||
}
|
||||
},
|
||||
.file, .ext_file => {
|
||||
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{ .make_path = true });
|
||||
defer atomic.deinit(io);
|
||||
|
||||
var rdr: DataReader = switch (self.data) {
|
||||
.file => |f| blk: {
|
||||
var ext: DataReader = try .init(alloc, io, fil, decomp, super.block_size, f.size, f.block_start, f.block_sizes);
|
||||
if (f.frag_idx != 0xFFFFFFFF)
|
||||
ext.addFrag(f.frag_block_offset, try frag_mgr.get(io, f.frag_idx));
|
||||
break :blk ext;
|
||||
},
|
||||
.ext_file => |f| blk: {
|
||||
var ext: DataReader = try .init(alloc, io, fil, decomp, super.block_size, f.size, f.block_start, f.block_sizes);
|
||||
if (f.frag_idx != 0xFFFFFFFF)
|
||||
ext.addFrag(f.frag_block_offset, try frag_mgr.get(io, f.frag_idx));
|
||||
break :blk ext;
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
defer rdr.deinit();
|
||||
|
||||
var buf: [512 * 1024]u8 = undefined;
|
||||
var wrt = atomic.file.writer(io, &buf);
|
||||
|
||||
_ = try rdr.interface.streamRemaining(&wrt.interface);
|
||||
|
||||
try wrt.flush();
|
||||
|
||||
try atomic.link(io);
|
||||
},
|
||||
.symlink, .ext_symlink => try Io.Dir.cwd().symLink(io, self.symlinkTarget() catch unreachable, path, .{}),
|
||||
else => {
|
||||
var mode: u32 = undefined;
|
||||
var dev: u32 = 0;
|
||||
|
||||
const DT = std.posix.DT;
|
||||
|
||||
switch (self.data) {
|
||||
.char_dev => |d| {
|
||||
dev = d.dev;
|
||||
mode = DT.CHR;
|
||||
},
|
||||
.ext_char_dev => |d| {
|
||||
dev = d.dev;
|
||||
mode = DT.CHR;
|
||||
},
|
||||
.block_dev => |d| {
|
||||
dev = d.dev;
|
||||
mode = DT.BLK;
|
||||
},
|
||||
.ext_block_dev => |d| {
|
||||
dev = d.dev;
|
||||
mode = DT.BLK;
|
||||
},
|
||||
.fifo, .ext_fifo => mode = DT.FIFO,
|
||||
.socket, .ext_socket => mode = DT.SOCK,
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &[_][]const u8{path}, 0);
|
||||
const res = std.os.linux.mknod(sentinel_path, mode, dev);
|
||||
alloc.free(sentinel_path);
|
||||
if (res != 0)
|
||||
return ExtractError.MknodFailed;
|
||||
},
|
||||
}
|
||||
if (options.ignore_permissions and options.ignore_xattr) return;
|
||||
|
||||
var f = try Io.Dir.cwd().openFile(io, path, .{});
|
||||
defer f.close(io);
|
||||
|
||||
if (!options.ignore_permissions) {
|
||||
try f.setPermissions(io, @enumFromInt(self.hdr.permissions));
|
||||
try f.setOwner(io, try id_table.get(io, self.hdr.uid_idx), try id_table.get(io, self.hdr.gid_idx));
|
||||
}
|
||||
if (xattr_table != null) {
|
||||
const idx = self.xattrIndex() catch return;
|
||||
|
||||
const xattrs = try xattr_table.?.get(alloc, io, idx);
|
||||
defer {
|
||||
for (xattrs) |x|
|
||||
x.deinit(alloc);
|
||||
alloc.free(xattrs);
|
||||
}
|
||||
|
||||
const sentinel_path = try std.mem.concatWithSentinel(alloc, u8, &[_][]const u8{path}, 0);
|
||||
defer alloc.free(sentinel_path);
|
||||
for (xattrs) |x| {
|
||||
const xattr_ret = std.os.linux.fsetxattr(f.handle, x.key, x.value.ptr, x.value.len, 0);
|
||||
if (xattr_ret != 0)
|
||||
return ExtractError.CannotSetXattr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-9
@@ -5,8 +5,10 @@ const Writer = std.Io.Writer;
|
||||
|
||||
const ExtractionOptions = @This();
|
||||
|
||||
// /// The number of threads used for extraction. 0 implies single threaded.
|
||||
// threads: usize = 1, // As of Zig 0.16 this should no longer be necessary, instead this should be set by the io instance used.
|
||||
/// Extract single-threaded only.
|
||||
/// Though not necessary if using Threaded.single_threaded,
|
||||
/// setting single_threaded is more efficient.
|
||||
single_threaded: bool = false,
|
||||
/// 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,16 +20,12 @@ verbose: bool = false,
|
||||
/// Where to print verbose log.
|
||||
verbose_writer: ?*Writer = null,
|
||||
|
||||
pub const SingleThreadedDefault: ExtractionOptions = .{};
|
||||
pub fn Default() !ExtractionOptions {
|
||||
return .{
|
||||
.threads = try std.Thread.getCpuCount(),
|
||||
};
|
||||
}
|
||||
pub const default: ExtractionOptions = .{};
|
||||
pub const default_single_threaded: ExtractionOptions = .{ .single_threaded = true };
|
||||
|
||||
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||
return .{
|
||||
.verbose = true,
|
||||
.verbose_writer = wrt,
|
||||
.threads = try std.Thread.getCpuCount(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
pub const Archive = @import("archive.zig");
|
||||
pub const ExtractionOptions = @import("options.zig");
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const io = std.testing.io;
|
||||
const alloc = std.testing.allocator;
|
||||
const stuff = @import("builtin");
|
||||
|
||||
const Archive = @import("archive.zig");
|
||||
const Superblock = Archive.Superblock;
|
||||
|
||||
const TestArchive = "testing/LinuxPATest.sfs";
|
||||
|
||||
test "Basics" {
|
||||
std.debug.print("Starting test: Basics...\n", .{});
|
||||
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock);
|
||||
const root_file = try sfs.root(alloc, io);
|
||||
defer root_file.deinit();
|
||||
}
|
||||
|
||||
const TestFile = "Start.exe";
|
||||
const TestFileExtractLocation = "testing/Start.exe";
|
||||
|
||||
test "ExtractSingleFile" {
|
||||
std.debug.print("Starting test: ExtractSingleFile...\n", .{});
|
||||
|
||||
Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {};
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
var test_fil = try sfs.open(alloc, io, TestFile);
|
||||
defer test_fil.deinit();
|
||||
try test_fil.extract(alloc, io, TestFileExtractLocation, try .Default());
|
||||
//TODO: validate extracted file.
|
||||
}
|
||||
|
||||
const TestFullExtractLocation = "testing/TestExtract";
|
||||
|
||||
test "ExtractCompleteArchive" {
|
||||
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
|
||||
|
||||
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||
defer fil.close(io);
|
||||
var sfs: Archive = try .init(io, fil, 0);
|
||||
defer sfs.deinit(io);
|
||||
try sfs.extract(alloc, io, TestFullExtractLocation, try .Default());
|
||||
}
|
||||
|
||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||
.magic = std.mem.readInt(u32, "hsqs", .little),
|
||||
.inode_count = 2974,
|
||||
.mod_time = 1632696724,
|
||||
.block_size = 131072,
|
||||
.frag_count = 264,
|
||||
.compression = .zstd,
|
||||
.block_log = 17,
|
||||
.flags = .{
|
||||
.inode_uncompressed = false,
|
||||
.data_uncompressed = false,
|
||||
.check = false,
|
||||
.frag_uncompressed = false,
|
||||
.fragment_never = false,
|
||||
.fragment_always = false,
|
||||
.duplicates = true,
|
||||
.exportable = true,
|
||||
.xattr_uncompressed = false,
|
||||
.xattr_never = false,
|
||||
.compression_options = false,
|
||||
.ids_uncompressed = false,
|
||||
._ = 0,
|
||||
},
|
||||
.id_count = 1,
|
||||
.ver_maj = 4,
|
||||
.ver_min = 0,
|
||||
.root_ref = .{
|
||||
.block_offset = 1363,
|
||||
.block_start = 29237,
|
||||
._ = 0,
|
||||
},
|
||||
.size = 106841744,
|
||||
.id_start = 106841632,
|
||||
.xattr_start = 106841720,
|
||||
.inode_start = 106778274,
|
||||
.dir_start = 106807998,
|
||||
.frag_start = 106837747,
|
||||
.export_start = 106841602,
|
||||
};
|
||||
@@ -49,6 +49,23 @@ fn numBlocks(self: DataExtractor) usize {
|
||||
return num;
|
||||
}
|
||||
|
||||
/// Starts extracting the data using the given group to spawn async tasks.
|
||||
pub fn extractConcurrent(self: DataExtractor, alloc: std.mem.Allocator, io: Io, fil: Io.File) (Error || Io.ConcurrentError)!void {
|
||||
var group: Io.Group = .init;
|
||||
defer group.cancel(io);
|
||||
var err: ?Error = null;
|
||||
|
||||
var read_offset: u64 = self.start;
|
||||
for (0..self.blocks.len) |idx| {
|
||||
try group.concurrent(io, blockThread, .{ self, alloc, io, fil, read_offset, idx, &err });
|
||||
read_offset += self.blocks[idx].size;
|
||||
}
|
||||
if (self.frag_block != null)
|
||||
try group.concurrent(io, fragThread, .{ self, io, fil, &err });
|
||||
|
||||
group.await(io) catch |cancel| return err orelse cancel;
|
||||
}
|
||||
|
||||
/// Starts extracting the data using the given group to spawn async tasks.
|
||||
pub fn extractAsync(self: DataExtractor, alloc: std.mem.Allocator, io: Io, fil: Io.File) Error!void {
|
||||
var group: Io.Group = .init;
|
||||
|
||||
@@ -18,7 +18,7 @@ pub fn init(io: Io, fil: File, archive_size: u64, init_offset: u64) !OffsetFile
|
||||
}),
|
||||
};
|
||||
}
|
||||
pub fn deinit(self: @This(), io: Io) void {
|
||||
pub fn deinit(self: *OffsetFile, io: Io) void {
|
||||
self.map.destroy(io);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user