15 Commits

Author SHA1 Message Date
Caleb Gardner 21f1a304ca Started changes to Zig 0.16.0 2026-04-29 02:20:46 -05:00
Caleb Gardner cfc0e58905 Added xattr & cached uid/gid to Inode 2026-04-24 05:52:31 -05:00
Caleb Gardner 0d5b727919 Added stateless Xattr lookup 2026-04-24 04:16:22 -05:00
Caleb J. Gardner b111859c4d DataReader! 2026-04-08 02:33:58 -05:00
Caleb J. Gardner 3e97aabe53 Fleshing out more of the library.
Mainly adding functions to File
2026-04-07 15:41:30 -05:00
Caleb J. Gardner eec468ff17 File stuff 2026-04-04 18:48:57 -05:00
Caleb J. Gardner c7c44029c9 Started working on Archive functions
Started working on File
2026-04-04 14:17:50 -05:00
Caleb J. Gardner e07f11d195 Lookup tables! 2026-04-04 01:37:36 -05:00
Caleb J. Gardner d0787a5200 Fixed a couple decompressor issues.
Re-added ExtractionOptions
2026-04-03 22:27:40 -05:00
Caleb J. Gardner 4ee15b036a Finished(?) decompression stuff 2026-04-03 21:25:13 -05:00
Caleb J. Gardner c01caba69b More decompressor work 2026-04-02 01:42:43 -05:00
Caleb J. Gardner 3093994ac1 Started work on actual decompression implementations 2026-03-29 03:36:43 -05:00
Caleb J. Gardner ad8222911f Further implementing everthing again.
Added Decompressor vtable interface.
Directory table parsing
Inode reading
Metadata Reader
2026-03-27 02:39:07 -05:00
Caleb J. Gardner a8c067e933 Some initial work
Some basic Inode stuff
Some basic Archive stuff
2026-03-26 22:30:15 -05:00
Caleb J. Gardner 30755f7d5c Reseting. Again. 2026-03-26 21:46:01 -05:00
54 changed files with 1940 additions and 2976 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
"build": { "build": {
"command": "zig", "command": "zig",
"args": ["build", "-Ddebug=true"], "args": ["build", "-Duse_c_libs=true", "-Ddebug=true"],
}, },
"program": "zig-out/bin/unsquashfs", "program": "zig-out/bin/unsquashfs",
+11 -15
View File
@@ -10,13 +10,9 @@ Overall works, but currently is missing some features ([see below](#capabilities
## Build options ## Build options
> `-Duse_zig_decomp=true` > `-Duse_c_libs=true`
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. 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.
> `-Ddynamic=true`
Dynamicly link C libraries (if they're used) instead of statically linking them.
> `-Dallow_lzo=true` > `-Dallow_lzo=true`
@@ -39,22 +35,22 @@ Most features are present except for the following:
## Performance ## 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 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) and with `--release=fast`.
Currently, my only performance checks are checking execution time, nothing deeper. Currently, my only performance checks are checking execution time, nothing deeper.
* Currently, using my test archive, performance aproximately matches `unsquashfs` when multi-threaded, but significantly slower when single-threaded. * Under ideal circumstances, my library is ~70% slower (.12s vs .20s).
* Using Zig decompression libraries *significantly* increases decompression time by 5x. Under ideal circumstances. * Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances.
* Performance improvements/regressions will be common. I'm still learning Zig. * Performance improvements/regressions will be common. I'm still learning Zig.
Example Times: Example Times:
* *unsquashfs, multi-threaded*: .15s * *unsquashfs, multi-threaded*: .12s
* *unsquashfs, single-threaded*: .16s * *unsquashfs, single-threaded*: .13s
* *C-libs, single-threaded*: .36s * *C-libs, single-threaded*: .45s
* *C-libs, multi-threaded*: .14s * *C-libs, multi-threaded*: .20s
* *Zig-libs, single-threaded*: CURRENTLY UNTESTED * *Zig-libs, single-threaded*: 5.78s
* *Zig-libs, multi-threaded*: .76s * *Zig-libs, multi-threaded*: 1.08s
## Build considerations ## Build considerations
-17
View File
@@ -1,17 +0,0 @@
#! /usr/bin/env bash
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"
+71 -145
View File
@@ -1,86 +1,62 @@
const std = @import("std"); const std = @import("std");
pub fn build(b: *std.Build) !void { 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 use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse true;
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false; // const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false;
<<<<<<< HEAD const debug = b.option(bool, "debug", "Enable options to make debugging easier.") orelse false;
=======
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"); const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
const zig_squashfs_options = b.addOptions(); const zig_squashfs_options = b.addOptions();
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp); zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo); // zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
var optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const mod = b.addModule("zig_squashfs", .{
if (debug == true) .root_source_file = b.path("src/root.zig"),
optimize = .Debug;
if (optimize == .Debug)
debug = true;
const c = b.addTranslateC(.{
.optimize = optimize,
.target = target, .target = target,
.root_source_file = b.path("src/c.h"), .optimize = if (debug == true) .Debug else optimize,
.valgrind = debug,
.error_tracing = debug,
.strip = if (debug == true) false else null,
}); });
mod.addOptions("config", zig_squashfs_options);
const lib = b.addLibrary(.{
.name = "squashfs",
.root_module = b.createModule(.{
.optimize = if (debug == true) .Debug else optimize,
.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 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"));
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
lib.root_module.linkLibrary(xz.artifact("lzma"));
const minilzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
lib.root_module.linkLibrary(minilzo.artifact("minilzo"));
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) { if (!use_zig_decomp) {
const c = b.addTranslateC(.{ mod.link_libc = true;
const c_imports = b.addTranslateC(.{
.optimize = optimize, .optimize = optimize,
.target = target, .target = target,
.root_source_file = b.path("src/c.h"), .root_source_file = b.path("src/imports.c"),
}); });
if (allow_lzo) c.defineCMacro("ALLOW_LZO", null); mod.addImport("c", c_imports.createModule());
lib.root_module.addImport("c", c.createModule());
if (dynamic) var zlib_ng = b.dependency("zlib_ng", .{
dynamicLinkLibraries(c, allow_lzo); .target = target,
.optimize = optimize,
});
mod.linkLibrary(zlib_ng.artifact("zng"));
mod.linkSystemLibrary("lzma", .{ .preferred_link_mode = .static });
var minilzo = b.dependency("minilzo", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(minilzo.artifact("minilzo"));
var lz4 = b.dependency("lz4", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(lz4.artifact("lz4"));
var zstd = b.dependency("zstd", .{
.target = target,
.optimize = optimize,
});
mod.linkLibrary(zstd.artifact("zstd"));
} }
>>>>>>> dfbfbda (Build is working again (on Zig master branch))
var version = version_string_option orelse "0.0.0-testing"; var version = version_string_option orelse "0.0.0-testing";
if (version[0] == 'v') version = version[1..]; if (version[0] == 'v') version = version[1..];
@@ -90,53 +66,42 @@ pub fn build(b: *std.Build) !void {
"version", "version",
try std.SemanticVersion.parse(version), try std.SemanticVersion.parse(version),
); );
var exe_mod = b.createModule(.{
.root_source_file = b.path("src/bin/unsquashfs.zig"),
.target = target,
.optimize = if (debug == true) .Debug else optimize,
.link_libc = !use_zig_decomp,
.imports = &.{
.{ .name = "zig_squashfs", .module = mod },
},
.valgrind = debug,
.error_tracing = debug,
.strip = if (debug == true) false else null,
});
exe_mod.addOptions("config", unsquashfs_options);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "unsquashfs", .name = "unsquashfs",
.root_module = b.createModule(.{ .root_module = exe_mod,
.optimize = if (debug == true) .Debug else optimize, .use_llvm = debug,
.target = target, });
.valgrind = debug,
.root_source_file = b.path("src/bin/unsquashfs.zig"), const lib = b.addLibrary(.{
.imports = &.{ .name = "squashfs",
.{ .name = "zig_squashfs", .module = lib.root_module }, .root_module = mod,
},
}),
.use_llvm = debug, .use_llvm = debug,
}); });
exe.root_module.addOptions("config", unsquashfs_options);
b.installArtifact(lib); b.installArtifact(lib);
b.installArtifact(exe); b.installArtifact(exe);
const mod_tests = b.addTest(.{ const mod_tests = b.addTest(.{
.root_module = b.createModule(.{ .root_module = mod,
.optimize = optimize, .test_runner = .{
.target = target, .mode = .simple,
.root_source_file = b.path("src/root.zig"), .path = b.path("src/test.zig"),
.imports = &.{ },
.{ .name = "options", .module = zig_squashfs_options.createModule() },
},
.valgrind = debug,
}),
.use_llvm = debug,
}); });
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 run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests"); const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step); test_step.dependOn(&run_mod_tests.step);
@@ -144,52 +109,13 @@ pub fn build(b: *std.Build) !void {
// zls build check steps // zls build check steps
const lib_check = b.addLibrary(.{ const lib_check = b.addLibrary(.{
.name = "squashfs", .name = "squashfs",
.root_module = exe.root_module, .root_module = mod,
}); });
const exe_check = b.addExecutable(.{ const exe_check = b.addExecutable(.{
.name = "unsquashfs", .name = "unsquashfs",
.root_module = lib.root_module, .root_module = exe_mod,
}); });
const check = b.step("check", "Check if unsquashfs compiles"); const check = b.step("check", "Check if unsquashfs compiles");
check.dependOn(&lib_check.step);
check.dependOn(&exe_check.step); check.dependOn(&exe_check.step);
} check.dependOn(&lib_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);
} }
+11 -9
View File
@@ -2,11 +2,12 @@
.name = .squashfs, .name = .squashfs,
.version = "0.0.6", .version = "0.0.6",
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications. .fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
.minimum_zig_version = "0.15.2", .minimum_zig_version = "0.16.0",
.dependencies = .{ .dependencies = .{
.zlib_ng = .{ .zlib_ng = .{
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133", // .url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133",
.hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa", // .hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa",
.path = "../zig-zlib-ng",
}, },
.zstd = .{ .zstd = .{
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3", .url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3",
@@ -17,13 +18,14 @@
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2", .hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
}, },
.minilzo = .{ .minilzo = .{
.url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90", // .url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90",
.hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP", // .hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP",
}, .path = "../zig-minilzo",
.xz = .{
.url = "git+https://github.com/akunaakwei/zig-xz.git#e2d389262c8291907e3e4c6fb119819141c16c0f",
.hash = "xz-5.8.2-6v47_JYeAABSL-jonprpL5-E_YaaGc4B5xrbe93WsJ3G",
}, },
// .fastlzma2 = .{
// .url = "git+https://github.com/allyourcodebase/fast-lzma2#d7615e0c957a62fcd6691b3fe9519a091885bfa2",
// .hash = "fastlzma2-0.0.0-gNWHgVeLAAD0Tlak3xhNcgpPSYcjyJppq0tlGmPKCC_V",
// },
}, },
.paths = .{ .paths = .{
"build.zig", "build.zig",
+100 -204
View File
@@ -1,134 +1,113 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Decomp = @import("decomp.zig"); const DecompTypes = @import("decomp/types.zig");
const Decompressor = @import("decomp.zig");
const ExtractionOptions = @import("options.zig"); const ExtractionOptions = @import("options.zig");
const File = @import("file.zig"); const File = @import("file.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const BlockSize = @import("inode/file.zig").BlockSize;
const LookupTable = @import("lookup_table.zig"); const LookupTable = @import("lookup_table.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig"); const MetadataReader = @import("util/metadata.zig");
const Utils = @import("util/misc.zig");
const OffsetFile = @import("util/offset_file.zig"); const OffsetFile = @import("util/offset_file.zig");
const Utils = @import("util/utils.zig");
pub const Error = error{
BadMagic,
BadBlockLog,
BadVersion,
BadCheck,
};
const Archive = @This(); const Archive = @This();
file: OffsetFile, file: OffsetFile,
super: Superblock, super: Superblock,
stateless_decomp: *const Decompressor, stateless_decomp: Decompressor,
pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive { /// Create an Archive from a File.
var rdr = file.reader(io, &[0]u8{}); pub fn init(io: Io, fil: Io.File, offset: u64) !Archive {
try rdr.seekTo(offset);
var super: Superblock = undefined; var super: Superblock = undefined;
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little); var fil_rdr = fil.reader(io, &[0]u8{});
if (offset > 0)
try fil_rdr.seekTo(offset);
try fil_rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
try super.validate(); try super.validate();
return .{ return .{
.file = try .init(io, file, super.size, offset), .file = .{ .fil = fil, .offset = offset },
.super = super, .super = super,
.stateless_decomp = .{ .vtable = &.{ .stateless = try DecompTypes.getStatelessFn(super.compression) } },
.stateless_decomp = try Decomp.StatelessDecomp(super.compression),
}; };
} }
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) !File { pub fn root(self: Archive, alloc: std.mem.Allocator) !File {
const root_inode = try Utils.inodeFromRef( return .{
alloc, .file = self.file,
self.file, .super = self.super.toMinimal(),
self.stateless_decomp, .decomp = self.stateless_decomp.statelessCopy(alloc),
self.super.inode_start,
self.super.block_size, .inode = try Utils.readInode(
self.super.root_ref, alloc,
); &self.stateless_decomp,
return .init(alloc, self, root_inode, ""); self.file,
self.super.inode_start,
self.super.block_size,
self.super.root_ref.block_start,
self.super.root_ref.block_offset,
),
.name = "",
};
} }
/// Opens a File within the archive. pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !File {
pub fn open(self: Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File { if (Utils.pathIsSelf(path)) return self.root(alloc);
const root_file = try self.root(alloc); var root_file = self.root(alloc);
const path = std.mem.trim(u8, filepath, "/");
if (Utils.pathIsSelf(path))
return root_file;
defer root_file.deinit(); defer root_file.deinit();
return root_file.open(alloc, io, filepath); return root_file.open(alloc, path);
} }
/// Returns the inode with the given inode number. pub fn fragEntry(self: Archive, idx: u32) !FragEntry {
/// Requires that the archive is exportable (has an export lookup table). return LookupTable.stateless(FragEntry, self.fil, &self.stateless_decomp, self.super.frag_start, idx);
pub fn inode(self: Archive, alloc: std.mem.Allocator, io: Io, num: u32) !Inode { }
if (!self.super.flags.exportable) pub fn id(self: Archive, idx: u32) !u16 {
return error.NotExportable; return LookupTable.stateless(u16, self.fil, &self.stateless_decomp, self.super.id_start, idx);
const ref = try LookupTable.lookupValue( }
Inode.Ref, pub fn inode(self: Archive, alloc: std.mem.Allocator, inode_num: u32) !Inode {
const ref = try LookupTable.stateless(Inode.Ref, self.file, &self.stateless_decomp, self.super.export_start, inode_num - 1);
return Utils.readInode(
alloc, alloc,
io,
&self.stateless_decomp, &self.stateless_decomp,
self.file, self.file,
self.super.export_start,
num + 1,
);
return Utils.inodeFromRef(
alloc,
io,
self.file,
&self.stateless_decomp,
self.super.inode_start, self.super.inode_start,
self.super.block_size, self.super.block_size,
ref, ref.block_start,
); ref.block_offset,
}
/// Returns a value at the given index from the Archive's id (uid/gid) table.
pub fn idTable(self: Archive, alloc: std.mem.Allocator, io: Io, idx: u32) !u16 {
return LookupTable.lookupValue(
u16,
alloc,
io,
&self.stateless_decomp,
self.file,
self.super.id_start,
idx,
); );
} }
/// Extract the entire archive contents to the given directory. pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
pub fn extract(self: Archive, alloc: std.mem.Allocator, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void { _ = self;
const root_inode = try Utils.inodeFromRef( _ = alloc;
alloc, _ = path;
self.file, _ = options;
self.stateless_decomp, return error.TODO;
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 // Superblock
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little); const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
const SuperblockError = error{ pub const Superblock = packed struct {
InvalidMagic,
InvalidBlockLog,
InvalidVersion,
InvalidCheck,
};
/// A squashfs Superblock
pub const Superblock = extern struct {
magic: u32, magic: u32,
inode_count: u32, inode_count: u32,
mod_time: u32, mod_time: u32,
block_size: u32, block_size: u32,
frag_count: u32, frag_count: u32,
compression: Decomp.Enum, compression: DecompTypes.Enum,
block_log: u16, block_log: u16,
flags: packed struct(u16) { flags: packed struct {
inode_uncompressed: bool, inode_uncompressed: bool,
data_uncompressed: bool, data_uncompressed: bool,
check: bool, check: bool,
@@ -156,133 +135,50 @@ pub const Superblock = extern struct {
export_start: u64, export_start: u64,
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive. /// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive.
pub fn validate(self: Superblock) !void { fn validate(self: Superblock) !void {
if (self.magic != SQUASHFS_MAGIC) if (self.magic != SQUASHFS_MAGIC)
return SuperblockError.InvalidMagic; return Error.BadMagic;
if (self.flags.check) if (self.flags.check)
return SuperblockError.InvalidCheck; return Error.BadCheck;
if (self.ver_maj != 4 or self.ver_min != 0) if (self.ver_maj != 4 or self.ver_min != 0)
return SuperblockError.InvalidVersion; return Error.BadVersion;
if (std.math.log2(self.block_size) != self.block_log) if (std.math.log2(self.block_size) != self.block_log)
return SuperblockError.InvalidBlockLog; return Error.BadBlockLog;
}
pub fn toMinimal(self: Superblock) MinimalSuperblock {
return .{
.inode_count = self.inode_count,
.block_size = self.block_size,
.frag_count = self.frag_count,
.id_count = self.id_count,
.id_start = self.id_start,
.xattr_start = self.xattr_start,
.inode_start = self.inode_start,
.dir_start = self.dir_start,
.frag_start = self.frag_start,
.export_start = self.export_start,
};
} }
}; };
// Tests pub const MinimalSuperblock = struct {
inode_count: u32,
const TestArchive = "testing/LinuxPATest.sfs"; block_size: u32,
frag_count: u32,
test "Basics" { id_count: u16,
std.debug.print("Starting test: Basics...\n", .{}); id_start: u64,
xattr_start: u64,
const alloc = std.testing.allocator; inode_start: u64,
const io = std.testing.io; dir_start: u64,
frag_start: u64,
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{}); export_start: u64,
defer fil.close(io); };
var sfs: Archive = try .init(io, fil, 0);
defer sfs.deinit(io); // Frag Entry
try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock);
const root_file = try sfs.root(alloc); pub const FragEntry = packed struct {
defer root_file.deinit(); block_start: u64,
} size: BlockSize,
_: u32,
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,
}; };
+4 -18
View File
@@ -18,7 +18,7 @@ const help_mgs =
\\ -dx Don't set xattr values \\ -dx Don't set xattr values
\\ -dp Don't set permissions (includes setting uid & gid owner) \\ -dp Don't set permissions (includes setting uid & gid owner)
\\ \\
\\ -p <threads> Specify how many threads to use. If not present or zero, the system's logical cores count is used. \\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
\\ -v Verbose \\ -v Verbose
\\ \\
\\ --force Force extraction. If the destination already exists, it will be deleted. \\ --force Force extraction. If the destination already exists, it will be deleted.
@@ -44,41 +44,27 @@ pub fn main(init: std.process.Init) !void {
const io = init.io; const io = init.io;
var stdout = std.Io.File.stdout(); var stdout = std.Io.File.stdout();
defer stdout.close(io);
var out = stdout.writer(io, &[0]u8{}); var out = stdout.writer(io, &[0]u8{});
defer out.interface.flush() catch {}; defer out.interface.flush() catch {};
try handleArgs(init.minimal.args, &out.interface); try handleArgs(init.minimal.args, &out.interface);
if (archive.len == 0) { if (archive.len == 0) {
try out.interface.print("You must provide a squashfs archive\n", .{}); try out.interface.print("You must provide a squashfs archive\n", .{});
try out.interface.print(help_mgs, .{}); try out.interface.print(help_mgs, .{});
return; return;
} }
var fil: Io.File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
var fil = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
defer fil.close(io); defer fil.close(io);
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully. var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully.
const options: squashfs.ExtractionOptions = .{ const options: squashfs.ExtractionOptions = .{
.single_threaded = threads == 1, .threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
.verbose = verbose, .verbose = verbose,
.verbose_writer = if (verbose) &out.interface else null, .verbose_writer = if (verbose) &out.interface else null,
.ignore_xattr = ignore_xattrs, .ignore_xattr = ignore_xattrs,
.ignore_permissions = ignore_permissions, .ignore_permissions = ignore_permissions,
}; };
if (force) if (force)
try Io.Dir.cwd().deleteTree(io, extLoc); try Io.Dir.cwd().deleteTree(io, extLoc);
if (threads != 0) { try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
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 { fn handleArgs(args: std.process.Args, out: *Writer) !void {
+23 -96
View File
@@ -1,104 +1,31 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io;
const options = @import("options"); pub const StatelessDecomp = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize;
const lzma = @import("decomp/zig_lzma.zig"); pub const Error = error{
const xz = @import("decomp/zig_xz.zig"); OutOfMemory,
const Decompressor = @import("util/decompressor.zig"); EndOfStream,
ReadFailed,
const zlib = if (options.use_zig_decomp) @import("decomp/zig_zlib.zig") else @import("decomp/c_zlib.zig"); WriteFailed,
<<<<<<< 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");
pub const Enum = enum(u16) {
gzip = 1,
lzma,
lzo,
xz,
lz4,
zstd,
}; };
pub fn StatelessDecomp(val: Enum) !*const Decompressor { const Decompressor = @This();
return switch (val) {
.gzip => &zlib.stateless_decompressor, alloc: std.mem.Allocator = std.heap.smp_allocator,
.lzma => &lzma.stateless_decompressor, vtable: *const struct {
.lzo => if (options.use_zig_decomp or !options.allow_lzo) decompress: *const fn (*const Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress,
error.LzoUnsupported stateless: StatelessDecomp,
else },
&lzo.stateless_decompressor,
.xz => &xz.stateless_decompressor, /// Create a copy of the decompressor using it's stateless function and the new allocator.
.lz4 => if (options.use_zig_decomp) pub fn statelessCopy(self: Decompressor, alloc: std.mem.Allocator) Decompressor {
error.Lz4Unsupported return &.{ .alloc = alloc, .vtable = &.{ .stateless = self.vtable.stateless } };
else
&lz4.stateless_decompressor,
.zstd => &zstd.stateless_decompressor,
};
} }
pub const Decomp = union(enum) { pub fn decompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
gzip: zlib, return self.vtable.decompress(self, in, out);
lzma: lzma, }
lzo: lzo,
xz: xz,
lz4: lz4,
zstd: zstd,
<<<<<<< HEAD fn defaultDecompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
pub fn init(val: Enum, alloc: std.mem.Allocator, io: std.Io, block_size: u32) !Decomp { return self.vtable.stateless(self.alloc, in, out);
return switch (val) { }
.gzip => .{ .gzip = zlib.init(alloc, io, block_size) },
.lzma => .{ .lzma = .{} },
.lzo => .{ .lzo = .{} },
.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, 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 => if (options.use_zig_decomp or !options.allow_lzo) unreachable else &lzo.stateless_decompressor,
.xz => &xz.stateless_decompressor,
.lz4 => if (options.use_zig_decomp) unreachable else &lz4.stateless_decompressor,
.zstd => &self.zstd.interface,
};
}
};
+12
View File
@@ -0,0 +1,12 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
if (res > 0) return @abs(res);
return Decompressor.Error.ReadFailed; // TOOD: Find out what errors can be returned.
}
+142
View File
@@ -0,0 +1,142 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Lzma = @This();
streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Lzma {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Lzma) void {
var values = self.streams.valueIterator();
while (values.next()) |val| {
c.lzma_end(val);
}
self.streams.deinit();
}
fn getOrCreate(self: *Lzma) !*c.lzma_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{
.alloc = .{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = &self.interface.alloc,
},
};
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Lzma = @fieldParentPtr("interface", decomp);
const stream = try self.getOrCreate();
stream.next_in = in.ptr;
stream.avail_in = in.len;
stream.next_out = out.ptr;
stream.avail_out = out.len;
var res = c.lzma_alone_decoder(stream, out.len);
decodeResult(res) catch |err| {
self.err = err;
return lzmaErrorToDecompError(err);
};
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| {
self.err = err;
return lzmaErrorToDecompError(err);
};
}
return stream.total_out;
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var stream: c.lzma_stream = .{
.next_in = in.ptr,
.avail_in = in.len,
.next_out = out.ptr,
.avail_out = out.len,
.allocator = &.{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = @ptrCast(@constCast(&alloc)),
},
};
var res = c.lzma_alone_decoder(&stream, out.len);
decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
}
return stream.total_out;
}
inline fn decodeResult(res: c_uint) Error!void {
return switch (res) {
c.LZMA_OK => {},
c.LZMA_STREAM_END => {},
c.LZMA_NO_CHECK => {},
c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck,
c.LZMA_MEM_ERROR => Error.OutOfMemory,
c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory,
c.LZMA_FORMAT_ERROR => Error.Format,
c.LZMA_OPTIONS_ERROR => Error.Options,
c.LZMA_DATA_ERROR => Error.Data,
c.LZMA_BUF_ERROR => Error.BufferExhausted,
c.LZMA_PROG_ERROR => Error.Programming,
c.LZMA_SEEK_NEEDED => Error.SeekNeeded,
else => Error.Unknown,
};
}
fn lzmaErrorToDecompError(err: Error) Decompressor.Error {
switch (err) {
Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
Error.Format => return Decompressor.Error.ReadFailed,
Error.Options => return Decompressor.Error.ReadFailed,
Error.Data => return Decompressor.Error.ReadFailed,
Error.BufferExhausted => return Decompressor.Error.WriteFailed,
Error.Programming => return Decompressor.Error.ReadFailed,
Error.SeekNeeded => return Decompressor.Error.ReadFailed,
Error.Unknown => return Decompressor.Error.ReadFailed,
}
}
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
return alloc.rawAlloc(size, .@"1", 0);
}
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
if (alloc_ptr == null) return;
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
}
pub const Error = error{
OutOfMemory,
UnsupportedCheck,
Format,
Options,
Data,
BufferExhausted,
Programming,
SeekNeeded,
Unknown,
};
+28
View File
@@ -0,0 +1,28 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var out_len = out.len;
const res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null);
return switch (res) {
c.LZO_E_OK => out_len,
c.LZO_E_ERROR => Decompressor.Error.ReadFailed,
c.LZO_E_OUT_OF_MEMORY => Decompressor.Error.OutOfMemory,
c.LZO_E_NOT_COMPRESSIBLE => Decompressor.Error.ReadFailed,
c.LZO_E_INPUT_OVERRUN => Decompressor.Error.ReadFailed,
c.LZO_E_OUTPUT_OVERRUN => Decompressor.Error.WriteFailed,
c.LZO_E_LOOKBEHIND_OVERRUN => Decompressor.Error.ReadFailed,
c.LZO_E_EOF_NOT_FOUND => Decompressor.Error.ReadFailed,
c.LZO_E_INPUT_NOT_CONSUMED => Decompressor.Error.ReadFailed,
c.LZO_E_NOT_YET_IMPLEMENTED => Decompressor.Error.ReadFailed,
c.LZO_E_INVALID_ARGUMENT => Decompressor.Error.ReadFailed,
c.LZO_E_INVALID_ALIGNMENT => Decompressor.Error.ReadFailed,
c.LZO_E_OUTPUT_NOT_CONSUMED => Decompressor.Error.WriteFailed,
c.LZO_E_INTERNAL_ERROR => Decompressor.Error.ReadFailed,
else => Decompressor.Error.ReadFailed,
};
}
+142
View File
@@ -0,0 +1,142 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Xz = @This();
streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Xz {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Xz) void {
var values = self.streams.valueIterator();
while (values.next()) |val| {
c.xz_end(val);
}
self.streams.deinit();
}
fn getOrCreate(self: *Xz) !*c.xz_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{
.alloc = .{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = &self.interface.alloc,
},
};
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Xz = @fieldParentPtr("interface", decomp);
const stream = try self.getOrCreate();
stream.next_in = in.ptr;
stream.avail_in = in.len;
stream.next_out = out.ptr;
stream.avail_out = out.len;
var res = c.lzma_alone_decoder(stream, out.len);
decodeResult(res) catch |err| {
self.err = err;
return xzErrorToDecompError(err);
};
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| {
self.err = err;
return xzErrorToDecompError(err);
};
}
return stream.total_out;
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var stream: c.lzma_stream = .{
.next_in = in.ptr,
.avail_in = in.len,
.next_out = out.ptr,
.avail_out = out.len,
.allocator = &.{
.alloc = lzmaAlloc,
.free = lzmaFree,
.@"opaque" = @ptrCast(@constCast(&alloc)),
},
};
var res = c.lzma_alone_decoder(&stream, out.len);
decodeResult(res) catch |err| return xzErrorToDecompError(err);
while (true) {
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res == c.LZMA_OK) continue;
if (res == c.LZMA_STREAM_END) break;
decodeResult(res) catch |err| return xzErrorToDecompError(err);
}
return stream.total_out;
}
inline fn decodeResult(res: c_uint) Error!void {
return switch (res) {
c.LZMA_OK => {},
c.LZMA_STREAM_END => {},
c.LZMA_NO_CHECK => {},
c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck,
c.LZMA_MEM_ERROR => Error.OutOfMemory,
c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory,
c.LZMA_FORMAT_ERROR => Error.Format,
c.LZMA_OPTIONS_ERROR => Error.Options,
c.LZMA_DATA_ERROR => Error.Data,
c.LZMA_BUF_ERROR => Error.BufferExhausted,
c.LZMA_PROG_ERROR => Error.Programming,
c.LZMA_SEEK_NEEDED => Error.SeekNeeded,
else => Error.Unknown,
};
}
fn xzErrorToDecompError(err: Error) Decompressor.Error {
switch (err) {
Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
Error.Format => return Decompressor.Error.ReadFailed,
Error.Options => return Decompressor.Error.ReadFailed,
Error.Data => return Decompressor.Error.ReadFailed,
Error.BufferExhausted => return Decompressor.Error.WriteFailed,
Error.Programming => return Decompressor.Error.ReadFailed,
Error.SeekNeeded => return Decompressor.Error.ReadFailed,
Error.Unknown => return Decompressor.Error.ReadFailed,
}
}
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
return alloc.rawAlloc(size, .@"1", 0);
}
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
if (alloc_ptr == null) return;
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
}
pub const Error = error{
OutOfMemory,
UnsupportedCheck,
Format,
Options,
Data,
BufferExhausted,
Programming,
SeekNeeded,
Unknown,
};
+106
View File
@@ -0,0 +1,106 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Zlib = @This();
streams: std.AutoHashMap(std.Thread.Id, c.zng_stream),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Zlib {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Zlib) void {
var values = self.streams.valueIterator();
while (values.next()) |val| {
_ = c.zng_deflateEnd(val);
}
self.streams.deinit();
}
fn getOrCreate(self: *Zlib) !*c.zng_stream {
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = .{
.@"opaque" = self,
.zalloc = zalloc,
.zfree = zfree,
};
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Zlib = @fieldParentPtr("interface", decomp);
var stream = try self.getOrCreate();
stream.next_in = in.ptr;
stream.avail_in = in.len;
stream.next_out = out.ptr;
stream.avail_out = out.len;
var res = c.zng_inflateReset(stream);
decodeError(res) catch |err| {
self.err = err;
return Decompressor.Error.ReadFailed;
};
res = c.zng_inflate(stream, c.Z_FINISH);
decodeError(res) catch |err| {
self.err = err;
return switch (err) {
Error.OutOfMemory => err,
else => Decompressor.Error.ReadFailed,
};
};
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
_ = alloc;
var out_len = out.len;
const res = c.zng_uncompress(out.ptr, &out_len, in.ptr, in.len);
return switch (res) {
c.Z_OK => out_len,
c.Z_MEM_ERROR => Decompressor.Error.OutOfMemory,
c.Z_BUF_ERROR => Decompressor.Error.WriteFailed,
else => Decompressor.Error.ReadFailed,
};
}
inline fn decodeError(res: i32) Error!void {
return switch (res) {
c.Z_OK => {},
c.Z_STREAM_ERROR => Error.Stream,
c.Z_BUF_ERROR => Error.Buffer,
c.Z_MEM_ERROR => Error.OutOfMemory,
c.Z_DATA_ERROR => Error.Data,
c.Z_VERSION_ERROR => Error.Version,
else => Error.Unknown,
};
}
fn zalloc(ptr: ?*anyopaque, items: c_uint, size: c_uint) callconv(.c) ?*anyopaque {
var self: *Zlib = @ptrCast(ptr);
return self.interface.alloc.rawAlloc(items * size, .@"1", 0);
}
fn zfree(ptr: ?*anyopaque, addr: ?*anyopaque) callconv(.c) void {
var self: *Zlib = @ptrCast(ptr);
self.interface.alloc.rawFree(@ptrCast(addr), .@"1", 0);
}
pub const Error = error{
OutOfMemory,
Stream,
Buffer,
Data,
Version,
Unknown,
};
+172
View File
@@ -0,0 +1,172 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../../decomp.zig");
const Zstd = @This();
context: std.AutoHashMap(std.Thread.Id, ?*c.ZSTD_DCtx),
interface: Decompressor,
err: ?Error = null,
pub fn init(alloc: std.mem.Allocator) !Zstd {
return .{
.streams = try .init(alloc),
.interface = &.{
.alloc = alloc,
.vtable = .{ .decompress = decompress, .stateless = stateless },
},
};
}
pub fn deinit(self: *Zstd) void {
var values = self.context.valueIterator();
while (values.next()) |val| {
_ = c.ZSTD_freeDCtx(val.*);
}
self.context.deinit();
}
fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx {
const res = try self.context.getOrPut(std.Thread.getCurrentId());
if (res.found_existing) return res.value_ptr;
res.value_ptr.* = c.ZSTD_createDCtx();
if (res.value_ptr.* == null) return Error.OutOfMemory;
return res.value_ptr;
}
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
var self: *Zstd = @fieldParentPtr("interface", decomp);
const ctx = self.getOrCreate();
const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len);
decodeError(res) catch |err| {
self.err = err;
return ZstdErrorToDecompError(err);
};
return res;
}
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
_ = alloc;
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
decodeError(res) catch |err| return ZstdErrorToDecompError(err);
return res;
}
inline fn decodeError(res: usize) Error!void {
if (c.ZSTD_isError(res) == 0) return;
return switch (c.ZSTD_getErrorCode(res)) {
c.ZSTD_error_prefix_unknown => Error.PrefixUnknown,
c.ZSTD_error_version_unsupported => Error.VersionUnsupported,
c.ZSTD_error_frameParameter_unsupported => Error.FrameParameterUnsupported,
c.ZSTD_error_frameParameter_windowTooLarge => Error.FrameParameterWindowTooLarge,
c.ZSTD_error_corruption_detected => Error.CorruptionDetected,
c.ZSTD_error_checksum_wrong => Error.ChecksumWrong,
c.ZSTD_error_literals_headerWrong => Error.LiteralsHeaderWrong,
c.ZSTD_error_dictionary_corrupted => Error.DictionaryCorrupted,
c.ZSTD_error_dictionary_wrong => Error.DictionaryWrong,
c.ZSTD_error_dictionaryCreation_failed => Error.DictionaryCreationFailed,
c.ZSTD_error_parameter_unsupported => Error.ParameterUnsupported,
c.ZSTD_error_parameter_combination_unsupported => Error.ParameterCombinationUnsupported,
c.ZSTD_error_parameter_outOfBound => Error.ParameterOutOfBound,
c.ZSTD_error_tableLog_tooLarge => Error.TableLogTooLarge,
c.ZSTD_error_maxSymbolValue_tooLarge => Error.MaxSymbolValueTooLarge,
c.ZSTD_error_maxSymbolValue_tooSmall => Error.MaxSymbolValueTooSmall,
c.ZSTD_error_cannotProduce_uncompressedBlock => Error.CannotProduceUncompressedBlock,
c.ZSTD_error_stabilityCondition_notRespected => Error.StabilityConditionNotRespected,
c.ZSTD_error_stage_wrong => Error.StageWrong,
c.ZSTD_error_init_missing => Error.InitMissing,
c.ZSTD_error_memory_allocation => Error.MemoryAllocation,
c.ZSTD_error_workSpace_tooSmall => Error.WorkSpaceTooSmall,
c.ZSTD_error_dstSize_tooSmall => Error.DstSizeTooSmall,
c.ZSTD_error_srcSize_wrong => Error.SrcSizeWrong,
c.ZSTD_error_dstBuffer_null => Error.DstBufferNull,
c.ZSTD_error_noForwardProgress_destFull => Error.NoForwardProgressDestFull,
c.ZSTD_error_noForwardProgress_inputEmpty => Error.NoForwardProgressInputEmpty,
c.ZSTD_error_frameIndex_tooLarge => Error.FrameIndexTooLarge,
c.ZSTD_error_seekableIO => Error.SeekableIo,
c.ZSTD_error_dstBuffer_wrong => Error.DstBufferWrong,
c.ZSTD_error_srcBuffer_wrong => Error.SrcBufferWrong,
c.ZSTD_error_sequenceProducer_failed => Error.SequenceProducerFailed,
c.ZSTD_error_externalSequences_invalid => Error.ExternalSequencesInvalid,
else => Error.Generic,
};
}
inline fn ZstdErrorToDecompError(err: Error) Decompressor.Error {
return switch (err) {
Error.OutOfMemory => Decompressor.Error.OutOfMemory,
Error.Generic => Decompressor.Error.ReadFailed,
Error.PrefixUnknown => Decompressor.Error.ReadFailed,
Error.VersionUnsupported => Decompressor.Error.ReadFailed,
Error.FrameParameterUnsupported => Decompressor.Error.ReadFailed,
Error.FrameParameterWindowTooLarge => Decompressor.Error.ReadFailed,
Error.CorruptionDetected => Decompressor.Error.ReadFailed,
Error.ChecksumWrong => Decompressor.Error.ReadFailed,
Error.LiteralsHeaderWrong => Decompressor.Error.ReadFailed,
Error.DictionaryCorrupted => Decompressor.Error.ReadFailed,
Error.DictionaryWrong => Decompressor.Error.ReadFailed,
Error.DictionaryCreationFailed => Decompressor.Error.ReadFailed,
Error.ParameterUnsupported => Decompressor.Error.ReadFailed,
Error.ParameterCombinationUnsupported => Decompressor.Error.ReadFailed,
Error.ParameterOutOfBound => Decompressor.Error.ReadFailed,
Error.TableLogTooLarge => Decompressor.Error.ReadFailed,
Error.MaxSymbolValueTooLarge => Decompressor.Error.ReadFailed,
Error.MaxSymbolValueTooSmall => Decompressor.Error.ReadFailed,
Error.CannotProduceUncompressedBlock => Decompressor.Error.ReadFailed,
Error.StabilityConditionNotRespected => Decompressor.Error.ReadFailed,
Error.StageWrong => Decompressor.Error.ReadFailed,
Error.InitMissing => Decompressor.Error.ReadFailed,
Error.MemoryAllocation => Decompressor.Error.OutOfMemory,
Error.WorkSpaceTooSmall => Decompressor.Error.WriteFailed,
Error.DstSizeTooSmall => Decompressor.Error.WriteFailed,
Error.SrcSizeWrong => Decompressor.Error.ReadFailed,
Error.DstBufferNull => Decompressor.Error.WriteFailed,
Error.NoForwardProgressDestFull => Decompressor.Error.WriteFailed,
Error.NoForwardProgressInputEmpty => Decompressor.Error.ReadFailed,
Error.FrameIndexTooLarge => Decompressor.Error.ReadFailed,
Error.SeekableIo => Decompressor.Error.ReadFailed,
Error.DstBufferWrong => Decompressor.Error.WriteFailed,
Error.SrcBufferWrong => Decompressor.Error.ReadFailed,
Error.SequenceProducerFailed => Decompressor.Error.ReadFailed,
Error.ExternalSequencesInvalid => Decompressor.Error.ReadFailed,
};
}
pub const Error = error{
OutOfMemory,
Generic,
PrefixUnknown,
VersionUnsupported,
FrameParameterUnsupported,
FrameParameterWindowTooLarge,
CorruptionDetected,
ChecksumWrong,
LiteralsHeaderWrong,
DictionaryCorrupted,
DictionaryWrong,
DictionaryCreationFailed,
ParameterUnsupported,
ParameterCombinationUnsupported,
ParameterOutOfBound,
TableLogTooLarge,
MaxSymbolValueTooLarge,
MaxSymbolValueTooSmall,
CannotProduceUncompressedBlock,
StabilityConditionNotRespected,
StageWrong,
InitMissing,
MemoryAllocation,
WorkSpaceTooSmall,
DstSizeTooSmall,
SrcSizeWrong,
DstBufferNull,
NoForwardProgressDestFull,
NoForwardProgressInputEmpty,
FrameIndexTooLarge,
SeekableIo,
DstBufferWrong,
SrcBufferWrong,
SequenceProducerFailed,
ExternalSequencesInvalid,
};
-19
View File
@@ -1,19 +0,0 @@
const std = @import("std");
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
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);
}
-44
View File
@@ -1,44 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.lzma_stream);
const Self = @This();
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.lzma_stream = .{
.next_in = in.ptr,
.avail_in = in.len,
.next_out = out.ptr,
.avail_out = out.len,
};
var res = c.lzma_alone_decoder(&stream, stream.avail_out * 2);
if (res != c.LZMA_OK) return Error.ReadFailed;
while (res == c.LZMA_OK)
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_FINISH) return Error.ReadFailed;
return stream.total_out;
}
// lzma_allocator
// 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.?)));
// }
-24
View File
@@ -1,24 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.lzma_stream);
const Self = @This();
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
_ = c.lzo_init();
var out_len = out.len;
const res = c.lzo1x_decompress_safe(in.ptr, in.len, out.ptr, &out_len, null);
if (res != c.LZO_E_OK) return Error.ReadFailed;
return out_len;
}
-45
View File
@@ -1,45 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.lzma_stream);
const Self = @This();
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.lzma_stream = .{
.next_in = in.ptr,
.avail_in = in.len,
.next_out = out.ptr,
.avail_out = out.len,
};
var res = c.lzma_alone_decoder(&stream, stream.avail_out * 2);
if (res != c.LZMA_OK) return Error.ReadFailed;
while (res == c.LZMA_OK)
res = c.lzma_code(&stream, c.LZMA_RUN);
if (res != c.LZMA_FINISH) return Error.ReadFailed;
return stream.total_out;
}
// lzma_allocator
// 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.?)));
// }
-97
View File
@@ -1,97 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(c.zng_stream);
const Self = @This();
interface: Decompressor = .{ .decomp_fn = decomp },
io: Io,
ctx: []c.zng_stream,
ctx_queue: Queue,
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, .{});
return .{
.io = io,
.ctx = buf,
.ctx_queue = queue,
};
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
self.ctx_queue.close(self.io);
alloc.free(self.ctx);
}
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
var stream = self.ctx_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.ctx_queue.putOne(self.io, stream) catch {};
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);
return stream.total_out;
}
inline fn zlibDecomp(stream: *c.zng_stream) !void {
_ = c.zng_inflateReset(stream);
const res = c.zng_inflate(stream, c.Z_FULL_FLUSH);
if (res != c.Z_OK) return Error.ReadFailed;
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var stream: c.zng_stream = .{
.next_in = in.ptr,
.avail_in = @truncate(in.len),
.next_out = out.ptr,
.avail_out = @truncate(out.len),
};
try zlibDecomp(&stream);
return stream.total_out;
}
// zalloc
fn zalloc(ptr: ?*anyopaque, size: c_uint, len: c_uint) callconv(.c) ?*anyopaque {
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
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))
}
-71
View File
@@ -1,71 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Node = std.SinglyLinkedList.Node;
const c = @import("c");
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue(?*c.ZSTD_DCtx);
const Self = @This();
interface: Decompressor = .{ .decomp_fn = decomp },
io: Io,
ctx: []?*c.ZSTD_DCtx,
ctx_queue: Queue,
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 .{
.io = io,
.ctx = buf,
.ctx_queue = queue,
};
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
self.ctx_queue.close(self.io);
for (self.ctx) |ctx|
_ = c.ZSTD_freeDCtx(ctx);
alloc.free(self.ctx);
}
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
// TODO: Fix
//
// if (d == null) {
return statelessDecomp(d, alloc, in, out);
// }
// 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 {};
// _ = 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;
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
if (c.ZSTD_isError(res) != 0)
return Error.ReadFailed;
return res;
}
+64
View File
@@ -0,0 +1,64 @@
const config = @import("config");
const Decompressor = @import("../decomp.zig");
pub fn getStatelessFn(decomp: Enum) !Decompressor.StatelessDecomp {
if (config.use_zig_decomp) {
return switch (decomp) {
.gzip => @import("zig/zlib.zig").stateless,
.lzma => @import("zig/lzma.zig").stateless,
.xz => @import("zig/xz.zig").stateless,
.zstd => @import("zig/zstd.zig").stateless,
.lz4 => error.ZigLz4Unsupported,
.lzo => error.ZigLzoUnsupported,
};
}
return switch (decomp) {
.gzip => @import("c/zlib.zig").stateless,
.lzma => @import("c/lzma.zig").stateless,
.lzo => @import("c/lzo.zig").stateless,
.xz => @import("c/xz.zig").stateless,
.lz4 => @import("c/lz4.zig").stateless,
.zstd => @import("c/zstd.zig").stateless,
};
}
pub const Enum = enum(u16) {
gzip = 1, // Though officially named gzip, it actually uses zlib.
lzma,
lzo,
xz,
lz4,
zstd,
};
pub const Decomp = if (config.use_zig_decomp)
union(enum) {
gzip: @import("zig/zlib.zig"),
lzma: @import("zig/lzma.zig"),
xz: @import("zig/xz.zig"),
zstd: @import("zig/zstd.zig"),
pub fn deinit(_: *Decomp) void {
return;
}
}
else
union(enum) {
gzip: @import("c/zlib.zig"),
lzma: @import("c/lzma.zig"),
lzo: @import("c/lzo.zig"),
xz: @import("c/xz.zig"),
lz4: @import("c/lz4.zig"),
zstd: @import("c/zstd.zig"),
pub fn deinit(self: *Decomp) void {
switch (self) {
.gzip => self.gzip.deinit(),
.lzma => self.lzma.deinit(),
.xz => self.xz.deinit(),
.zstd => self.zstd.deinit(),
else => {},
}
}
};
+23
View File
@@ -0,0 +1,23 @@
const std = @import("std");
const lzma = std.compress.lzma;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
var decomp = lzma.Decompress.initOptions(&rdr, alloc, buf, .{}, out.len) catch |err|
return switch (err) {
error.Overflow => Decompressor.Error.ReadFailed,
error.CorruptInput => Decompressor.Error.ReadFailed,
error.InvalidRangeCode => Decompressor.Error.ReadFailed,
else => @errorCast(err),
};
defer decomp.deinit();
return decomp.reader.readSliceShort(out);
}
+22
View File
@@ -0,0 +1,22 @@
const std = @import("std");
const xz = std.compress.xz;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
var decomp = xz.Decompress.init(&rdr, alloc, buf) catch |err|
return switch (err) {
error.WrongChecksum => Decompressor.Error.ReadFailed,
error.NotXzStream => Decompressor.Error.ReadFailed,
else => @errorCast(err),
};
defer decomp.deinit();
return decomp.reader.readSliceShort(out);
}
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
const flate = std.compress.flate;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, out.len);
defer alloc.free(buf);
var decomp = flate.Decompress.init(&rdr, .zlib, buf);
return decomp.reader.readSliceShort(out);
}
+16
View File
@@ -0,0 +1,16 @@
const std = @import("std");
const zstd = std.compress.zstd;
const Reader = std.Io.Reader;
const Decompressor = @import("../../decomp.zig");
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
var rdr: Reader = .fixed(in);
const buf = try alloc.alloc(u8, out.len * 2);
defer alloc.free(buf);
var decomp = zstd.Decompress.init(&rdr, buf, .{ .window_len = @truncate(out.len) });
return decomp.reader.readSliceShort(out);
}
-81
View File
@@ -1,81 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const lzma = std.compress.lzma;
const Node = std.SinglyLinkedList.Node;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = Io.Queue([]u8);
const Self = @This();
const Buffer = struct {
node: Node,
buf: []u8,
};
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
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.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
var buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return lzmaDecomp(self.alloc, &buf, in, out) catch return Error.ReadFailed;
}
inline fn lzmaDecomp(alloc: std.mem.Allocator, buffer: *[]u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = try lzma.Decompress.initOptions(&rdr, alloc, buffer.*, .{}, in.len * 2);
defer {
buffer.* = d.takeBuffer();
d.deinit();
}
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
return lzmaDecomp(alloc, &buf, in, out) catch return Error.ReadFailed;
}
-81
View File
@@ -1,81 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const xz = std.compress.xz;
const Node = std.SinglyLinkedList.Node;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = Io.Queue([]u8);
const Self = @This();
const Buffer = struct {
node: Node,
buf: []u8,
};
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
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.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
var buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return xzDecomp(self.alloc, &buf, in, out) catch return Error.ReadFailed;
}
inline fn xzDecomp(alloc: std.mem.Allocator, buffer: *[]u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = try xz.Decompress.init(&rdr, alloc, buffer.*);
defer {
buffer.* = d.takeBuffer();
d.deinit();
}
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
var buf = try alloc.alloc(u8, in.len);
defer alloc.free(buf);
return xzDecomp(alloc, &buf, in, out) catch return Error.ReadFailed;
}
-77
View File
@@ -1,77 +0,0 @@
const std = @import("std");
const Io = std.Io;
const flate = std.compress.flate;
const Node = std.SinglyLinkedList.Node;
const Reader = Io.Reader;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = Io.Queue([]u8);
const Self = @This();
const Buffer = struct {
node: Node,
buf: []u8,
};
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
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.
var queue: Queue = .init(buf);
for (0..20) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
return statelessDecomp(d, alloc, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
const buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return zlibDecomp(buf, in, out);
}
inline fn zlibDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = flate.Decompress.init(&rdr, .zlib, buffer);
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const buf = try alloc.alloc(u8, out.len);
defer alloc.free(buf);
return zlibDecomp(buf, in, out);
}
-73
View File
@@ -1,73 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Reader = std.Io.Reader;
const zstd = std.compress.zstd;
const Decompressor = @import("../util/decompressor.zig");
const Error = Decompressor.Error;
const Queue = std.Io.Queue([]u8);
const Self = @This();
interface: Decompressor = .{ .decomp_fn = decomp },
alloc: std.mem.Allocator,
io: Io,
block_size: u32,
buf: [][]u8,
buf_queue: Queue,
pub fn init(alloc: std.mem.Allocator, io: Io, block_size: u32) !Self {
const buf = try alloc.alloc([]u8, 5); // TODO: Choose a better number instead of a random one.
var queue: Queue = .init(buf);
for (buf) |_|
try queue.putOne(io, try alloc.alloc(u8, block_size + zstd.block_size_max));
return .{
.alloc = alloc,
.io = io,
.block_size = block_size,
.buf = buf,
.buf_queue = queue,
};
}
pub fn deinit(self: *Self) void {
self.buf_queue.close(self.io);
for (self.buf) |buf|
self.alloc.free(buf);
self.alloc.free(self.buf);
}
fn decomp(d: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
if (d == null) {
const buf = try alloc.alloc(u8, in.len * 2);
defer alloc.free(buf);
return zstdDecomp(buf, in, out);
}
var self: *Self = @fieldParentPtr("interface", @constCast(d.?));
const buf = self.buf_queue.getOne(self.io) catch return Error.ReadFailed;
defer self.buf_queue.putOne(self.io, buf) catch {};
return zstdDecomp(buf, in, out);
}
inline fn zstdDecomp(buffer: []u8, in: []u8, out: []u8) !usize {
var rdr: Reader = .fixed(in);
var d = zstd.Decompress.init(&rdr, buffer, .{ .window_len = @truncate(out.len) });
return d.reader.readSliceShort(out);
}
// Stateless
pub const stateless_decompressor: Decompressor = .{ .decomp_fn = statelessDecomp };
fn statelessDecomp(_: ?*const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
const buf = try alloc.alloc(u8, out.len + zstd.block_size_max);
defer alloc.free(buf);
return zstdDecomp(buf, in, out);
}
+28 -34
View File
@@ -1,53 +1,34 @@
const std = @import("std"); const std = @import("std");
const Reader = std.Io.Reader; const Reader = std.Io.Reader;
const Inode = @import("inode.zig"); const InodeType = @import("inode.zig").Type;
pub const Error = error{OutOfMemory} || Reader.Error; pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) []Entry {
var read: u32 = 3;
const DirEntry = @This();
block_start: u32,
block_offset: u16,
type: Inode.Type,
name: []const u8,
pub fn deinit(self: DirEntry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) Error![]DirEntry {
var hdr: Header = undefined; var hdr: Header = undefined;
var raw: RawEntry = undefined; var raw: RawEntry = undefined;
var out: std.ArrayList(DirEntry) = try .initCapacity(alloc, 30); var out: std.ArrayList(Entry) = .initCapacity(alloc, 50);
errdefer { errdefer {
for (out.items) |ent| for (out.items) |i|
alloc.free(ent.name); alloc.free(i.name);
out.deinit(alloc); out.deinit(alloc);
} }
while (read < size) {
var tot_red: u32 = 3;
while (tot_red < size) {
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little); try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
try out.ensureUnusedCapacity(alloc, hdr.count + 1); try out.ensureUnusedCapacity(alloc, hdr.count + 1);
read += @sizeOf(Header);
tot_red += @sizeOf(Header);
for (0..hdr.count + 1) |_| { for (0..hdr.count + 1) |_| {
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little); try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
read += @sizeOf(RawEntry) + raw.name_size + 1;
const new_name = try alloc.alloc(u8, raw.name_size + 1);
try rdr.readSliceEndian(u8, new_name, .little);
const new = out.addOneAssumeCapacity(); const new = out.addOneAssumeCapacity();
new.* = .{ new.* = .{
.block_start = hdr.block_start, .block_start = hdr.block_start,
.block_offset = raw.block_offset, .block_offset = raw.block_offset,
.type = raw.type, .num = @abs(hdr.num + raw.num_offset),
.name = new_name, .inode_type = raw.inode_type,
.name = try alloc.alloc(u8, raw.name_size + 1),
}; };
try rdr.readSliceEndian(u8, new.name, .little);
tot_red += @sizeOf(RawEntry) + raw.name_size + 1;
} }
} }
return out.toOwnedSlice(alloc); return out.toOwnedSlice(alloc);
@@ -55,15 +36,28 @@ pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) Error![]
// Types // Types
pub const Entry = struct {
block_start: u32,
block_offset: u16,
num: u32,
inode_type: InodeType,
name: []u8,
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
alloc.free(self.name);
}
};
// extern instead of packed due to alignment issues (packed will read it as 16 bytes instead of 12).
const Header = extern struct { const Header = extern struct {
count: u32, count: u32,
block_start: u32, block_start: u32,
num: u32, num: u32,
}; };
const RawEntry = extern struct { const RawEntry = packed struct {
block_offset: u16, block_offset: u16,
num_offset: i16, num_offset: i16,
type: Inode.Type, inode_type: InodeType,
name_size: u16, name_size: u16,
}; };
+90 -66
View File
@@ -1,94 +1,118 @@
//! An easier to use wrapper around an inode.
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const Archive = @import("archive.zig"); const Archive = @import("archive.zig");
const DirEntry = @import("directory.zig"); const Decompressor = @import("decomp.zig");
const Directory = @import("directory.zig");
const ExtractionOptions = @import("options.zig"); const ExtractionOptions = @import("options.zig");
const Inode = @import("inode.zig"); const Inode = @import("inode.zig");
const DataExtractor = @import("util/data_extractor.zig"); const DataReader = @import("util/data_reader.zig");
const Decompressor = @import("util/decompressor.zig"); const FileIter = @import("util/iter.zig");
const MetadataReader = @import("util/metadata.zig"); const MetadataReader = @import("util/metadata.zig");
const SharedCache = @import("util/shared_cache.zig"); const OffsetFile = @import("util/offset_file.zig");
const Utils = @import("util/utils.zig");
pub const Error = error{
NotDirectory,
NotRegularFile,
};
const File = @This(); const File = @This();
alloc: std.mem.Allocator, file: OffsetFile,
super: Archive.MinimalSuperblock,
decomp: Decompressor,
archive: Archive,
inode: Inode,
name: []const u8, name: []const u8,
inode: Inode,
/// Creates a new File from an inode. Takes ownership of the Inode and creates a copy of the given name. pub fn init(alloc: std.mem.Allocator, archive: Archive, entry: Directory.Entry) !File {
/// Requires the given allocator was used to create the Inode. const new_name = try alloc.alloc(u8, entry.name.len);
pub fn init(alloc: std.mem.Allocator, archive: Archive, in: Inode, name: []const u8) !File { errdefer alloc.free(new_name);
const new_name = try alloc.alloc(u8, name.len); @memcpy(new_name, entry.name);
@memcpy(new_name, name);
return .{ return .{
.alloc = alloc, .file = archive.file,
.super = archive.super,
.archive = archive, .decomp = archive.stateless_decomp.statelessCopy(alloc),
.inode = in,
.name = new_name, .name = new_name,
.inode = try Utils.readInode(
alloc,
&archive.decomp,
archive.file,
archive.super.inode_start,
archive.super.block_size,
entry.block_start,
entry.block_offset,
),
}; };
} }
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, archive.stateless_decomp);
try meta.interface.discardAll(ent.block_offset);
var in: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
errdefer in.deinit(alloc);
return .init(alloc, archive, in, ent.name);
}
pub fn deinit(self: File) void { pub fn deinit(self: File) void {
self.alloc.free(self.name); self.decomp.alloc.free(self.name);
self.inode.deinit(self.alloc); self.inode.deinit(self.decomp.alloc);
} }
pub fn open(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File { // Directory functions
const entries = try self.inode.readDirectory(
pub fn isDir(self: File) bool {
return switch (self.inode.hdr.inode_type) {
.dir, .ext_dir => true,
else => false,
};
}
/// Opens a sub-file. If the given path is "" or "." (after trimming /) a copy of the File is returned.
pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
var res = try self.inode.findInode(
alloc, alloc,
self.archive.file, &self.decomp,
self.archive.stateless_decomp, self.file,
self.archive.super.dir_start, self.super.dir_start,
self.super.inode_start,
self.super.block_size,
filepath,
); );
defer { if (res.name.len == 0) {
for (entries) |ent| res.name = try alloc.alloc(u8, self.name.len);
alloc.free(ent.name); @memcpy(res.name, self.name);
alloc.free(entries);
} }
const path = std.mem.trim(u8, filepath, "/"); return .{
const first_element: []const u8 = std.mem.sliceTo(path, '/'); .file = self.file,
.super = self.super,
var search_slice = entries; .decomp = self.decomp.statelessCopy(alloc),
var idx: usize = undefined; .name = res.name,
while (search_slice.len > 0) { .inode = res.inode,
idx = search_slice.len / 2; };
const middle = search_slice[idx]; }
switch (std.mem.order(u8, first_element, middle.name)) { pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter {
.eq => break, return .{
.lt => search_slice = search_slice[0..idx], .alloc = alloc,
.gt => search_slice = search_slice[idx + 1 ..], .entries = try self.inode.readDirectory(alloc, &self.decomp, self.file, self.super.dir_start),
} };
} else return Error.FileNotFound;
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();
return first_elem_file.open(alloc, io, path[first_element.len + 1 ..]);
} }
pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8, options: ExtractionOptions) !void { // Regular file functions
return self.inode.extract(alloc, io, self.archive.file, self.archive.super, filepath, options);
pub fn isRegularFile(self: File) bool {
return switch (self.inode.hdr.inode_type) {
.file, .ext_file => true,
else => false,
};
}
// a std.Io.Reader compatible reader that reads a regular file's data.
pub fn dataReader(self: File, alloc: std.mem.Allocator) !DataReader {
return self.inode.dataReader(
&self.decomp.statelessCopy(alloc),
self.file,
self.super.frag_start,
self.super.block_size,
);
} }
// Types // Universal functions
pub const Error = error{ pub fn extract(self: File, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
FileNotFound, _ = self;
} || Inode.Error; _ = alloc;
_ = path;
_ = options;
return error.TODO;
}
-85
View File
@@ -1,85 +0,0 @@
const std = @import("std");
const Io = std.Io;
const BlockSize = @import("inode_data/file.zig").BlockSize;
const LookupTable = @import("lookup_table.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const FragManager = @This();
pub const FragEntry = extern struct {
start: u64,
size: BlockSize,
_: u32,
};
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *const Decompressor,
block_size: u32,
entries: []FragEntry,
frag_cache: std.array_hash_map.Auto(u32, []u8),
cache_mut: std.Io.RwLock = .init,
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, frag_start: u64, frag_num: u32, block_size: u32) !FragManager {
const first_offset: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[frag_start .. frag_start + 8]), .little);
var rdr = fil.readerAt(first_offset);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
const entries = try alloc.alloc(FragEntry, frag_num);
errdefer alloc.free(entries);
try meta.interface.readSliceEndian(FragEntry, entries, .little);
return .{
.alloc = alloc,
.fil = fil,
.decomp = decomp,
.block_size = block_size,
.entries = entries,
.frag_cache = .empty,
};
}
pub fn deinit(self: *FragManager, io: Io) void {
self.cache_mut.lockUncancelable(io);
self.alloc.free(self.entries);
for (self.frag_cache.values()) |v|
self.alloc.free(v);
self.frag_cache.deinit(self.alloc);
}
pub fn get(self: *FragManager, io: Io, idx: u32) ![]u8 {
{
try self.cache_mut.lockShared(io);
defer self.cache_mut.unlockShared(io);
if (self.frag_cache.contains(idx))
return self.frag_cache.get(idx).?;
}
try self.cache_mut.lock(io);
defer self.cache_mut.unlock(io);
if (self.frag_cache.contains(idx))
return self.frag_cache.get(idx).?;
const entry = self.entries[idx];
const out = try self.alloc.alloc(u8, if (entry.size.uncompressed) entry.size.size else self.block_size);
if (entry.size.uncompressed) {
@memcpy(out, self.fil.map.memory[entry.start .. entry.start + entry.size.size]);
} else {
@branchHint(.likely);
_ = try self.decomp.Decompress(self.alloc, self.fil.map.memory[entry.start .. entry.start + entry.size.size], out);
}
try self.frag_cache.put(self.alloc, idx, out);
return out;
}
+2 -2
View File
@@ -1,5 +1,5 @@
#include <zstd.h>
#include <zlib-ng.h> #include <zlib-ng.h>
// #include <lzma.h>
#include <lzo/minilzo.h> #include <lzo/minilzo.h>
#include <lz4.h> #include <lz4.h>
#include <zstd.h>
#include <lzma.h>
+284 -564
View File
@@ -1,27 +1,22 @@
//! A file-system object. Represents a File or directory. //! This is the raw squashfs representation of a file/directory.
//! Most of the time using File is a better experience and using Inodes directory
//! is only required for more technical use cases.
const std = @import("std"); const std = @import("std");
const Reader = std.Io.Reader; const Reader = std.Io.Reader;
const Io = std.Io;
const Archive = @import("archive.zig"); const Decompressor = @import("decomp.zig");
const Decomp = @import("decomp.zig").Decomp; const Directory = @import("directory.zig");
const DirEntry = @import("directory.zig"); const FragEntry = @import("archive.zig").FragEntry;
const ExtractionOptions = @import("options.zig"); const Dir = @import("inode/dir.zig");
const FragEntry = @import("frag.zig").FragEntry; const File = @import("inode/file.zig");
const FragManager = @import("frag.zig"); const Misc = @import("inode/misc.zig");
const dir = @import("inode_data/dir.zig"); const Sym = @import("inode/sym.zig");
const file = @import("inode_data/file.zig");
const misc = @import("inode_data/misc.zig");
const LookupTable = @import("lookup_table.zig"); const LookupTable = @import("lookup_table.zig");
const CachedTable = LookupTable.CachedTable; const MinimalSuperblock = @import("archive.zig").MinimalSuperblock;
const DataExtractor = @import("util/data_extractor.zig");
const DataReader = @import("util/data_reader.zig"); const DataReader = @import("util/data_reader.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig"); const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig"); const OffsetFile = @import("util/offset_file.zig");
const SharedCache = @import("util/shared_cache.zig");
const XattrTable = @import("xattr_table.zig");
const Inode = @This(); const Inode = @This();
@@ -34,602 +29,327 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
return .{ return .{
.hdr = hdr, .hdr = hdr,
.data = switch (hdr.inode_type) { .data = switch (hdr.inode_type) {
.dir => .{ .dir = try .read(rdr) }, .dir => .{ .dir = .read(rdr) },
.file => .{ .file = try .read(alloc, rdr, block_size) }, .file => .{ .file = .read(alloc, rdr, block_size) },
.symlink => .{ .symlink = try .read(alloc, rdr) }, .symlink => .{ .symlink = .read(alloc, rdr) },
.block_dev => .{ .block_dev = try .read(rdr) }, .block => .{ .block = .read(rdr) },
.char_dev => .{ .char_dev = try .read(rdr) }, .char => .{ .char = .read(rdr) },
.fifo => .{ .fifo = try .read(rdr) }, .fifo => .{ .fifo = .read(rdr) },
.socket => .{ .socket = try .read(rdr) }, .sock => .{ .sock = .read(rdr) },
.ext_dir => .{ .ext_dir = try .read(rdr) }, .ext_dir => .{ .ext_dir = .read(rdr) },
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) }, .ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) }, .ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) }, .ext_block => .{ .ext_block = .read(rdr) },
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) }, .ext_char => .{ .ext_char = .read(rdr) },
.ext_fifo => .{ .ext_fifo = try .read(rdr) }, .ext_fifo => .{ .ext_fifo = .read(rdr) },
.ext_socket => .{ .ext_socket = try .read(rdr) }, .ext_sock => .{ .ext_sock = .read(rdr) },
}, },
}; };
} }
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void { pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
switch (self.data) { switch (self.data) {
.file => |d| d.deinit(alloc), .file => |f| alloc.free(f.block_sizes),
.symlink => |d| d.deinit(alloc), .ext_file => |f| alloc.free(f.block_sizes),
.ext_file => |d| d.deinit(alloc), .symlink => |s| alloc.free(s.target),
.ext_symlink => |d| d.deinit(alloc), .ext_symlink => |s| alloc.free(s.target),
else => {}, else => {},
} }
} }
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
// Utility Functions switch (self.data) {
.dir,
/// Read the directory entries .ext_dir,
pub fn readDirectory(self: Inode, alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, dir_offset: u64) ![]DirEntry { .block,
return switch (self.data) { .ext_block,
.dir => |d| readDirFromData(alloc, fil, decomp, dir_offset, d), .char,
.ext_dir => |d| readDirFromData(alloc, fil, decomp, dir_offset, d), .ext_char,
else => Error.NotDirectory, .fifo,
}; .ext_fifo,
} .sock,
fn readDirFromData(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, dir_offset: u64, d: anytype) ![]DirEntry { .ext_sock,
var rdr = fil.readerAt(dir_offset + d.block_start); => return self,
var meta: MetadataReader = .init(alloc, &rdr, decomp); .file => |f| {
try meta.interface.discardAll(d.block_offset); const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
@memcpy(new_sizes, f.block_sizes);
return DirEntry.readDirectory(alloc, &meta.interface, d.size); return .{
} .hdr = self.hdr,
/// Get a reader for a regular file's data. .data = .{ .file = .{
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32) !DataReader { .block_start = f.block_start,
return switch (self.data) { .frag_idx = f.frag_idx,
.file => |f| getReaderFromData(alloc, io, fil, cache, decomp, block_size, f), .block_offset = f.block_offset,
.ext_file => |f| getReaderFromData(alloc, io, fil, cache, decomp, block_size, f), .size = f.size,
else => Error.NotRegularFile, .block_sizes = new_sizes,
}; } },
} };
fn getReaderFromData(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32, d: anytype) !DataReader { },
const ext: DataReader = .init(alloc, io, fil, cache, decomp, block_size, d.size, d.block_start, d.blocks); .ext_file => |f| {
if (d.frag_block_offset == 0xFFFFFFFF) { const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
// TODO: @memcpy(new_sizes, f.block_sizes);
return error.TODO; return .{
.hdr = self.hdr,
.data = .{ .ext_file = .{
.block_start = self.block_start,
.size = self.size,
.sparse = self.sparse,
.hard_links = self.hard_links,
.frag_idx = self.frag_idx,
.block_offset = self.block_offset,
.xattr_idx = self.xattr_idx,
.block_sizes = new_sizes,
} },
};
},
.symlink => |s| {
const new_target = try alloc.alloc(u8, s.target.len);
@memcpy(new_target, s.target);
return .{
.hdr = self.hdr,
.data = .{ .symlink = .{
.hard_links = s.hard_links,
.target = new_target,
} },
};
},
.ext_symlink => |s| {
const new_target = try alloc.alloc(u8, s.target.len);
@memcpy(new_target, s.target);
return .{
.hdr = self.hdr,
.data = .{ .ext_symlink = .{
.hard_links = s.hard_links,
.xattr_idx = s.xattr_idx,
.target = new_target,
} },
};
},
} }
return ext;
}
/// Get an extractor for a regular file's data.
pub fn dataExtractor(self: Inode, fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32) !DataExtractor {
return switch (self.data) {
.file => |f| getExtractorFromData(fil, cache, decomp, block_size, f),
.ext_file => |f| getExtractorFromData(fil, cache, decomp, block_size, f),
else => Error.NotRegularFile,
};
}
fn getExtractorFromData(fil: OffsetFile, cache: *SharedCache, decomp: *const Decompressor, block_size: u32, d: anytype) !DataExtractor {
const ext: DataExtractor = .init(fil, cache, decomp, block_size, d.size, d.block_start, d.blocks);
if (d.frag_block_offset == 0xFFFFFFFF) {
// TODO:
return error.TODO;
}
return ext;
}
/// Get a symlink's target path
pub fn symlinkTarget(self: Inode) ![]const u8 {
return switch (self.data) {
.symlink => |s| s.target,
.ext_symlink => |s| s.target,
else => Error.NotSymlink,
};
}
/// Get inode's gid
pub fn gid(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, id_table_start: u64) !u16 {
return LookupTable.lookupValue(u16, alloc, io, decomp, fil, id_table_start, self.hdr.gid_idx);
}
/// Get inode's uid
pub fn uid(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, id_table_start: u64) !u16 {
return LookupTable.lookupValue(u16, alloc, io, decomp, fil, id_table_start, self.hdr.uid_idx);
}
/// Get the inode's xattr values as an index into the Archive's xattr table.
/// Returns error.NoXattr if the inode doesn't have extended attributes.
pub fn xattrIndex(self: Inode) !u32 {
const idx = switch (self.data) {
.ext_dir => |e| e.xattr_idx,
.ext_file => |e| e.xattr_idx,
.ext_symlink => |e| e.xattr_idx,
.ext_block_dev => |e| e.xattr_idx,
.ext_char_dev => |e| e.xattr_idx,
.ext_fifo => |e| e.xattr_idx,
.ext_socket => |e| e.xattr_idx,
else => return error.NoXattr,
};
if (idx == 0xFFFFFFFF) return error.NoXattr;
return idx;
}
// Get an inode's xattr values. If the inode does not have xattr values (including if the inode is not an extended type), an empty slice is returned.
pub fn xattrValues(self: Inode, alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, xattr_table_start: u64) ![]XattrTable.XattrOwned {
const idx = self.xattrIndex() catch &[0]XattrTable.XattrOwned{};
return XattrTable.statelessLookup(alloc, io, decomp, fil, xattr_table_start, idx);
} }
// Types // Types
pub const Error = error{ pub const Ref = packed struct {
NotDirectory,
NotRegularFile,
NotSymlink,
NotExtended,
};
pub const Ref = packed struct(u64) {
block_offset: u16, block_offset: u16,
block_start: u32, block_start: u32,
_: u16 = 0, _: u16,
}; };
pub const Type = enum(u16) { pub const Type = enum(u16) {
dir = 1, dir = 1,
file, file,
symlink, symlink,
block_dev, block,
char_dev, char,
fifo, fifo,
socket, sock,
ext_dir, ext_dir,
ext_file, ext_file,
ext_symlink, ext_symlink,
ext_block_dev, ext_block,
ext_char_dev, ext_char,
ext_fifo, ext_fifo,
ext_socket, ext_sock,
}; };
pub const Data = union(Type) { const Header = packed struct {
dir: dir.Dir,
file: file.File,
symlink: misc.Symlink,
block_dev: misc.Dev,
char_dev: misc.Dev,
fifo: misc.IPC,
socket: misc.IPC,
ext_dir: dir.ExtDir,
ext_file: file.ExtFile,
ext_symlink: misc.ExtSymlink,
ext_block_dev: misc.ExtDev,
ext_char_dev: misc.ExtDev,
ext_fifo: misc.ExtIPC,
ext_socket: misc.ExtIPC,
};
pub const Header = extern struct {
inode_type: Type, inode_type: Type,
permissions: u16, permission: u16,
uid_idx: u16, uid_idx: u16,
gid_idx: u16, gid_idx: u16,
mod_time: u32, mod_time: u32,
num: u32, num: u32,
}; };
// Extract pub const Data = union(Type) {
dir: Dir.Dir,
file: File.File,
symlink: Sym.Symlink,
block: Misc.Device,
char: Misc.Device,
fifo: Misc.Ipc,
sock: Misc.Ipc,
ext_dir: Dir.ExtDir,
ext_file: File.ExtFile,
ext_symlink: Sym.ExtSymlink,
ext_block: Misc.ExtDevice,
ext_char: Misc.ExtDevice,
ext_fifo: Misc.ExtIpc,
ext_sock: Misc.ExtIpc,
};
const ExtractError = error{ MknodFailed, CannotSetXattr } || DataExtractor.Error || DirEntry.Error || // Errors
Decompressor.Error || Io.File.Atomic.InitError || Io.File.Atomic.LinkError || Io.Dir.SymLinkError;
const PathRet = struct { pub const Error = error{
path: []const u8, NotDirectory,
NotFound,
NotRegularFile,
};
// Utils functions
// Universal
pub fn uid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 {
return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.uid_idx);
}
pub fn uidCached(self: Inode, table: LookupTable.CachedTable(u16)) !u16 {
return table.get(self.hdr.uid_idx);
}
pub fn gid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 {
return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.gid_idx);
}
pub fn gidCached(self: Inode, table: LookupTable.CachedTable(u16)) !u16 {
return table.get(self.hdr.gid_idx);
}
pub fn xattr(self: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, xattr_start: u64) !?LookupTable.XattrValues {
if (@intFromEnum(self.hdr.inode_type) < 8) return null;
const idx: u32 = switch (self.data) {
.ext_dir => |d| d.xattr_idx,
.ext_file => |f| f.xattr_idx,
.ext_symlink => |s| s.xattr_idx,
.ext_block, .ext_char => |d| d.xattr_idx,
.ext_fifo, .ext_sock => |d| d.xattr_idx,
else => unreachable,
};
if (idx == 0xFFFFFFFF) return null;
return LookupTable.statelessXattr(alloc, fil, decomp, xattr_start, idx);
}
// Dir inodes
/// For directory inodes, tries to find the inode at the given path. Returns both the inode, and it's file name.
/// If the path is empty or "." then a copy of this inode is returned with no name ("").
pub fn findInode(
inode: Inode, inode: Inode,
origin: bool,
fn deinit(self: PathRet, alloc: std.mem.Allocator) void {
if (self.origin) return;
alloc.free(self.path);
self.inode.deinit(alloc);
}
fn setMetadata(self: PathRet, alloc: std.mem.Allocator, io: Io, id_table: *CachedTable(u16), xattr_table: ?*XattrTable, options: ExtractionOptions) !void {
var fil = try Io.Dir.cwd().openFile(io, self.path, .{});
defer fil.close(io);
const inode = self.inode;
if (!options.ignore_permissions) {
try fil.setPermissions(io, @enumFromInt(inode.hdr.permissions));
try fil.setOwner(io, try id_table.get(io, inode.hdr.uid_idx), try id_table.get(io, inode.hdr.gid_idx));
}
if (xattr_table != null) {
const idx = inode.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{self.path}, 0);
defer alloc.free(sentinel_path);
for (xattrs) |x| {
const xattr_ret = std.os.linux.fsetxattr(fil.handle, x.key, x.value.ptr, x.value.len, 0);
if (xattr_ret != 0)
return ExtractError.CannotSetXattr;
}
}
}
};
fn DirCompare(_: void, a: PathRet, b: PathRet) std.math.Order {
return std.math.order(std.mem.count(u8, a.path, "/"), std.mem.count(u8, b.path, "/"));
}
const ExtractReturnUnion = union(enum) {
path_ret: ExtractError!PathRet,
};
const Tables = struct {
id: LookupTable.CachedTable(u16),
frag: LookupTable.CachedTable(FragEntry),
xattr: XattrTable,
};
/// 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.
pub fn extract(
self: Inode,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
io: Io,
fil: OffsetFile,
super: Archive.Superblock,
filepath: []const u8,
options: ExtractionOptions,
) !void {
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();
var loop = io.async(finishLoop, .{ alloc, io, fil, decomp, super, options, &sel });
sel.async(.path_ret, extractRealAsync, .{ self, alloc, io, fil, super, decomp, &sel, &frag_mgr, path, true });
try loop.await(io);
}
fn extractRealAsync(
self: Inode,
alloc: std.mem.Allocator,
io: Io,
fil: OffsetFile,
super: Archive.Superblock,
decomp: *const Decompressor, decomp: *const Decompressor,
sel: *Io.Select(ExtractReturnUnion), fil: OffsetFile,
frag_mgr: *FragManager, dir_start: u64,
inode_start: u64,
block_size: u32,
filepath: []const u8,
) !struct { inode: Inode, name: []const u8 } {
switch (inode.data) {
.dir => |d| {
const path: []const u8 = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return .{ .inode = inode.copy(alloc), .name = "" };
return findInodeRaw(
alloc,
decomp,
fil,
dir_start,
inode_start,
block_size,
path,
d,
);
},
.ext_dir => |d| {
const path: []const u8 = std.mem.trim(u8, filepath, "/");
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
return .{ .inode = inode.copy(alloc), .name = "" };
return findInodeRaw(
alloc,
decomp,
fil,
dir_start,
inode_start,
block_size,
path,
d,
);
},
else => return Error.NotDirectory,
}
}
inline fn findInodeRaw(
inode: Inode,
alloc: std.mem.Allocator,
decomp: *const Decompressor,
fil: OffsetFile,
dir_start: u64,
inode_start: u64,
block_size: u32,
path: []const u8, path: []const u8,
origin: bool, dat: anytype,
) ExtractError!PathRet { ) !struct { inode: Inode, name: []const u8 } {
errdefer { const first_element: []const u8 = std.mem.sliceTo(path, '/');
if (!origin) {
self.deinit(alloc); const dirs = try readDirRaw(alloc, decomp, fil, dir_start, dat);
alloc.free(path); defer {
for (dirs) |dir|
dir.deinit(alloc);
alloc.free(dirs);
}
// Directories are stored ASCIIbetically, so we can use binary search.
var cur_slice = dirs;
var idx: usize = 0;
while (cur_slice.len > 0) {
idx = cur_slice.len / 2;
const mid_name = cur_slice[idx].name;
switch (std.mem.order(u8, first_element, mid_name)) {
.gt => {
cur_slice = cur_slice[idx + 1 ..];
continue;
},
.lt => {
cur_slice = cur_slice[0..idx];
continue;
},
.eq => break,
} }
} else return Error.NotFound;
const entry = cur_slice[idx];
var rdr = try fil.readerAt(inode_start + entry.block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(entry.block_offset);
const ret_inode: Inode = try .read(alloc, &meta_rdr.interface, block_size);
if (first_element.len == path.len) {
const name_copy = try alloc.alloc(u8, entry.name.len);
@memcpy(name_copy, entry.name.len);
return .{ .inode = ret_inode, .name = name_copy };
} }
switch (self.hdr.inode_type) { return inode.findInode(alloc, decomp, fil, dir_start, inode_start, block_size, path[first_element.len..]);
.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) { /// Get the directory entries for a directory inode.
Error.NotDirectory, Error.NotExtended, Error.NotRegularFile, Error.NotSymlink => unreachable, pub fn readDirectory(inode: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64) ![]Directory.Entry {
else => |e| return e, return switch (inode.data) {
}; .dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
defer { .ext_dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
for (entries) |e| else => Error.NotDirectory,
e.deinit(alloc);
alloc.free(entries);
}
for (entries) |e| {
const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", e.name });
errdefer 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);
errdefer new_inode.deinit(alloc);
sel.async(.path_ret, extractRealAsync, .{ new_inode, alloc, io, fil, super, decomp, sel, frag_mgr, new_path, false });
}
},
.file, .ext_file => {
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{ .make_path = true });
defer atomic.deinit(io);
var ext: DataExtractor = switch (self.data) {
.file => |f| blk: {
var ext: DataExtractor = .init(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: DataExtractor = .init(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,
};
try ext.extractAsync(alloc, io, atomic.file);
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;
},
}
return .{
.path = path,
.inode = self,
.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 { inline fn readDirRaw(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64, dat: anytype) ![]Directory.Entry {
var id_table: CachedTable(u16) = .init(alloc, fil, decomp, super.id_start, super.id_count); var rdr = try fil.readerAt(dir_start + dat.block_start, &[0]u8{});
defer id_table.deinit(io); var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(dat.block_offset);
var xattr_table: ?XattrTable = if (super.flags.xattr_never or options.ignore_xattr or !@hasField(std.os, "linux")) return Directory.readDirectory(alloc, meta_rdr, dat.size);
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")) // file inodes
null
else
try .init(alloc, fil, decomp, super.xattr_start);
defer if (xattr_table != null) xattr_table.?.deinit(io);
return self.extractReal( /// Gets the data reader for a file inode.
alloc, pub fn dataReader(inode: Inode, decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32) !DataReader {
io, return switch (inode.data) {
fil, .file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f),
super, .ext_file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f),
else => Error.NotRegularFile,
};
}
inline fn dataReaderRaw(decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32, dat: anytype) !DataReader {
return .init(
decomp, decomp,
frag, fil,
&id_table, block_size,
if (xattr_table == null) null else &xattr_table.?, dat.block_sizes,
path, dat.size,
options, dat.block_start,
if (dat.frag_idx != 0xFFFFFFFF)
try LookupTable.stateless(FragEntry, fil, decomp, frag_start, dat.frag_idx)
else
null,
dat.frag_offset,
); );
} }
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;
}
}
}
+35
View File
@@ -0,0 +1,35 @@
const Reader = @import("std").Io.Reader;
pub const Dir = packed struct {
block_start: u32,
hard_links: u32,
size: u16,
block_offset: u16,
parent_num: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const ExtDir = packed struct {
hard_links: u32,
size: u32,
block_start: u32,
parent_num: u32,
idx_count: u16,
block_offset: u16,
xattr_idx: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
+69
View File
@@ -0,0 +1,69 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const BlockSize = packed struct {
size: u31,
uncompressed: bool,
};
pub const File = struct {
block_start: u32,
frag_idx: u32,
frag_offset: u32,
size: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
var buf: [16]u8 = undefined;
try rdr.readSliceAll(&buf);
const frag_idx = std.mem.readVarInt(u32, buf[4..8], .little);
const size = std.mem.readVarInt(u32, buf[12..], .little);
const sizes_len = size / block_size;
if (frag_idx != 0xFFFFFFFF and size % block_size > 0)
sizes_len += 1;
const sizes = try alloc.alloc(BlockSize, sizes_len);
errdefer alloc.free(sizes);
try rdr.readSliceEndian(BlockSize, sizes, .little);
return .{
.block_start = std.mem.readVarInt(u32, buf[0..4], .little),
.frag_idx = frag_idx,
.frag_offset = std.mem.readVarInt(u32, buf[8..12], .little),
.size = size,
.block_sizes = sizes,
};
}
};
pub const ExtFile = struct {
block_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_offset: u32,
xattr_idx: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
var buf: [40]u8 = undefined;
try rdr.readSliceAll(&buf);
const frag_idx = std.mem.readVarInt(u32, buf[28..32], .little);
const size = std.mem.readVarInt(u64, buf[8..16], .little);
const sizes_len = size / block_size;
if (frag_idx != 0xFFFFFFFF and size % block_size > 0)
sizes_len += 1;
const sizes = try alloc.alloc(BlockSize, sizes_len);
errdefer alloc.free(sizes);
try rdr.readSliceEndian(BlockSize, sizes, .little);
return .{
.block_start = std.mem.readVarInt(u64, buf[0..8], .little),
.size = size,
.sparse = std.mem.readVarInt(u64, buf[16..24], .little),
.hard_links = std.mem.readVarInt(u32, buf[24..28], .little),
.frag_idx = frag_idx,
.frag_offset = std.mem.readVarInt(u32, buf[32..36], .little),
.xattr_idx = std.mem.readVarInt(u32, buf[36..40], .little),
.block_sizes = sizes,
};
}
};
+53
View File
@@ -0,0 +1,53 @@
const Reader = @import("std").Io.Reader;
pub const Device = packed struct {
hard_links: u32,
device: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const ExtDevice = packed struct {
hard_links: u32,
device: u32,
xattr_idx: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const Ipc = packed struct {
hard_links: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
pub const ExtIpc = packed struct {
hard_links: u32,
xattr_idx: u32,
const Self = @This();
pub fn read(rdr: *Reader) !Self {
var new: Self = undefined;
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
return new;
}
};
+42
View File
@@ -0,0 +1,42 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const Symlink = struct {
hard_links: u32,
target: []const u8,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
var buf: [8]u8 = undefined;
try rdr.readSliceAll(&buf);
const size = std.mem.readVarInt(u32, buf[4..], .little);
const target = try alloc.alloc(u8, size);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
return .{
.hard_links = std.mem.readVarInt(u32, buf[0..4], .little),
.target = target,
};
}
};
pub const ExtSymlink = struct {
hard_links: u32,
xattr_idx: u32,
target: []const u8,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
var buf: [8]u8 = undefined;
try rdr.readSliceAll(&buf);
const size = std.mem.readVarInt(u32, buf[4..], .little);
const target = try alloc.alloc(u8, size);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
var xattr_idx: u32 = undefined;
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
return .{
.hard_links = std.mem.readVarInt(u32, buf[0..4], .little),
.target = target,
.xattr_idx = xattr_idx,
};
}
};
-32
View File
@@ -1,32 +0,0 @@
const Reader = @import("std").Io.Reader;
pub const Dir = extern struct {
block_start: u32,
hard_links: u32,
size: u16,
block_offset: u16,
parent_num: u32,
pub fn read(rdr: *Reader) !Dir {
var d: Dir = undefined;
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
return d;
}
};
pub const ExtDir = extern struct {
hard_links: u32,
size: u32,
block_start: u32,
parent_num: u32,
idx_count: u16,
block_offset: u16,
xattr_idx: u32,
// index: []DirIndex
pub fn read(rdr: *Reader) !ExtDir {
var d: ExtDir = undefined;
try rdr.readSliceEndian(ExtDir, @ptrCast(&d), .little);
return d;
}
};
-97
View File
@@ -1,97 +0,0 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const BlockSize = packed struct(u32) {
size: u24,
uncompressed: bool,
_: u7,
};
const FileRawRead = extern struct {
block_start: u32,
frag_idx: u32,
frag_block_offset: u32,
size: u32,
};
pub const File = struct {
block_start: u32,
frag_idx: u32,
frag_block_offset: u32,
size: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
var raw: FileRawRead = undefined;
try rdr.readSliceEndian(FileRawRead, @ptrCast(&raw), .little);
var num_blocks: u32 = raw.size / block_size;
if (raw.size % block_size != 0 and raw.frag_idx == 0xFFFFFFFF)
num_blocks += 1;
const sizes = try alloc.alloc(BlockSize, num_blocks);
errdefer alloc.free(sizes);
try rdr.readSliceEndian(BlockSize, sizes, .little);
return .{
.block_start = raw.block_start,
.frag_idx = raw.frag_idx,
.frag_block_offset = raw.frag_block_offset,
.size = raw.size,
.block_sizes = sizes,
};
}
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
alloc.free(self.block_sizes);
}
};
const ExtFileRawRead = extern struct {
block_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_block_offset: u32,
xattr_idx: u32,
};
pub const ExtFile = struct {
block_start: u64,
size: u64,
sparse: u64,
hard_links: u32,
frag_idx: u32,
frag_block_offset: u32,
xattr_idx: u32,
block_sizes: []BlockSize,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
var raw: ExtFileRawRead = undefined;
try rdr.readSliceEndian(ExtFileRawRead, @ptrCast(&raw), .little);
var num_blocks: u32 = @truncate(raw.size / block_size);
if (raw.size % block_size != 0 and raw.frag_idx == 0xFFFFFFFF)
num_blocks += 1;
const sizes = try alloc.alloc(BlockSize, num_blocks);
errdefer alloc.free(sizes);
try rdr.readSliceEndian(BlockSize, sizes, .little);
return .{
.block_start = raw.block_start,
.size = raw.size,
.sparse = raw.sparse,
.hard_links = raw.hard_links,
.frag_idx = raw.frag_idx,
.frag_block_offset = raw.frag_block_offset,
.xattr_idx = raw.xattr_idx,
.block_sizes = sizes,
};
}
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
alloc.free(self.block_sizes);
}
};
-98
View File
@@ -1,98 +0,0 @@
const std = @import("std");
const Reader = std.Io.Reader;
pub const Symlink = struct {
hard_links: u32,
target: []const u8,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
var start: [8]u8 = undefined;
try rdr.readSliceAll(&start);
const target_size = std.mem.readInt(u32, start[4..8], .little);
const target = try alloc.alloc(u8, target_size + 1);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
return .{
.hard_links = std.mem.readInt(u32, start[0..4], .little),
.target = target,
};
}
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
alloc.free(self.target);
}
};
pub const ExtSymlink = struct {
hard_links: u32,
target: []const u8,
xattr_idx: u32,
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
var start: [8]u8 = undefined;
try rdr.readSliceAll(&start);
const target_size = std.mem.readInt(u32, start[4..8], .little);
const target = try alloc.alloc(u8, target_size + 1);
errdefer alloc.free(target);
try rdr.readSliceEndian(u8, target, .little);
var xattr_idx: u32 = undefined;
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
return .{
.hard_links = std.mem.readInt(u32, start[0..4], .little),
.target = target,
.xattr_idx = xattr_idx,
};
}
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
alloc.free(self.target);
}
};
/// A block or character device.
pub const Dev = extern struct {
hard_links: u32,
dev: u32,
pub fn read(rdr: *Reader) !Dev {
var d: Dev = undefined;
try rdr.readSliceEndian(Dev, @ptrCast(&d), .little);
return d;
}
};
/// An extended block or character device.
pub const ExtDev = extern struct {
hard_links: u32,
dev: u32,
xattr_idx: u32,
pub fn read(rdr: *Reader) !ExtDev {
var d: ExtDev = undefined;
try rdr.readSliceEndian(ExtDev, @ptrCast(&d), .little);
return d;
}
};
/// A socket or FIFO file.
pub const IPC = extern struct {
hard_links: u32,
pub fn read(rdr: *Reader) !IPC {
var d: IPC = undefined;
try rdr.readSliceEndian(IPC, @ptrCast(&d), .little);
return d;
}
};
/// An extended socket or FIFO file.
pub const ExtIPC = extern struct {
hard_links: u32,
xattr_idx: u32,
pub fn read(rdr: *Reader) !ExtIPC {
var d: ExtIPC = undefined;
try rdr.readSliceEndian(ExtIPC, @ptrCast(&d), .little);
return d;
}
};
+129 -86
View File
@@ -1,125 +1,168 @@
const std = @import("std"); const std = @import("std");
const Io = std.Io;
const Decompressor = @import("util/decompressor.zig"); const Decompressor = @import("decomp.zig");
const MetadataReader = @import("util/metadata.zig"); const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig"); const OffsetFile = @import("util/offset_file.zig");
pub fn lookupValue(comptime T: anytype, alloc: std.mem.Allocator, decomp: *const Decompressor, file: OffsetFile, table_start: u64, idx: u32) !T { pub fn stateless(comptime T: anytype, fil: OffsetFile, decomp: *const Decompressor, table_start: u64, idx: u32) !T {
const T_PER_BLOCK: u16 = 8192 / @sizeOf(T); const VALS_PER_BLOCK = 8192 / @sizeOf(T);
const block = idx / VALS_PER_BLOCK;
const block_idx = idx % VALS_PER_BLOCK;
const block = idx / T_PER_BLOCK; const offset = try fil.valueAt(u64, table_start + (8 * block));
const block_offset = idx % T_PER_BLOCK; var buf: [8192]u8 = undefined;
var rdr = try fil.readerAt(offset, &buf);
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(@sizeOf(T) * block_idx);
const offset_pos = table_start + (8 * block);
const offset: u64 = std.mem.readInt(u64, @ptrCast(file.map.memory[offset_pos .. offset_pos + 8]), .little);
var rdr = file.readerAt(offset);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(@sizeOf(T) * block_offset);
var out: T = undefined; var out: T = undefined;
try meta.interface.readSliceEndian(T, @ptrCast(&out), .little); try meta_rdr.interface.readSliceEndian(T, @ptrCast(&out), .little);
return out; return out;
} }
pub const Error = Io.Cancelable || Io.File.Reader.SeekError || Io.Reader.ReadAllocError; const InodeRef = @import("inode.zig").Ref;
const XattrLookup = packed struct {
// This isn't actuall an inode ref, but is stored that exact same way.
ref: InodeRef,
kv_count: u32,
size: u32,
};
const XattrKey = packed struct {
type: enum(u2) {
user,
trusted,
security,
},
out_of_line: bool,
_: u13,
name_size: u16,
};
pub const XattrValues = std.AutoHashMap([:0]u8, []u8);
pub fn statelessXattr(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, table_start: u64, idx: u32) !XattrValues {
const xattr_start = try fil.valueAt(u64, table_start);
const block = idx / 512;
const block_idx = idx % 512;
const block_start = try fil.valueAt(u64, table_start + 8 + (block * 8));
var rdr = try fil.readerAt(block_start, &[0]u8{});
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(16 * block_idx);
var lookup: XattrLookup = undefined;
try meta_rdr.interface.readSliceEndian(XattrLookup, @ptrCast(&lookup), .little);
rdr = try fil.readerAt(xattr_start + lookup.ref.block_start, &[0]u8{});
meta_rdr = .init(&rdr.interface, decomp);
try meta_rdr.interface.discardAll(lookup.ref.block_offset);
var out: XattrValues = try .init(alloc);
for (0..lookup.kv_count) |_| {
var key: XattrKey = undefined;
try meta_rdr.interface.readSliceEndian(XattrKey, @ptrCast(&key), .little);
const prefix_size = switch (key.type) {
.user => 4,
.trusted => 7,
.security => 8,
};
const name: [:0]u8 = try alloc.alloc(u8, prefix_size + key.name_size + 1);
name[prefix_size + key.name_size] = 0;
try meta_rdr.interface.readSliceEndian(u8, name[prefix_size .. prefix_size + key.name_size], .little);
switch (key.type) {
.user => @memcpy(name[0..4], "user"),
.trusted => @memcpy(name[0..7], "trusted"),
.security => @memcpy(name[0..8], "security"),
}
if (key.out_of_line) {
try meta_rdr.interface.discardAll(4);
var value_offset: InodeRef = undefined;
try meta_rdr.interface.readSliceEndian(InodeRef, @ptrCast(&value_offset), .little);
var value_rdr = try fil.readerAt(xattr_start + value_offset.block_start, &[0]u8{});
var value_meta: MetadataReader = .init(&value_rdr.interface, decomp);
try value_meta.interface.discardAll(value_offset.block_offset);
var val_size: u32 = undefined;
try value_meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const value = try alloc.alloc(u8, val_size);
try value_meta.interface.readSliceEndian(u8, value, .little);
try out.put(name, value);
} else {
var val_size: u32 = undefined;
try meta_rdr.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const value = try alloc.alloc(u8, val_size);
try meta_rdr.interface.readSliceEndian(u8, value, .little);
try out.put(name, value);
}
}
return out;
}
pub fn CachedTable(comptime T: anytype) type { pub fn CachedTable(comptime T: anytype) type {
return struct { return struct {
const T_PER_BLOCK: u16 = 8192 / @sizeOf(T); const Self = @This();
const Table = @This(); const VALS_PER_BLOCK = 8192 / @sizeOf(T);
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *const Decompressor, decomp: *const Decompressor,
fil: OffsetFile,
table_start: u64, table_start: u64,
total_num: u32, num: u32,
table: std.AutoHashMap(u32, []T), cache: std.AutoHashMap(u32, []T),
cache_mut: std.Thread.Mutex = .{},
mut: Io.RwLock = .init, pub fn init(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, table_offset: u64, num: u32) !Self {
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, offset: u64, total_num: u32) Table {
return .{ return .{
.alloc = alloc, .alloc = alloc,
.fil = fil,
.decomp = decomp, .decomp = decomp,
.table_start = offset, .fil = fil,
.total_num = total_num, .table_start = table_offset,
.num = num,
.table = .init(alloc), .cache = .init(alloc),
}; };
} }
pub fn deinit(self: *Table, io: Io) void { pub fn deinit(self: *Self) void {
self.mut.lockUncancelable(io); var values = self.cache.valueIterator();
var iter = self.table.valueIterator(); while (values.next()) |val|
while (iter.next()) |val| self.alloc.free(val);
self.alloc.free(val.*); self.cache.deinit();
self.table.deinit();
} }
pub fn fill(self: *Table, io: Io) Error!void { pub fn get(self: *Self, idx: u32) !T {
try self.mut.lock(io); const block = idx / VALS_PER_BLOCK;
defer self.mut.unlock(io); const block_idx = idx % VALS_PER_BLOCK;
var num_blocks = self.total_num / T_PER_BLOCK; if (self.cache.get(block)) |val|
if (self.total_num % T_PER_BLOCK > 0) return val[block_idx];
num_blocks += 1;
for (0..num_blocks) |block| { self.cache_mut.lock();
const offset_pos = self.table_start + (8 * block); defer self.cache_mut.unlock();
const offset: u64 = std.mem.readInt(u64, @ptrCast(self.fil.map.memory[offset_pos .. offset_pos + 8]), .little);
const len: u16 = if (self.total_num % T_PER_BLOCK != 0 and block == (self.total_num - 1) / T_PER_BLOCK) // Double check in case another thread was doing your work.
@truncate(self.total_num % T_PER_BLOCK) if (self.cache.get(block)) |val|
else return val[block_idx];
T_PER_BLOCK;
var rdr = self.fil.readerAt(offset); const offset = try self.fil.valueAt(u64, self.table_start + (8 * block));
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp); var buf: [8192]u8 = undefined;
var rdr = try self.fil.readerAt(offset, &buf);
const slice = try meta.interface.readSliceEndianAlloc(self.alloc, T, len, .little); var meta_rdr: MetadataReader = .init(&rdr.interface, self.decomp);
try self.table.put(@truncate(block), slice); const block_size = if (block == (self.num - 1) / VALS_PER_BLOCK)
} self.num % VALS_PER_BLOCK
}
pub fn get(self: *Table, io: Io, idx: u32) Error!T {
const block = idx / T_PER_BLOCK;
const block_offset = idx % T_PER_BLOCK;
{
try self.mut.lockShared(io);
defer self.mut.unlockShared(io);
if (self.table.contains(block))
return self.table.get(block).?[block_offset];
}
try self.mut.lock(io);
defer self.mut.unlock(io);
if (self.table.contains(block))
return self.table.get(block).?[block_offset];
const offset_pos = self.table_start + (8 * block);
const offset: u64 = std.mem.readInt(u64, @ptrCast(self.fil.map.memory[offset_pos .. offset_pos + 8]), .little);
const len: u16 = if (self.total_num % T_PER_BLOCK != 0 and block == (self.total_num - 1) / T_PER_BLOCK)
@truncate(self.total_num % T_PER_BLOCK)
else else
T_PER_BLOCK; VALS_PER_BLOCK;
const new_block = try self.alloc.alloc(T, block_size);
var rdr = self.fil.readerAt(offset); errdefer self.alloc.free(new_block);
var meta: MetadataReader = .init(self.alloc, &rdr, self.decomp); try meta_rdr.interface.readSliceEndian(T, new_block, .little);
try self.cache.put(block, new_block);
const slice = try meta.interface.readSliceEndianAlloc(self.alloc, T, len, .little); return new_block[block_idx];
try self.table.put(@truncate(block), slice);
return slice[block_offset];
} }
}; };
} }
+9 -7
View File
@@ -5,10 +5,8 @@ const Writer = std.Io.Writer;
const ExtractionOptions = @This(); const ExtractionOptions = @This();
/// Extract single-threaded only. /// The number of threads used for extraction. 0 implies single threaded.
/// Though not necessary if using Threaded.single_threaded, threads: usize = 1,
/// setting single_threaded is more efficient.
single_threaded: bool = false,
/// Don't set the file's owner & permissions after extraction /// Don't set the file's owner & permissions after extraction
ignore_permissions: bool = false, ignore_permissions: bool = false,
/// Don't set xattr values. Currently xattrs are never set anyway. /// Don't set xattr values. Currently xattrs are never set anyway.
@@ -20,12 +18,16 @@ verbose: bool = false,
/// Where to print verbose log. /// Where to print verbose log.
verbose_writer: ?*Writer = null, verbose_writer: ?*Writer = null,
pub const default: ExtractionOptions = .{}; pub const SingleThreadedDefault: ExtractionOptions = .{};
pub const default_single_threaded: ExtractionOptions = .{ .single_threaded = true }; pub fn Default() !ExtractionOptions {
return .{
.threads = try std.Thread.getCpuCount(),
};
}
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions { pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
return .{ return .{
.verbose = true, .verbose = true,
.verbose_writer = wrt, .verbose_writer = wrt,
.threads = try std.Thread.getCpuCount(),
}; };
} }
+1 -4
View File
@@ -1,6 +1,3 @@
pub const Archive = @import("archive.zig"); pub const Archive = @import("archive.zig");
pub const ExtractionOptions = @import("options.zig");
test { pub const ExtractionOptions = @import("options.zig");
@import("std").testing.refAllDecls(@This());
}
-162
View File
@@ -1,162 +0,0 @@
//! The DataExtractor is meant to extract a regular file's data to a given file asyncronously.
const std = @import("std");
const Io = std.Io;
const FragEntry = @import("../frag.zig").FragEntry;
const BlockSize = @import("../inode_data/file.zig").BlockSize;
const Decompressor = @import("decompressor.zig");
const OffsetFile = @import("offset_file.zig");
// const SharedCache = @import("shared_cache.zig");
pub const Error = Decompressor.Error || Io.File.MemoryMap.CreateError || Io.File.WritePositionalError;
const DataExtractor = @This();
fil: OffsetFile,
decomp: *const Decompressor,
block_size: u32,
file_size: u64,
start: u64,
blocks: []BlockSize,
frag_offset: u32 = 0,
frag_block: ?[]u8 = null,
err: ?Error = null,
pub fn init(fil: OffsetFile, decomp: *const Decompressor, block_size: u32, file_size: u64, data_start: u64, blocks: []BlockSize) DataExtractor {
return .{
.fil = fil,
.decomp = decomp,
.block_size = block_size,
.file_size = file_size,
.start = data_start,
.blocks = blocks,
};
}
pub fn addFrag(self: *DataExtractor, frag_offset: u32, block: []u8) void {
self.frag_offset = frag_offset;
self.frag_block = block;
}
fn numBlocks(self: DataExtractor) usize {
var num = self.blocks.len;
if (self.frag_block != null) num += 1;
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;
defer group.cancel(io);
var err: ?Error = null;
var read_offset: u64 = self.start;
for (0..self.blocks.len) |idx| {
group.async(io, blockThread, .{ self, alloc, io, fil, read_offset, idx, &err });
read_offset += self.blocks[idx].size;
}
if (self.frag_block != null)
group.async(io, fragThread, .{ self, io, fil, &err });
group.await(io) catch |cancel| return err orelse cancel;
}
fn blockThread(self: DataExtractor, alloc: std.mem.Allocator, io: Io, fil: Io.File, read_offset: u64, idx: usize, ret_err: *?Error) Io.Cancelable!void {
const block = self.blocks[idx];
const cur_block_size = if (idx == self.numBlocks() - 1)
self.file_size % self.block_size
else
self.block_size;
const write_offset = self.block_size * idx;
var wrt = fil.writer(io, &[0]u8{});
wrt.seekTo(write_offset) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
if (block.size == 0) {
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
} else {
if (block.uncompressed) {
wrt.interface.writeAll(self.fil.map.memory[read_offset..][0..cur_block_size]) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
} else {
@branchHint(.likely);
var tmp: [1024 * 1024]u8 = undefined;
_ = self.decomp.Decompress(alloc, self.fil.map.memory[read_offset..][0..block.size], tmp[0..cur_block_size]) catch |err| {
ret_err.* = err;
return Io.Cancelable.Canceled;
};
wrt.interface.writeAll(tmp[0..cur_block_size]) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
}
}
wrt.flush() catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
}
fn fragThread(self: DataExtractor, io: Io, fil: Io.File, ret_err: *?Error) Io.Cancelable!void {
const cur_block_size = self.file_size % self.block_size;
const write_offset = self.blocks.len * self.block_size;
var wrt = fil.writer(io, &[0]u8{});
wrt.seekTo(write_offset) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
wrt.interface.writeAll(self.frag_block.?[self.frag_offset..][0..cur_block_size]) catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
wrt.flush() catch |err| {
ret_err.* = err;
if (err == error.Canceled) io.recancel();
return Io.Cancelable.Canceled;
};
}
+104 -155
View File
@@ -1,188 +1,137 @@
//! DataReader reads a regular file's data linearly from start to finish using Io.Reader interface.
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Reader = std.Io.Reader;
const Reader = Io.Reader; const Writer = std.Io.Writer;
const Writer = Io.Writer; const Limit = std.Io.Limit;
const Limit = Io.Limit;
const BlockSize = @import("../inode_data/file.zig").BlockSize; const FragEntry = @import("../archive.zig").FragEntry;
const Decompressor = @import("decompressor.zig"); const Decompressor = @import("../decomp.zig");
const BlockSize = @import("../inode/file.zig").BlockSize;
const OffsetFile = @import("offset_file.zig"); const OffsetFile = @import("offset_file.zig");
// const SharedCache = @import("shared_cache.zig");
const DataReader = @This(); const DataReader = @This();
alloc: std.mem.Allocator,
fil: OffsetFile,
io: Io,
decomp: *const Decompressor, decomp: *const Decompressor,
file: OffsetFile,
block_size: u32, block_size: u32,
file_size: u64,
cur_offset: u64,
blocks: []BlockSize, blocks: []BlockSize,
size: u64,
frag: ?FragEntry,
frag_offset: u32,
frag_offset: u32 = 0, offset: u64,
frag_block: ?[]u8 = null, idx: usize = 0,
sparse: bool = false,
block_idx: usize = 0, interface: Reader,
sparse_block: bool = false,
interface: Io.Reader, pub fn init(decomp: *const Decompressor, file: OffsetFile, block_size: u32, blocks: []BlockSize, size: u64, init_offset: u64, frag: ?FragEntry, frag_offset: u32) DataReader {
pub fn init(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *const Decompressor, block_size: u32, file_size: u64, data_start: u64, blocks: []BlockSize) !DataReader {
return .{ return .{
.alloc = alloc,
.fil = fil,
.io = io,
.decomp = decomp, .decomp = decomp,
.file = file,
.block_size = block_size, .block_size = block_size,
.file_size = file_size,
.cur_offset = data_start,
.blocks = blocks, .blocks = blocks,
.size = size,
.frag = frag,
.frag_offset = frag_offset,
.offset = init_offset,
.interface = .{ .interface = .{
.buffer = try alloc.alloc(u8, block_size), .buffer = &[1]u8{undefined} ** (1024 * 1024),
.seek = 0,
.end = 0, .end = 0,
.vtable = &.{ .seek = 0,
.stream = stream, .vtable = &.{ .stream = stream, .discard = discard, .readVec = readVec },
.discard = discard,
.readVec = readVec,
},
}, },
}; };
} }
pub fn deinit(self: *DataReader) void {
self.alloc.free(self.interface.buffer);
}
pub fn addFrag(self: *DataReader, frag_offset: u32, block: []u8) void {
self.frag_offset = frag_offset;
self.frag_block = block;
}
fn numBlocks(self: DataReader) usize { fn numBlocks(self: *DataReader) usize {
var num = self.blocks.len; return if (self.frag == null)
if (self.frag_block != null) num += 1; self.blocks.len
return num;
}
fn advanceBuffer(self: *DataReader) !void {
if (self.block_idx >= self.numBlocks())
return Reader.Error.EndOfStream;
errdefer self.interface.end = 0;
defer self.block_idx += 1;
self.interface.end = if (self.block_idx == self.numBlocks() - 1)
self.file_size % self.block_size
else else
self.block_size; self.blocks.len + 1;
}
// Fragment fn advanceBuffer(self: *DataReader) Reader.Error!void {
if (self.block_idx == self.blocks.len) { if (self.idx >= self.numBlocks()) return Reader.Error.EndOfStream;
@memcpy(self.interface.buffer[0..self.interface.end], self.frag_block.?[self.frag_offset .. self.frag_offset + self.interface.end]); defer self.idx += 1;
self.interface.seek = 0; self.sparse = false;
return; self.interface.end = 0; // If we error out and the error is ignored, we'll stil end up back here to error again.
}
// Normal Block
const block = self.blocks[self.block_idx];
if (block.size == 0) {
self.interface.seek = 0;
self.sparse_block = true;
return;
} else {
self.sparse_block = false;
}
if (block.uncompressed) {
@memcpy(self.interface.buffer[0..self.interface.end], self.fil.map.memory[self.cur_offset .. self.cur_offset + self.interface.end]);
self.cur_offset += self.interface.end;
} else {
@branchHint(.likely);
_ = try self.decomp.Decompress(self.alloc, self.fil.map.memory[self.cur_offset .. self.cur_offset + block.size], self.interface.buffer[0..self.interface.end]);
self.cur_offset += block.size;
}
self.interface.seek = 0; self.interface.seek = 0;
} if (self.idx == self.blocks.len) { // Fragment
var rdr = self.file.readerAt(self.frag.?.block_start, &[0]u8{}) catch return Reader.Error.ReadFailed;
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize { const size = self.size % self.block_size;
var data: *DataReader = @fieldParentPtr("interface", rdr); if (self.frag.?.size.uncompressed) {
if (rdr.seek == rdr.end) try rdr.interface.discardAll(self.frag_offset);
data.advanceBuffer() catch |err| return switch (err) { try rdr.interface.readSliceAll(self.interface.buffer[0..size]);
error.ReadFailed => error.ReadFailed, self.interface.end = size;
error.EndOfStream => error.EndOfStream, return;
else => error.ReadFailed, }
}; const raw_loc = self.interface.buffer.len - self.frag.?.size.size;
try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]);
switch (limit) { _ = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch
.nothing => return 0, return Reader.Error.ReadFailed;
.unlimited => { @memmove(self.interface.buffer[0..size], self.interface.buffer[self.frag_offset .. self.frag_offset + size]);
const wrote = if (data.sparse_block) self.interface.end = size;
try wrt.splatByte(0, rdr.end - rdr.seek) return;
else
try wrt.write(rdr.buffer[rdr.seek..rdr.end]);
rdr.seek += wrote;
return wrote;
},
else => {
const to_read = @min(rdr.end - rdr.seek, @intFromEnum(limit));
const wrote = if (data.sparse_block)
try wrt.splatByte(0, to_read)
else
try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_read]);
rdr.seek += wrote;
return wrote;
},
} }
} const block = self.blocks[self.idx];
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize { if (block.size == 0) {
var data: *DataReader = @fieldParentPtr("interface", rdr); self.interface.end = if (self.idx == self.numBlocks() - 1)
if (rdr.seek == rdr.end) self.size % self.block_size
data.advanceBuffer() catch |err| return switch (err) { else
error.ReadFailed => error.ReadFailed, self.block_size;
error.EndOfStream => error.EndOfStream, self.sparse = true;
else => error.ReadFailed, return;
};
switch (limit) {
.nothing => return 0,
.unlimited => {
const adv = rdr.end - rdr.seek;
rdr.seek = rdr.end;
return adv;
},
else => {
const adv = @min(rdr.end - rdr.seek, @intFromEnum(limit));
rdr.seek += adv;
return adv;
},
} }
defer self.offset += block.size;
var rdr = try self.file.readerAt(self.offset, &[0]u8{});
if (block.uncompressed) {
try rdr.interface.readSliceAll(self.interface.buffer[0..block.size]);
self.interface.end = block.size;
return;
}
const raw_loc = self.interface.buffer.len - block.size;
try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]);
self.interface.end = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch
return Reader.Error.ReadFailed;
} }
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
var data: *DataReader = @fieldParentPtr("interface", rdr); fn stream(r: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
if (rdr.seek == rdr.end) var self: *DataReader = @fieldParentPtr("interface", r);
data.advanceBuffer() catch |err| return switch (err) { if (r.seek == r.end) try self.advanceBuffer();
error.ReadFailed => error.ReadFailed, if (limit == .nothing) return 0;
error.EndOfStream => error.EndOfStream,
else => error.ReadFailed, const to_write = @min(r.end - r.seek, @intFromEnum(limit));
}; const wrote = if (self.sparse)
try wrt.splatByte(0, to_write)
else
try wrt.write(r.buffer[r.seek .. r.seek + to_write]);
r.seek += wrote;
return wrote;
}
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
var self: *DataReader = @fieldParentPtr("interface", r);
if (r.seek == r.end) try self.advanceBuffer();
if (limit == .nothing) return 0;
const adv = @min(r.end - r.seek, @intFromEnum(limit));
r.seek += adv;
return adv;
}
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
var self: *DataReader = @fieldParentPtr("interface", r);
if (r.seek == r.end) try self.advanceBuffer();
var wrote: usize = 0; var wrote: usize = 0;
for (vec) |buf| { for (vec) |slice| {
if (rdr.seek == rdr.end) break; if (r.seek == r.end) break;
const to_copy = @min(r.end - r.seek, slice.len);
const to_copy = @min(rdr.end - rdr.seek, buf.len); if (self.sparse) {
if (data.sparse_block) @memset(slice[0..to_copy], 0);
@memset(buf[0..to_copy], 0) } else {
else @memcpy(slice[0..to_copy], r.buffer[r.seek .. r.seek + to_copy]);
@memcpy(buf[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]); }
rdr.seek += to_copy; r.seek += to_copy;
wrote += to_copy; wrote += to_copy;
} }
return wrote; return wrote;
-15
View File
@@ -1,15 +0,0 @@
//! A decompression interface
const std = @import("std");
const Decompressor = @This();
pub const Error = std.Io.Reader.StreamError || std.mem.Allocator.Error;
/// The actual decompression function.
/// If the given decompressor is null, then the decompression should be done "stateless" without lasting allocations.
decomp_fn: *const fn (?*const Decompressor, std.mem.Allocator, in: []u8, out: []u8) Error!usize,
pub fn Decompress(self: *const Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
return self.decomp_fn(self, alloc, in, out);
}
+54
View File
@@ -0,0 +1,54 @@
const std = @import("std");
const MinimalSuperblock = @import("../archive.zig").MinimalSuperblock;
const Decompressor = @import("../decomp.zig");
const DirEntry = @import("../directory.zig").Entry;
const File = @import("../file.zig");
const Inode = @import("../inode.zig");
const MetadataReader = @import("metadata.zig");
const OffsetFile = @import("offset_file.zig");
const Utils = @import("utils.zig");
const Iter = @This();
file: OffsetFile,
super: MinimalSuperblock,
decomp: Decompressor,
entries: []DirEntry,
idx: usize = 0,
pub fn deinit(self: Iter) void {
for (self.entries) |ent|
ent.deinit(self.decomp.alloc);
self.decomp.alloc.free(self.entries);
}
pub fn next(self: *Iter) !?File {
if (self.idx >= self.entries.len) return null;
defer self.idx += 1;
const entry = self.entries[self.idx];
const new_name = try self.decomp.alloc.alloc(u8, entry.name.len);
@memcpy(new_name, entry.name);
return .{
.file = self.file,
.super = self.super,
.decomp = self.decomp,
.name = new_name,
.inode = Utils.readInode(
self.decomp.alloc,
&self.decomp,
self.file,
self.super.inode_start,
self.super.block_size,
entry.block_start,
entry.block_offset,
),
};
}
pub fn reset(self: *Iter) void {
self.idx = 0;
}
+38 -64
View File
@@ -2,102 +2,76 @@ const std = @import("std");
const Reader = std.Io.Reader; const Reader = std.Io.Reader;
const Writer = std.Io.Writer; const Writer = std.Io.Writer;
const Limit = std.Io.Limit; const Limit = std.Io.Limit;
const StreamError = std.Io.Reader.StreamError;
const Decompressor = @import("decompressor.zig"); const Decompressor = @import("../decomp.zig");
const BlockHeader = packed struct(u16) { const Header = packed struct {
size: u15, size: u15,
uncompressed: bool, uncompressed: bool,
}; };
const This = @This(); const MetadataReader = @This();
alloc: std.mem.Allocator,
rdr: *Reader, rdr: *Reader,
decomp: *const Decompressor, decomp: *const Decompressor,
cur_block_start: u32 = 0, read_buf: [8192]u8 = undefined,
next_start_start: u32 = 0,
buf: [8192]u8 = undefined,
err: ?anyerror = null,
interface: Reader = .{ interface: Reader = .{
.buffer = &[0]u8{}, .buffer = &([1]u8{undefined} ** 8192),
.end = 0, .end = 0,
.seek = 0, .seek = 0,
.vtable = &.{ .vtable = &.{
.stream = stream, .stream = stream,
.discard = discard, .discard = discard,
.readVec = readVec, .readVec = readVec,
// TODO: Potentially add rebase so that we can guarentee that self.block_start & interface.seek is correct.
}, },
}, },
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: *const Decompressor) This { pub fn init(rdr: *Reader, decomp: *const Decompressor) MetadataReader {
return .{ return .{ .rdr = rdr, .decomp = decomp };
.alloc = alloc,
.rdr = rdr,
.decomp = decomp,
};
} }
fn advanceBuffer(self: *MetadataReader) Reader.Error!void {
fn advance(self: *This) !void {
self.interface.seek = 0; self.interface.seek = 0;
var hdr: BlockHeader = undefined; var hdr: Header = undefined;
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little); try self.rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
self.cur_block_start = self.next_start_start; try self.rdr.readSliceAll(self.read_buf[0..hdr.size]);
self.next_start_start += hdr.size;
if (hdr.uncompressed) { if (hdr.uncompressed) {
try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little); @memcpy(self.interface.buffer[0..hdr.size], self.read_buf[0..hdr.size]);
self.interface.end = hdr.size; self.interface.end = hdr.size;
self.interface.buffer = self.buf[0..hdr.size];
return; return;
} else {
@branchHint(.likely);
var tmp_buf: [8192]u8 = undefined;
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
self.interface.end = try self.decomp.Decompress(self.alloc, tmp_buf[0..hdr.size], &self.buf);
self.interface.buffer = self.buf[0..self.interface.end];
} }
self.interface.end = self.decomp.decompress(self.read_buf[0..hdr.size], self.interface.buffer) catch |err|
return switch (err) {
error.OutOfMemory => error.ReadFailed,
else => err,
};
} }
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize { fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
const self: *This = @fieldParentPtr("interface", rdr); var self: *MetadataReader = @fieldParentPtr("interface", rdr);
if (rdr.end == rdr.seek) self.advance() catch |err| { if (rdr.seek == rdr.end) try self.advanceBuffer();
self.err = err; const to_write = @min(@intFromEnum(limit), rdr.end - rdr.seek);
return StreamError.ReadFailed; const wrote = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_write]);
}; rdr.seek += wrote;
if (@intFromEnum(limit) == 0) return 0;
const to_write = @min(rdr.end - rdr.seek, @intFromEnum(limit));
const wrote = try wrt.write(self.buf[rdr.seek .. rdr.seek + to_write]);
self.interface.seek += wrote;
return wrote; return wrote;
} }
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize { fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
const self: *This = @fieldParentPtr("interface", rdr); var self: *MetadataReader = @fieldParentPtr("interface", rdr);
if (rdr.end == rdr.seek) self.advance() catch |err| { if (rdr.seek == rdr.end) try self.advanceBuffer();
self.err = err; const to_adv = @min(@intFromEnum(limit), rdr.end - rdr.seek);
return error.ReadFailed; rdr.seek += to_adv;
}; return to_adv;
if (@intFromEnum(limit) == 0) return 0;
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
rdr.seek += to_skip;
return to_skip;
} }
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize { fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
const self: *This = @fieldParentPtr("interface", rdr); var self: *MetadataReader = @fieldParentPtr("interface", rdr);
if (rdr.end == rdr.seek) self.advance() catch |err| { if (rdr.seek == rdr.end) try self.advanceBuffer();
self.err = err; var wrote = 0;
return error.ReadFailed; for (vec) |v| {
}; if (rdr.seek == rdr.end) break;
var cur_red: usize = 0; const to_write = @min(v.len, rdr.end - rdr.seek);
for (vec) |s| { @memcpy(v[0..to_write], rdr.buffer[rdr.seek .. rdr.seek + to_write]);
const to_copy: usize = @min(rdr.end - rdr.seek, s.len); wrote += to_write;
@memcpy(s[0..to_copy], self.buf[rdr.seek .. rdr.seek + to_copy]); rdr.seek += to_write;
rdr.seek += to_copy;
cur_red += to_copy;
if (rdr.end == rdr.seek) break;
} }
return cur_red; return wrote;
} }
-25
View File
@@ -1,25 +0,0 @@
//! Miscellaneous utility functions.
const std = @import("std");
const Io = std.Io;
const Inode = @import("../inode.zig");
const Decompressor = @import("decompressor.zig");
const MetadataReader = @import("metadata.zig");
const OffsetFile = @import("offset_file.zig");
/// check is the path is referencing itself ("" or ".").
/// separators must be trimmed before calling this function for it to work properly.
pub fn pathIsSelf(path: []const u8) bool {
if (path.len == 0) return true;
if (path.len > 1) return false;
return path[0] == '.';
}
/// Creates an Inode from an Inode.Ref.
pub fn inodeFromRef(alloc: std.mem.Allocator, file: OffsetFile, decomp: *const Decompressor, inode_start: u64, block_size: u32, ref: Inode.Ref) !Inode {
var rdr = file.readerAt(inode_start + ref.block_start);
var meta: MetadataReader = .init(alloc, &rdr, decomp);
try meta.interface.discardAll(ref.block_offset);
return .read(alloc, &meta.interface, block_size);
}
+13 -19
View File
@@ -1,27 +1,21 @@
//! A File where it's meaningful (to us) content starts at a given offset.
const std = @import("std"); const std = @import("std");
const Io = std.Io; const Io = std.Io;
const File = Io.File; const FileReader = Io.File.Reader;
const Reader = Io.Reader;
const OffsetFile = @This(); const OffsetFile = @This();
map: Io.File.MemoryMap, fil: Io.File,
offset: u64 = 0,
pub fn init(io: Io, fil: File, archive_size: u64, init_offset: u64) !OffsetFile { pub fn readerAt(self: OffsetFile, io: Io, offset: u64, buf: []u8) !FileReader {
return .{ var rdr = self.fil.reader(io, buf);
.map = try fil.createMemoryMap(io, .{ try rdr.seekTo(self.offset + offset);
.protection = .{ .read = true, .write = false, .execute = false }, return rdr;
.len = archive_size,
.offset = init_offset,
}),
};
} }
pub fn deinit(self: *OffsetFile, io: Io) void { pub fn valueAt(self: OffsetFile, comptime T: type, io: Io, offset: u64) !T {
self.map.destroy(io); var rdr = self.fil.reader(io, &[0]u8{});
} try rdr.seekTo(self.offset + offset);
var new: T = undefined;
pub fn readerAt(self: OffsetFile, offset: u64) Reader { try rdr.interface.readSliceEndian(T, @ptrCast(&new), .little);
return .fixed(self.map.memory[offset..]); return new;
} }
-52
View File
@@ -1,52 +0,0 @@
const std = @import("std");
const Io = std.Io;
const Node = std.SinglyLinkedList.Node;
const SharedCache = @This();
pub const CACHE_SIZE = 1024 * 1024;
pub const BufferNode = struct {
node: Node,
cache: [CACHE_SIZE]u8,
};
alloc: std.mem.Allocator,
caches: std.ArrayList(BufferNode),
cache_queue: std.SinglyLinkedList,
queue_mut: Io.Mutex,
pub fn init(alloc: std.mem.Allocator, init_cache_size: u32) !SharedCache {
const caches: std.ArrayList(BufferNode) = try .initCapacity(alloc, init_cache_size);
var queue: std.SinglyLinkedList = .{};
for (caches.items) |item|
queue.prepend(&item.node);
return .{
.alloc = alloc,
.caches = caches,
.cache_queue = queue,
};
}
pub fn deinit(self: *SharedCache) void {
self.caches.deinit(self.alloc);
}
pub fn getCache(self: *SharedCache, io: Io) !*BufferNode {
self.queue_mut.lock(io);
const nxt = self.cache_queue.popFirst();
self.queue_mut.unlock(io);
if (nxt == null) {
const new = try self.caches.addOne(self.alloc);
new.* = .{
.node = .{},
.cache = undefined,
};
return new;
}
return @fieldParentPtr("node", nxt.?);
}
pub fn returnCache(self: *SharedCache, buf: *BufferNode) void {
self.cache_queue.prepend(buf);
}
+25
View File
@@ -0,0 +1,25 @@
const std = @import("std");
const Decompressor = @import("../decomp.zig");
const DirEntry = @import("../directory.zig").Entry;
const Inode = @import("../inode.zig");
const MetadataReader = @import("metadata.zig");
const OffsetFile = @import("offset_file.zig");
pub fn pathIsSelf(path: []const u8) bool {
if (path.len == 0) return true;
if (path.len == 1) {
return switch (path[0]) {
'.', '/' => true,
else => false,
};
}
return std.mem.eql(u8, path, "./");
}
pub fn readInode(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, inode_start: u64, block_size: u32, block_start: u32, block_offset: u16) !Inode {
var rdr = try fil.readerAt(inode_start + block_start, &[0]u8{});
var meta: MetadataReader = .init(&rdr.interface, decomp);
try meta.interface.discardAll(block_offset);
return .read(alloc, &meta.interface, block_size);
}
-292
View File
@@ -1,292 +0,0 @@
const std = @import("std");
const Io = std.Io;
const InodeRef = @import("inode.zig").Ref;
const LookupTable = @import("lookup_table.zig");
const Decompressor = @import("util/decompressor.zig");
const MetadataReader = @import("util/metadata.zig");
const OffsetFile = @import("util/offset_file.zig");
const XattrCachedTable = @This();
alloc: std.mem.Allocator,
fil: OffsetFile,
decomp: *const Decompressor,
kv_start: u64,
table: LookupTable.CachedTable(TableValue),
value_cache: std.AutoHashMap(InodeRef, []const u8),
value_mut: Io.RwLock = .init,
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, xattr_start: u64) !XattrCachedTable {
const start: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[xattr_start .. xattr_start + 8]), .little);
const num: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[xattr_start + 8 .. xattr_start + 16]), .little);
return .{
.alloc = alloc,
.fil = fil,
.decomp = decomp,
.kv_start = start,
.table = .init(alloc, fil, decomp, xattr_start + 16, num),
.value_cache = .init(alloc),
};
}
pub fn deinit(self: *XattrCachedTable, io: Io) void {
self.value_mut.lockUncancelable(io);
self.table.deinit(io);
self.value_cache.deinit();
}
pub fn get(self: *XattrCachedTable, alloc: std.mem.Allocator, io: Io, idx: u32) ![]XattrSemiOwned {
const lookup = try self.table.get(io, idx);
var rdr = self.fil.readerAt(self.kv_start + lookup.ref.block_start);
var meta: MetadataReader = .init(alloc, &rdr, self.decomp);
try meta.interface.discardAll(lookup.ref.block_offset);
const out = try alloc.alloc(XattrSemiOwned, lookup.count);
errdefer alloc.free(out);
for (0..lookup.count) |i| {
var key_entry: KeyEntry = undefined;
try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little);
const key: [:0]u8 = switch (key_entry.type.namespace) {
.user => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 5);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[5 .. tmp.len - 1], .little);
@memcpy(tmp[0..5], "user.");
tmp[tmp.len - 1] = 0;
break :blk @ptrCast(tmp);
},
.trusted => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 8);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[8 .. tmp.len - 1], .little);
@memcpy(tmp[0..8], "trusted.");
tmp[tmp.len - 1] = 0;
break :blk @ptrCast(tmp);
},
.security => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 9);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[9 .. tmp.len - 1], .little);
@memcpy(tmp[0..9], "security.");
tmp[tmp.len - 1] = 0;
break :blk @ptrCast(tmp);
},
};
errdefer alloc.free(key);
if (key_entry.type.out_of_line) {
var value: ValueOutOfLineEntry = undefined;
try meta.interface.readSliceEndian(ValueOutOfLineEntry, @ptrCast(&value), .little);
out[i] = .{
.key = key,
.value = try self.valueAt(io, value.ref),
};
continue;
}
const val_ref: InodeRef = .{ .block_start = meta.cur_block_start, .block_offset = @truncate(meta.interface.seek) };
{
try self.value_mut.lockShared(io);
defer self.value_mut.unlockShared(io);
if (self.value_cache.contains(val_ref)) {
out[i] = .{
.key = key,
.value = try self.valueAt(io, val_ref),
};
continue;
}
}
try self.value_mut.lock(io);
defer self.value_mut.unlock(io);
if (self.value_cache.contains(val_ref)) {
out[i] = .{
.key = key,
.value = try self.valueAt(io, val_ref),
};
continue;
}
var val_size: u32 = undefined;
try meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const val = try self.alloc.alloc(u8, val_size);
errdefer alloc.free(val);
try meta.interface.readSliceEndian(u8, val, .little);
try self.value_cache.put(val_ref, val);
out[i] = .{
.key = key,
.value = val,
};
}
return out;
}
fn valueAt(self: *XattrCachedTable, io: Io, ref: InodeRef) ![]const u8 {
try self.value_mut.lock(io);
defer self.value_mut.unlock(io);
if (self.value_cache.contains(ref)) return self.value_cache.get(ref).?;
var rdr = self.fil.readerAt(self.kv_start + ref.block_start);
var meta: MetadataReader = .init(self.alloc, &rdr, self.decomp);
try meta.interface.discardAll(ref.block_offset);
var val_size: u32 = undefined;
try meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
const val = try self.alloc.alloc(u8, val_size);
errdefer self.alloc.free(val);
try meta.interface.readSliceEndian(u8, val, .little);
try self.value_cache.put(ref, val);
return val;
}
// Types
/// An Xattr return value where the reciever only owns the key value.
pub const XattrSemiOwned = struct {
key: [:0]const u8,
value: []const u8,
pub fn deinit(self: XattrSemiOwned, alloc: std.mem.Allocator) void {
alloc.free(self.key);
}
};
/// An Xattr return value where the reciever owns both the key & value.
pub const XattrOwned = struct {
key: [:0]const u8,
value: []const u8,
pub fn deinit(self: XattrSemiOwned, alloc: std.mem.Allocator) void {
alloc.free(self.key);
alloc.free(self.value);
}
};
const TableValue = extern struct {
ref: InodeRef,
count: u32,
size: u32,
};
const KeyEntry = extern struct {
type: XattrPrefix,
name_size: u16,
};
const ValueOutOfLineEntry = extern struct {
_: u32,
ref: InodeRef,
};
const XattrPrefix = packed struct(u16) {
namespace: enum(u8) {
user,
trusted,
security,
fn prefixSize(self: @This()) u16 {
return switch (self) {
.user => 5,
.trusted => 8,
.security => 9,
};
}
},
out_of_line: bool,
_: u7,
};
// Stateless
pub fn statelessLookup(alloc: std.mem.Allocator, io: Io, decomp: *const Decompressor, fil: OffsetFile, table_start: u64, idx: u16) ![]XattrOwned {
const kv_start: u64 = std.mem.readInt(u64, @ptrCast(fil.map.memory[table_start .. table_start + 8]), .little);
const lookup = try LookupTable.lookupValue(TableValue, alloc, io, decomp, fil, table_start + 16, idx);
var rdr = fil.readerAt(kv_start + lookup.ref.block_start);
var meta: MetadataReader = .init(alloc, &rdr.interface, decomp);
try meta.interface.discardAll(lookup.ref.block_offset);
const out = try alloc.alloc(XattrOwned, lookup.count);
errdefer alloc.free(out);
for (0..lookup.count) |i| {
const key_entry: KeyEntry = undefined;
try meta.interface.readSliceEndian(KeyEntry, @ptrCast(&key_entry), .little);
const key = switch (key_entry.type.namespace) {
.user => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 5);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[5 .. tmp.len - 1], .little);
@memset(tmp[0..5], "user.");
break :blk tmp;
},
.trusted => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 8);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[8 .. tmp.len - 1], .little);
@memset(tmp[0..8], "trusted.");
break :blk tmp;
},
.security => blk: {
const tmp = try alloc.alloc(u8, key_entry.name_size + 1 + 9);
errdefer alloc.free(tmp);
try meta.interface.readSliceEndian(u8, tmp[9 .. tmp.len - 1], .little);
@memset(tmp[0..9], "security.");
break :blk tmp;
},
};
key[key.len - 1] = 0;
errdefer alloc.free(key);
if (key_entry.type.out_of_line) {
const value: ValueOutOfLineEntry = undefined;
try meta.interface.readSliceEndian(ValueOutOfLineEntry, @ptrCast(&value), .little);
var ool_rdr = fil.readerAt(kv_start + value.ref.block_start);
var ool_meta: MetadataReader = .init(alloc, &ool_rdr.interface, decomp);
try ool_meta.interface.discardAll(value.ref.block_offset);
var val_size: u32 = undefined;
try ool_meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little);
const val = try alloc.alloc(u8, val_size);
errdefer alloc.free(val);
try ool_meta.interface.readSliceEndian(u8, val, .little);
out[i] = .{
.key = key,
.value = val,
};
continue;
}
var val_size: u32 = undefined;
try meta.interface.readSliceEndian(val_size, @ptrCast(&val_size), .little);
const val = try alloc.alloc(u8, val_size);
errdefer alloc.free(val);
try meta.interface.readSliceEndian(u8, val, .little);
out[i] = .{
.key = key,
.value = val,
};
}
return out;
}