Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a8838b544 | |||
| a9e50a0ff5 | |||
| 712c4d0a19 | |||
| 5975bbb4a2 | |||
| 3ea3d8e9a0 | |||
| 5f1089406e | |||
| 1dae4d8bb7 | |||
| 3239bf0e01 | |||
| 0df14b8adc | |||
| 8186c3fe9a | |||
| 2b49395ab2 | |||
| 84a9cf17b9 | |||
| d1d453ac29 | |||
| 69ce562b6c | |||
| 10e9b66ac6 | |||
| 3c57a2d1e4 | |||
| 700993b0e3 | |||
| 78d1ee2937 | |||
| 2b0625e178 | |||
| ad05e5dff1 | |||
| 688ca53206 | |||
| 93a55aa5c7 | |||
| d76b164e45 | |||
| 5521b2ce6a | |||
| cbd2697c19 | |||
| a3f7b86e67 | |||
| ab606bdfa5 | |||
| 274d088490 | |||
| b67d02074d | |||
| 4b2b7021c7 | |||
| a1b9828578 | |||
| 8e4661c4c6 | |||
| 54aaf30ea5 | |||
| df22cf6529 | |||
| 6b5c830234 | |||
| 116234cf9c | |||
| 4601e8f323 | |||
| 50cae8b63d | |||
| 8b8c9a772f | |||
| f563648b20 | |||
| 8d4f3d72f8 | |||
| 0b6129f1ae | |||
| 7308faad36 | |||
| c9499251f8 | |||
| d470ca98e3 | |||
| a606f5e11a | |||
| a4e23a840d | |||
| 3a10572953 | |||
| edfe919c1b | |||
| 4515610082 | |||
| beca6a2ae6 | |||
| 760e11df0b | |||
| 5f629df47c | |||
| b0160e005b | |||
| b7b99325da |
@@ -13,19 +13,19 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- uses: mlugg/setup-zig@v2
|
- uses: mlugg/setup-zig@v2
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
run: sudo apt update && sudo apt install -y liblzma-dev liblzo2-dev
|
||||||
- name: Build normal version
|
- name: Build normal version
|
||||||
run: zig build -Drelease=true -Dversion=${{ github.ref_name }}
|
run: zig build --release=fast -Duse_zig_decomp=true -Dversion=${{ github.ref_name }}
|
||||||
- name: Move normal build out
|
- name: Move zig build out
|
||||||
run: mv zig-out/bin/unsquashfs ./
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-zig-libs
|
||||||
- name: Rebuild with C libraries
|
- name: Rebuild with C libraries
|
||||||
run: zig build -Drelease=true -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
|
run: zig build --release=fast -Dversion="${{ github.ref_name }}"
|
||||||
- name: Move C build out
|
- name: Move C build out
|
||||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
unsquashfs
|
unsquashfs-x86_64-zig-libs
|
||||||
unsquashfs-c-libs
|
unsquashfs-x86_64-c-libs
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ testing/
|
|||||||
|
|
||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
|
zig-pkg/
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Project-local debug tasks
|
||||||
|
//
|
||||||
|
// For more documentation on how to configure debug tasks,
|
||||||
|
// see: https://zed.dev/docs/debugger
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Build & Run",
|
||||||
|
|
||||||
|
"adapter": "CodeLLDB",
|
||||||
|
"request": "launch",
|
||||||
|
|
||||||
|
"build": {
|
||||||
|
"command": "zig",
|
||||||
|
"args": ["build", "-Ddebug=true"],
|
||||||
|
},
|
||||||
|
|
||||||
|
"program": "zig-out/bin/unsquashfs",
|
||||||
|
"args": [
|
||||||
|
"--force",
|
||||||
|
"-d",
|
||||||
|
"testing/TestExtractUnsquashfs",
|
||||||
|
"testing/LinuxPATest.sfs",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -6,19 +6,27 @@ A library and application to decompress or view squashfs archives.
|
|||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
Overall works, but currently is missing some features (see below). Extraction is a bit slow compared to the normal `unsquashfs` (from my _very_ basic testing it's about ~3x slower). Only properly work on Linux, any other OSes probably won't work fully and are untested.
|
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
||||||
|
|
||||||
## Build options
|
## Build options
|
||||||
|
|
||||||
> `-Duse_c_libs`
|
> `-Duse_zig_decomp=true`
|
||||||
|
|
||||||
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
Instead of using C libraries for decompression, use Zig's standard library for decompression. If using this option LZO and LZ4 decomrpession types are unsupported and decompression times will be significantly longer.
|
||||||
|
|
||||||
> `-Dallow_lzo`
|
> `-Ddynamic=true`
|
||||||
|
|
||||||
|
Dynamicly link C libraries (if they're used) instead of statically linking them.
|
||||||
|
|
||||||
|
> `-Dallow_lzo=true`
|
||||||
|
|
||||||
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
||||||
|
|
||||||
> `-Dversion`
|
> `-Ddebug=true`
|
||||||
|
|
||||||
|
Sets various build options that make debugging easier. Specifically, debug optimization is forced, valgrind support is enabled, error tracing is enabled, stipping is disabled, and copmilation uses LLVM (this is due to some linking issues when on Debug optimization and is required for debugging tools such as `lldb`. In the future this may be removed from the debug flag).
|
||||||
|
|
||||||
|
> `-Dversion=0.0.0`
|
||||||
|
|
||||||
Sets the version of `unsquashfs` shown when `--version` is passed.
|
Sets the version of `unsquashfs` shown when `--version` is passed.
|
||||||
|
|
||||||
@@ -26,11 +34,29 @@ Sets the version of `unsquashfs` shown when `--version` is passed.
|
|||||||
|
|
||||||
Most features are present except for the following:
|
Most features are present except for the following:
|
||||||
|
|
||||||
* mod_time is not set on extraction
|
|
||||||
* xattrs are not applied on extraction
|
|
||||||
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||||
|
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
|
||||||
|
|
||||||
## Building considerations
|
## 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`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
* Using Zig decompression libraries *significantly* increases decompression time.
|
||||||
|
* Performance improvements/regressions will be common. I'm still learning Zig.
|
||||||
|
|
||||||
|
Example Times:
|
||||||
|
|
||||||
|
* *unsquashfs, multi-threaded*: .11s
|
||||||
|
* *unsquashfs, single-threaded*: .13s
|
||||||
|
* *C-libs, multi-threaded*: .10s
|
||||||
|
* *C-libs, single-threaded*: ..28s
|
||||||
|
* *Zig-libs, single-threaded*: .74s
|
||||||
|
* *Zig-libs, multi-threaded*: 2.70s
|
||||||
|
|
||||||
|
## Build considerations
|
||||||
|
|
||||||
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
|
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
|
||||||
|
|
||||||
|
|||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#! /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"
|
||||||
@@ -1,30 +1,54 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
|
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse false;
|
||||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support");
|
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false;
|
||||||
|
const dynamic = b.option(bool, "dynamic", "Dynamicly link C decompression libraries") orelse false;
|
||||||
|
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_c_libs", use_c_libs_option orelse false);
|
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
||||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false);
|
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
var optimize = b.standardOptimizeOption(.{});
|
||||||
const mod = b.addModule("zig_squashfs", .{
|
|
||||||
.root_source_file = b.path("src/root.zig"),
|
if (debug == true)
|
||||||
|
optimize = .Debug;
|
||||||
|
if (optimize == .Debug)
|
||||||
|
debug = true;
|
||||||
|
|
||||||
|
const lib = b.addLibrary(.{
|
||||||
|
.name = "squashfs",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.optimize = if (debug == true) .Debug else optimize,
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.valgrind = debug,
|
||||||
.link_libc = if (use_c_libs_option == true) true else false,
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "options", .module = zig_squashfs_options.createModule() },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
mod.addOptions("config", zig_squashfs_options);
|
|
||||||
if (use_c_libs_option == true) {
|
const deps = try dependencies(b, optimize, target, use_zig_decomp, allow_lzo, dynamic);
|
||||||
mod.linkSystemLibrary("zlib", .{});
|
defer b.allocator.free(deps);
|
||||||
mod.linkSystemLibrary("lzma", .{});
|
for (deps) |d|
|
||||||
if (allow_lzo == true)
|
lib.root_module.linkLibrary(d);
|
||||||
mod.linkSystemLibrary("minilzo", .{});
|
|
||||||
mod.linkSystemLibrary("lz4", .{});
|
if (!use_zig_decomp) {
|
||||||
mod.linkSystemLibrary("zstd", .{});
|
const c = b.addTranslateC(.{
|
||||||
|
.optimize = optimize,
|
||||||
|
.target = target,
|
||||||
|
.root_source_file = b.path("src/c.h"),
|
||||||
|
});
|
||||||
|
if (allow_lzo) c.defineCMacro("ALLOW_LZO", null);
|
||||||
|
lib.root_module.addImport("c", c.createModule());
|
||||||
|
|
||||||
|
if (dynamic)
|
||||||
|
dynamicLinkLibraries(c, allow_lzo);
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = version_string_option orelse "0.0.0-testing";
|
var version = version_string_option orelse "0.0.0-testing";
|
||||||
@@ -35,45 +59,106 @@ 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 = optimize,
|
|
||||||
.link_libc = if (use_c_libs_option == true) true else false,
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "zig_squashfs", .module = mod },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
exe_mod.addOptions("config", unsquashfs_options);
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "unsquashfs",
|
.name = "unsquashfs",
|
||||||
.root_module = exe_mod,
|
.root_module = b.createModule(.{
|
||||||
});
|
.optimize = if (debug == true) .Debug else optimize,
|
||||||
|
.target = target,
|
||||||
const lib = b.addLibrary(.{
|
.valgrind = debug,
|
||||||
.name = "squashfs",
|
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||||
.root_module = mod,
|
.imports = &.{
|
||||||
|
.{ .name = "zig_squashfs", .module = lib.root_module },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
|
exe.root_module.addOptions("config", unsquashfs_options);
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
const run_step = b.step("run", "Run the app");
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
|
||||||
run_step.dependOn(&run_cmd.step);
|
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
|
||||||
if (b.args) |args| {
|
|
||||||
run_cmd.addArgs(args);
|
|
||||||
}
|
|
||||||
const mod_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
.root_module = mod,
|
.root_module = b.createModule(.{
|
||||||
|
.optimize = optimize,
|
||||||
|
.target = target,
|
||||||
|
.root_source_file = b.path("src/root.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 exe_tests = b.addTest(.{
|
|
||||||
.root_module = exe.root_module,
|
|
||||||
});
|
|
||||||
const run_exe_tests = b.addRunArtifact(exe_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);
|
||||||
test_step.dependOn(&run_exe_tests.step);
|
|
||||||
|
// zls build check steps
|
||||||
|
const lib_check = b.addLibrary(.{
|
||||||
|
.name = "squashfs",
|
||||||
|
.root_module = exe.root_module,
|
||||||
|
});
|
||||||
|
const exe_check = b.addExecutable(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = lib.root_module,
|
||||||
|
});
|
||||||
|
const check = b.step("check", "Check if unsquashfs compiles");
|
||||||
|
check.dependOn(&lib_check.step);
|
||||||
|
check.dependOn(&exe_check.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dynamicLinkLibraries(mod: *std.Build.Step.TranslateC, allow_lzo: bool) void {
|
||||||
|
mod.linkSystemLibrary("zstd", .{});
|
||||||
|
mod.linkSystemLibrary("zlib-ng", .{});
|
||||||
|
mod.linkSystemLibrary("lzma", .{});
|
||||||
|
mod.linkSystemLibrary("lz4", .{});
|
||||||
|
if (allow_lzo)
|
||||||
|
mod.linkSystemLibrary("minilzo", .{});
|
||||||
|
}
|
||||||
|
fn dependencies(
|
||||||
|
b: *std.Build,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
target: std.Build.ResolvedTarget,
|
||||||
|
use_zig_decomp: bool,
|
||||||
|
allow_lzo: bool,
|
||||||
|
dynamic: bool,
|
||||||
|
) ![]*std.Build.Step.Compile {
|
||||||
|
if (use_zig_decomp or dynamic) return &.{};
|
||||||
|
|
||||||
|
var list: std.ArrayList(*std.Build.Step.Compile) = .empty;
|
||||||
|
|
||||||
|
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
|
||||||
|
try list.append(b.allocator, zstd.artifact("zstd"));
|
||||||
|
|
||||||
|
const zng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target });
|
||||||
|
try list.append(b.allocator, zng.artifact("zng"));
|
||||||
|
|
||||||
|
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
|
||||||
|
try list.append(b.allocator, xz.artifact("lzma"));
|
||||||
|
|
||||||
|
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
|
||||||
|
try list.append(b.allocator, lz4.artifact("lz4"));
|
||||||
|
|
||||||
|
if (allow_lzo) {
|
||||||
|
const minilzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
|
||||||
|
try list.append(b.allocator, minilzo.artifact("minilzo"));
|
||||||
|
}
|
||||||
|
return list.toOwnedSlice(b.allocator);
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-35
@@ -1,43 +1,29 @@
|
|||||||
.{
|
.{
|
||||||
.name = .squashfs,
|
.name = .squashfs,
|
||||||
.version = "0.0.1",
|
.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.15.2",
|
||||||
// This field is optional.
|
|
||||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
|
||||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
|
||||||
// Once all dependencies are fetched, `zig build` no longer requires
|
|
||||||
// internet connectivity.
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
.zlib_ng = .{
|
||||||
//.example = .{
|
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133",
|
||||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
.hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa",
|
||||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
},
|
||||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
.zstd = .{
|
||||||
// // which will prevent zig from using it.
|
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3",
|
||||||
// .url = "https://example.com/foo.tar.gz",
|
.hash = "zstd-1.5.7-1-KEItkAMwAAD6OKY3m0OOmXG7aL-aLUfrDqbP5J5oYapU",
|
||||||
//
|
},
|
||||||
// // This is computed from the file contents of the directory of files that is
|
.lz4 = .{
|
||||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
.url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102",
|
||||||
// // `paths`.
|
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
|
||||||
// //
|
},
|
||||||
// // This field is the source of truth; packages do not come from a `url`; they
|
.minilzo = .{
|
||||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
.url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90",
|
||||||
// // obtain a package matching this `hash`.
|
.hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP",
|
||||||
// //
|
},
|
||||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
.xz = .{
|
||||||
// .hash = "...",
|
.url = "git+https://github.com/akunaakwei/zig-xz.git#e2d389262c8291907e3e4c6fb119819141c16c0f",
|
||||||
//
|
.hash = "xz-5.8.2-6v47_JYeAABSL-jonprpL5-E_YaaGc4B5xrbe93WsJ3G",
|
||||||
// // When this is provided, the package is found in a directory relative to the
|
},
|
||||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
|
||||||
// // computed. This field and `url` are mutually exclusive.
|
|
||||||
// .path = "foo",
|
|
||||||
//
|
|
||||||
// // When this is set to `true`, a package is declared to be lazily
|
|
||||||
// // fetched. This makes the dependency only get fetched if it is
|
|
||||||
// // actually used.
|
|
||||||
// .lazy = false,
|
|
||||||
//},
|
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
zig test \
|
|
||||||
-lc \
|
|
||||||
-lz \
|
|
||||||
-llzma \
|
|
||||||
-lminilzo \
|
|
||||||
-llz4 \
|
|
||||||
-lzstd \
|
|
||||||
src/test.zig
|
|
||||||
+266
-146
@@ -1,168 +1,288 @@
|
|||||||
//! A squashfs archive read from a file.
|
|
||||||
//! Can be used to directly access File's contents or extract to the filesystem.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const Io = std.Io;
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const Decomp = @import("decomp.zig");
|
const Decomp = @import("decomp.zig");
|
||||||
const ExtractionOptions = @import("options.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const File = @import("file.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const InodeRef = Inode.Ref;
|
const LookupTable = @import("lookup_table.zig");
|
||||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
const Decompressor = @import("util/decompressor.zig");
|
||||||
const SfsFile = @import("file.zig");
|
|
||||||
const Superblock = @import("super.zig").Superblock;
|
|
||||||
const Table = @import("table.zig").Table;
|
|
||||||
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 config = if (builtin.is_test) .{
|
|
||||||
.use_c_libs = true,
|
|
||||||
.allow_lzo = false,
|
|
||||||
} else @import("config");
|
|
||||||
|
|
||||||
/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry.
|
|
||||||
/// The offset into the block and fragment size is stored in the file's inode.
|
|
||||||
pub const FragEntry = packed struct {
|
|
||||||
start: u64,
|
|
||||||
size: BlockSize,
|
|
||||||
_: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Archive = @This();
|
const Archive = @This();
|
||||||
|
|
||||||
// 4 Gigs
|
file: OffsetFile,
|
||||||
const DEFAULT_MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
|
||||||
|
|
||||||
parent_alloc: std.mem.Allocator,
|
|
||||||
alloc: std.heap.ThreadSafeAllocator,
|
|
||||||
// alloc: std.heap.FixedBufferAllocator,
|
|
||||||
// fixed_buf: []u8,
|
|
||||||
thread_count: usize,
|
|
||||||
|
|
||||||
fil: OffsetFile,
|
|
||||||
|
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
setup: bool = false,
|
stateless_decomp: Decompressor,
|
||||||
|
|
||||||
decomp: Decomp.DecompFn,
|
pub fn init(io: Io, file: std.Io.File, offset: u64) !Archive {
|
||||||
|
var rdr = file.reader(io, &[0]u8{});
|
||||||
frag_table: Table(FragEntry) = undefined,
|
try rdr.seekTo(offset);
|
||||||
id_table: Table(u16) = undefined,
|
|
||||||
export_table: Table(InodeRef) = undefined,
|
|
||||||
|
|
||||||
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
|
||||||
return initAdvanced(
|
|
||||||
alloc,
|
|
||||||
fil,
|
|
||||||
0,
|
|
||||||
try std.Thread.getCpuCount(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/// Create the Archive dictating the amount of threads used for extraction.
|
|
||||||
/// If you're planning on only interacting with a small number of files, it should be fine to use few (or one) threads.
|
|
||||||
pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: usize) !Archive {
|
|
||||||
var super: Superblock = undefined;
|
var super: Superblock = undefined;
|
||||||
const red = try fil.pread(@ptrCast(&super), offset);
|
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
|
||||||
std.debug.assert(red == @sizeOf(Superblock));
|
|
||||||
try super.validate();
|
try super.validate();
|
||||||
// const fixed_buf = try alloc.alloc(u8, mem);
|
|
||||||
return .{
|
|
||||||
.parent_alloc = alloc,
|
|
||||||
.alloc = .{ .child_allocator = alloc },
|
|
||||||
// .fixed_buf = fixed_buf,
|
|
||||||
.thread_count = if (threads > 0) threads else try std.Thread.getCpuCount(),
|
|
||||||
.fil = .init(fil, offset),
|
|
||||||
.decomp = switch (super.compression) {
|
|
||||||
.gzip => Decomp.gzipDecompress,
|
|
||||||
.lzma => Decomp.lzmaDecompress,
|
|
||||||
.xz => Decomp.xzDecompress,
|
|
||||||
.zstd => Decomp.zstdDecompress,
|
|
||||||
.lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported,
|
|
||||||
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
|
||||||
},
|
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.file = try .init(io, file, super.size, offset),
|
||||||
.super = super,
|
.super = super,
|
||||||
|
|
||||||
|
.stateless_decomp = try Decomp.StatelessDecomp(super.compression),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *Archive) void {
|
pub fn deinit(self: *Archive, io: Io) void {
|
||||||
// self.parent_alloc.free(self.fixed_buf);
|
self.file.deinit(io);
|
||||||
if (self.setup) {
|
}
|
||||||
self.frag_table.deinit();
|
|
||||||
self.export_table.deinit();
|
/// The root folder of the Archive. Used to open other Files.
|
||||||
self.id_table.deinit();
|
pub fn root(self: *Archive, alloc: std.mem.Allocator) !File {
|
||||||
|
const root_inode = try Utils.inodeFromRef(
|
||||||
|
alloc,
|
||||||
|
self.file,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
self.super.root_ref,
|
||||||
|
);
|
||||||
|
return .init(alloc, self.*, root_inode, "");
|
||||||
|
}
|
||||||
|
/// Opens a File within the archive.
|
||||||
|
pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||||
|
var root_file = try self.root(alloc);
|
||||||
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
|
if (Utils.pathIsSelf(path))
|
||||||
|
return root_file;
|
||||||
|
defer root_file.deinit();
|
||||||
|
return root_file.open(alloc, io, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inode with the given inode number.
|
||||||
|
/// Requires that the archive is exportable (has an export lookup table).
|
||||||
|
pub fn inode(self: *Archive, alloc: std.mem.Allocator, io: Io, num: u32) !Inode {
|
||||||
|
if (!self.super.flags.exportable)
|
||||||
|
return error.NotExportable;
|
||||||
|
const ref = try LookupTable.lookupValue(
|
||||||
|
Inode.Ref,
|
||||||
|
alloc,
|
||||||
|
io,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.file,
|
||||||
|
self.super.export_start,
|
||||||
|
num + 1,
|
||||||
|
);
|
||||||
|
return Utils.inodeFromRef(
|
||||||
|
alloc,
|
||||||
|
io,
|
||||||
|
self.file,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// 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, io: Io, extract_dir: []const u8, options: ExtractionOptions) !void {
|
||||||
|
const root_inode = try Utils.inodeFromRef(
|
||||||
|
alloc,
|
||||||
|
self.file,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
self.super.root_ref,
|
||||||
|
);
|
||||||
|
return root_inode.extract(alloc, io, self.file, self.super, extract_dir, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Superblock
|
||||||
|
|
||||||
|
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
||||||
|
|
||||||
|
const SuperblockError = error{
|
||||||
|
InvalidMagic,
|
||||||
|
InvalidBlockLog,
|
||||||
|
InvalidVersion,
|
||||||
|
InvalidCheck,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A squashfs Superblock
|
||||||
|
pub const Superblock = extern struct {
|
||||||
|
magic: u32,
|
||||||
|
inode_count: u32,
|
||||||
|
mod_time: u32,
|
||||||
|
block_size: u32,
|
||||||
|
frag_count: u32,
|
||||||
|
compression: Decomp.Enum,
|
||||||
|
block_log: u16,
|
||||||
|
flags: packed struct(u16) {
|
||||||
|
inode_uncompressed: bool,
|
||||||
|
data_uncompressed: bool,
|
||||||
|
check: bool,
|
||||||
|
frag_uncompressed: bool,
|
||||||
|
fragment_never: bool,
|
||||||
|
fragment_always: bool,
|
||||||
|
duplicates: bool,
|
||||||
|
exportable: bool,
|
||||||
|
xattr_uncompressed: bool,
|
||||||
|
xattr_never: bool,
|
||||||
|
compression_options: bool,
|
||||||
|
ids_uncompressed: bool,
|
||||||
|
_: u4,
|
||||||
|
},
|
||||||
|
id_count: u16,
|
||||||
|
ver_maj: u16,
|
||||||
|
ver_min: u16,
|
||||||
|
root_ref: Inode.Ref,
|
||||||
|
size: u64,
|
||||||
|
id_start: u64,
|
||||||
|
xattr_start: u64,
|
||||||
|
inode_start: u64,
|
||||||
|
dir_start: u64,
|
||||||
|
frag_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.
|
||||||
|
pub fn validate(self: Superblock) !void {
|
||||||
|
if (self.magic != SQUASHFS_MAGIC)
|
||||||
|
return SuperblockError.InvalidMagic;
|
||||||
|
if (self.flags.check)
|
||||||
|
return SuperblockError.InvalidCheck;
|
||||||
|
if (self.ver_maj != 4 or self.ver_min != 0)
|
||||||
|
return SuperblockError.InvalidVersion;
|
||||||
|
if (std.math.log2(self.block_size) != self.block_log)
|
||||||
|
return SuperblockError.InvalidBlockLog;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
|
||||||
|
const TestArchive = "testing/LinuxPATest.sfs";
|
||||||
|
|
||||||
|
test "Basics" {
|
||||||
|
std.debug.print("Starting test: Basics...\n", .{});
|
||||||
|
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
const io = std.testing.io;
|
||||||
|
|
||||||
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
var sfs: Archive = try .init(io, fil, 0);
|
||||||
|
defer sfs.deinit(io);
|
||||||
|
try std.testing.expectEqualDeep(sfs.super, LinuxPATestCorrectSuperblock);
|
||||||
|
const root_file = try sfs.root(alloc);
|
||||||
|
defer root_file.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestFile = "Start.exe";
|
||||||
|
const TestFileExtractLocation = "testing/Start.exe";
|
||||||
|
|
||||||
|
test "ExtractSingleFile" {
|
||||||
|
std.debug.print("Starting test: ExtractSingleFile...\n", .{});
|
||||||
|
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
const io = std.testing.io;
|
||||||
|
|
||||||
|
Io.Dir.cwd().deleteFile(io, TestFileExtractLocation) catch {};
|
||||||
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
var sfs: Archive = try .init(io, fil, 0);
|
||||||
|
defer sfs.deinit(io);
|
||||||
|
var test_fil = try sfs.open(alloc, io, TestFile);
|
||||||
|
defer test_fil.deinit();
|
||||||
|
try test_fil.extract(alloc, io, TestFileExtractLocation, .default);
|
||||||
|
//TODO: validate extracted file.
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestFullExtractLocation = "testing/TestExtract";
|
||||||
|
|
||||||
|
test "ExtractCompleteArchive" {
|
||||||
|
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
|
||||||
|
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
const io = std.testing.io;
|
||||||
|
|
||||||
|
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||||
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
var sfs: Archive = try .init(io, fil, 0);
|
||||||
|
defer sfs.deinit(io);
|
||||||
|
try sfs.extract(alloc, io, TestFullExtractLocation, .default);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ExtractCompleteArchiveSingleThreaded" {
|
||||||
|
std.debug.print("Starting test: ExtractCompleteArchive...\n", .{});
|
||||||
|
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
const io = std.testing.io;
|
||||||
|
|
||||||
|
var fil = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
{
|
||||||
|
std.debug.print("First testing using Threaded.global_single_threaded...\n", .{});
|
||||||
|
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||||
|
var sfs: Archive = try .init(Io.Threaded.global_single_threaded.io(), fil, 0);
|
||||||
|
defer sfs.deinit(Io.Threaded.global_single_threaded.io());
|
||||||
|
try sfs.extract(alloc, Io.Threaded.global_single_threaded.io(), TestFullExtractLocation, .default);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std.debug.print("Next testing using ExtractionOptions.single_threaded...\n", .{});
|
||||||
|
Io.Dir.cwd().deleteTree(io, TestFullExtractLocation) catch {};
|
||||||
|
var sfs: Archive = try .init(io, fil, 0);
|
||||||
|
defer sfs.deinit(io);
|
||||||
|
try sfs.extract(alloc, io, TestFullExtractLocation, .default_single_threaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocator(self: *Archive) std.mem.Allocator {
|
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||||
return self.alloc.allocator();
|
.magic = std.mem.readInt(u32, "hsqs", .little),
|
||||||
}
|
.inode_count = 2974,
|
||||||
|
.mod_time = 1632696724,
|
||||||
fn setupValues(self: *Archive) !void {
|
.block_size = 131072,
|
||||||
const alloc = self.allocator();
|
.frag_count = 264,
|
||||||
self.frag_table = try .init(alloc, self.fil, self.decomp, self.super.frag_start, self.super.frag_count);
|
.compression = .zstd,
|
||||||
self.id_table = try .init(alloc, self.fil, self.decomp, self.super.id_start, self.super.id_count);
|
.block_log = 17,
|
||||||
self.export_table = try .init(alloc, self.fil, self.decomp, self.super.export_start, self.super.inode_count);
|
.flags = .{
|
||||||
self.setup = true;
|
.inode_uncompressed = false,
|
||||||
}
|
.data_uncompressed = false,
|
||||||
|
.check = false,
|
||||||
pub fn id(self: *Archive, idx: u32) !u16 {
|
.frag_uncompressed = false,
|
||||||
if (!self.setup) try self.setupValues();
|
.fragment_never = false,
|
||||||
return self.id_table.get(idx);
|
.fragment_always = false,
|
||||||
}
|
.duplicates = true,
|
||||||
|
.exportable = true,
|
||||||
pub fn frag(self: *Archive, idx: u32) !FragEntry {
|
.xattr_uncompressed = false,
|
||||||
if (!self.setup) try self.setupValues();
|
.xattr_never = false,
|
||||||
return self.frag_table.get(idx);
|
.compression_options = false,
|
||||||
}
|
.ids_uncompressed = false,
|
||||||
|
._ = 0,
|
||||||
pub fn inode(self: *Archive, num: u32) !Inode {
|
},
|
||||||
if (!self.setup) try self.setupValues();
|
.id_count = 1,
|
||||||
const ref = try self.export_table.get(num - 1);
|
.ver_maj = 4,
|
||||||
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
.ver_min = 0,
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
|
.root_ref = .{
|
||||||
try meta.interface.discardAll(ref.block_offset);
|
.block_offset = 1363,
|
||||||
return try .read(self.allocator(), &meta.interface, self.super.block_size);
|
.block_start = 29237,
|
||||||
}
|
._ = 0,
|
||||||
|
},
|
||||||
pub fn root(self: *Archive) !SfsFile {
|
.size = 106841744,
|
||||||
if (!self.setup) try self.setupValues();
|
.id_start = 106841632,
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
.xattr_start = 106841720,
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
.inode_start = 106778274,
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
.dir_start = 106807998,
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
.frag_start = 106837747,
|
||||||
return .init(self, in, "");
|
.export_start = 106841602,
|
||||||
}
|
};
|
||||||
|
|
||||||
pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
var root_fil = try self.root();
|
|
||||||
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
|
||||||
return root_fil.open(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
var alloc = self.allocator();
|
|
||||||
var ext_path: []u8 = undefined;
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind == .directory) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else return error.ExtractionPathExists;
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else {
|
|
||||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
|
||||||
try in.extractToThreaded(self, ext_path, options, self.thread_count);
|
|
||||||
}
|
|
||||||
|
|||||||
+69
-20
@@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Writer = std.Io.Writer;
|
const Io = std.Io;
|
||||||
|
const Writer = Io.Writer;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const config = @import("config");
|
const config = @import("config");
|
||||||
@@ -14,8 +15,13 @@ const help_mgs =
|
|||||||
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
||||||
\\
|
\\
|
||||||
\\ -o <offset> Start reading the archive at the given offset.
|
\\ -o <offset> Start reading the archive at the given offset.
|
||||||
|
\\ -dx Don't set xattr values
|
||||||
|
\\ -dp Don't set permissions (includes setting uid & gid owner)
|
||||||
\\
|
\\
|
||||||
\\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
|
\\ -p <threads> Specify how many threads to use. If not present or zero, the system's logical cores count is used.
|
||||||
|
\\ -v Verbose
|
||||||
|
\\
|
||||||
|
\\ --force Force extraction. If the destination already exists, it will be deleted.
|
||||||
\\
|
\\
|
||||||
\\ --help Display this messages
|
\\ --help Display this messages
|
||||||
\\ --version Display the version
|
\\ --version Display the version
|
||||||
@@ -28,32 +34,63 @@ var archive: []const u8 = "";
|
|||||||
var extLoc: []const u8 = "squashfs-root";
|
var extLoc: []const u8 = "squashfs-root";
|
||||||
var offset: u64 = 0;
|
var offset: u64 = 0;
|
||||||
var threads: u32 = 0;
|
var threads: u32 = 0;
|
||||||
|
var verbose: bool = false;
|
||||||
|
var ignore_xattrs: bool = false;
|
||||||
|
var ignore_permissions: bool = false;
|
||||||
|
var force: bool = false;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
const alloc = std.heap.smp_allocator;
|
const alloc = init.gpa;
|
||||||
var stdout = std.fs.File.stdout();
|
// const io = init.io;
|
||||||
var out = stdout.writer(&[0]u8{});
|
var evented: Io.Evented = undefined;
|
||||||
|
try evented.init(alloc, .{});
|
||||||
|
const io = evented.io();
|
||||||
|
|
||||||
|
var stdout = std.Io.File.stdout();
|
||||||
|
defer stdout.close(io);
|
||||||
|
var out = stdout.writer(io, &[0]u8{});
|
||||||
defer out.interface.flush() catch {};
|
defer out.interface.flush() catch {};
|
||||||
try handleArgs(alloc, &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: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
|
||||||
defer fil.close();
|
var fil = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
||||||
var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, threads); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
defer fil.close(io);
|
||||||
defer arc.deinit();
|
|
||||||
try arc.extract(extLoc, .Default); //TODO: Handle error gracefully.
|
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully.
|
||||||
|
const options: squashfs.ExtractionOptions = .{
|
||||||
|
.single_threaded = threads == 1,
|
||||||
|
.verbose = verbose,
|
||||||
|
.verbose_writer = if (verbose) &out.interface else null,
|
||||||
|
.ignore_xattr = ignore_xattrs,
|
||||||
|
.ignore_permissions = ignore_permissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (force)
|
||||||
|
try Io.Dir.cwd().deleteTree(io, extLoc);
|
||||||
|
if (threads != 0) {
|
||||||
|
var limited_io = Io.Threaded.init(alloc, .{
|
||||||
|
.async_limit = .limited(threads - 1),
|
||||||
|
.concurrent_limit = .limited(threads - 1),
|
||||||
|
.argv0 = .init(init.minimal.args),
|
||||||
|
.environ = init.minimal.environ,
|
||||||
|
});
|
||||||
|
return arc.extract(alloc, limited_io.io(), extLoc, options); //TODO: Handle error gracefully.
|
||||||
|
}
|
||||||
|
return arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
||||||
var args = try std.process.argsWithAllocator(alloc);
|
var arg_iter = args.iterate();
|
||||||
defer args.deinit();
|
defer arg_iter.deinit();
|
||||||
_ = args.next(); // args[0] is the application launch command.
|
_ = arg_iter.next(); // args[0] is the application launch command.
|
||||||
while (args.next()) |arg| {
|
while (arg_iter.next()) |arg| {
|
||||||
if (std.mem.eql(u8, arg, "-o")) {
|
if (std.mem.eql(u8, arg, "-o")) {
|
||||||
const nxt = args.next();
|
const nxt = arg_iter.next();
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
if (nxt == null or nxt.?.len == 0) {
|
||||||
try out.print("-o must be followed by a number\n", .{});
|
try out.print("-o must be followed by a number\n", .{});
|
||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
@@ -64,7 +101,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-d")) {
|
} else if (std.mem.eql(u8, arg, "-d")) {
|
||||||
const nxt = args.next();
|
const nxt = arg_iter.next();
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
if (nxt == null or nxt.?.len == 0) {
|
||||||
try out.print("-d must be followed by a location\n", .{});
|
try out.print("-d must be followed by a location\n", .{});
|
||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
@@ -72,7 +109,7 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
extLoc = nxt.?;
|
extLoc = nxt.?;
|
||||||
continue;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
} else if (std.mem.eql(u8, arg, "-p")) {
|
||||||
const nxt = args.next();
|
const nxt = arg_iter.next();
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
if (nxt == null or nxt.?.len == 0) {
|
||||||
try out.print("-p must be followed by a number\n", .{});
|
try out.print("-p must be followed by a number\n", .{});
|
||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
@@ -82,6 +119,18 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "-v")) {
|
||||||
|
verbose = true;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "-dx")) {
|
||||||
|
ignore_xattrs = true;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "-dp")) {
|
||||||
|
ignore_permissions = true;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "--force")) {
|
||||||
|
force = true;
|
||||||
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
} else if (std.mem.eql(u8, arg, "--version")) {
|
||||||
try out.print("zig-unsquashfs v", .{});
|
try out.print("zig-unsquashfs v", .{});
|
||||||
try config.version.format(out);
|
try config.version.format(out);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#include <zstd.h>
|
||||||
|
#include <zlib-ng.h>
|
||||||
|
#include <lzma.h>
|
||||||
|
#ifdef ALLOW_LZO
|
||||||
|
#include <lzo/minilzo.h>
|
||||||
|
#endif
|
||||||
|
#include <lz4.h>
|
||||||
+66
-239
@@ -1,26 +1,18 @@
|
|||||||
//! Implementations for decompression.
|
|
||||||
//! TODO: change to vtable interface to allow for shared decompressors for better performance/resource usage.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Reader = std.Io.Reader;
|
const Io = std.Io;
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const config = if (builtin.is_test) .{
|
const options = @import("options");
|
||||||
.use_c_libs = builtin.link_libc == true,
|
|
||||||
.allow_lzo = false, // Change once LZO compilation is fixed
|
|
||||||
} else @import("config");
|
|
||||||
|
|
||||||
const c = @cImport({
|
const Decompressor = @import("util/decompressor.zig");
|
||||||
@cInclude("zlib.h");
|
|
||||||
@cInclude("lzma.h");
|
|
||||||
@cInclude("lz4.h");
|
|
||||||
@cInclude("zstd.h");
|
|
||||||
@cInclude("zstd_errors.h");
|
|
||||||
if (config.allow_lzo)
|
|
||||||
@cInclude("lzo/minilzo.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const CompressionType = enum(u16) {
|
const zlib = if (options.use_zig_decomp) @import("decomp/zig_zlib.zig") else @import("decomp/c_zlib.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");
|
||||||
|
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,
|
gzip = 1,
|
||||||
lzma,
|
lzma,
|
||||||
lzo,
|
lzo,
|
||||||
@@ -29,232 +21,67 @@ pub const CompressionType = enum(u16) {
|
|||||||
zstd,
|
zstd,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
pub fn StatelessDecomp(val: Enum) !Decompressor {
|
||||||
|
return switch (val) {
|
||||||
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
.gzip => zlib.stateless_decompressor,
|
||||||
|
.lzma => lzma.stateless_decompressor,
|
||||||
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
.lzo => if (options.use_zig_decomp or !options.allow_lzo)
|
||||||
var rdr: Reader = .fixed(in);
|
error.LzoUnsupported
|
||||||
const buf = try alloc.alloc(u8, out.len);
|
else
|
||||||
defer alloc.free(buf);
|
lzo.stateless_decompressor,
|
||||||
var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, buf);
|
.xz => xz.stateless_decompressor,
|
||||||
return decomp.reader.readSliceShort(out);
|
.lz4 => if (options.use_zig_decomp)
|
||||||
}
|
error.Lz4Unsupported
|
||||||
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
else
|
||||||
_ = alloc;
|
lz4.stateless_decompressor,
|
||||||
var out_len: usize = out.len;
|
.zstd => zstd.stateless_decompressor,
|
||||||
const res = c.uncompress(out.ptr, &out_len, in.ptr, in.len);
|
|
||||||
return switch (res) {
|
|
||||||
c.Z_OK => out_len,
|
|
||||||
c.Z_MEM_ERROR => error.NotEnoughMemory,
|
|
||||||
c.Z_BUF_ERROR => error.OutBufferTooSmall,
|
|
||||||
c.Z_DATA_ERROR => error.BadData,
|
|
||||||
else => error.UnknownResult,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma;
|
pub const Decomp = union(enum) {
|
||||||
|
gzip: zlib,
|
||||||
|
lzma: lzma,
|
||||||
|
lzo: lzo,
|
||||||
|
xz: xz,
|
||||||
|
lz4: lz4,
|
||||||
|
zstd: zstd,
|
||||||
|
|
||||||
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
pub fn init(val: Enum, alloc: std.mem.Allocator, io: Io, block_size: u32) !Decomp {
|
||||||
var rdr: Reader = .fixed(in);
|
return switch (val) {
|
||||||
var decomp = try std.compress.lzma.decompress(alloc, rdr.adaptToOldInterface());
|
.gzip => .{ .gzip = if (options.use_zig_decomp) try zlib.init(alloc, io, block_size) else try zlib.init(alloc, io) },
|
||||||
return decomp.read(out);
|
.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 = .{} },
|
||||||
fn cLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
.xz => .{ .xz = if (options.use_zig_decomp) try xz.init(alloc, io, block_size) else .{} },
|
||||||
_ = alloc;
|
.lz4 => if (options.use_zig_decomp) error.Lz4Unsupported else .{ .lz4 = .{} },
|
||||||
var stream: c.lzma_stream = .{
|
.zstd => .{ .zstd = if (options.use_zig_decomp) try zstd.init(alloc, io, block_size) else try zstd.init(alloc, io) },
|
||||||
.next_in = in.ptr,
|
|
||||||
.avail_in = in.len,
|
|
||||||
.next_out = out.ptr,
|
|
||||||
.avail_out = out.len,
|
|
||||||
};
|
};
|
||||||
var res = c.lzma_alone_decoder(&stream, in.len * 2);
|
|
||||||
switch (res) {
|
|
||||||
c.LZMA_OK => {},
|
|
||||||
c.LZMA_MEM_ERROR => return error.LzmaMemoryError,
|
|
||||||
c.LZMA_PROG_ERROR => return error.LzmaProgramError,
|
|
||||||
else => return error.UnknownResult,
|
|
||||||
}
|
}
|
||||||
defer c.lzma_end(&stream);
|
pub fn deinit(self: *Decomp, alloc: std.mem.Allocator) void {
|
||||||
while (res == c.LZMA_OK)
|
if (options.use_zig_decomp) {
|
||||||
res = c.lzma_code(&stream, c.LZMA_RUN);
|
switch (self.*) {
|
||||||
return switch (res) {
|
.gzip => self.gzip.deinit(),
|
||||||
c.LZMA_STREAM_END => stream.total_out,
|
.lzma => self.lzma.deinit(),
|
||||||
c.LZMA_MEM_ERROR => error.LzmaMemoryError,
|
.xz => self.xz.deinit(),
|
||||||
c.LZMA_MEMLIMIT_ERROR => error.LzmaMemoryLimit,
|
.zstd => self.zstd.deinit(),
|
||||||
c.LZMA_FORMAT_ERROR => error.LzmaBadFormat,
|
else => {},
|
||||||
c.LZMA_DATA_ERROR => error.LzmaDataCorrupt,
|
|
||||||
c.LZMA_BUF_ERROR => error.LzmaCannotProgress,
|
|
||||||
c.LZMA_PROG_ERROR => error.LzmaProgramError,
|
|
||||||
else => error.UnknownResult,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub const lzoDecompress = if (config.use_c_libs) cLzo else zigLzo;
|
|
||||||
|
|
||||||
// fn zigLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
// _ = alloc;
|
|
||||||
// _ = in;
|
|
||||||
// _ = out;
|
|
||||||
// return error.LzoUnsupported;
|
|
||||||
// }
|
|
||||||
pub fn cLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
_ = alloc;
|
|
||||||
var res = c.lzo_init();
|
|
||||||
if (res != 0) return error.LzoInitFailed;
|
|
||||||
var out_len: usize = out.len;
|
|
||||||
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 => error.LzoError,
|
|
||||||
c.LZO_E_OUT_OF_MEMORY => error.LzoOutOfMemory,
|
|
||||||
c.LZO_E_NOT_COMPRESSIBLE => error.LzoNotCompressible,
|
|
||||||
c.LZO_E_INPUT_OVERRUN => error.LzoInputOverrun,
|
|
||||||
c.LZO_E_OUTPUT_OVERRUN => error.LzoOutputOverrun,
|
|
||||||
c.LZO_E_LOOKBEHIND_OVERRUN => error.LzoLookbehindOverrun,
|
|
||||||
c.LZO_E_EOF_NOT_FOUND => error.LzoEofNotFound,
|
|
||||||
c.LZO_E_INPUT_NOT_CONSUMED => error.LzoInputNotConsumed,
|
|
||||||
c.LZO_E_NOT_YET_IMPLEMENTED => error.LzoNotYetImplemented,
|
|
||||||
c.LZO_E_INVALID_ARGUMENT => error.LzoInvalidArgument,
|
|
||||||
c.LZO_E_INVALID_ALIGNMENT => error.LzoInvalidAlignment,
|
|
||||||
c.LZO_E_OUTPUT_NOT_CONSUMED => error.LzoOutputNotConsumed,
|
|
||||||
c.LZO_E_INTERNAL_ERROR => error.LzoInternalError,
|
|
||||||
else => error.UnknownResult,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const xzDecompress = if (config.use_c_libs) cXz else zigXz;
|
|
||||||
|
|
||||||
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
var decomp = try std.compress.xz.decompress(alloc, rdr.adaptToOldInterface());
|
|
||||||
return decomp.read(out);
|
|
||||||
}
|
|
||||||
fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
_ = alloc;
|
|
||||||
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_stream_decoder(&stream, in.len * 2, 0);
|
|
||||||
switch (res) {
|
|
||||||
c.LZMA_OK => {},
|
|
||||||
c.LZMA_MEM_ERROR => return error.LzmaMemoryError,
|
|
||||||
c.LZMA_PROG_ERROR => return error.LzmaProgramError,
|
|
||||||
else => return error.UnknownResult,
|
|
||||||
}
|
}
|
||||||
defer c.lzma_end(&stream);
|
} else {
|
||||||
while (res == c.LZMA_OK)
|
switch (self.*) {
|
||||||
res = c.lzma_code(&stream, c.LZMA_RUN);
|
.gzip => self.gzip.deinit(alloc),
|
||||||
return switch (res) {
|
.zstd => self.zstd.deinit(alloc),
|
||||||
c.LZMA_STREAM_END => stream.total_out,
|
else => {},
|
||||||
c.LZMA_MEM_ERROR => error.LzmaMemoryError,
|
}
|
||||||
c.LZMA_MEMLIMIT_ERROR => error.LzmaMemoryLimit,
|
}
|
||||||
c.LZMA_FORMAT_ERROR => error.LzmaBadFormat,
|
}
|
||||||
c.LZMA_DATA_ERROR => error.LzmaDataCorrupt,
|
|
||||||
c.LZMA_BUF_ERROR => error.LzmaCannotProgress,
|
pub fn decompressor(self: *Decomp) *Decompressor {
|
||||||
c.LZMA_PROG_ERROR => error.LzmaProgramError,
|
return switch (self.*) {
|
||||||
else => error.UnknownResult,
|
.gzip => &self.gzip.interface,
|
||||||
|
.lzma => &self.lzma.interface,
|
||||||
|
.lzo => if (options.use_zig_decomp or !options.allow_lzo) unreachable else &self.lzo.interface,
|
||||||
|
.xz => &self.xz.interface,
|
||||||
|
.lz4 => if (options.use_zig_decomp) unreachable else &self.lz4.interface,
|
||||||
|
.zstd => &self.zstd.interface,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub const lz4Decompress = if (config.use_c_libs) cLz4 else zigLz4;
|
|
||||||
|
|
||||||
// fn zigLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
// _ = alloc;
|
|
||||||
// _ = in;
|
|
||||||
// _ = out;
|
|
||||||
// return error.Lz4Unsupported;
|
|
||||||
// }
|
|
||||||
pub fn cLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
_ = alloc;
|
|
||||||
const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
|
|
||||||
if (res > 0) return @abs(res); // TODO: Find out what error values it can return.
|
|
||||||
return error.Lz4DecompressFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const zstdDecompress = if (config.use_c_libs) cZstd else zigZstd;
|
|
||||||
|
|
||||||
pub fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
const buf = try alloc.alloc(u8, 1024 * 1024);
|
|
||||||
defer alloc.free(buf);
|
|
||||||
var decomp = std.compress.zstd.Decompress.init(&rdr, buf, .{});
|
|
||||||
return decomp.reader.readSliceShort(out) catch |err| {
|
|
||||||
return decomp.err orelse err;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn cZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
_ = alloc;
|
|
||||||
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
|
|
||||||
if (c.ZSTD_isError(res) == 0) return res;
|
|
||||||
return switch (c.ZSTD_getErrorCode(res)) {
|
|
||||||
c.ZSTD_error_prefix_unknown => cZstdError.PrefixUnknown,
|
|
||||||
c.ZSTD_error_version_unsupported => cZstdError.VersionUnsupported,
|
|
||||||
c.ZSTD_error_frameParameter_unsupported => cZstdError.FrameParameterUnsupported,
|
|
||||||
c.ZSTD_error_frameParameter_windowTooLarge => cZstdError.FrameParameterWindowTooLarge,
|
|
||||||
c.ZSTD_error_corruption_detected => cZstdError.CorruptionDetected,
|
|
||||||
c.ZSTD_error_checksum_wrong => cZstdError.ChecksumWrong,
|
|
||||||
c.ZSTD_error_literals_headerWrong => cZstdError.LiteralsHeaderWrong,
|
|
||||||
c.ZSTD_error_dictionary_corrupted => cZstdError.DictionaryCorrupted,
|
|
||||||
c.ZSTD_error_dictionary_wrong => cZstdError.DictionaryWrong,
|
|
||||||
c.ZSTD_error_dictionaryCreation_failed => cZstdError.DictionaryCreationFailed,
|
|
||||||
c.ZSTD_error_parameter_unsupported => cZstdError.ParameterUnsupported,
|
|
||||||
c.ZSTD_error_parameter_combination_unsupported => cZstdError.ParameterCombinationUnsupported,
|
|
||||||
c.ZSTD_error_parameter_outOfBound => cZstdError.ParameterOutOfBound,
|
|
||||||
c.ZSTD_error_tableLog_tooLarge => cZstdError.TableLogTooLarge,
|
|
||||||
c.ZSTD_error_maxSymbolValue_tooLarge => cZstdError.MaxSymbolValueTooLarge,
|
|
||||||
c.ZSTD_error_maxSymbolValue_tooSmall => cZstdError.MaxSymbolValueTooSmall,
|
|
||||||
c.ZSTD_error_stabilityCondition_notRespected => cZstdError.StabilityConditionNotRespected,
|
|
||||||
c.ZSTD_error_stage_wrong => cZstdError.StageWrong,
|
|
||||||
c.ZSTD_error_init_missing => cZstdError.InitMissing,
|
|
||||||
c.ZSTD_error_memory_allocation => cZstdError.MemoryAllocation,
|
|
||||||
c.ZSTD_error_workSpace_tooSmall => cZstdError.WorkSpaceTooSmall,
|
|
||||||
c.ZSTD_error_dstSize_tooSmall => cZstdError.DstSizeTooSmall,
|
|
||||||
c.ZSTD_error_srcSize_wrong => cZstdError.SrcSizeWrong,
|
|
||||||
c.ZSTD_error_dstBuffer_null => cZstdError.DstBufferNull,
|
|
||||||
c.ZSTD_error_noForwardProgress_destFull => cZstdError.NoForwardProgressDestFull,
|
|
||||||
c.ZSTD_error_noForwardProgress_inputEmpty => cZstdError.NoForwardProgressInputEmpty,
|
|
||||||
else => cZstdError.Generic,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const cZstdError = error{
|
|
||||||
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,
|
|
||||||
MaxCode,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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 };
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*Decompressor, _: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
const out_len: c_int = @bitCast(@as(u32, @truncate(out.len)));
|
||||||
|
const res = c.LZ4_decompress_fast(in.ptr, out.ptr, out_len);
|
||||||
|
if (res < 0) return Error.ReadFailed;
|
||||||
|
return @abs(res);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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 };
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*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.?)));
|
||||||
|
// }
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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 };
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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 };
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .decomp_fn = statelessDecomp },
|
||||||
|
|
||||||
|
fn statelessDecomp(_: ?*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.?)));
|
||||||
|
// }
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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: ?*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(_: ?*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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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: ?*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 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(_: ?*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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
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: ?*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(_: ?*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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
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: ?*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(_: ?*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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
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: ?*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(_: ?*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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
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: ?*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(_: ?*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);
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//! Directory entry from the directory table.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
|
|
||||||
const InodeType = @import("inode.zig").InodeType;
|
|
||||||
|
|
||||||
const Entry = @This();
|
|
||||||
|
|
||||||
const Header = extern struct { // use extern due to bad alignment with packed.
|
|
||||||
count: u32,
|
|
||||||
block_start: u32,
|
|
||||||
num: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
const RawEntry = packed struct {
|
|
||||||
offset: u16,
|
|
||||||
inode_offset: i16,
|
|
||||||
inode_type: InodeType,
|
|
||||||
name_size: u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
block_start: u32,
|
|
||||||
block_offset: u16,
|
|
||||||
num: u32,
|
|
||||||
inode_type: InodeType,
|
|
||||||
name: []const u8,
|
|
||||||
|
|
||||||
pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry {
|
|
||||||
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
|
|
||||||
var hdr: Header = undefined;
|
|
||||||
var raw: RawEntry = undefined;
|
|
||||||
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 100); // Start out with a decent capacity instead of needing to allocate per header.
|
|
||||||
errdefer out.deinit(alloc);
|
|
||||||
while (cur_red < size) {
|
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
|
||||||
cur_red += @sizeOf(Header);
|
|
||||||
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
|
||||||
for (0..hdr.count + 1) |_| {
|
|
||||||
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
|
||||||
const name = try alloc.alloc(u8, raw.name_size + 1);
|
|
||||||
errdefer alloc.free(name);
|
|
||||||
try rdr.readSliceEndian(u8, name, .little);
|
|
||||||
const val = out.addOneAssumeCapacity();
|
|
||||||
val.* = .{
|
|
||||||
.block_start = hdr.block_start,
|
|
||||||
.block_offset = raw.offset,
|
|
||||||
.num = @abs(hdr.num + raw.offset),
|
|
||||||
.inode_type = raw.inode_type,
|
|
||||||
.name = name,
|
|
||||||
};
|
|
||||||
cur_red += @sizeOf(RawEntry) + raw.name_size + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.toOwnedSlice(alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.name);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
|
||||||
|
pub const Error = error{OutOfMemory} || Reader.Error;
|
||||||
|
|
||||||
|
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 raw: RawEntry = undefined;
|
||||||
|
var out: std.ArrayList(DirEntry) = try .initCapacity(alloc, 30);
|
||||||
|
errdefer {
|
||||||
|
for (out.items) |ent|
|
||||||
|
alloc.free(ent.name);
|
||||||
|
out.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tot_red: u32 = 3;
|
||||||
|
while (tot_red < size) {
|
||||||
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
|
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||||
|
|
||||||
|
tot_red += @sizeOf(Header);
|
||||||
|
|
||||||
|
for (0..hdr.count + 1) |_| {
|
||||||
|
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
||||||
|
|
||||||
|
const new_name = try alloc.alloc(u8, raw.name_size + 1);
|
||||||
|
try rdr.readSliceEndian(u8, new_name, .little);
|
||||||
|
|
||||||
|
const new = out.addOneAssumeCapacity();
|
||||||
|
new.* = .{
|
||||||
|
.block_start = hdr.block_start,
|
||||||
|
.block_offset = raw.block_offset,
|
||||||
|
.type = raw.type,
|
||||||
|
.name = new_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
tot_red += @sizeOf(RawEntry) + raw.name_size + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toOwnedSlice(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
const Header = extern struct {
|
||||||
|
count: u32,
|
||||||
|
block_start: u32,
|
||||||
|
num: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const RawEntry = extern struct {
|
||||||
|
block_offset: u16,
|
||||||
|
num_offset: i16,
|
||||||
|
type: Inode.Type,
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
+62
-199
@@ -1,230 +1,93 @@
|
|||||||
//! A file/directory within the squashfs archive.
|
//! An easier to use wrapper around an inode.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const Io = std.Io;
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
|
||||||
const Mutex = std.Thread.Mutex;
|
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
const Archive = @import("archive.zig");
|
||||||
const DirEntry = @import("dir_entry.zig");
|
const DirEntry = @import("directory.zig");
|
||||||
const ExtractionOptions = @import("options.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
const DataExtractor = @import("util/data_extractor.zig");
|
||||||
const DataReader = @import("util/data.zig");
|
const Decompressor = @import("util/decompressor.zig");
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
const FileError = error{
|
const File = @This();
|
||||||
NotDirectory,
|
|
||||||
NotRegularFile,
|
|
||||||
NotSymlink,
|
|
||||||
NotDevice,
|
|
||||||
NotFound,
|
|
||||||
ExtractionPathExists,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SfsFile = @This();
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
archive: *Archive,
|
archive: Archive,
|
||||||
|
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
|
||||||
/// Initialize a new File.
|
/// Creates a new File from an inode. Takes ownership of the Inode and creates a copy of the given name.
|
||||||
/// name is copied to the File so can be safely freed afterwards.
|
/// Requires the given allocator was used to create the Inode.
|
||||||
pub fn init(archive: *Archive, inode: Inode, name: []const u8) !SfsFile {
|
pub fn init(alloc: std.mem.Allocator, archive: Archive, in: Inode, name: []const u8) !File {
|
||||||
const new_name = try archive.allocator().alloc(u8, name.len);
|
const new_name = try alloc.alloc(u8, name.len);
|
||||||
@memcpy(new_name, name);
|
@memcpy(new_name, name);
|
||||||
return .{
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
.archive = archive,
|
.archive = archive,
|
||||||
.inode = inode,
|
|
||||||
|
.inode = in,
|
||||||
.name = new_name,
|
.name = new_name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn fromEntry(archive: *Archive, entry: DirEntry) !SfsFile {
|
pub fn fromDirEntry(alloc: std.mem.Allocator, archive: *Archive, ent: DirEntry) !File {
|
||||||
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
var rdr = archive.file.readerAt(archive.super.inode_start + ent.block_start);
|
||||||
var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, archive.decomp);
|
var meta: MetadataReader = .init(alloc, &rdr, &archive.stateless_decomp);
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
try meta.interface.discardAll(ent.block_offset);
|
||||||
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
|
||||||
errdefer inode.deinit(archive.allocator());
|
var in: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
|
||||||
return .init(archive, inode, entry.name);
|
errdefer in.deinit(alloc);
|
||||||
|
return .init(alloc, archive.*, in, ent.name);
|
||||||
|
}
|
||||||
|
pub fn deinit(self: File) void {
|
||||||
|
self.alloc.free(self.name);
|
||||||
|
self.inode.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: SfsFile) void {
|
pub fn open(self: *File, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !File {
|
||||||
var alloc = self.archive.allocator();
|
const entries = try self.inode.readDirectory(
|
||||||
alloc.free(self.name);
|
alloc,
|
||||||
self.inode.deinit(alloc);
|
self.archive.file,
|
||||||
}
|
&self.archive.stateless_decomp,
|
||||||
|
self.archive.super.dir_start,
|
||||||
fn getEntries(self: SfsFile) ![]DirEntry {
|
);
|
||||||
return self.inode.dirEntries(self.archive.allocator(), self.archive.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ownerUid(self: SfsFile) !u16 {
|
|
||||||
return self.archive.id(self.inode.hdr.uid_idx);
|
|
||||||
}
|
|
||||||
pub fn ownerGid(self: SfsFile) !u16 {
|
|
||||||
return self.archive.id(self.inode.hdr.gid_idx);
|
|
||||||
}
|
|
||||||
pub fn permissions(self: SfsFile) u16 {
|
|
||||||
return self.inode.hdr.permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isRegular(self: SfsFile) bool {
|
|
||||||
return switch (self.inode.hdr.inode_type) {
|
|
||||||
.file, .ext_file => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// The returned DataReader will no longer work if the File's deinit function is called
|
|
||||||
/// or, more specifically, it's inode's deinit function is called.
|
|
||||||
pub fn dataReader(self: SfsFile) !DataReader {
|
|
||||||
return self.inode.dataReader(self.archive);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isDir(self: SfsFile) bool {
|
|
||||||
return switch (self.inode.hdr.inode_type) {
|
|
||||||
.dir, .ext_dir => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn iterate(self: SfsFile) !Iterator {
|
|
||||||
if (!self.isDir()) return FileError.NotDirectory;
|
|
||||||
return .{
|
|
||||||
.entries = try self.getEntries(),
|
|
||||||
.archive = self.archive,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// Open a sub-file/folder within a directory at the given path.
|
|
||||||
/// If path is "", ".", "/", or "./", this File is returned.
|
|
||||||
pub fn open(self: SfsFile, path: []const u8) !SfsFile {
|
|
||||||
if (!self.isDir()) return FileError.NotDirectory;
|
|
||||||
if (pathIsSelf(path)) return self;
|
|
||||||
|
|
||||||
// Recursively stip ending & leading path separators.
|
|
||||||
if (path[0] == '/') return self.open(path[1..]);
|
|
||||||
if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]);
|
|
||||||
|
|
||||||
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
|
|
||||||
const first_element = path[0..idx];
|
|
||||||
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]);
|
|
||||||
const entries = try self.getEntries();
|
|
||||||
defer {
|
defer {
|
||||||
var alloc = self.archive.allocator();
|
for (entries) |ent|
|
||||||
for (entries) |e| {
|
alloc.free(ent.name);
|
||||||
e.deinit(alloc);
|
|
||||||
}
|
|
||||||
alloc.free(entries);
|
alloc.free(entries);
|
||||||
}
|
}
|
||||||
var cur_slice = entries;
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
var split = cur_slice.len / 2;
|
const first_element: []const u8 = std.mem.sliceTo(path, '/');
|
||||||
while (cur_slice.len > 0) {
|
|
||||||
split = cur_slice.len / 2;
|
var search_slice = entries;
|
||||||
const comp = std.mem.order(u8, first_element, cur_slice[split].name);
|
var idx: usize = undefined;
|
||||||
switch (comp) {
|
while (search_slice.len > 0) {
|
||||||
.eq => {
|
idx = search_slice.len / 2;
|
||||||
var fil: SfsFile = try .fromEntry(self.archive, cur_slice[split]);
|
const middle = search_slice[idx];
|
||||||
if (idx == path.len) {
|
switch (std.mem.order(u8, first_element, middle.name)) {
|
||||||
return fil;
|
.eq => break,
|
||||||
|
.lt => search_slice = search_slice[0..idx],
|
||||||
|
.gt => search_slice = search_slice[idx + 1 ..],
|
||||||
}
|
}
|
||||||
defer fil.deinit();
|
} else return Error.FileNotFound;
|
||||||
return fil.open(path[idx + 1 ..]);
|
|
||||||
},
|
var first_elem_file = try fromDirEntry(alloc, &self.archive, search_slice[idx]);
|
||||||
.lt => cur_slice = cur_slice[0..split],
|
if (first_element.len == path.len)
|
||||||
.gt => cur_slice = cur_slice[split + 1 ..],
|
return first_elem_file;
|
||||||
}
|
defer first_elem_file.deinit();
|
||||||
}
|
return first_elem_file.open(alloc, io, path[first_element.len + 1 ..]);
|
||||||
return FileError.NotFound;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isSymlink(self: SfsFile) bool {
|
pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, filepath: []const u8, options: ExtractionOptions) !void {
|
||||||
return switch (self.inode.hdr.inode_type) {
|
return self.inode.extract(alloc, io, self.archive.file, self.archive.super, filepath, options);
|
||||||
.symlink, .ext_symlink => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn symlinkPath(self: SfsFile) ![]const u8 {
|
|
||||||
if (!self.isSymlink()) return FileError.NotSymlink;
|
|
||||||
return switch (self.inode.data) {
|
|
||||||
.symlink => |s| s.target,
|
|
||||||
.ext_symlink => |s| s.target,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the File is a block or character device.
|
// Types
|
||||||
pub fn isDevice(self: SfsFile) bool {
|
|
||||||
return switch (self.inode.hdr.inode_type) {
|
|
||||||
.block_dev, .char_dev, .ext_block_dev, .ext_char_dev => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// If the File is a block or character device, get's it's device number.
|
|
||||||
pub fn devNum(self: SfsFile) !u32 {
|
|
||||||
if (!self.isDevice()) return FileError.NotDevice;
|
|
||||||
return switch (self.inode.data) {
|
|
||||||
.block_dev, .char_dev => |d| d.dev,
|
|
||||||
.ext_block_dev, .ext_char_dev => |d| d.dev,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist.
|
pub const Error = error{
|
||||||
/// If the gievn path is a folder, the File's contents will be extracted within.
|
FileNotFound,
|
||||||
pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !void {
|
} || Inode.Error;
|
||||||
var alloc = self.archive.allocator();
|
|
||||||
var ext_path: []u8 = undefined;
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind == .directory) {
|
|
||||||
if (!self.isDir()) {
|
|
||||||
const has_end_sep = path[path.len - 1] == '/';
|
|
||||||
const alloc_size = if (has_end_sep)
|
|
||||||
path.len + self.name.len
|
|
||||||
else
|
|
||||||
path.len + self.name.len + 1;
|
|
||||||
ext_path = try alloc.alloc(u8, alloc_size);
|
|
||||||
@memcpy(ext_path[0..path.len], path);
|
|
||||||
@memcpy(ext_path[ext_path.len - self.name.len ..], self.name);
|
|
||||||
if (!has_end_sep) ext_path[path.len] = '/';
|
|
||||||
} else {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
}
|
|
||||||
} else return FileError.ExtractionPathExists;
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else {
|
|
||||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
||||||
return self.inode.extractToThreaded(self.archive, path, options, self.archive.thread_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function.
|
|
||||||
pub fn pathIsSelf(path: []const u8) bool {
|
|
||||||
if (path.len == 0) return true;
|
|
||||||
if (path.len == 1 and (path[0] == '/' or path[0] == '.')) return true;
|
|
||||||
if (path.len == 2 and (path[0] == '.' and path[1] == '/')) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Iterator = struct {
|
|
||||||
entries: []DirEntry,
|
|
||||||
archive: *Archive,
|
|
||||||
|
|
||||||
idx: u32 = 0,
|
|
||||||
|
|
||||||
pub fn next(self: *Iterator) !?SfsFile {
|
|
||||||
if (self.idx >= self.entries.len) return null;
|
|
||||||
defer self.idx += 1;
|
|
||||||
return try SfsFile.fromEntry(self.archive, self.entries[self.idx]);
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Iterator) void {
|
|
||||||
var alloc = self.archive.allocator();
|
|
||||||
for (self.entries) |e| {
|
|
||||||
e.deinit(alloc);
|
|
||||||
}
|
|
||||||
alloc.free(self.entries);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
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: *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: *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;
|
||||||
|
}
|
||||||
+552
-405
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
const Reader = @import("std").Io.Reader;
|
const Reader = @import("std").Io.Reader;
|
||||||
|
|
||||||
pub const Dir = packed struct {
|
pub const Dir = extern struct {
|
||||||
block_start: u32,
|
block_start: u32,
|
||||||
hard_links: u32,
|
hard_links: u32,
|
||||||
size: u16,
|
size: u16,
|
||||||
@@ -14,19 +14,19 @@ pub const Dir = packed struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ExtDir = packed struct {
|
pub const ExtDir = extern struct {
|
||||||
hard_links: u32,
|
hard_links: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
block_start: u32,
|
block_start: u32,
|
||||||
parent_num: u32,
|
parent_num: u32,
|
||||||
idx_count: u16,
|
idx_count: u16,
|
||||||
block_offset: u16,
|
block_offset: u16,
|
||||||
xattr_id: u32,
|
xattr_idx: u32,
|
||||||
// index: []DirIndex
|
// index: []DirIndex
|
||||||
|
|
||||||
pub fn read(rdr: *Reader) !ExtDir {
|
pub fn read(rdr: *Reader) !ExtDir {
|
||||||
var d: ExtDir = undefined;
|
var d: ExtDir = undefined;
|
||||||
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
try rdr.readSliceEndian(ExtDir, @ptrCast(&d), .little);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+56
-35
@@ -1,34 +1,43 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Reader = std.Io.Reader;
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
pub const BlockSize = packed struct {
|
pub const BlockSize = packed struct(u32) {
|
||||||
size: u24,
|
size: u24,
|
||||||
uncompressed: bool,
|
uncompressed: bool,
|
||||||
_: u7,
|
_: u7,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FileRawRead = extern struct {
|
||||||
|
block_start: u32,
|
||||||
|
frag_idx: u32,
|
||||||
|
frag_block_offset: u32,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
|
||||||
pub const File = struct {
|
pub const File = struct {
|
||||||
block_start: u32, // bytes 0-3
|
block_start: u32,
|
||||||
frag_idx: u32, // bytes 4-7
|
frag_idx: u32,
|
||||||
frag_block_offset: u32, // bytes 8-11
|
frag_block_offset: u32,
|
||||||
size: u32, // bytes 12-15
|
size: u32,
|
||||||
block_sizes: []BlockSize,
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||||
var start: [16]u8 = undefined;
|
var raw: FileRawRead = undefined;
|
||||||
try rdr.readSliceAll(&start);
|
try rdr.readSliceEndian(FileRawRead, @ptrCast(&raw), .little);
|
||||||
const frag_idx: u32 = std.mem.readInt(u32, start[4..8], .little);
|
|
||||||
const size: u32 = std.mem.readInt(u32, start[12..16], .little);
|
var num_blocks: u32 = raw.size / block_size;
|
||||||
var num_blocks: u32 = size / block_size;
|
if (raw.size % block_size != 0 and raw.frag_idx == 0xFFFFFFFF)
|
||||||
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
num_blocks += 1;
|
||||||
|
|
||||||
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||||
errdefer alloc.free(sizes);
|
errdefer alloc.free(sizes);
|
||||||
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.block_start = std.mem.readInt(u32, start[0..4], .little),
|
.block_start = raw.block_start,
|
||||||
.frag_idx = frag_idx,
|
.frag_idx = raw.frag_idx,
|
||||||
.frag_block_offset = std.mem.readInt(u32, start[8..12], .little),
|
.frag_block_offset = raw.frag_block_offset,
|
||||||
.size = size,
|
.size = raw.size,
|
||||||
.block_sizes = sizes,
|
.block_sizes = sizes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -38,34 +47,46 @@ pub const File = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
pub const ExtFile = struct {
|
||||||
block_start: u64, // bytes 0-7
|
block_start: u64,
|
||||||
size: u64, // bytes 8-15
|
size: u64,
|
||||||
sparse: u64, // bytes 16-23
|
sparse: u64,
|
||||||
hard_links: u32, // bytes 24-27
|
hard_links: u32,
|
||||||
frag_idx: u32, // bytes 28-31
|
frag_idx: u32,
|
||||||
frag_block_offset: u32, // bytes 32-35
|
frag_block_offset: u32,
|
||||||
xattr_idx: u32, // bytes 36-39
|
xattr_idx: u32,
|
||||||
block_sizes: []BlockSize,
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
||||||
var start: [40]u8 = undefined;
|
var raw: ExtFileRawRead = undefined;
|
||||||
try rdr.readSliceAll(&start);
|
try rdr.readSliceEndian(ExtFileRawRead, @ptrCast(&raw), .little);
|
||||||
const frag_idx: u32 = std.mem.readInt(u32, start[28..32], .little);
|
|
||||||
const size: u64 = std.mem.readInt(u64, start[8..16], .little);
|
var num_blocks: u32 = @truncate(raw.size / block_size);
|
||||||
var num_blocks: u32 = @truncate(size / block_size);
|
if (raw.size % block_size != 0 and raw.frag_idx == 0xFFFFFFFF)
|
||||||
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
num_blocks += 1;
|
||||||
|
|
||||||
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||||
errdefer alloc.free(sizes);
|
errdefer alloc.free(sizes);
|
||||||
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.block_start = std.mem.readInt(u64, start[0..8], .little),
|
.block_start = raw.block_start,
|
||||||
.size = size,
|
.size = raw.size,
|
||||||
.sparse = std.mem.readInt(u64, start[16..24], .little),
|
.sparse = raw.sparse,
|
||||||
.hard_links = std.mem.readInt(u32, start[24..28], .little),
|
.hard_links = raw.hard_links,
|
||||||
.frag_idx = frag_idx,
|
.frag_idx = raw.frag_idx,
|
||||||
.frag_block_offset = std.mem.readInt(u32, start[32..36], .little),
|
.frag_block_offset = raw.frag_block_offset,
|
||||||
.xattr_idx = std.mem.readInt(u32, start[36..40], .little),
|
.xattr_idx = raw.xattr_idx,
|
||||||
.block_sizes = sizes,
|
.block_sizes = sizes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ pub const ExtSymlink = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A block or character device.
|
/// A block or character device.
|
||||||
pub const Dev = packed struct {
|
pub const Dev = extern struct {
|
||||||
hard_links: u32,
|
hard_links: u32,
|
||||||
dev: u32,
|
dev: u32,
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ pub const Dev = packed struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// An extended block or character device.
|
/// An extended block or character device.
|
||||||
pub const ExtDev = packed struct {
|
pub const ExtDev = extern struct {
|
||||||
hard_links: u32,
|
hard_links: u32,
|
||||||
dev: u32,
|
dev: u32,
|
||||||
xattr_idx: u32,
|
xattr_idx: u32,
|
||||||
@@ -75,7 +75,7 @@ pub const ExtDev = packed struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A socket or FIFO file.
|
/// A socket or FIFO file.
|
||||||
pub const IPC = packed struct {
|
pub const IPC = extern struct {
|
||||||
hard_links: u32,
|
hard_links: u32,
|
||||||
|
|
||||||
pub fn read(rdr: *Reader) !IPC {
|
pub fn read(rdr: *Reader) !IPC {
|
||||||
@@ -86,7 +86,7 @@ pub const IPC = packed struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// An extended socket or FIFO file.
|
/// An extended socket or FIFO file.
|
||||||
pub const ExtIPC = packed struct {
|
pub const ExtIPC = extern struct {
|
||||||
hard_links: u32,
|
hard_links: u32,
|
||||||
xattr_idx: u32,
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Decompressor = @import("util/decompressor.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
|
pub fn lookupValue(comptime T: anytype, alloc: std.mem.Allocator, decomp: *Decompressor, file: OffsetFile, table_start: u64, idx: u32) !T {
|
||||||
|
const T_PER_BLOCK: u16 = 8192 / @sizeOf(T);
|
||||||
|
|
||||||
|
const block = idx / T_PER_BLOCK;
|
||||||
|
const block_offset = idx % T_PER_BLOCK;
|
||||||
|
|
||||||
|
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;
|
||||||
|
try meta.interface.readSliceEndian(T, @ptrCast(&out), .little);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Error = Io.Cancelable || Io.File.Reader.SeekError || Io.Reader.ReadAllocError;
|
||||||
|
|
||||||
|
pub fn CachedTable(comptime T: anytype) type {
|
||||||
|
return struct {
|
||||||
|
const T_PER_BLOCK: u16 = 8192 / @sizeOf(T);
|
||||||
|
|
||||||
|
const Table = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
fil: OffsetFile,
|
||||||
|
decomp: *Decompressor,
|
||||||
|
|
||||||
|
table_start: u64,
|
||||||
|
total_num: u32,
|
||||||
|
|
||||||
|
table: std.AutoHashMap(u32, []T),
|
||||||
|
|
||||||
|
mut: Io.RwLock = .init,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *Decompressor, offset: u64, total_num: u32) Table {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.fil = fil,
|
||||||
|
.decomp = decomp,
|
||||||
|
|
||||||
|
.table_start = offset,
|
||||||
|
.total_num = total_num,
|
||||||
|
|
||||||
|
.table = .init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Table, io: Io) void {
|
||||||
|
self.mut.lockUncancelable(io);
|
||||||
|
var iter = self.table.valueIterator();
|
||||||
|
while (iter.next()) |val|
|
||||||
|
self.alloc.free(val.*);
|
||||||
|
self.table.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill(self: *Table, io: Io) Error!void {
|
||||||
|
try self.mut.lock(io);
|
||||||
|
defer self.mut.unlock(io);
|
||||||
|
|
||||||
|
var num_blocks = self.total_num / T_PER_BLOCK;
|
||||||
|
if (self.total_num % T_PER_BLOCK > 0)
|
||||||
|
num_blocks += 1;
|
||||||
|
|
||||||
|
for (0..num_blocks) |block| {
|
||||||
|
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
|
||||||
|
T_PER_BLOCK;
|
||||||
|
|
||||||
|
var rdr = self.fil.readerAt(offset);
|
||||||
|
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
||||||
|
|
||||||
|
const slice = try meta.interface.readSliceEndianAlloc(self.alloc, T, len, .little);
|
||||||
|
try self.table.put(@truncate(block), slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
T_PER_BLOCK;
|
||||||
|
|
||||||
|
var rdr = self.fil.readerAt(offset);
|
||||||
|
var meta: MetadataReader = .init(self.alloc, &rdr, self.decomp);
|
||||||
|
|
||||||
|
const slice = try meta.interface.readSliceEndianAlloc(self.alloc, T, len, .little);
|
||||||
|
try self.table.put(@truncate(block), slice);
|
||||||
|
|
||||||
|
return slice[block_offset];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
+8
-2
@@ -5,6 +5,10 @@ const Writer = std.Io.Writer;
|
|||||||
|
|
||||||
const ExtractionOptions = @This();
|
const ExtractionOptions = @This();
|
||||||
|
|
||||||
|
/// Extract single-threaded only.
|
||||||
|
/// Though not necessary if using Threaded.single_threaded,
|
||||||
|
/// setting single_threaded is more efficient.
|
||||||
|
single_threaded: bool = false,
|
||||||
/// Don't set the file's owner & permissions after extraction
|
/// 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.
|
||||||
@@ -16,8 +20,10 @@ 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 default: ExtractionOptions = .{};
|
||||||
pub fn VerboseDefault(wrt: *Writer) ExtractionOptions {
|
pub const default_single_threaded: ExtractionOptions = .{ .single_threaded = true };
|
||||||
|
|
||||||
|
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||||
return .{
|
return .{
|
||||||
.verbose = true,
|
.verbose = true,
|
||||||
.verbose_writer = wrt,
|
.verbose_writer = wrt,
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
pub const Archive = @import("archive.zig");
|
pub const Archive = @import("archive.zig");
|
||||||
|
pub const ExtractionOptions = @import("options.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const math = std.math;
|
|
||||||
|
|
||||||
const CompressionType = @import("decomp.zig").CompressionType;
|
|
||||||
const InodeRef = @import("inode.zig").Ref;
|
|
||||||
|
|
||||||
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
|
||||||
|
|
||||||
const SuperblockError = error{
|
|
||||||
InvalidMagic,
|
|
||||||
InvalidBlockLog,
|
|
||||||
InvalidVersion,
|
|
||||||
InvalidCheck,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A squashfs Superblock
|
|
||||||
pub const Superblock = packed struct {
|
|
||||||
magic: u32,
|
|
||||||
inode_count: u32,
|
|
||||||
mod_time: u32,
|
|
||||||
block_size: u32,
|
|
||||||
frag_count: u32,
|
|
||||||
compression: CompressionType,
|
|
||||||
block_log: u16,
|
|
||||||
flags: packed struct {
|
|
||||||
inode_uncompressed: bool,
|
|
||||||
data_uncompressed: bool,
|
|
||||||
check: bool,
|
|
||||||
frag_uncompressed: bool,
|
|
||||||
fragment_never: bool,
|
|
||||||
fragment_always: bool,
|
|
||||||
duplicates: bool,
|
|
||||||
exportable: bool,
|
|
||||||
xattr_uncompressed: bool,
|
|
||||||
xattr_never: bool,
|
|
||||||
compression_options: bool,
|
|
||||||
ids_uncompressed: bool,
|
|
||||||
_: u4,
|
|
||||||
},
|
|
||||||
id_count: u16,
|
|
||||||
ver_maj: u16,
|
|
||||||
ver_min: u16,
|
|
||||||
root_ref: InodeRef,
|
|
||||||
size: u64,
|
|
||||||
id_start: u64,
|
|
||||||
xattr_start: u64,
|
|
||||||
inode_start: u64,
|
|
||||||
dir_start: u64,
|
|
||||||
frag_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.
|
|
||||||
pub fn validate(self: Superblock) !void {
|
|
||||||
if (self.magic != SQUASHFS_MAGIC)
|
|
||||||
return SuperblockError.InvalidMagic;
|
|
||||||
if (self.flags.check)
|
|
||||||
return SuperblockError.InvalidCheck;
|
|
||||||
if (self.ver_maj != 4 or self.ver_min != 0)
|
|
||||||
return SuperblockError.InvalidVersion;
|
|
||||||
if (math.log2(self.block_size) != self.block_log)
|
|
||||||
return SuperblockError.InvalidBlockLog;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Mutex = std.Thread.Mutex;
|
|
||||||
|
|
||||||
const DecompFn = @import("decomp.zig").DecompFn;
|
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
|
||||||
|
|
||||||
const TableError = error{
|
|
||||||
InvalidIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A two-layer metadata table.
|
|
||||||
pub fn Table(T: anytype) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
fil: OffsetFile,
|
|
||||||
decomp: DecompFn,
|
|
||||||
tab_start: u64,
|
|
||||||
|
|
||||||
tab: std.AutoHashMap(u32, []T),
|
|
||||||
values: u32,
|
|
||||||
|
|
||||||
mut: Mutex = .{},
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.fil = fil,
|
|
||||||
.decomp = decomp,
|
|
||||||
.tab_start = tab_start,
|
|
||||||
|
|
||||||
.tab = .init(alloc),
|
|
||||||
.values = values,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var iter = self.tab.valueIterator();
|
|
||||||
while (iter.next()) |s| {
|
|
||||||
self.alloc.free(s.*);
|
|
||||||
}
|
|
||||||
self.tab.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Self, idx: u32) !T {
|
|
||||||
if (idx >= self.values) return TableError.InvalidIndex;
|
|
||||||
const block_num = idx / VALS_PER_BLOCK;
|
|
||||||
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
|
||||||
if (self.tab.contains(block_num)) {
|
|
||||||
const block = self.tab.get(block_num).?;
|
|
||||||
return block[idx_offset];
|
|
||||||
}
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
// Double check in case of race condition..
|
|
||||||
if (self.tab.contains(block_num)) {
|
|
||||||
const block = self.tab.get(block_num).?;
|
|
||||||
return block[idx_offset];
|
|
||||||
}
|
|
||||||
const is_last = (self.values - 1) / VALS_PER_BLOCK == block_num;
|
|
||||||
const slice_size = if (is_last) self.values - (block_num * VALS_PER_BLOCK) else VALS_PER_BLOCK;
|
|
||||||
const slice = try self.alloc.alloc(T, slice_size);
|
|
||||||
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
|
|
||||||
var offset: u64 = 0;
|
|
||||||
try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little);
|
|
||||||
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
|
||||||
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
|
||||||
try meta.interface.readSliceEndian(T, @ptrCast(slice), .little);
|
|
||||||
try self.tab.put(block_num, slice);
|
|
||||||
return slice[idx_offset];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const stuff = @import("builtin");
|
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
|
||||||
const Superblock = @import("super.zig").Superblock;
|
|
||||||
|
|
||||||
const TestArchive = "testing/LinuxPATest.sfs";
|
|
||||||
|
|
||||||
test "Basics" {
|
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
|
||||||
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
|
||||||
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
|
||||||
return error.BadSuperblock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestFile = "Start.exe";
|
|
||||||
const TestFileExtractLocation = "testing/Start.exe";
|
|
||||||
|
|
||||||
test "ExtractSingleFile" {
|
|
||||||
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
|
||||||
var test_fil = try sfs.open(TestFile);
|
|
||||||
defer test_fil.deinit();
|
|
||||||
try test_fil.extract(TestFileExtractLocation, .Default);
|
|
||||||
//TODO: validate extracted file.
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestFullExtractLocation = "testing/TestExtract";
|
|
||||||
|
|
||||||
test "ExtractCompleteArchive" {
|
|
||||||
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
|
||||||
try sfs.extract(TestFullExtractLocation, .Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
//! A reader for a regular file.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
const Writer = std.Io.Writer;
|
|
||||||
const Limit = std.Io.Limit;
|
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
|
||||||
const OffsetFile = @import("offset_file.zig");
|
|
||||||
|
|
||||||
const DataReader = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
fil: OffsetFile,
|
|
||||||
decomp: DecompFn,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
blocks: []BlockSize,
|
|
||||||
|
|
||||||
frag: ?FragEntry = null, // TODO: do something better?
|
|
||||||
frag_offset: u32 = 0,
|
|
||||||
size: u64,
|
|
||||||
|
|
||||||
interface: Reader,
|
|
||||||
|
|
||||||
cur_offset: u64,
|
|
||||||
block_idx: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) DataReader {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.fil = archive.fil,
|
|
||||||
.decomp = archive.decomp,
|
|
||||||
.block_size = archive.super.block_size,
|
|
||||||
.blocks = blocks,
|
|
||||||
.size = size,
|
|
||||||
.cur_offset = start,
|
|
||||||
.interface = .{
|
|
||||||
.end = 0,
|
|
||||||
.seek = 0,
|
|
||||||
.buffer = &[0]u8{},
|
|
||||||
.vtable = &.{
|
|
||||||
.stream = stream,
|
|
||||||
.discard = discard,
|
|
||||||
.readVec = readVec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *DataReader) void {
|
|
||||||
self.alloc.free(self.interface.buffer);
|
|
||||||
self.interface.end = 0;
|
|
||||||
self.interface.seek = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *DataReader, entry: FragEntry, frag_offset: u32) void {
|
|
||||||
self.frag = entry;
|
|
||||||
self.frag_offset = frag_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn numBlocks(self: DataReader) usize {
|
|
||||||
var res = self.blocks.len;
|
|
||||||
if (self.frag != null) res += 1;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(self: *DataReader) !void {
|
|
||||||
if (self.block_idx > self.blocks.len or (self.block_idx == self.blocks.len and self.frag == null)) {
|
|
||||||
if (self.interface.buffer.len > 0) {
|
|
||||||
self.alloc.free(self.interface.buffer);
|
|
||||||
self.interface.buffer = &[0]u8{};
|
|
||||||
self.interface.end = 0;
|
|
||||||
self.interface.seek = 0;
|
|
||||||
}
|
|
||||||
return Reader.Error.EndOfStream;
|
|
||||||
}
|
|
||||||
defer self.block_idx += 1;
|
|
||||||
const cur_block_size = if (self.block_idx == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
|
||||||
try self.resizeBuffer(cur_block_size);
|
|
||||||
self.interface.seek = 0;
|
|
||||||
self.interface.end = cur_block_size;
|
|
||||||
if (self.block_idx == self.blocks.len) { // fragment
|
|
||||||
var rdr = try self.fil.readerAt(self.frag.?.start, &[0]u8{});
|
|
||||||
if (self.frag.?.size.uncompressed) {
|
|
||||||
try rdr.interface.discardAll(self.frag_offset);
|
|
||||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size);
|
|
||||||
defer self.alloc.free(tmp_buf);
|
|
||||||
try rdr.interface.readSliceAll(tmp_buf);
|
|
||||||
const needed_block = try self.alloc.alloc(u8, self.block_size);
|
|
||||||
defer self.alloc.free(needed_block);
|
|
||||||
_ = try self.decomp(self.alloc, tmp_buf, needed_block);
|
|
||||||
@memcpy(self.interface.buffer, needed_block[self.frag_offset .. self.frag_offset + cur_block_size]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const block = self.blocks[self.block_idx];
|
|
||||||
if (block.size == 0) {
|
|
||||||
@memset(self.interface.buffer, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rdr = try self.fil.readerAt(self.cur_offset, &[0]u8{});
|
|
||||||
self.cur_offset += block.size;
|
|
||||||
if (block.uncompressed) {
|
|
||||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp_buf = try self.alloc.alloc(u8, block.size);
|
|
||||||
defer self.alloc.free(tmp_buf);
|
|
||||||
try rdr.interface.readSliceAll(tmp_buf);
|
|
||||||
_ = try self.decomp(self.alloc, tmp_buf, self.interface.buffer);
|
|
||||||
}
|
|
||||||
/// Does not guarentee that data currently in the buffer is retained.
|
|
||||||
fn resizeBuffer(self: *DataReader, size: usize) !void {
|
|
||||||
if (self.interface.buffer.len == size) return;
|
|
||||||
if (!self.alloc.resize(self.interface.buffer, size)) {
|
|
||||||
self.alloc.free(self.interface.buffer);
|
|
||||||
self.interface.buffer = self.alloc.alloc(u8, size) catch |err| {
|
|
||||||
self.interface.buffer = &[0]u8{};
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
self.interface.buffer.len = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
|
|
||||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
|
||||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
|
||||||
if (err == error.EndOfStream) return error.EndOfStream;
|
|
||||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
if (limit == .nothing) return 0;
|
|
||||||
const to_read = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
|
||||||
const res = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_read]);
|
|
||||||
rdr.seek += res;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
|
||||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
|
||||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
|
||||||
if (err == error.EndOfStream) return error.EndOfStream;
|
|
||||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
if (limit == .nothing) return 0;
|
|
||||||
const to_adv = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
|
||||||
rdr.seek += to_adv;
|
|
||||||
return to_adv;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
|
||||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
|
||||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
|
||||||
if (err == error.EndOfStream) return error.EndOfStream;
|
|
||||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
var cur_red: usize = 0;
|
|
||||||
for (vec) |s| {
|
|
||||||
const to_copy: usize = @min(rdr.end - rdr.seek, s.len);
|
|
||||||
@memcpy(s[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]);
|
|
||||||
rdr.seek += to_copy;
|
|
||||||
cur_red += to_copy;
|
|
||||||
if (rdr.end == rdr.seek) break;
|
|
||||||
}
|
|
||||||
return cur_red;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
//! 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: *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: *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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
//! DataReader reads a regular file's data linearly from start to finish using Io.Reader interface.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
const Reader = Io.Reader;
|
||||||
|
const Writer = Io.Writer;
|
||||||
|
const Limit = Io.Limit;
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
const DataReader = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
fil: OffsetFile,
|
||||||
|
io: Io,
|
||||||
|
decomp: *Decompressor,
|
||||||
|
block_size: u32,
|
||||||
|
|
||||||
|
file_size: u64,
|
||||||
|
cur_offset: u64,
|
||||||
|
blocks: []BlockSize,
|
||||||
|
|
||||||
|
frag_offset: u32 = 0,
|
||||||
|
frag_block: ?[]u8 = null,
|
||||||
|
|
||||||
|
block_idx: usize = 0,
|
||||||
|
sparse_block: bool = false,
|
||||||
|
|
||||||
|
interface: Io.Reader,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, io: Io, fil: OffsetFile, decomp: *Decompressor, block_size: u32, file_size: u64, data_start: u64, blocks: []BlockSize) !DataReader {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.fil = fil,
|
||||||
|
.io = io,
|
||||||
|
.decomp = decomp,
|
||||||
|
.block_size = block_size,
|
||||||
|
|
||||||
|
.file_size = file_size,
|
||||||
|
.cur_offset = data_start,
|
||||||
|
.blocks = blocks,
|
||||||
|
|
||||||
|
.interface = .{
|
||||||
|
.buffer = try alloc.alloc(u8, block_size),
|
||||||
|
.seek = 0,
|
||||||
|
.end = 0,
|
||||||
|
.vtable = &.{
|
||||||
|
.stream = stream,
|
||||||
|
.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 {
|
||||||
|
var num = self.blocks.len;
|
||||||
|
if (self.frag_block != null) num += 1;
|
||||||
|
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
|
||||||
|
self.block_size;
|
||||||
|
|
||||||
|
// Fragment
|
||||||
|
if (self.block_idx == self.blocks.len) {
|
||||||
|
@memcpy(self.interface.buffer[0..self.interface.end], self.frag_block.?[self.frag_offset .. self.frag_offset + self.interface.end]);
|
||||||
|
self.interface.seek = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
|
||||||
|
var data: *DataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.seek == rdr.end)
|
||||||
|
data.advanceBuffer() catch |err| return switch (err) {
|
||||||
|
error.ReadFailed => error.ReadFailed,
|
||||||
|
error.EndOfStream => error.EndOfStream,
|
||||||
|
else => error.ReadFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (limit) {
|
||||||
|
.nothing => return 0,
|
||||||
|
.unlimited => {
|
||||||
|
const wrote = if (data.sparse_block)
|
||||||
|
try wrt.splatByte(0, rdr.end - rdr.seek)
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
||||||
|
var data: *DataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.seek == rdr.end)
|
||||||
|
data.advanceBuffer() catch |err| return switch (err) {
|
||||||
|
error.ReadFailed => error.ReadFailed,
|
||||||
|
error.EndOfStream => error.EndOfStream,
|
||||||
|
else => error.ReadFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||||
|
var data: *DataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.seek == rdr.end)
|
||||||
|
data.advanceBuffer() catch |err| return switch (err) {
|
||||||
|
error.ReadFailed => error.ReadFailed,
|
||||||
|
error.EndOfStream => error.EndOfStream,
|
||||||
|
else => error.ReadFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
var wrote: usize = 0;
|
||||||
|
for (vec) |buf| {
|
||||||
|
if (rdr.seek == rdr.end) break;
|
||||||
|
|
||||||
|
const to_copy = @min(rdr.end - rdr.seek, buf.len);
|
||||||
|
if (data.sparse_block)
|
||||||
|
@memset(buf[0..to_copy], 0)
|
||||||
|
else
|
||||||
|
@memcpy(buf[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]);
|
||||||
|
rdr.seek += to_copy;
|
||||||
|
wrote += to_copy;
|
||||||
|
}
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
//! Similiar to DataReader, but set-up for threaded writing to files.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
const Writer = std.Io.Writer;
|
|
||||||
const Limit = std.Io.Limit;
|
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
|
||||||
const Pool = std.Thread.Pool;
|
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
|
||||||
const OffsetFile = @import("offset_file.zig");
|
|
||||||
|
|
||||||
const ThreadedDataReader = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
fil: OffsetFile,
|
|
||||||
decomp: DecompFn,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
blocks: []BlockSize,
|
|
||||||
|
|
||||||
frag: ?FragEntry = null, // TODO: do something better?
|
|
||||||
frag_offset: u32 = 0,
|
|
||||||
size: u64,
|
|
||||||
|
|
||||||
start_offset: u64,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) ThreadedDataReader {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.fil = archive.fil,
|
|
||||||
.decomp = archive.decomp,
|
|
||||||
.block_size = archive.super.block_size,
|
|
||||||
.blocks = blocks,
|
|
||||||
.size = size,
|
|
||||||
.start_offset = start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void {
|
|
||||||
self.frag = entry;
|
|
||||||
self.frag_offset = frag_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn numBlocks(self: ThreadedDataReader) usize {
|
|
||||||
var res = self.blocks.len;
|
|
||||||
if (self.frag != null) res += 1;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the data to the file threadedly, using pool to spawn threads.
|
|
||||||
/// If multiple errors occur, thread spawning errors will have, then the last decompression error that occurs;
|
|
||||||
///
|
|
||||||
/// The function must be called from an unused DataReader. The DataReader is still usable afterwards.
|
|
||||||
/// If only extractThreaded is used, there is no need to call deinit() afterwards.
|
|
||||||
///
|
|
||||||
/// The file will always be written to starting at 0.
|
|
||||||
pub fn extractThreaded(self: ThreadedDataReader, file: std.fs.File, pool: *Pool) !void {
|
|
||||||
var wg: WaitGroup = .{};
|
|
||||||
wg.startMany(self.numBlocks());
|
|
||||||
var out_err: ?anyerror = null;
|
|
||||||
|
|
||||||
var cur_write_offset: u64 = 0;
|
|
||||||
var cur_read_offset: u64 = self.start_offset;
|
|
||||||
for (0..self.blocks.len) |i| {
|
|
||||||
const cur_block_size = if (i == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
|
||||||
try pool.spawn(workThreadBlocks, .{ self, file, cur_write_offset, cur_read_offset, self.blocks[i], cur_block_size, &wg, &out_err });
|
|
||||||
cur_write_offset += cur_block_size;
|
|
||||||
cur_read_offset += self.blocks[i].size;
|
|
||||||
}
|
|
||||||
if (self.frag != null) {
|
|
||||||
try pool.spawn(workThreadFragment, .{ self, file, cur_write_offset, &wg, &out_err });
|
|
||||||
}
|
|
||||||
pool.waitAndWork(&wg);
|
|
||||||
if (out_err != null) return out_err.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn workThreadBlocks(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, read_offset: u64, block: BlockSize, cur_block_size: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
|
||||||
defer wg.finish();
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
|
||||||
wrt.seekTo(write_offset) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer wrt.interface.flush() catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
if (block.size == 0) {
|
|
||||||
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rdr = self.fil.readerAt(read_offset, &[0]u8{}) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if (block.uncompressed) {
|
|
||||||
rdr.interface.streamExact(&wrt.interface, block.size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: shared buffers
|
|
||||||
const read_buf = self.alloc.alloc(u8, block.size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(read_buf);
|
|
||||||
rdr.interface.readSliceAll(read_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// TODO: shared buffers
|
|
||||||
const res_buf = self.alloc.alloc(u8, cur_block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(res_buf);
|
|
||||||
_ = self.decomp(self.alloc, read_buf, res_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
wrt.interface.writeAll(res_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn workThreadFragment(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
|
||||||
defer wg.finish();
|
|
||||||
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
|
||||||
wrt.seekTo(write_offset) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer wrt.interface.flush() catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
|
|
||||||
var rdr = self.fil.readerAt(self.frag.?.start, &[0]u8{}) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if (self.frag.?.size.uncompressed) {
|
|
||||||
rdr.interface.discardAll(self.frag_offset) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
rdr.interface.streamExact(&wrt.interface, self.size % self.block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp_buf = self.alloc.alloc(u8, self.frag.?.size.size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(tmp_buf);
|
|
||||||
rdr.interface.readSliceAll(tmp_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const needed_block = self.alloc.alloc(u8, self.block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(needed_block);
|
|
||||||
_ = self.decomp(self.alloc, tmp_buf, needed_block) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
wrt.interface.writeAll(needed_block[self.frag_offset .. self.frag_offset + (self.size % self.block_size)]) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//! 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 (?*Decompressor, std.mem.Allocator, in: []u8, out: []u8) Error!usize,
|
||||||
|
|
||||||
|
pub fn Decompress(self: *Decompressor, alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
return self.decomp_fn(self, alloc, in, out);
|
||||||
|
}
|
||||||
+24
-18
@@ -4,9 +4,9 @@ const Writer = std.Io.Writer;
|
|||||||
const Limit = std.Io.Limit;
|
const Limit = std.Io.Limit;
|
||||||
const StreamError = std.Io.Reader.StreamError;
|
const StreamError = std.Io.Reader.StreamError;
|
||||||
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
const Decompressor = @import("decompressor.zig");
|
||||||
|
|
||||||
const BlockHeader = packed struct {
|
const BlockHeader = packed struct(u16) {
|
||||||
size: u15,
|
size: u15,
|
||||||
uncompressed: bool,
|
uncompressed: bool,
|
||||||
};
|
};
|
||||||
@@ -15,19 +15,14 @@ const This = @This();
|
|||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
rdr: *Reader,
|
rdr: *Reader,
|
||||||
decomp: DecompFn,
|
decomp: *Decompressor,
|
||||||
|
|
||||||
|
cur_block_start: u32 = 0,
|
||||||
|
next_start_start: u32 = 0,
|
||||||
buf: [8192]u8 = undefined,
|
buf: [8192]u8 = undefined,
|
||||||
|
|
||||||
interface: Reader,
|
|
||||||
err: ?anyerror = null,
|
err: ?anyerror = null,
|
||||||
|
interface: Reader = .{
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: DecompFn) This {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.rdr = rdr,
|
|
||||||
.decomp = decomp,
|
|
||||||
.interface = .{
|
|
||||||
.buffer = &[0]u8{},
|
.buffer = &[0]u8{},
|
||||||
.end = 0,
|
.end = 0,
|
||||||
.seek = 0,
|
.seek = 0,
|
||||||
@@ -35,8 +30,15 @@ pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: DecompFn) This {
|
|||||||
.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: *Decompressor) This {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.rdr = rdr,
|
||||||
|
.decomp = decomp,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,16 +46,20 @@ fn advance(self: *This) !void {
|
|||||||
self.interface.seek = 0;
|
self.interface.seek = 0;
|
||||||
var hdr: BlockHeader = undefined;
|
var hdr: BlockHeader = undefined;
|
||||||
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little);
|
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little);
|
||||||
|
self.cur_block_start = self.next_start_start;
|
||||||
|
self.next_start_start += hdr.size;
|
||||||
if (hdr.uncompressed) {
|
if (hdr.uncompressed) {
|
||||||
try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little);
|
try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little);
|
||||||
self.interface.end = hdr.size;
|
self.interface.end = hdr.size;
|
||||||
self.interface.buffer = self.buf[0..hdr.size];
|
self.interface.buffer = self.buf[0..hdr.size];
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
|
@branchHint(.likely);
|
||||||
var tmp_buf: [8192]u8 = undefined;
|
var tmp_buf: [8192]u8 = undefined;
|
||||||
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
|
try self.rdr.readSliceAll(tmp_buf[0..hdr.size]);
|
||||||
self.interface.end = try self.decomp(self.alloc, tmp_buf[0..hdr.size], &self.buf);
|
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.buffer = self.buf[0..self.interface.end];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
|
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
|
||||||
@@ -68,22 +74,22 @@ fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
|
|||||||
self.interface.seek += wrote;
|
self.interface.seek += wrote;
|
||||||
return wrote;
|
return wrote;
|
||||||
}
|
}
|
||||||
fn discard(rdr: *Reader, limit: Limit) error{ EndOfStream, ReadFailed }!usize {
|
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
||||||
const self: *This = @fieldParentPtr("interface", rdr);
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
self.err = err;
|
self.err = err;
|
||||||
return StreamError.ReadFailed;
|
return error.ReadFailed;
|
||||||
};
|
};
|
||||||
if (@intFromEnum(limit) == 0) return 0;
|
if (@intFromEnum(limit) == 0) return 0;
|
||||||
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||||
rdr.seek += to_skip;
|
rdr.seek += to_skip;
|
||||||
return to_skip;
|
return to_skip;
|
||||||
}
|
}
|
||||||
fn readVec(rdr: *Reader, vec: [][]u8) error{ EndOfStream, ReadFailed }!usize {
|
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||||
const self: *This = @fieldParentPtr("interface", rdr);
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
self.err = err;
|
self.err = err;
|
||||||
return StreamError.ReadFailed;
|
return error.ReadFailed;
|
||||||
};
|
};
|
||||||
var cur_red: usize = 0;
|
var cur_red: usize = 0;
|
||||||
for (vec) |s| {
|
for (vec) |s| {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//! 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: *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);
|
||||||
|
}
|
||||||
+17
-10
@@ -1,20 +1,27 @@
|
|||||||
//! A File where it's meaningful (to us) content starts at a given offset.
|
//! A File where it's meaningful (to us) content starts at a given offset.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const Io = std.Io;
|
||||||
const Reader = std.fs.File.Reader;
|
const File = Io.File;
|
||||||
|
const Reader = Io.Reader;
|
||||||
|
|
||||||
const OffsetFile = @This();
|
const OffsetFile = @This();
|
||||||
|
|
||||||
fil: File,
|
map: Io.File.MemoryMap,
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
pub fn init(fil: File, init_offset: u64) OffsetFile {
|
pub fn init(io: Io, fil: File, archive_size: u64, init_offset: u64) !OffsetFile {
|
||||||
return .{ .fil = fil, .offset = init_offset };
|
return .{
|
||||||
|
.map = try fil.createMemoryMap(io, .{
|
||||||
|
.protection = .{ .read = true, .write = false, .execute = false },
|
||||||
|
.len = archive_size,
|
||||||
|
.offset = init_offset,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *OffsetFile, io: Io) void {
|
||||||
|
self.map.destroy(io);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readerAt(self: OffsetFile, offset: u64, buffer: []u8) !Reader {
|
pub fn readerAt(self: OffsetFile, offset: u64) Reader {
|
||||||
var rdr = self.fil.reader(buffer);
|
return .fixed(self.map.memory[offset..]);
|
||||||
try rdr.seekTo(self.offset + offset);
|
|
||||||
return rdr;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
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: *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: *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: *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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user