Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a710b98cf |
@@ -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 liblzma-dev liblzo2-dev
|
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
||||||
- name: Build normal version
|
- name: Build normal version
|
||||||
run: zig build --release=fast -Duse_zig_decomp=true -Dversion=${{ github.ref_name }}
|
run: zig build -Drelease=true -Dversion=${{ github.ref_name }}
|
||||||
- name: Move zig build out
|
- name: Move normal build out
|
||||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-zig-libs
|
run: mv zig-out/bin/unsquashfs ./
|
||||||
- name: Rebuild with C libraries
|
- name: Rebuild with C libraries
|
||||||
run: zig build --release=fast -Dversion="${{ github.ref_name }}"
|
run: zig build -Drelease=true -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
|
||||||
- name: Move C build out
|
- name: Move C build out
|
||||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
unsquashfs-x86_64-zig-libs
|
unsquashfs
|
||||||
unsquashfs-x86_64-c-libs
|
unsquashfs-c-libs
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ testing/
|
|||||||
|
|
||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
zig-pkg/
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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,23 +6,19 @@ A library and application to decompress or view squashfs archives.
|
|||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
Overall works, but currently is missing some features (see below). Extraction is a bit slow compared to the normal `unsquashfs` (from my _very_ basic testing it's about ~3x slower). Only properly work on Linux, any other OSes probably won't work fully and are untested.
|
||||||
|
|
||||||
## Build options
|
## Build options
|
||||||
|
|
||||||
> `-Duse_c_libs=true`
|
> `-Duse_c_libs`
|
||||||
|
|
||||||
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
||||||
|
|
||||||
> `-Dallow_lzo=true`
|
> `-Dallow_lzo`
|
||||||
|
|
||||||
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
||||||
|
|
||||||
> `-Ddebug=true`
|
> `-Dversion`
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -30,29 +26,11 @@ Sets the version of `unsquashfs` shown when `--version` is passed.
|
|||||||
|
|
||||||
Most features are present except for the following:
|
Most features are present except for the following:
|
||||||
|
|
||||||
|
* mod_time is not set on extraction
|
||||||
|
* xattrs are not applied on extraction
|
||||||
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||||
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
|
|
||||||
|
|
||||||
## Performance
|
## Building considerations
|
||||||
|
|
||||||
This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) and with `--release=fast`.
|
|
||||||
|
|
||||||
Currently, my only performance checks are checking execution time, nothing deeper.
|
|
||||||
|
|
||||||
* Under ideal circumstances, my library is ~70% slower (.12s vs .20s).
|
|
||||||
* Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances.
|
|
||||||
* Performance improvements/regressions will be common. I'm still learning Zig.
|
|
||||||
|
|
||||||
Example Times:
|
|
||||||
|
|
||||||
* *unsquashfs, multi-threaded*: .12s
|
|
||||||
* *unsquashfs, single-threaded*: .13s
|
|
||||||
* *C-libs, single-threaded*: .45s
|
|
||||||
* *C-libs, multi-threaded*: .20s
|
|
||||||
* *Zig-libs, single-threaded*: 5.78s
|
|
||||||
* *Zig-libs, multi-threaded*: 1.08s
|
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||
|
|||||||
@@ -1,125 +1,88 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Build = std.Build;
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse false;
|
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
|
||||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo decompression support.") orelse false;
|
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support");
|
||||||
const dynamic = b.option(bool, "dynamic", "Dynamic link C decompression libraries.") orelse false;
|
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
||||||
const debug = b.option(bool, "debug", "Enable options to make debugging easier.") orelse false;
|
|
||||||
const version_string = b.option([]const u8, "version", "Version of the library/binary") orelse "0.0.0-testing";
|
|
||||||
|
|
||||||
const version: std.SemanticVersion = try .parse(version_string);
|
|
||||||
|
|
||||||
const zig_squashfs_options = b.addOptions();
|
const zig_squashfs_options = b.addOptions();
|
||||||
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
zig_squashfs_options.addOption(bool, "use_c_libs", use_c_libs_option orelse false);
|
||||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
|
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false);
|
||||||
|
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
||||||
const c = b.addTranslateC(.{
|
const mod = b.addModule("zig_squashfs", .{
|
||||||
.optimize = optimize,
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.root_source_file = b.path("src/c.h"),
|
.optimize = optimize,
|
||||||
|
.link_libc = if (use_c_libs_option == true) true else false,
|
||||||
});
|
});
|
||||||
if (allow_lzo)
|
mod.addOptions("config", zig_squashfs_options);
|
||||||
c.defineCMacro("ALLOW_LZO", null);
|
if (use_c_libs_option == true) {
|
||||||
if (dynamic) {
|
mod.linkSystemLibrary("zlib", .{});
|
||||||
c.linkSystemLibrary("z", .{});
|
mod.linkSystemLibrary("lzma", .{});
|
||||||
c.linkSystemLibrary("lzma", .{});
|
if (allow_lzo == true)
|
||||||
c.linkSystemLibrary("lz4", .{});
|
mod.linkSystemLibrary("minilzo", .{});
|
||||||
c.linkSystemLibrary("zstd", .{});
|
mod.linkSystemLibrary("lz4", .{});
|
||||||
if (allow_lzo)
|
mod.linkSystemLibrary("zstd", .{});
|
||||||
c.linkSystemLibrary("minilzo", .{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const deps = try getDependencies(b, optimize, target, allow_lzo);
|
var version = version_string_option orelse "0.0.0-testing";
|
||||||
|
if (version[0] == 'v') version = version[1..];
|
||||||
|
const unsquashfs_options = b.addOptions();
|
||||||
|
unsquashfs_options.addOption(
|
||||||
|
std.SemanticVersion,
|
||||||
|
"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(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = exe_mod,
|
||||||
|
});
|
||||||
|
|
||||||
const lib = b.addLibrary(.{
|
const lib = b.addLibrary(.{
|
||||||
.name = "squashfs",
|
.name = "squashfs",
|
||||||
.use_llvm = debug,
|
.root_module = mod,
|
||||||
.version = version,
|
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "options", .module = zig_squashfs_options.createModule() },
|
|
||||||
.{ .name = "c", .module = c.createModule() },
|
|
||||||
},
|
|
||||||
.optimize = optimize,
|
|
||||||
.target = target,
|
|
||||||
.root_source_file = b.path("src/root.zig"),
|
|
||||||
.valgrind = debug,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (deps) |d|
|
|
||||||
lib.root_module.linkLibrary(d);
|
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
|
|
||||||
const exe_config = b.addOptions();
|
|
||||||
exe_config.addOption(std.SemanticVersion,"version", version);
|
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = "unsquashfs",
|
|
||||||
.use_llvm = debug,
|
|
||||||
.version = version,
|
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.optimize = optimize,
|
|
||||||
.target = target,
|
|
||||||
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
|
||||||
.valgrind = debug,
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "config", .module = exe_config.createModule() },
|
|
||||||
.{ .name = "squashfs", .module = lib.root_module }
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
const lib_test = b.addTest(.{
|
const run_step = b.step("run", "Run the app");
|
||||||
.name = "squashfs-test",
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
.root_module = lib.root_module,
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
const mod_tests = b.addTest(.{
|
||||||
|
.root_module = mod,
|
||||||
});
|
});
|
||||||
const test_step = b.step("test", "Run tests");
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
test_step.dependOn(&lib_test.step);
|
const exe_tests = b.addTest(.{
|
||||||
|
|
||||||
// zls check step
|
|
||||||
const lib_check = b.addLibrary(.{
|
|
||||||
.name = "squashfs-check",
|
|
||||||
.root_module = lib.root_module,
|
|
||||||
});
|
|
||||||
const exe_check = b.addLibrary(.{
|
|
||||||
.name = "unsquashfs-check",
|
|
||||||
.root_module = exe.root_module,
|
.root_module = exe.root_module,
|
||||||
});
|
});
|
||||||
|
const run_exe_tests = b.addRunArtifact(exe_tests);
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
|
test_step.dependOn(&run_exe_tests.step);
|
||||||
|
|
||||||
const check = b.step("check", "Check if squashfs compiles");
|
// Wanted by ZLS for better detection.
|
||||||
check.dependOn(&lib_check.step);
|
const exe_check = b.addExecutable(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = mod,
|
||||||
|
});
|
||||||
|
const check = b.step("check", "Check if unsquashfs compiles");
|
||||||
check.dependOn(&exe_check.step);
|
check.dependOn(&exe_check.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getDependencies(b: *Build, optimize: std.builtin.OptimizeMode, target: Build.ResolvedTarget, allow_lzo: bool) ![]*Build.Step.Compile {
|
|
||||||
const alloc = b.allocator;
|
|
||||||
|
|
||||||
var list: std.ArrayList(*Build.Step.Compile) = .empty;
|
|
||||||
|
|
||||||
const zlib_ng = b.dependency("zlib_ng", .{ .optimize = optimize, .target = target });
|
|
||||||
try list.append(alloc, zlib_ng.artifact("zng"));
|
|
||||||
|
|
||||||
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
|
|
||||||
try list.append(alloc, xz.artifact("lzma"));
|
|
||||||
|
|
||||||
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
|
|
||||||
try list.append(alloc, lz4.artifact("lz4"));
|
|
||||||
|
|
||||||
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
|
|
||||||
try list.append(alloc, zstd.artifact("zstd"));
|
|
||||||
|
|
||||||
if (allow_lzo) {
|
|
||||||
const minilzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
|
|
||||||
try list.append(alloc, minilzo.artifact("minilzo"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.toOwnedSlice(b.allocator);
|
|
||||||
}
|
|
||||||
|
|||||||
+36
-22
@@ -1,29 +1,43 @@
|
|||||||
.{
|
.{
|
||||||
.name = .squashfs,
|
.name = .squashfs,
|
||||||
.version = "0.0.6",
|
.version = "0.0.1",
|
||||||
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
||||||
.minimum_zig_version = "0.16.0",
|
.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 = .{
|
||||||
.zlib_ng = .{
|
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||||
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#5f2f02dfb28acca2517dacbbd09e9b987f57b133",
|
//.example = .{
|
||||||
.hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa",
|
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||||
},
|
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||||
.zstd = .{
|
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||||
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3",
|
// // which will prevent zig from using it.
|
||||||
.hash = "zstd-1.5.7-1-KEItkAMwAAD6OKY3m0OOmXG7aL-aLUfrDqbP5J5oYapU",
|
// .url = "https://example.com/foo.tar.gz",
|
||||||
},
|
//
|
||||||
.lz4 = .{
|
// // This is computed from the file contents of the directory of files that is
|
||||||
.url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102",
|
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||||
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
|
// // `paths`.
|
||||||
},
|
// //
|
||||||
.minilzo = .{
|
// // This field is the source of truth; packages do not come from a `url`; they
|
||||||
.url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90",
|
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||||
.hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP",
|
// // obtain a package matching this `hash`.
|
||||||
},
|
// //
|
||||||
.xz = .{
|
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||||
.url = "git+https://github.com/akunaakwei/zig-xz.git#e2d389262c8291907e3e4c6fb119819141c16c0f",
|
// .hash = "...",
|
||||||
.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",
|
||||||
|
|||||||
+85
-157
@@ -1,185 +1,113 @@
|
|||||||
|
//! 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 Io = std.Io;
|
const File = std.fs.File;
|
||||||
const File = Io.File;
|
const builtin = @import("builtin");
|
||||||
const MemoryMap = File.MemoryMap;
|
|
||||||
|
|
||||||
const Decomp = @import("decomp.zig");
|
const Decomp = @import("decomp.zig");
|
||||||
const DecompCache = @import("decomp_cache.zig");
|
|
||||||
const Extract = @import("extract.zig");
|
|
||||||
const ExtractionOptions = @import("options.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
|
const InodeRef = Inode.Ref;
|
||||||
|
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
||||||
const SfsFile = @import("file.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 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();
|
||||||
|
|
||||||
const CACHE_MEM_MAX = 1024 * 1024 * 1024;
|
fil: OffsetFile,
|
||||||
|
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
cache: DecompCache,
|
decomp: Decomp.DecompFn,
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, io: Io, fil: File) !Archive {
|
frag_table: Table(FragEntry),
|
||||||
return initAdvanced(alloc, io, fil, 0, 0);
|
id_table: Table(u16),
|
||||||
}
|
export_table: Table(InodeRef),
|
||||||
pub fn initAdvanced(alloc: std.mem.Allocator, io: Io, fil: File, offset: u64, cache_memory_max: u64) !Archive {
|
|
||||||
var rdr = fil.reader(io, &[0]u8{});
|
/// Begin reading a squashfs archive from the given File at the given offset.
|
||||||
try rdr.seekTo(offset);
|
pub fn init(fil: File, offset: u64) !Archive {
|
||||||
var super: Superblock = undefined;
|
var super: Superblock = undefined;
|
||||||
try rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
|
_ = try fil.pread(@ptrCast(&super), offset);
|
||||||
try super.validate();
|
try super.validate();
|
||||||
|
const decomp: Decomp.DecompFn = switch (super.compression) {
|
||||||
const map = try fil.createMemoryMap(io, .{
|
.gzip => Decomp.gzipDecompress,
|
||||||
.offset = offset,
|
.lzma => Decomp.lzmaDecompress,
|
||||||
.len = super.size,
|
.xz => Decomp.xzDecompress,
|
||||||
.protection = .{ .read = true },
|
.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,
|
||||||
|
};
|
||||||
|
const offset_fil: OffsetFile = .init(fil, offset);
|
||||||
return .{
|
return .{
|
||||||
|
.fil = offset_fil,
|
||||||
.super = super,
|
.super = super,
|
||||||
|
|
||||||
.cache = try .init(
|
.decomp = decomp,
|
||||||
alloc,
|
|
||||||
map,
|
.frag_table = .init(offset_fil, decomp, super.frag_start, super.frag_count),
|
||||||
super.compression,
|
.id_table = .init(offset_fil, decomp, super.id_start, super.id_count),
|
||||||
if (cache_memory_max != 0)
|
.export_table = .init(offset_fil, decomp, super.export_start, super.inode_count),
|
||||||
cache_memory_max
|
|
||||||
else
|
|
||||||
@min(CACHE_MEM_MAX, (try std.process.totalSystemMemory()) / 2),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *Archive, io: Io) void {
|
|
||||||
self.cache.deinit(io);
|
pub fn inode(self: Archive, num: u32) !Inode {
|
||||||
|
if (!self.setup) try self.setupValues();
|
||||||
|
const ref = try self.export_table.get(num - 1);
|
||||||
|
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(self.alloc, &rdr.interface, &self.decomp);
|
||||||
|
try meta.interface.discardAll(ref.block_offset);
|
||||||
|
return try .read(self.alloc, &meta.interface, self.super.block_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root(self: *Archive, alloc: std.mem.Allocator, io: Io) !SfsFile {
|
pub fn root(self: Archive, alloc: std.mem.Allocator) !SfsFile {
|
||||||
const inode: Inode = try .initRef(
|
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
alloc,
|
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||||
io,
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
&self.cache,
|
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
|
||||||
self.super.inode_start,
|
return .init(alloc, self, in, "");
|
||||||
self.super.block_size,
|
|
||||||
self.super.root_ref,
|
|
||||||
);
|
|
||||||
return .init(alloc, self, inode, "");
|
|
||||||
}
|
|
||||||
pub fn open(self: *Archive, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile {
|
|
||||||
const path = std.mem.trim(u8, filepath, "/");
|
|
||||||
|
|
||||||
var root_file = try self.root(alloc, io);
|
|
||||||
|
|
||||||
if (path.len == 0 or path[0] == '.') return root_file;
|
|
||||||
|
|
||||||
defer root_file.deinit();
|
|
||||||
return root_file.open(alloc, io, filepath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: ExtractionOptions) !void {
|
pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !SfsFile {
|
||||||
const root_inode: Inode = try .initRef(
|
var root_fil = try self.root(alloc);
|
||||||
alloc,
|
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
||||||
io,
|
return root_fil.open(alloc, path);
|
||||||
&self.cache,
|
|
||||||
self.super.inode_start,
|
|
||||||
self.super.block_size,
|
|
||||||
self.super.root_ref,
|
|
||||||
);
|
|
||||||
return Extract.extract(alloc, io, root_inode, &self.cache, self.super, ext_loc, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Superblock
|
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var ext_path: []u8 = undefined;
|
||||||
pub const Superblock = extern struct {
|
if (std.fs.cwd().statFile(path)) |stat| {
|
||||||
magic: u32,
|
if (stat.kind == .directory) {
|
||||||
inode_count: u32,
|
ext_path = @constCast(path);
|
||||||
mod_time: u32,
|
} else return error.ExtractionPathExists;
|
||||||
block_size: u32,
|
} else |err| {
|
||||||
frag_count: u32,
|
if (err == error.FileNotFound) {
|
||||||
compression: Decomp.Enum,
|
ext_path = @constCast(path);
|
||||||
block_log: u16,
|
} else {
|
||||||
flags: packed struct(u16) {
|
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
||||||
inode_uncompressed: bool,
|
return err;
|
||||||
data_uncompressed: bool,
|
}
|
||||||
check: bool,
|
|
||||||
frag_uncompressed: bool,
|
|
||||||
frag_never: bool,
|
|
||||||
frag_always: bool,
|
|
||||||
de_dupe: bool,
|
|
||||||
exportable: bool,
|
|
||||||
xattr_uncompressed: bool,
|
|
||||||
xattr_never: bool,
|
|
||||||
compression_options: bool,
|
|
||||||
id_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,
|
|
||||||
|
|
||||||
pub fn validate(self: Superblock) !void {
|
|
||||||
if (self.magic != std.mem.readInt(u32, "hsqs", .little))
|
|
||||||
return error.BadMagic;
|
|
||||||
if (self.ver_maj != 4 or self.ver_min != 0)
|
|
||||||
return error.InvalidVersion;
|
|
||||||
if (self.block_log != std.math.log2(self.block_size))
|
|
||||||
return error.BadBlockLog;
|
|
||||||
if (self.flags.check)
|
|
||||||
return error.BadCheckFlag;
|
|
||||||
}
|
}
|
||||||
};
|
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{});
|
||||||
// Test
|
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||||
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
const TestArchive = "testing/LinuxPATest.sfs";
|
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
|
||||||
|
try in.extractTo(alloc, self, ext_path, options);
|
||||||
test "Basics" {
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
const io = std.testing.io;
|
|
||||||
|
|
||||||
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
|
||||||
defer archive_file.close(io);
|
|
||||||
var arc: Archive = try .init(alloc, io, archive_file);
|
|
||||||
defer arc.deinit(io);
|
|
||||||
|
|
||||||
var root_file = try arc.root(alloc, io);
|
|
||||||
defer root_file.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestFile = "Start.exe";
|
|
||||||
const TestFileExtractLocation = "testing/Start.exe";
|
|
||||||
|
|
||||||
test "SingleFileExtraction" {
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
const io = std.testing.io;
|
|
||||||
|
|
||||||
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
|
||||||
defer archive_file.close(io);
|
|
||||||
var arc: Archive = try .init(alloc, io, archive_file);
|
|
||||||
defer arc.deinit(io);
|
|
||||||
|
|
||||||
var ext_file = try arc.open(alloc, io, TestFile);
|
|
||||||
defer ext_file.deinit();
|
|
||||||
|
|
||||||
try ext_file.extract(alloc, io, TestFileExtractLocation, .default);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestFullExtractLocation = "testing/TestExtract";
|
|
||||||
|
|
||||||
test "FullExtraction" {
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
const io = std.testing.io;
|
|
||||||
|
|
||||||
var archive_file = try Io.Dir.cwd().openFile(io, TestArchive, .{});
|
|
||||||
defer archive_file.close(io);
|
|
||||||
var arc: Archive = try .init(alloc, io, archive_file);
|
|
||||||
defer arc.deinit(io);
|
|
||||||
|
|
||||||
try arc.extract(alloc, io, TestFullExtractLocation, .default);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-48
@@ -1,11 +1,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Writer = std.Io.Writer;
|
||||||
const Writer = Io.Writer;
|
|
||||||
const File = Io.File;
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const config = @import("config");
|
const config = @import("config");
|
||||||
const squashfs = @import("squashfs");
|
const squashfs = @import("zig_squashfs");
|
||||||
|
|
||||||
//TODO: Add more options
|
//TODO: Add more options
|
||||||
const help_mgs =
|
const help_mgs =
|
||||||
@@ -16,14 +14,10 @@ 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 no present or zero, the system's logical cores count is used.
|
||||||
\\ -v Verbose
|
\\ -v Verbose
|
||||||
\\
|
\\
|
||||||
\\ --force Force extraction. If the destination already exists, it will be deleted.
|
|
||||||
\\
|
|
||||||
\\ --help Display this messages
|
\\ --help Display this messages
|
||||||
\\ --version Display the version
|
\\ --version Display the version
|
||||||
\\
|
\\
|
||||||
@@ -36,45 +30,31 @@ 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 verbose: bool = false;
|
||||||
var ignore_xattrs: bool = false;
|
|
||||||
var ignore_permissions: bool = false;
|
|
||||||
var force: bool = false;
|
|
||||||
|
|
||||||
pub fn main(init: std.process.Init) !void {
|
pub fn main() !void {
|
||||||
const alloc = init.gpa;
|
const alloc = std.heap.smp_allocator;
|
||||||
const io = init.io;
|
var stdout = std.fs.File.stdout();
|
||||||
|
var out = stdout.writer(&[0]u8{});
|
||||||
var stdout = File.stdout();
|
|
||||||
var out = stdout.writer(io, &[0]u8{});
|
|
||||||
defer out.interface.flush() catch {};
|
defer out.interface.flush() catch {};
|
||||||
try handleArgs(init.minimal.args, &out.interface);
|
try handleArgs(alloc, &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: File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
||||||
defer fil.close(io);
|
defer fil.close();
|
||||||
var arc: squashfs.Archive = try .initAdvanced(alloc, io, fil, offset, 0); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
var arc: squashfs.Archive = try .init(fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||||
defer arc.deinit(io);
|
try arc.extract(alloc, extLoc, if (verbose) try .VerboseDefault(&out.interface) else try .Default()); //TODO: Handle error gracefully.
|
||||||
const options: squashfs.ExtractionOptions = .{
|
|
||||||
.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);
|
|
||||||
try arc.extract(alloc, io, extLoc, options); //TODO: Handle error gracefully.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
||||||
var arg_iter = args.iterate();
|
var args = try std.process.argsWithAllocator(alloc);
|
||||||
defer arg_iter.deinit();
|
defer args.deinit();
|
||||||
_ = arg_iter.skip(); // args[0] is the application launch command.
|
_ = args.next(); // args[0] is the application launch command.
|
||||||
while (arg_iter.next()) |arg| {
|
while (args.next()) |arg| {
|
||||||
if (std.mem.eql(u8, arg, "-o")) {
|
if (std.mem.eql(u8, arg, "-o")) {
|
||||||
const nxt = arg_iter.next();
|
const nxt = args.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;
|
||||||
@@ -85,7 +65,7 @@ fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
|||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-d")) {
|
} else if (std.mem.eql(u8, arg, "-d")) {
|
||||||
const nxt = arg_iter.next();
|
const nxt = args.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;
|
||||||
@@ -93,7 +73,7 @@ fn handleArgs(args: std.process.Args, 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 = arg_iter.next();
|
const nxt = args.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;
|
||||||
@@ -106,15 +86,6 @@ fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
|||||||
} else if (std.mem.eql(u8, arg, "-v")) {
|
} else if (std.mem.eql(u8, arg, "-v")) {
|
||||||
verbose = true;
|
verbose = true;
|
||||||
continue;
|
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);
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#include <zlib.h>
|
|
||||||
#include <lzma.h>
|
|
||||||
#include <lz4.h>
|
|
||||||
#include <zstd.h>
|
|
||||||
#ifdef ALLOW_LZO
|
|
||||||
#include <lzo/minilzo.h>
|
|
||||||
#endif
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const c = @import("c");
|
|
||||||
|
|
||||||
const Error = @import("decomp.zig").Error;
|
|
||||||
|
|
||||||
pub fn zlibDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
var strem: c.z_stream = .{
|
|
||||||
.next_in = in.ptr,
|
|
||||||
.avail_in = @truncate(in.len),
|
|
||||||
.next_out = out.ptr,
|
|
||||||
.avail_out = @truncate(out.len),
|
|
||||||
};
|
|
||||||
var res = c.inflateInit(&strem);
|
|
||||||
if (res != c.Z_OK) return Error.ReadFailed;
|
|
||||||
defer _ = c.inflateEnd(&strem);
|
|
||||||
|
|
||||||
res = c.inflate(&strem, c.Z_FULL_FLUSH);
|
|
||||||
if (res != c.Z_OK) return Error.ReadFailed;
|
|
||||||
|
|
||||||
return strem.total_out;
|
|
||||||
}
|
|
||||||
pub fn lzmaDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
var strem: c.lzma_stream = .{
|
|
||||||
.next_in = in.ptr,
|
|
||||||
.avail_in = in.len,
|
|
||||||
.next_out = out.ptr,
|
|
||||||
.avail_out = out.len,
|
|
||||||
};
|
|
||||||
var res = c.lzma_auto_decoder(&strem, out.len * 2, 0);
|
|
||||||
if (res != c.LZMA_OK) return Error.ReadFailed;
|
|
||||||
defer c.lzma_end(&strem);
|
|
||||||
|
|
||||||
while (res == c.LZMA_OK)
|
|
||||||
res = c.lzma_code(&strem, c.LZMA_RUN);
|
|
||||||
if (res != c.LZMA_FINISH) return Error.ReadFailed;
|
|
||||||
|
|
||||||
return strem.total_out;
|
|
||||||
}
|
|
||||||
pub fn lzoDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
var out_len = out.len;
|
|
||||||
const res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null);
|
|
||||||
if (res != c.LZO_E_OK) return Error.ReadFailed;
|
|
||||||
return out_len;
|
|
||||||
}
|
|
||||||
pub fn lz4Decompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
const res = c.LZ4_decompress_safe(
|
|
||||||
in.ptr,
|
|
||||||
out.ptr,
|
|
||||||
@bitCast(@as(u32, @truncate(in.len))),
|
|
||||||
@bitCast(@as(u32, @truncate(out.len))),
|
|
||||||
);
|
|
||||||
if (res < 0) return Error.ReadFailed;
|
|
||||||
return @abs(res);
|
|
||||||
}
|
|
||||||
pub fn zstdDecompress(_: 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;
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
|
|
||||||
const DecompCache = @import("../decomp_cache.zig");
|
|
||||||
const DataBlock = @import("../inode.zig").DataBlock;
|
|
||||||
|
|
||||||
const Extractor = @This();
|
|
||||||
|
|
||||||
cache: *DecompCache,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
start: u64,
|
|
||||||
size: u64,
|
|
||||||
blocks: []DataBlock,
|
|
||||||
|
|
||||||
frag_data: ?[]u8 = null,
|
|
||||||
frag_offset: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(cache: *DecompCache, block_size: u32, size: u64, start: u64, blocks: []DataBlock) Extractor {
|
|
||||||
return .{
|
|
||||||
.cache = cache,
|
|
||||||
.block_size = block_size,
|
|
||||||
|
|
||||||
.start = start,
|
|
||||||
.size = size,
|
|
||||||
.blocks = blocks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *Extractor, data: []u8, offset: u32) void {
|
|
||||||
self.frag_data = data;
|
|
||||||
self.frag_offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn asyncExtract(self: Extractor, io: Io, fil: Io.File) Error!void {
|
|
||||||
try fil.writePositionalAll(io, &.{&.{0}}, self.size - 1);
|
|
||||||
|
|
||||||
var map = try fil.createMemoryMap(io, .{ .len = self.size, .protection = .{ .write = true } });
|
|
||||||
defer map.destroy(io);
|
|
||||||
|
|
||||||
var group: Io.Group = .init;
|
|
||||||
defer group.cancel(io);
|
|
||||||
|
|
||||||
var ret_err: ?Error = null;
|
|
||||||
|
|
||||||
var offset = self.start;
|
|
||||||
for (0..self.blocks.len) |i| {
|
|
||||||
group.async(io, blockThread, .{ self, io, map, offset, i, &ret_err });
|
|
||||||
|
|
||||||
offset += self.blocks[i].size;
|
|
||||||
}
|
|
||||||
if (self.frag_data != null)
|
|
||||||
group.async(io, fragThread, .{ self, map });
|
|
||||||
|
|
||||||
group.await(io) catch |err| return ret_err orelse err;
|
|
||||||
|
|
||||||
try map.write(io);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blockThread(self: Extractor, io: Io, map: Io.File.MemoryMap, read_offset: u64, idx: usize, ret_err: *?Error) error{Canceled}!void {
|
|
||||||
const write_pos = idx * self.block_size;
|
|
||||||
const size = if (self.frag_data == null and idx == self.block_size.len - 1)
|
|
||||||
self.size % self.block_size
|
|
||||||
else
|
|
||||||
self.block_size;
|
|
||||||
const block = self.blocks[idx];
|
|
||||||
|
|
||||||
if (block.size == 0) {
|
|
||||||
@memset(map.memory[write_pos..][0..size], 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (block.uncompressed) {
|
|
||||||
@memcpy(map[write_pos..][0..size], self.cache.map.memory[read_offset..][0..size]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = self.cache.get(io, read_offset, block.size, size) catch |err| switch (err) {
|
|
||||||
error.Canceled => {
|
|
||||||
io.recancel();
|
|
||||||
return error.Canceled;
|
|
||||||
},
|
|
||||||
else => |e| {
|
|
||||||
ret_err.* = e;
|
|
||||||
return error.Canceled;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
defer self.cache.finished(io, read_offset);
|
|
||||||
if (data.len != size) {
|
|
||||||
std.debug.print("Size of decompression at {} is {} and should be {}\n", .{ read_offset, data.len, size });
|
|
||||||
return Error.BadDecompressionSize;
|
|
||||||
}
|
|
||||||
@memcpy(map[write_pos..][0..size], data);
|
|
||||||
}
|
|
||||||
fn fragThread(self: Extractor, map: Io.File.MemoryMap) error{Canceled}!void {
|
|
||||||
const write_pos = self.blocks.len * self.block_size;
|
|
||||||
const size = self.size % self.block_size;
|
|
||||||
|
|
||||||
@memcpy(map.memory[write_pos..][0..size], self.frag_data.?[self.frag_offset..][0..size]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types
|
|
||||||
|
|
||||||
pub const Error = error{BadDecompressionSize} || Io.File.WritePositionalError || Io.File.MemoryMap.CreateError;
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
|
|
||||||
const DecompCache = @import("../decomp_cache.zig");
|
|
||||||
const DataBlock = @import("../inode.zig").DataBlock;
|
|
||||||
|
|
||||||
const Reader = @This();
|
|
||||||
|
|
||||||
io: Io,
|
|
||||||
|
|
||||||
cache: *DecompCache,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
size: u64,
|
|
||||||
blocks: []DataBlock,
|
|
||||||
|
|
||||||
frag_data: ?[]u8 = null,
|
|
||||||
frag_offset: u32 = 0,
|
|
||||||
|
|
||||||
cur_offset: u64 = 0,
|
|
||||||
next_offset: u64,
|
|
||||||
|
|
||||||
idx: u32 = 0,
|
|
||||||
cur_block_sparse: bool = false,
|
|
||||||
|
|
||||||
interface: Io.Reader = .{
|
|
||||||
.buffer = &[0]u8{},
|
|
||||||
.end = 0,
|
|
||||||
.seek = 0,
|
|
||||||
.vtable = &.{
|
|
||||||
.stream = stream,
|
|
||||||
.discard = discard,
|
|
||||||
.readVec = readVec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
pub fn init(io: Io, cache: *DecompCache, block_size: u32, size: u64, start: u64, blocks: []DataBlock) Reader {
|
|
||||||
return .{
|
|
||||||
.io = io,
|
|
||||||
|
|
||||||
.cache = cache,
|
|
||||||
.block_size = block_size,
|
|
||||||
|
|
||||||
.size = size,
|
|
||||||
.blocks = blocks,
|
|
||||||
|
|
||||||
.next_offset = start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Reader) void {
|
|
||||||
self.cache.finished(self.io);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *Reader, data: []u8, offset: u32) void {
|
|
||||||
self.frag_data = data;
|
|
||||||
self.frag_offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(self: *Reader) Io.Reader.Error!void {
|
|
||||||
errdefer self.interface.end = 0;
|
|
||||||
self.interface.seek = 0;
|
|
||||||
|
|
||||||
if (self.idx > self.blocks.len) return error.EndOfStream;
|
|
||||||
defer self.idx += 1;
|
|
||||||
self.cache.finished(self.io, self.cur_offset);
|
|
||||||
|
|
||||||
if (self.idx == self.blocks.len) {
|
|
||||||
if (self.frag_data == null) return error.EndOfStream;
|
|
||||||
self.cur_offset = 0;
|
|
||||||
|
|
||||||
const size = self.size % self.block_size;
|
|
||||||
self.interface.buffer = self.frag_data.?[self.frag_offset..][0..size];
|
|
||||||
self.interface.end = size;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const block = self.blocks[self.idx];
|
|
||||||
|
|
||||||
const size = if (self.idx == self.blocks.len - 1 and self.frag_data == null)
|
|
||||||
self.size % self.block_size
|
|
||||||
else
|
|
||||||
self.block_size;
|
|
||||||
|
|
||||||
if (block.size == 0) {
|
|
||||||
self.interface.buffer = &[0]u8{};
|
|
||||||
self.cur_block_sparse = true;
|
|
||||||
self.interface.end = size;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
self.cur_block_sparse = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cur_offset = self.next_offset;
|
|
||||||
self.next_offset = self.cur_offset + block.size;
|
|
||||||
|
|
||||||
if (block.uncompressed) {
|
|
||||||
self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..size];
|
|
||||||
self.interface.end = size;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = self.cache.get(self.io, self.cur_offset, block.size, size);
|
|
||||||
if (data.len != size) {
|
|
||||||
std.debug.print("Size of decompression at {} is {} and should be {}\n", .{ self.cur_offset, data.len, size });
|
|
||||||
return Io.Reader.Error.ReadFailed;
|
|
||||||
}
|
|
||||||
self.interface.buffer = data;
|
|
||||||
self.interface.end = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
|
||||||
const self: *Reader = @fieldParentPtr("interface", r);
|
|
||||||
if (r.seek >= r.end) {
|
|
||||||
try self.advance();
|
|
||||||
}
|
|
||||||
const to_write = @min(@intFromEnum(limit), r.end - r.seek);
|
|
||||||
const wrote = try if (self.cur_block_sparse)
|
|
||||||
w.splatByte(0, to_write)
|
|
||||||
else
|
|
||||||
w.write(r.buffer[r.seek..][0..to_write]);
|
|
||||||
r.seek += wrote;
|
|
||||||
return wrote;
|
|
||||||
}
|
|
||||||
fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
|
|
||||||
if (r.seek >= r.end) {
|
|
||||||
const self: *Reader = @fieldParentPtr("interface", r);
|
|
||||||
try self.advance();
|
|
||||||
}
|
|
||||||
const to_discard = @min(@intFromEnum(limit), r.end - r.seek);
|
|
||||||
r.seek += to_discard;
|
|
||||||
return to_discard;
|
|
||||||
}
|
|
||||||
fn readVec(r: *Io.Reader, vec: [][]u8) Io.Reader.Error!usize {
|
|
||||||
const self: *Reader = @fieldParentPtr("interface", r);
|
|
||||||
if (r.seek >= r.end) {
|
|
||||||
try self.advance();
|
|
||||||
}
|
|
||||||
var total: usize = 0;
|
|
||||||
for (vec) |v| {
|
|
||||||
const to_copy = @min(v.len, r.end - r.seek);
|
|
||||||
if (self.cur_block_sparse) {
|
|
||||||
@memset(v[0..to_copy], 0);
|
|
||||||
} else {
|
|
||||||
@memcpy(v[0..to_copy], r.buffer[r.seek..][0..to_copy]);
|
|
||||||
}
|
|
||||||
total += to_copy;
|
|
||||||
r.seek += to_copy;
|
|
||||||
|
|
||||||
if (r.seek >= r.end) break;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
+247
-27
@@ -1,14 +1,27 @@
|
|||||||
|
//! 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 builtin = @import("builtin");
|
||||||
|
|
||||||
const options = @import("options");
|
const config = if (builtin.is_test) .{
|
||||||
|
.use_c_libs = builtin.link_libc == true,
|
||||||
|
.allow_lzo = false, // Change once LZO compilation is fixed
|
||||||
|
} else @import("config");
|
||||||
|
|
||||||
const c_decomp = @import("c_decomp.zig");
|
const c = @cImport({
|
||||||
const zig_decomp = @import("zig_decomp.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 Error = error{} || std.Io.Reader.UnlimitedAllocError;
|
pub const CompressionType = enum(u16) {
|
||||||
|
gzip = 1,
|
||||||
pub const Enum = enum(u16) {
|
|
||||||
zlib = 1,
|
|
||||||
lzma,
|
lzma,
|
||||||
lzo,
|
lzo,
|
||||||
xz,
|
xz,
|
||||||
@@ -16,26 +29,233 @@ pub const Enum = enum(u16) {
|
|||||||
zstd,
|
zstd,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Fn = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize;
|
/// A generic decompression function. alloc is only used for internal use and any allocations made will be freed.
|
||||||
|
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
||||||
|
|
||||||
pub fn DecompFn(comp: Enum) !Fn {
|
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
||||||
return if (options.use_zig_decomp)
|
|
||||||
switch (comp) {
|
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
.zlib => zig_decomp.zlibDecompress,
|
var rdr: Reader = .fixed(in);
|
||||||
.lzma => zig_decomp.lzmaDecompress,
|
const buf = try alloc.alloc(u8, out.len);
|
||||||
.xz => zig_decomp.xzDecompress,
|
defer alloc.free(buf);
|
||||||
.zstd => zig_decomp.zstdDecompress,
|
var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, buf);
|
||||||
.lz4 => error.Lz4Unsupported,
|
return decomp.reader.readSliceShort(out);
|
||||||
.lzo => error.LzoUnsupported,
|
}
|
||||||
}
|
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
else switch (comp) {
|
_ = alloc;
|
||||||
.zlib => c_decomp.zlibDecompress,
|
var out_len: usize = out.len;
|
||||||
.lzma, .xz => c_decomp.lzmaDecompress,
|
const res = c.uncompress(out.ptr, &out_len, in.ptr, in.len);
|
||||||
.zstd => c_decomp.zstdDecompress,
|
return switch (res) {
|
||||||
.lz4 => c_decomp.lz4Decompress,
|
c.Z_OK => out_len,
|
||||||
.lzo => if (options.allow_lzo)
|
c.Z_MEM_ERROR => error.NotEnoughMemory,
|
||||||
c_decomp.zstdDecompress
|
c.Z_BUF_ERROR => error.OutBufferTooSmall,
|
||||||
else
|
c.Z_DATA_ERROR => error.BadData,
|
||||||
error.LzoUnsupported,
|
else => error.UnknownResult,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma;
|
||||||
|
|
||||||
|
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
var decomp = try std.compress.lzma.decompress(alloc, rdr.adaptToOldInterface());
|
||||||
|
return decomp.read(out);
|
||||||
|
}
|
||||||
|
fn cLzma(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_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);
|
||||||
|
while (res == c.LZMA_OK)
|
||||||
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
|
return switch (res) {
|
||||||
|
c.LZMA_STREAM_END => stream.total_out,
|
||||||
|
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,
|
||||||
|
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);
|
||||||
|
while (res == c.LZMA_OK)
|
||||||
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
|
return switch (res) {
|
||||||
|
c.LZMA_STREAM_END => stream.total_out,
|
||||||
|
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,
|
||||||
|
c.LZMA_PROG_ERROR => error.LzmaProgramError,
|
||||||
|
else => error.UnknownResult,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
const File = Io.File;
|
|
||||||
const MemoryMap = File.MemoryMap;
|
|
||||||
const Atomic = std.atomic.Value;
|
|
||||||
|
|
||||||
const Decomp = @import("decomp.zig");
|
|
||||||
|
|
||||||
const DecompCache = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
map: MemoryMap,
|
|
||||||
decomp_fn: Decomp.Fn,
|
|
||||||
|
|
||||||
cache: std.AutoHashMap(u64, Cache),
|
|
||||||
mut: std.Io.RwLock = .init,
|
|
||||||
cond: std.Io.Condition = .init,
|
|
||||||
|
|
||||||
max_mem: u64,
|
|
||||||
cur_mem: u64 = 0,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, map: MemoryMap, compression: Decomp.Enum, max_mem: u64) !DecompCache {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.map = map,
|
|
||||||
.decomp_fn = try Decomp.DecompFn(compression),
|
|
||||||
|
|
||||||
.cache = .init(alloc),
|
|
||||||
|
|
||||||
.max_mem = max_mem,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *DecompCache, io: Io) void {
|
|
||||||
self.mut.lockUncancelable(io);
|
|
||||||
|
|
||||||
var iter = self.cache.valueIterator();
|
|
||||||
while (iter.next()) |v|
|
|
||||||
self.alloc.free(v.data);
|
|
||||||
self.cache.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *DecompCache, io: Io, offset: u64, compressed_size: u32, max_size: u32) ![]u8 {
|
|
||||||
{
|
|
||||||
try self.mut.lockShared(io);
|
|
||||||
defer self.mut.unlockShared(io);
|
|
||||||
|
|
||||||
const cache = self.cache.getPtr(offset);
|
|
||||||
if (cache != null) {
|
|
||||||
_ = cache.?.usage.fetchAdd(1, .acquire);
|
|
||||||
return cache.?.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try self.mut.lock(io);
|
|
||||||
defer self.mut.unlock(io);
|
|
||||||
|
|
||||||
const cache = try self.cache.getOrPut(offset);
|
|
||||||
if (cache.found_existing) {
|
|
||||||
_ = cache.value_ptr.usage.fetchAdd(1, .acquire);
|
|
||||||
return cache.value_ptr.data;
|
|
||||||
}
|
|
||||||
errdefer self.cache.removeByPtr(cache.key_ptr);
|
|
||||||
|
|
||||||
try self.ensureSpace(io, max_size);
|
|
||||||
|
|
||||||
var out = try self.alloc.alloc(u8, max_size);
|
|
||||||
errdefer self.alloc.free(out);
|
|
||||||
|
|
||||||
const decomp_size = try self.decomp_fn(self.alloc, self.map.memory[offset..][0..compressed_size], out);
|
|
||||||
if (decomp_size != max_size) {
|
|
||||||
if (!self.alloc.resize(out, decomp_size)) {
|
|
||||||
const new_out = try self.alloc.alloc(u8, decomp_size);
|
|
||||||
@memcpy(new_out, out[0..decomp_size]);
|
|
||||||
out = new_out;
|
|
||||||
} else {
|
|
||||||
out.len = decomp_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.cur_mem += decomp_size;
|
|
||||||
|
|
||||||
cache.value_ptr.data = out;
|
|
||||||
_ = cache.value_ptr.usage.fetchAdd(1, .acquire);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
pub fn finished(self: *DecompCache, io: Io, offset: u64) void {
|
|
||||||
const cache = self.cache.getPtr(offset);
|
|
||||||
if (cache == null) {
|
|
||||||
std.debug.print("Finished using cache, but cache does not exist: {}\n", .{offset});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const use = cache.?.usage.fetchSub(1, .acquire);
|
|
||||||
if (use == 0)
|
|
||||||
self.cond.broadcast(io);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensureSpace(self: *DecompCache, io: Io, size: u64) !void {
|
|
||||||
while (self.cur_mem + size > self.max_mem) {
|
|
||||||
var iter = self.cache.valueIterator();
|
|
||||||
while (iter.next()) |cache| {
|
|
||||||
if (cache.usage.load(.unordered) == 0) {
|
|
||||||
self.alloc.free(cache.data);
|
|
||||||
self.cur_mem -= cache.data.len;
|
|
||||||
|
|
||||||
if (self.cur_mem + size <= self.max_mem) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (self.cur_mem + size <= self.max_mem) return;
|
|
||||||
try self.cond.wait(io, &self.mut.mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types
|
|
||||||
|
|
||||||
const Cache = struct {
|
|
||||||
data: []u8,
|
|
||||||
usage: Atomic(u32),
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
//! 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);
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
const Reader = Io.Reader;
|
|
||||||
|
|
||||||
const Inode = @import("inode.zig");
|
|
||||||
|
|
||||||
const Directory = @This();
|
|
||||||
|
|
||||||
entries: []Entry,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, size: u32) Error!Directory {
|
|
||||||
if (size <= 3) return .{ .entries = &[0]Entry{} };
|
|
||||||
|
|
||||||
var entries: std.ArrayList(Entry) = try .initCapacity(alloc, 50);
|
|
||||||
errdefer {
|
|
||||||
for (entries.items) |ent|
|
|
||||||
ent.deinit(alloc);
|
|
||||||
entries.deinit(alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
var read: u32 = 3;
|
|
||||||
while (read < size) {
|
|
||||||
var hdr: Header = undefined;
|
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
|
||||||
read += @sizeOf(Header);
|
|
||||||
|
|
||||||
try entries.ensureUnusedCapacity(alloc, hdr.count + 1);
|
|
||||||
for (0..hdr.count + 1) |_| {
|
|
||||||
var raw: RawEntry = undefined;
|
|
||||||
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);
|
|
||||||
|
|
||||||
entries.appendAssumeCapacity(.{
|
|
||||||
.inode_num = if (raw.inode_num_offset > 0)
|
|
||||||
hdr.inode_num + @abs(raw.inode_num_offset)
|
|
||||||
else
|
|
||||||
hdr.inode_num - @abs(raw.inode_num_offset),
|
|
||||||
.block_start = hdr.block_start,
|
|
||||||
.block_offset = raw.block_offset,
|
|
||||||
.type = raw.type,
|
|
||||||
.name = name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .entries = try entries.toOwnedSlice(alloc) };
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Directory, alloc: std.mem.Allocator) void {
|
|
||||||
for (self.entries) |entry|
|
|
||||||
entry.deinit(alloc);
|
|
||||||
alloc.free(self.entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types
|
|
||||||
|
|
||||||
pub const Error = Reader.Error || std.mem.Allocator.Error;
|
|
||||||
|
|
||||||
pub const Entry = struct {
|
|
||||||
inode_num: u32,
|
|
||||||
block_start: u32,
|
|
||||||
block_offset: u16,
|
|
||||||
type: Inode.Enum,
|
|
||||||
name: []const u8,
|
|
||||||
|
|
||||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Header = extern struct {
|
|
||||||
count: u32,
|
|
||||||
block_start: u32,
|
|
||||||
inode_num: u32,
|
|
||||||
};
|
|
||||||
const RawEntry = extern struct {
|
|
||||||
block_offset: u16,
|
|
||||||
inode_num_offset: i16,
|
|
||||||
type: Inode.Enum,
|
|
||||||
name_size: u16,
|
|
||||||
};
|
|
||||||
-283
@@ -1,283 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
|
|
||||||
const Atomic = std.atomic.Value;
|
|
||||||
|
|
||||||
const DecompCache = @import("decomp_cache.zig");
|
|
||||||
const ExtractionOptions = @import("options.zig");
|
|
||||||
const Inode = @import("inode.zig");
|
|
||||||
const Superblock = @import("archive.zig").Superblock;
|
|
||||||
const Directory = @import("directory.zig");
|
|
||||||
const DataExtractor = @import("data/extractor.zig");
|
|
||||||
const DataReader = @import("data/reader.zig");
|
|
||||||
const Lookup = @import("lookup.zig");
|
|
||||||
|
|
||||||
pub fn extract(alloc: std.mem.Allocator, io: Io, inode: Inode, cache: *DecompCache, super: Superblock, ext_loc: []const u8, options: ExtractionOptions) !void {
|
|
||||||
const path = std.mem.trim(u8, ext_loc, "/");
|
|
||||||
|
|
||||||
var buf: [50]ReturnUnion = undefined;
|
|
||||||
var sel: Io.Select(ReturnUnion) = .init(io, &buf);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
while (sel.cancel()) |ret| {
|
|
||||||
switch (ret) {
|
|
||||||
.dir_ret => |d| {
|
|
||||||
const res = d catch continue;
|
|
||||||
alloc.free(res.path);
|
|
||||||
},
|
|
||||||
.file_ret => |f| {
|
|
||||||
const res = f catch continue;
|
|
||||||
alloc.free(res.path);
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var frag_table: Lookup.Table(Lookup.FragmentEntry) = .init(alloc, cache, super.frag_start, super.frag_count);
|
|
||||||
defer frag_table.deinit();
|
|
||||||
|
|
||||||
var ret_loop = io.async(returnLoop, .{ alloc, &sel, options });
|
|
||||||
|
|
||||||
try extractReal(alloc, io, cache, super, &sel, &frag_table, path, inode, null, false);
|
|
||||||
|
|
||||||
ret_loop.await(io) catch |err| {
|
|
||||||
// TODO: Drain sel
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extractReal(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
io: Io,
|
|
||||||
cache: *DecompCache,
|
|
||||||
super: Superblock,
|
|
||||||
sel: *Io.Select(ReturnUnion),
|
|
||||||
frag_table: *Lookup.Table(Lookup.FragmentEntry),
|
|
||||||
path: []const u8,
|
|
||||||
inode: Inode,
|
|
||||||
parent: ?*Atomic(usize),
|
|
||||||
origin: bool,
|
|
||||||
) Error!void {
|
|
||||||
try io.checkCancel();
|
|
||||||
|
|
||||||
switch (inode.data) {
|
|
||||||
.dir, .ext_dir => sel.async(
|
|
||||||
.dir_ret,
|
|
||||||
extractDir,
|
|
||||||
.{ alloc, io, cache, super, sel, frag_table, path, inode, parent, origin },
|
|
||||||
),
|
|
||||||
.file, .ext_file => sel.async(
|
|
||||||
.file_ret,
|
|
||||||
extractFile,
|
|
||||||
.{ alloc, io, cache, super.block_size, frag_table, path, inode, parent, origin },
|
|
||||||
),
|
|
||||||
else => return error.Canceled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extractDir(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
io: Io,
|
|
||||||
cache: *DecompCache,
|
|
||||||
super: Superblock,
|
|
||||||
sel: *Io.Select(ReturnUnion),
|
|
||||||
frag_table: *Lookup.Table(Lookup.FragmentEntry),
|
|
||||||
path: []const u8,
|
|
||||||
inode: Inode,
|
|
||||||
parent: ?*Atomic(usize),
|
|
||||||
origin: bool,
|
|
||||||
) Error!DirReturn {
|
|
||||||
defer {
|
|
||||||
if (parent != null)
|
|
||||||
_ = parent.?.fetchSub(1, .acquire);
|
|
||||||
if (!origin) inode.deinit(alloc);
|
|
||||||
}
|
|
||||||
errdefer if (!origin) alloc.free(path);
|
|
||||||
|
|
||||||
const dir = inode.directory(alloc, io, cache, super.dir_start) catch |err| switch (err) {
|
|
||||||
error.NotDirectory => unreachable,
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
|
||||||
defer dir.deinit(alloc);
|
|
||||||
|
|
||||||
const sub_files = try alloc.create(Atomic(usize));
|
|
||||||
sub_files.* = .init(dir.entries.len);
|
|
||||||
|
|
||||||
const ret: DirReturn = .{
|
|
||||||
.path = path,
|
|
||||||
.sub_files = sub_files,
|
|
||||||
.origin = origin,
|
|
||||||
|
|
||||||
.uid_idx = inode.hdr.uid_idx,
|
|
||||||
.gid_idx = inode.hdr.gid_idx,
|
|
||||||
.mod_time = inode.hdr.mod_time,
|
|
||||||
.permissions = inode.hdr.permission,
|
|
||||||
|
|
||||||
.xattr_idx = switch (inode.data) {
|
|
||||||
.ext_dir => |d| if (d.xattr_idx != 0xFFFFFFFF) d.xattr_idx else null,
|
|
||||||
else => null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (dir.entries) |entry| {
|
|
||||||
const new_inode: Inode = try .initDirEntry(alloc, io, cache, super.inode_start, super.block_size, entry);
|
|
||||||
errdefer new_inode.deinit(alloc);
|
|
||||||
|
|
||||||
const new_path = try std.mem.concat(alloc, u8, &.{ path, "/", entry.name });
|
|
||||||
|
|
||||||
try extractReal(
|
|
||||||
alloc,
|
|
||||||
io,
|
|
||||||
cache,
|
|
||||||
super,
|
|
||||||
sel,
|
|
||||||
frag_table,
|
|
||||||
new_path,
|
|
||||||
new_inode,
|
|
||||||
sub_files,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
fn extractFile(
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
io: Io,
|
|
||||||
cache: *DecompCache,
|
|
||||||
block_size: u32,
|
|
||||||
frag_table: *Lookup.Table(Lookup.FragmentEntry),
|
|
||||||
path: []const u8,
|
|
||||||
inode: Inode,
|
|
||||||
parent: ?*Atomic(usize),
|
|
||||||
origin: bool,
|
|
||||||
) Error!FileReturn {
|
|
||||||
defer {
|
|
||||||
if (parent != null)
|
|
||||||
_ = parent.?.fetchSub(1, .acquire);
|
|
||||||
if (!origin) inode.deinit(alloc);
|
|
||||||
}
|
|
||||||
errdefer if (!origin) alloc.free(path);
|
|
||||||
|
|
||||||
var atomic = try Io.Dir.cwd().createFileAtomic(io, path, .{});
|
|
||||||
defer atomic.deinit(io);
|
|
||||||
|
|
||||||
var ret: FileReturn = .{
|
|
||||||
.path = path,
|
|
||||||
.origin = origin,
|
|
||||||
|
|
||||||
.uid_idx = inode.hdr.uid_idx,
|
|
||||||
.gid_idx = inode.hdr.gid_idx,
|
|
||||||
.permissions = inode.hdr.permission,
|
|
||||||
.mod_time = inode.hdr.mod_time,
|
|
||||||
};
|
|
||||||
|
|
||||||
const data: DataExtractor = switch (inode.data) {
|
|
||||||
.file => |f| blk: {
|
|
||||||
var data: DataExtractor = .init(cache, block_size, f.size, f.data_start, f.blocks);
|
|
||||||
if (f.frag_idx != 0xFFFFFFFF) {
|
|
||||||
const entry: Lookup.FragmentEntry = try frag_table.get(io, f.frag_idx);
|
|
||||||
if (entry.size.uncompressed) {
|
|
||||||
data.addFragment(cache.map.memory[entry.start..][0..entry.size.size], f.frag_offset);
|
|
||||||
} else {
|
|
||||||
const block = try cache.get(io, entry.start, entry.size.size, block_size);
|
|
||||||
data.addFragment(block, f.frag_offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break :blk data;
|
|
||||||
},
|
|
||||||
.ext_file => |f| blk: {
|
|
||||||
var data: DataExtractor = .init(cache, block_size, f.size, f.data_start, f.blocks);
|
|
||||||
if (f.frag_idx != 0xFFFFFFFF) {
|
|
||||||
const entry: Lookup.FragmentEntry = try frag_table.get(io, f.frag_idx);
|
|
||||||
if (entry.size.uncompressed) {
|
|
||||||
data.addFragment(cache.map.memory[entry.start..][0..entry.size.size], f.frag_offset);
|
|
||||||
} else {
|
|
||||||
const block = try cache.get(io, entry.start, entry.size.size, block_size);
|
|
||||||
data.addFragment(block, f.frag_offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (f.xattr_idx != 0xFFFFFFFF)
|
|
||||||
ret.xattr_idx = f.xattr_idx;
|
|
||||||
break :blk data;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
try data.asyncExtract(io, atomic.file);
|
|
||||||
|
|
||||||
try atomic.link(io);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop
|
|
||||||
|
|
||||||
fn returnLoop(alloc: std.mem.Allocator, sel: *Io.Select(ReturnUnion), options: ExtractionOptions) !void {
|
|
||||||
while (true) {
|
|
||||||
const finished = try sel.await();
|
|
||||||
|
|
||||||
switch (finished) {
|
|
||||||
.dir_ret => |d| {
|
|
||||||
const ret = try d;
|
|
||||||
if (ret.sub_files.load(.unordered) != 0) {
|
|
||||||
sel.queue.putOne(sel.io, .{ .dir_ret = ret }) catch |err| {
|
|
||||||
if (!ret.origin) alloc.free(ret.path);
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!ret.origin) alloc.free(ret.path);
|
|
||||||
alloc.destroy(ret.sub_files);
|
|
||||||
|
|
||||||
if (!options.ignore_permissions and !options.ignore_xattr) {
|
|
||||||
// TODO: set permissions & xattr.
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.file_ret => |f| {
|
|
||||||
const ret = try f;
|
|
||||||
if (!ret.origin) alloc.free(ret.path);
|
|
||||||
|
|
||||||
if (!options.ignore_permissions and !options.ignore_xattr) {
|
|
||||||
// TODO: set permissions & xattr.
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.void_ret => |v| try v,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sel.group.token.load(.unordered) == null) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility types
|
|
||||||
|
|
||||||
const ReturnUnion = union(enum) {
|
|
||||||
file_ret: Error!FileReturn,
|
|
||||||
dir_ret: Error!DirReturn,
|
|
||||||
void_ret: Error!void,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Error = error{Canceled} || Directory.Error || Io.Dir.CreateFileAtomicError || Io.File.Atomic.LinkError || DataExtractor.Error;
|
|
||||||
|
|
||||||
const FileReturn = struct {
|
|
||||||
path: []const u8,
|
|
||||||
origin: bool,
|
|
||||||
|
|
||||||
uid_idx: u32,
|
|
||||||
gid_idx: u32,
|
|
||||||
mod_time: u32,
|
|
||||||
permissions: u16,
|
|
||||||
|
|
||||||
xattr_idx: ?u32 = null,
|
|
||||||
};
|
|
||||||
const DirReturn = struct {
|
|
||||||
path: []const u8,
|
|
||||||
sub_files: *Atomic(usize),
|
|
||||||
origin: bool,
|
|
||||||
|
|
||||||
uid_idx: u32,
|
|
||||||
gid_idx: u32,
|
|
||||||
mod_time: u32,
|
|
||||||
permissions: u16,
|
|
||||||
|
|
||||||
xattr_idx: ?u32 = null,
|
|
||||||
};
|
|
||||||
+171
-69
@@ -1,102 +1,204 @@
|
|||||||
|
//! A file/directory within the squashfs archive.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const File = std.fs.File;
|
||||||
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
const Archive = @import("archive.zig");
|
||||||
const Directory = @import("directory.zig");
|
const DirEntry = @import("dir_entry.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 DataReader = @import("util/data.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
|
const FileError = error{
|
||||||
|
NotDirectory,
|
||||||
|
NotRegularFile,
|
||||||
|
NotSymlink,
|
||||||
|
NotDevice,
|
||||||
|
NotFound,
|
||||||
|
ExtractionPathExists,
|
||||||
|
};
|
||||||
|
|
||||||
const SfsFile = @This();
|
const SfsFile = @This();
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
archive: *Archive,
|
archive: Archive,
|
||||||
|
|
||||||
inode: Inode,
|
inode: Inode,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
|
||||||
/// The given allocator must have been used to create the Inode and name.
|
/// Initialize a new File.
|
||||||
pub fn init(alloc: std.mem.Allocator, archive: *Archive, inode: Inode, name: []const u8) SfsFile {
|
/// name is copied to the File so can be safely freed afterwards.
|
||||||
|
pub fn init(alloc: std.mem.Allocator, archive: Archive, inode: Inode, name: []const u8) !SfsFile {
|
||||||
|
const new_name = try alloc.alloc(u8, name.len);
|
||||||
|
@memcpy(new_name, name);
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.archive = archive,
|
.archive = archive,
|
||||||
|
|
||||||
.inode = inode,
|
.inode = inode,
|
||||||
.name = name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, archive: *Archive, entry: Directory.Entry) !SfsFile {
|
|
||||||
const new_name = try alloc.alloc(u8, entry.name.len);
|
|
||||||
defer alloc.free(new_name);
|
|
||||||
@memcpy(new_name, entry.name);
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.archive = archive,
|
|
||||||
|
|
||||||
.inode = try .initDirEntry(
|
|
||||||
alloc,
|
|
||||||
io,
|
|
||||||
&archive.cache,
|
|
||||||
archive.super.inode_start,
|
|
||||||
archive.super.block_size,
|
|
||||||
entry,
|
|
||||||
),
|
|
||||||
.name = new_name,
|
.name = new_name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/// Creates a new copy of the given SfsFile using the given allocator
|
pub fn fromEntry(alloc: std.mem.Allocator, archive: Archive, entry: DirEntry) !SfsFile {
|
||||||
pub fn copy(self: SfsFile, alloc: std.mem.Allocator) !SfsFile {
|
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
||||||
const new_name = try alloc(u8, self.name.len);
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
errdefer alloc.free(new_name);
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
|
const inode: Inode = try .read(alloc, &meta.interface, archive.super.block_size);
|
||||||
return .{
|
errdefer inode.deinit(alloc);
|
||||||
.alloc = alloc,
|
return .init(alloc, archive, inode, entry.name);
|
||||||
.archive = self.archive,
|
|
||||||
|
|
||||||
.inode = try self.inode.copy(alloc),
|
|
||||||
.name = new_name,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: SfsFile) void {
|
pub fn deinit(self: SfsFile) void {
|
||||||
self.inode.deinit(self.alloc);
|
|
||||||
self.alloc.free(self.name);
|
self.alloc.free(self.name);
|
||||||
|
self.inode.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to open the filepath if the SfsFile is a directory.
|
fn getEntries(self: SfsFile, alloc: std.mem.Allocator) ![]DirEntry {
|
||||||
/// If the given path refers to itself (such as "" or "."), a copied SfsFile is returned.
|
return self.inode.dirEntries(alloc, self.archive);
|
||||||
pub fn open(self: SfsFile, alloc: std.mem.Allocator, io: Io, filepath: []const u8) !SfsFile {
|
}
|
||||||
const path = std.mem.trim(u8, filepath, "/");
|
|
||||||
|
|
||||||
const first_element: []const u8 = std.mem.sliceTo(path, '/');
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
const dir: Directory = try self.inode.directory(alloc, io, &self.archive.cache, self.archive.super.dir_start);
|
pub fn isRegular(self: SfsFile) bool {
|
||||||
defer dir.deinit(alloc);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
var cur_slice = dir.entries;
|
pub fn isDir(self: SfsFile) bool {
|
||||||
var idx: usize = undefined;
|
return switch (self.inode.hdr.inode_type) {
|
||||||
while (cur_slice.len > 0) {
|
.dir, .ext_dir => true,
|
||||||
idx = cur_slice.len / 2;
|
else => false,
|
||||||
switch (std.mem.order(u8, first_element, cur_slice[idx].name)) {
|
};
|
||||||
.eq => break,
|
}
|
||||||
.lt => cur_slice = cur_slice[0..idx],
|
pub fn iterate(self: SfsFile) !Iterator {
|
||||||
.gt => cur_slice = cur_slice[idx..],
|
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, alloc: std.mem.Allocator, 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(alloc, path[1..]);
|
||||||
|
if (path[path.len - 1] == '/') return self.open(alloc, path[0 .. path.len - 1]);
|
||||||
|
|
||||||
|
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
|
||||||
|
const first_element = path[0..idx];
|
||||||
|
if (std.mem.eql(u8, first_element, ".")) return self.open(alloc, path[idx + 1 ..]);
|
||||||
|
const entries = try self.getEntries(alloc);
|
||||||
|
defer {
|
||||||
|
for (entries) |e| {
|
||||||
|
e.deinit(alloc);
|
||||||
}
|
}
|
||||||
} else {
|
alloc.free(entries);
|
||||||
return error.NotFound;
|
|
||||||
}
|
}
|
||||||
if (first_element.len == path.len) return .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
|
var cur_slice = entries;
|
||||||
if (cur_slice[idx].type != .dir) return error.NotFound;
|
var split = cur_slice.len / 2;
|
||||||
const tmp_file: SfsFile = try .initDirEntry(alloc, io, self.archive, cur_slice[idx]);
|
while (cur_slice.len > 0) {
|
||||||
defer tmp_file.deinit();
|
split = cur_slice.len / 2;
|
||||||
|
const comp = std.mem.order(u8, first_element, cur_slice[split].name);
|
||||||
return tmp_file.open(alloc, io, path[first_element.len..]);
|
switch (comp) {
|
||||||
|
.eq => {
|
||||||
|
var fil: SfsFile = try .fromEntry(alloc, self.archive, cur_slice[split]);
|
||||||
|
if (idx == path.len) {
|
||||||
|
return fil;
|
||||||
|
}
|
||||||
|
defer fil.deinit();
|
||||||
|
return fil.open(alloc, path[idx + 1 ..]);
|
||||||
|
},
|
||||||
|
.lt => cur_slice = cur_slice[0..split],
|
||||||
|
.gt => cur_slice = cur_slice[split + 1 ..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FileError.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract(self: SfsFile, alloc: std.mem.Allocator, io: Io, ext_dir: []const u8, options: ExtractionOptions) !void {
|
pub fn isSymlink(self: SfsFile) bool {
|
||||||
_ = self;
|
return switch (self.inode.hdr.inode_type) {
|
||||||
_ = alloc;
|
.symlink, .ext_symlink => true,
|
||||||
_ = io;
|
else => false,
|
||||||
_ = ext_dir;
|
};
|
||||||
_ = options;
|
|
||||||
return error.TODO;
|
|
||||||
}
|
}
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
/// If the gievn path is a folder, the File's contents will be extracted within.
|
||||||
|
pub fn extract(self: SfsFile, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
return self.inode.extractTo(alloc, self.archive, path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
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 {
|
||||||
|
for (self.entries) |e| {
|
||||||
|
e.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
self.alloc.free(self.entries);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
+482
-337
@@ -1,118 +1,28 @@
|
|||||||
|
//! A file-system object. Represents a File or directory.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Reader = std.Io.Reader;
|
||||||
const Reader = Io.Reader;
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
const Pool = std.Thread.Pool;
|
||||||
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
const DecompCache = @import("decomp_cache.zig");
|
const Archive = @import("archive.zig");
|
||||||
const Directory = @import("directory.zig");
|
const DirEntry = @import("dir_entry.zig");
|
||||||
const MetadataReader = @import("meta_rdr.zig");
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const dir = @import("inode_data/dir.zig");
|
||||||
|
const file = @import("inode_data/file.zig");
|
||||||
|
const misc = @import("inode_data/misc.zig");
|
||||||
|
const DataReader = @import("util/data.zig");
|
||||||
|
const ThreadedDataReader = @import("util/data_threaded.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
const Inode = @This();
|
pub const Ref = packed struct {
|
||||||
|
|
||||||
hdr: Header,
|
|
||||||
data: Data,
|
|
||||||
|
|
||||||
/// Read an inode given an inode Ref.
|
|
||||||
pub fn initRef(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, ref: Ref) !Inode {
|
|
||||||
var meta: MetadataReader = .init(io, cache, inode_start + ref.block_start);
|
|
||||||
defer meta.deinit(io);
|
|
||||||
try meta.interface.discardAll(ref.block_offset);
|
|
||||||
|
|
||||||
return .init(alloc, &meta.interface, block_size);
|
|
||||||
}
|
|
||||||
pub fn initDirEntry(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, inode_start: u64, block_size: u32, entry: Directory.Entry) !Inode {
|
|
||||||
var meta: MetadataReader = .init(io, cache, inode_start + entry.block_start);
|
|
||||||
defer meta.deinit(io);
|
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
|
||||||
|
|
||||||
return .init(alloc, &meta.interface, block_size);
|
|
||||||
}
|
|
||||||
/// Read the inode from the given Reader.
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
|
||||||
var hdr: Header = undefined;
|
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
|
||||||
const data: Data = switch (hdr.type) {
|
|
||||||
.dir => .{ .dir = try .init(rdr) },
|
|
||||||
.file => .{ .file = try .init(alloc, rdr, block_size) },
|
|
||||||
.symlink => .{ .symlink = try .init(alloc, rdr) },
|
|
||||||
.block_dev => .{ .block_dev = try .init(rdr) },
|
|
||||||
.char_dev => .{ .char_dev = try .init(rdr) },
|
|
||||||
.fifo => .{ .fifo = try .init(rdr) },
|
|
||||||
.socket => .{ .socket = try .init(rdr) },
|
|
||||||
.ext_dir => .{ .ext_dir = try .init(rdr) },
|
|
||||||
.ext_file => .{ .ext_file = try .init(alloc, rdr, block_size) },
|
|
||||||
.ext_symlink => .{ .ext_symlink = try .init(alloc, rdr) },
|
|
||||||
.ext_block_dev => .{ .ext_block_dev = try .init(rdr) },
|
|
||||||
.ext_char_dev => .{ .ext_char_dev = try .init(rdr) },
|
|
||||||
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
|
|
||||||
.ext_socket => .{ .ext_socket = try .init(rdr) },
|
|
||||||
};
|
|
||||||
return .{
|
|
||||||
.hdr = hdr,
|
|
||||||
.data = data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
|
|
||||||
var new_inode = self;
|
|
||||||
switch (new_inode.data) {
|
|
||||||
.file => |*f| {
|
|
||||||
if (f.blocks.len > 0) {
|
|
||||||
f.blocks = try alloc.alloc(DataBlock, f.blocks.len);
|
|
||||||
@memcpy(f.blocks, self.data.file.blocks);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.ext_file => |*f| {
|
|
||||||
if (f.blocks.len > 0) {
|
|
||||||
f.blocks = try alloc.alloc(DataBlock, f.blocks.len);
|
|
||||||
@memcpy(f.blocks, self.data.ext_file.blocks);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.symlink => |*s| {
|
|
||||||
s.target = try alloc.alloc(u8, s.target.len);
|
|
||||||
@memcpy(s.target, self.data.symlink.target);
|
|
||||||
},
|
|
||||||
.ext_symlink => |*s| {
|
|
||||||
s.target = try alloc.alloc(u8, s.target.len);
|
|
||||||
@memcpy(s.target, self.data.ext_symlink.target);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return new_inode;
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
|
||||||
switch (self.data) {
|
|
||||||
.file => |f| f.deinit(alloc),
|
|
||||||
.ext_file => |f| f.deinit(alloc),
|
|
||||||
.symlink => |s| s.deinit(alloc),
|
|
||||||
.ext_symlink => |s| s.deinit(alloc),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility functions
|
|
||||||
|
|
||||||
pub fn directory(self: Inode, alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64) !Directory {
|
|
||||||
return switch (self.data) {
|
|
||||||
.dir => |d| readDirectory(alloc, io, cache, dir_start, d),
|
|
||||||
.ext_dir => |d| readDirectory(alloc, io, cache, dir_start, d),
|
|
||||||
else => error.NotDirectory,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn readDirectory(alloc: std.mem.Allocator, io: Io, cache: *DecompCache, dir_start: u64, d: anytype) !Directory {
|
|
||||||
var meta: MetadataReader = .init(io, cache, dir_start + d.block_start);
|
|
||||||
defer meta.deinit(io);
|
|
||||||
try meta.interface.discardAll(d.block_offset);
|
|
||||||
|
|
||||||
return .init(alloc, &meta.interface, d.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types
|
|
||||||
|
|
||||||
pub const Ref = packed struct(u64) {
|
|
||||||
block_offset: u16,
|
block_offset: u16,
|
||||||
block_start: u32,
|
block_start: u32,
|
||||||
_: u16,
|
_: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Enum = enum(u16) {
|
pub const InodeType = enum(u16) {
|
||||||
dir = 1,
|
dir = 1,
|
||||||
file,
|
file,
|
||||||
symlink,
|
symlink,
|
||||||
@@ -129,270 +39,505 @@ pub const Enum = enum(u16) {
|
|||||||
ext_socket,
|
ext_socket,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Header = extern struct {
|
pub const InodeData = union(InodeType) {
|
||||||
type: Enum,
|
dir: dir.Dir,
|
||||||
permission: u16,
|
file: file.File,
|
||||||
|
symlink: misc.Symlink,
|
||||||
|
block_dev: misc.Dev,
|
||||||
|
char_dev: misc.Dev,
|
||||||
|
fifo: misc.IPC,
|
||||||
|
socket: misc.IPC,
|
||||||
|
ext_dir: dir.ExtDir,
|
||||||
|
ext_file: file.ExtFile,
|
||||||
|
ext_symlink: misc.ExtSymlink,
|
||||||
|
ext_block_dev: misc.ExtDev,
|
||||||
|
ext_char_dev: misc.ExtDev,
|
||||||
|
ext_fifo: misc.ExtIPC,
|
||||||
|
ext_socket: misc.ExtIPC,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Header = packed struct {
|
||||||
|
inode_type: InodeType,
|
||||||
|
permissions: u16,
|
||||||
uid_idx: u16,
|
uid_idx: u16,
|
||||||
gid_idx: u16,
|
gid_idx: u16,
|
||||||
mod_time: u32,
|
mod_time: u32,
|
||||||
num: u32,
|
num: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Data = union(Enum) {
|
const Inode = @This();
|
||||||
dir: Dir,
|
|
||||||
file: File,
|
|
||||||
symlink: Symlink,
|
|
||||||
block_dev: Device,
|
|
||||||
char_dev: Device,
|
|
||||||
fifo: IPC,
|
|
||||||
socket: IPC,
|
|
||||||
ext_dir: ExtDir,
|
|
||||||
ext_file: ExtFile,
|
|
||||||
ext_symlink: ExtSymlink,
|
|
||||||
ext_block_dev: ExtDevice,
|
|
||||||
ext_char_dev: ExtDevice,
|
|
||||||
ext_fifo: ExtIPC,
|
|
||||||
ext_socket: ExtIPC,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DataBlock = packed struct(u32) {
|
hdr: Header,
|
||||||
size: u24,
|
data: InodeData,
|
||||||
uncompressed: bool,
|
|
||||||
_: u7,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dir = extern struct {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
||||||
block_start: u32,
|
var hdr: Header = undefined;
|
||||||
hard_links: u32,
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
size: u16,
|
return .{
|
||||||
block_offset: u16,
|
.hdr = hdr,
|
||||||
parent: u32,
|
.data = switch (hdr.inode_type) {
|
||||||
|
.dir => .{ .dir = try .read(rdr) },
|
||||||
const Self = @This();
|
.file => .{ .file = try .read(alloc, rdr, block_size) },
|
||||||
|
.symlink => .{ .symlink = try .read(alloc, rdr) },
|
||||||
fn init(rdr: *Reader) !Self {
|
.block_dev => .{ .block_dev = try .read(rdr) },
|
||||||
var dir: Self = undefined;
|
.char_dev => .{ .char_dev = try .read(rdr) },
|
||||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
.fifo => .{ .fifo = try .read(rdr) },
|
||||||
return dir;
|
.socket => .{ .socket = try .read(rdr) },
|
||||||
}
|
.ext_dir => .{ .ext_dir = try .read(rdr) },
|
||||||
};
|
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
|
||||||
const ExtDir = extern struct {
|
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
|
||||||
hard_links: u32,
|
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
|
||||||
size: u32,
|
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
|
||||||
block_start: u32,
|
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
|
||||||
parent: u32,
|
.ext_socket => .{ .ext_socket = try .read(rdr) },
|
||||||
idx_count: u16,
|
},
|
||||||
block_offset: u16,
|
|
||||||
xattr_idx: u32,
|
|
||||||
// []DirIndex
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn init(rdr: *Reader) !Self {
|
|
||||||
var dir: Self = undefined;
|
|
||||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const File = struct {
|
|
||||||
data_start: u32,
|
|
||||||
frag_idx: u32,
|
|
||||||
frag_offset: u32,
|
|
||||||
size: u32,
|
|
||||||
blocks: []DataBlock,
|
|
||||||
|
|
||||||
const Raw = extern struct {
|
|
||||||
data_start: u32,
|
|
||||||
frag_idx: u32,
|
|
||||||
frag_offset: u32,
|
|
||||||
size: u32,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
pub fn readFromEntry(alloc: std.mem.Allocator, archive: Archive, entry: DirEntry) !Inode {
|
||||||
|
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
|
return read(alloc, &meta.interface, archive.super.block_size);
|
||||||
|
}
|
||||||
|
|
||||||
const Self = @This();
|
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||||
|
switch (self.data) {
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self {
|
.file => |f| alloc.free(f.block_sizes),
|
||||||
var raw: Raw = undefined;
|
.ext_file => |f| alloc.free(f.block_sizes),
|
||||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
.symlink => |s| alloc.free(s.target),
|
||||||
|
.ext_symlink => |s| alloc.free(s.target),
|
||||||
var blocks_num = raw.size / block_size;
|
else => {},
|
||||||
if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0)
|
|
||||||
blocks_num += 1;
|
|
||||||
|
|
||||||
const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num);
|
|
||||||
errdefer alloc.free(blocks);
|
|
||||||
|
|
||||||
try rdr.readSliceEndian(DataBlock, blocks, .little);
|
|
||||||
return .{
|
|
||||||
.data_start = raw.data_start,
|
|
||||||
.frag_idx = raw.frag_idx,
|
|
||||||
.frag_offset = raw.frag_offset,
|
|
||||||
.size = raw.size,
|
|
||||||
.blocks = blocks,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
|
}
|
||||||
alloc.free(self.blocks);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const ExtFile = struct {
|
|
||||||
data_start: u64,
|
|
||||||
size: u64,
|
|
||||||
sparse: u64,
|
|
||||||
hard_links: u32,
|
|
||||||
frag_idx: u32,
|
|
||||||
frag_offset: u32,
|
|
||||||
xattr_idx: u32,
|
|
||||||
blocks: []DataBlock,
|
|
||||||
|
|
||||||
const Raw = extern struct {
|
/// Get the data reader for a file inode.
|
||||||
data_start: u64,
|
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive) !DataReader {
|
||||||
size: u64,
|
return switch (self.hdr.inode_type) {
|
||||||
sparse: u64,
|
.file => readerFromData(alloc, archive, self.data.file),
|
||||||
hard_links: u32,
|
.ext_file => readerFromData(alloc, archive, self.data.ext_file),
|
||||||
frag_idx: u32,
|
else => error.NotRegularFile,
|
||||||
frag_offset: u32,
|
|
||||||
xattr_idx: u32,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
const Self = @This();
|
fn readerFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) !DataReader {
|
||||||
|
return .init(
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Self {
|
alloc,
|
||||||
var raw: Raw = undefined;
|
archive,
|
||||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
data.block_sizes,
|
||||||
|
data.block_start,
|
||||||
var blocks_num = raw.size / block_size;
|
data.size,
|
||||||
if (raw.frag_idx == 0xFFFFFFFF and raw.size % block_size > 0)
|
data.frag_block_offset,
|
||||||
blocks_num += 1;
|
if (data.frag_idx == 0xFFFFFFFF) null else try archive.frag_table.get(alloc, data.frag_idx),
|
||||||
|
);
|
||||||
const blocks: []DataBlock = try alloc.alloc(DataBlock, blocks_num);
|
}
|
||||||
errdefer alloc.free(blocks);
|
/// Get a threaded data reader for a file inode.
|
||||||
|
pub fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive) !ThreadedDataReader {
|
||||||
try rdr.readSliceEndian(DataBlock, blocks, .little);
|
return switch (self.hdr.inode_type) {
|
||||||
return .{
|
.file => threadedReaderFromData(alloc, archive, self.data.file),
|
||||||
.data_start = raw.data_start,
|
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
|
||||||
.size = raw.size,
|
else => error.NotRegularFile,
|
||||||
.sparse = raw.sparse,
|
|
||||||
.hard_links = raw.hard_links,
|
|
||||||
.frag_idx = raw.frag_idx,
|
|
||||||
.frag_offset = raw.frag_offset,
|
|
||||||
.xattr_idx = raw.xattr_idx,
|
|
||||||
.blocks = blocks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.blocks);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Symlink = struct {
|
|
||||||
hard_links: u32,
|
|
||||||
target: []const u8,
|
|
||||||
|
|
||||||
const Raw = extern struct {
|
|
||||||
hard_links: u32,
|
|
||||||
target_size: u32,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) !ThreadedDataReader {
|
||||||
|
return .init(
|
||||||
|
alloc,
|
||||||
|
archive,
|
||||||
|
data.block_sizes,
|
||||||
|
data.block_start,
|
||||||
|
data.size,
|
||||||
|
data.frag_block_offset,
|
||||||
|
if (data.frag_idx == 0xFFFFFFFF) null else try archive.frag_table.get(alloc, data.frag_idx),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const Self = @This();
|
/// Get the directory entries for a directory inode.
|
||||||
|
pub fn dirEntries(self: Inode, alloc: std.mem.Allocator, archive: Archive) ![]DirEntry {
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self {
|
return switch (self.hdr.inode_type) {
|
||||||
var raw: Raw = undefined;
|
.dir => entriesFromData(alloc, archive, self.data.dir),
|
||||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
.ext_dir => entriesFromData(alloc, archive, self.data.ext_dir),
|
||||||
|
else => error.NotDirectory,
|
||||||
const target = try alloc.alloc(u8, raw.target_size);
|
|
||||||
try rdr.readSliceEndian(u8, target, .little);
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.hard_links = raw.hard_links,
|
|
||||||
.target = target,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const ExtSymlink = struct {
|
|
||||||
hard_links: u32,
|
|
||||||
xattr_idx: u32,
|
|
||||||
target: []const u8,
|
|
||||||
|
|
||||||
const Raw = extern struct {
|
|
||||||
hard_links: u32,
|
|
||||||
target_size: u32,
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
fn entriesFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) ![]DirEntry {
|
||||||
|
var rdr = try archive.fil.readerAt(archive.super.dir_start + data.block_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
|
try meta.interface.discardAll(data.block_offset);
|
||||||
|
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
||||||
|
}
|
||||||
|
|
||||||
const Self = @This();
|
/// Extract the inode to the given path.
|
||||||
|
pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
if (options.threads > 1) return self.extractToThreaded(alloc, archive, path, options); // We go to a dedicated function for mutli-threaded
|
||||||
|
switch (self.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
// Removing any trailing separators since that's the easiest path forward.
|
||||||
|
if (path[path.len - 1] == '/') return self.extractTo(alloc, archive, path[0 .. path.len - 1], options);
|
||||||
|
std.fs.cwd().makeDir(path) catch |err| {
|
||||||
|
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
|
||||||
|
};
|
||||||
|
const entries = try self.dirEntries(alloc, archive);
|
||||||
|
defer {
|
||||||
|
for (entries) |entry| entry.deinit(alloc);
|
||||||
|
alloc.free(entries);
|
||||||
|
}
|
||||||
|
for (entries) |entry| {
|
||||||
|
var new_path = try alloc.alloc(u8, path.len + 1 + entry.name.len);
|
||||||
|
@memcpy(new_path[0..path.len], path);
|
||||||
|
@memcpy(new_path[path.len + 1 ..], entry.name);
|
||||||
|
new_path[path.len] = '/';
|
||||||
|
defer alloc.free(new_path);
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: *Reader) !Self {
|
var inode: Inode = try readFromEntry(alloc, archive, entry);
|
||||||
var raw: Raw = undefined;
|
defer inode.deinit(alloc);
|
||||||
try rdr.readSliceEndian(Raw, @ptrCast(&raw), .little);
|
try inode.extractTo(alloc, archive, new_path, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.file, .ext_file => try self.extractRegFile(alloc, archive, path, options),
|
||||||
|
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||||
|
else => try self.extractDevice(alloc, archive, path, options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const target = try alloc.alloc(u8, raw.target_size);
|
const Parent = struct {
|
||||||
errdefer alloc.free(target);
|
alloc: std.mem.Allocator,
|
||||||
try rdr.readSliceEndian(u8, target, .little);
|
|
||||||
|
|
||||||
var xattr_idx: u32 = undefined;
|
path: []const u8,
|
||||||
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
uid: u16,
|
||||||
|
gid: u16,
|
||||||
|
perm: u16,
|
||||||
|
mod_time: u32,
|
||||||
|
|
||||||
return .{
|
ignore_permissions: bool,
|
||||||
.hard_links = raw.hard_links,
|
ignore_xattr: bool,
|
||||||
.target = target,
|
|
||||||
.xattr_idx = xattr_idx,
|
wg: WaitGroup,
|
||||||
|
mut: Mutex = .{},
|
||||||
|
|
||||||
|
fn create(alloc: std.mem.Allocator, hdr: Header, archive: Archive, path: []const u8, options: ExtractionOptions, dir_size: usize) !*Parent {
|
||||||
|
const out = try alloc.create(Parent);
|
||||||
|
errdefer alloc.destroy(out);
|
||||||
|
out.* = .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.path = path,
|
||||||
|
.uid = try archive.id_table.get(alloc, hdr.uid_idx),
|
||||||
|
.gid = try archive.id_table.get(alloc, hdr.gid_idx),
|
||||||
|
.perm = hdr.permissions,
|
||||||
|
.mod_time = hdr.mod_time,
|
||||||
|
|
||||||
|
.ignore_permissions = options.ignore_permissions,
|
||||||
|
.ignore_xattr = options.ignore_xattr,
|
||||||
|
|
||||||
|
.wg = .{},
|
||||||
};
|
};
|
||||||
|
out.wg.startMany(dir_size);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
|
fn finish(p: *Parent) !void {
|
||||||
alloc.free(self.target);
|
p.mut.lock();
|
||||||
|
{
|
||||||
|
defer p.mut.unlock();
|
||||||
|
p.wg.finish();
|
||||||
|
if (!p.wg.isDone()) return;
|
||||||
|
}
|
||||||
|
defer p.alloc.destroy(p);
|
||||||
|
var fil = try std.fs.cwd().openFile(p.path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
const time = @as(i128, p.mod_time) * 1000000000;
|
||||||
|
try fil.updateTimes(time, time);
|
||||||
|
if (p.ignore_permissions) {
|
||||||
|
try fil.chmod(p.perm);
|
||||||
|
try fil.chown(p.uid, p.gid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Device = extern struct {
|
/// Extract the inode to the given path. Multi-threaded.
|
||||||
hard_links: u32,
|
/// Functions identically to extractTo on all but regular files and directories.
|
||||||
device: u32,
|
fn extractToThreaded(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
switch (self.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
// Removing any trailing separators since that's the easiest path forward.
|
||||||
|
if (path[path.len - 1] == '/') return self.extractToThreaded(alloc, archive, path[0 .. path.len - 1], options);
|
||||||
|
|
||||||
const Self = @This();
|
// Arena Allocator
|
||||||
|
var arena_alloc: std.heap.ArenaAllocator = .init(alloc);
|
||||||
|
defer arena_alloc.deinit();
|
||||||
|
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
||||||
|
|
||||||
fn init(rdr: *Reader) !Self {
|
var wg: WaitGroup = .{};
|
||||||
var dir: Self = undefined;
|
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
|
||||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
var pool: Pool = undefined;
|
||||||
return dir;
|
try pool.init(.{ .allocator = alloc, .n_jobs = options.threads - 1 });
|
||||||
|
defer pool.deinit();
|
||||||
|
var out_err: ?anyerror = null;
|
||||||
|
|
||||||
|
wg.start();
|
||||||
|
self.extractThread(thread_alloc.allocator(), archive, path, options, &wg, &pool, &out_err, null);
|
||||||
|
pool.waitAndWork(&wg);
|
||||||
|
if (out_err != null) return out_err.?;
|
||||||
|
|
||||||
|
var fil = try std.fs.cwd().openFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
|
try fil.updateTimes(time, time);
|
||||||
|
if (options.ignore_permissions) {
|
||||||
|
try fil.chmod(self.hdr.permissions);
|
||||||
|
try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.file, .ext_file => {
|
||||||
|
var pool: Pool = undefined;
|
||||||
|
try pool.init(.{ .allocator = alloc, .n_jobs = options.threads - 1 });
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = alloc };
|
||||||
|
|
||||||
|
try self.extractRegFileThreaded(thread_alloc.allocator(), archive, path, options, &pool);
|
||||||
|
|
||||||
|
var fil = try std.fs.cwd().openFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
|
try fil.updateTimes(time, time);
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.chmod(self.hdr.permissions);
|
||||||
|
try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.symlink, .ext_symlink => try self.extractSymlink(path),
|
||||||
|
else => try self.extractDevice(alloc, archive, path, options),
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
const ExtDevice = extern struct {
|
|
||||||
hard_links: u32,
|
|
||||||
device: u32,
|
|
||||||
xattr_idx: u32,
|
|
||||||
|
|
||||||
const Self = @This();
|
fn extractThreadEntry(
|
||||||
|
entry: DirEntry,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
archive: Archive,
|
||||||
|
path: []const u8,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
wg: *WaitGroup,
|
||||||
|
pool: *Pool,
|
||||||
|
out_err: *?anyerror,
|
||||||
|
parent: ?*Parent,
|
||||||
|
) void {
|
||||||
|
var new_path = alloc.alloc(u8, path.len + entry.name.len + 1) catch |err| {
|
||||||
|
wg.finish();
|
||||||
|
out_err.* = err;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
@memcpy(new_path[0..path.len], path);
|
||||||
|
@memcpy(new_path[path.len + 1 ..], entry.name);
|
||||||
|
new_path[path.len] = '/';
|
||||||
|
var inode = readFromEntry(alloc, archive, entry) catch |err| {
|
||||||
|
out_err.* = err;
|
||||||
|
wg.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
inode.extractThread(alloc, archive, new_path, options, wg, pool, out_err, parent);
|
||||||
|
}
|
||||||
|
|
||||||
fn init(rdr: *Reader) !Self {
|
/// Extract threadedly the inode to the path.
|
||||||
var dir: Self = undefined;
|
fn extractThread(
|
||||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
self: Inode,
|
||||||
return dir;
|
alloc: std.mem.Allocator,
|
||||||
|
archive: Archive,
|
||||||
|
path: []const u8,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
wg: *WaitGroup,
|
||||||
|
pool: *Pool,
|
||||||
|
out_err: *?anyerror,
|
||||||
|
parent: ?*Parent,
|
||||||
|
) void {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Extracting inode #{} to {s}\n", .{ self.hdr.num, path }) catch {};
|
||||||
|
defer {
|
||||||
|
if (parent != null) parent.?.finish() catch |err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error setting folder permission to {s}: {}\n", .{ parent.?.path, err }) catch {};
|
||||||
|
out_err.* = err;
|
||||||
|
};
|
||||||
|
wg.finish();
|
||||||
}
|
}
|
||||||
};
|
if (out_err.* != null) return;
|
||||||
|
switch (self.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
_ = std.fs.cwd().makePathStatus(path) catch |err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error creating {s}: {}\n", .{ path, err }) catch {};
|
||||||
|
out_err.* = err;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const IPC = extern struct {
|
const entries = self.dirEntries(alloc, archive) catch |err| {
|
||||||
hard_links: u32,
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error getting directory entries for inode #{} (extracting to {s}): {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||||
const Self = @This();
|
out_err.* = err;
|
||||||
|
return;
|
||||||
fn init(rdr: *Reader) !Self {
|
};
|
||||||
var dir: Self = undefined;
|
const p = Parent.create(alloc, self.hdr, archive, path, options, entries.len) catch |err| {
|
||||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
out_err.* = err;
|
||||||
return dir;
|
return;
|
||||||
|
};
|
||||||
|
wg.startMany(entries.len);
|
||||||
|
// defer files.deinit(alloc); We don't need to do this due to ArenaAllocator
|
||||||
|
for (entries) |entry| {
|
||||||
|
if (entry.inode_type == .dir) {
|
||||||
|
extractThreadEntry(entry, alloc, archive, path, options, wg, pool, out_err, p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pool.spawn(
|
||||||
|
extractThreadEntry,
|
||||||
|
.{
|
||||||
|
entry,
|
||||||
|
alloc,
|
||||||
|
archive,
|
||||||
|
path,
|
||||||
|
options,
|
||||||
|
wg,
|
||||||
|
pool,
|
||||||
|
out_err,
|
||||||
|
p,
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
wg.finish();
|
||||||
|
p.finish() catch |e| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error setting folder permission to {s}: {}\n", .{ p.path, e }) catch {};
|
||||||
|
};
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error starting extraction thread: {}\n", .{err}) catch {};
|
||||||
|
out_err.* = err;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.file, .ext_file => {
|
||||||
|
self.extractRegFileThreaded(alloc, archive, path, options, pool) catch |err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error extracting file inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||||
|
out_err.* = err;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.symlink, .ext_symlink => {
|
||||||
|
self.extractSymlink(path) catch |err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error extracting symlink inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||||
|
out_err.* = err;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
self.extractDevice(alloc, archive, path, options) catch |err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Error extracting device/IPC inode #{} to {s}: {}\n", .{ self.hdr.num, path, err }) catch {};
|
||||||
|
out_err.* = err;
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
const ExtIPC = extern struct {
|
/// Creates and writes the inode file contents to the given path.
|
||||||
hard_links: u32,
|
/// Optionally set owner & permissions.
|
||||||
xattr_idx: u32,
|
///
|
||||||
|
/// Assumes the inode is a file or ext_file type.
|
||||||
|
fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true });
|
||||||
|
defer fil.close();
|
||||||
|
var wrt = fil.writer(&[0]u8{});
|
||||||
|
var dat_rdr = try self.dataReader(alloc, archive);
|
||||||
|
defer dat_rdr.deinit();
|
||||||
|
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
||||||
|
try wrt.interface.flush();
|
||||||
|
|
||||||
const Self = @This();
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
|
try fil.updateTimes(time, time);
|
||||||
fn init(rdr: *Reader) !Self {
|
if (!options.ignore_permissions) {
|
||||||
var dir: Self = undefined;
|
try fil.chmod(self.hdr.permissions);
|
||||||
try rdr.readSliceEndian(Self, @ptrCast(&dir), .little);
|
try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
|
||||||
return dir;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
/// Extract the inode file contents to the given path threadedly.
|
||||||
|
/// pool is used to spawn threads.
|
||||||
|
///
|
||||||
|
/// Assumes the inode is a file or ext_file type.
|
||||||
|
fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions, pool: *Pool) !void {
|
||||||
|
var fil = try std.fs.cwd().createFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
var data = try self.threadedDataReader(alloc, archive);
|
||||||
|
try data.extractThreaded(fil, pool);
|
||||||
|
|
||||||
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
|
try fil.updateTimes(time, time);
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.chmod(self.hdr.permissions);
|
||||||
|
try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates the symlink described by the inode.
|
||||||
|
///
|
||||||
|
/// Assumes the inode is a symlink or ext_symlink type.
|
||||||
|
fn extractSymlink(self: Inode, path: []const u8) !void {
|
||||||
|
const target = switch (self.data) {
|
||||||
|
.symlink => |s| s.target,
|
||||||
|
.ext_symlink => |s| s.target,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
try std.fs.cwd().symLink(target, path, .{});
|
||||||
|
}
|
||||||
|
/// Creates the device described by the inode.
|
||||||
|
///
|
||||||
|
/// Optionally set owner & permissions.
|
||||||
|
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
|
||||||
|
fn extractDevice(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var mode: u32 = undefined;
|
||||||
|
var dev: u32 = 0;
|
||||||
|
switch (self.data) {
|
||||||
|
.char_dev => |d| {
|
||||||
|
mode = std.posix.S.IFCHR;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.ext_char_dev => |d| {
|
||||||
|
mode = std.posix.S.IFCHR;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.block_dev => |d| {
|
||||||
|
mode = std.posix.S.IFBLK;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.ext_block_dev => |d| {
|
||||||
|
mode = std.posix.S.IFBLK;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.fifo, .ext_fifo => mode = std.posix.S.IFIFO,
|
||||||
|
.socket, .ext_socket => mode = std.posix.S.IFSOCK,
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
const res: std.os.linux.E = @enumFromInt(std.os.linux.mknod(@ptrCast(path), mode, dev));
|
||||||
|
switch (res) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
|
||||||
|
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
|
||||||
|
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
|
||||||
|
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
|
||||||
|
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
|
||||||
|
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
|
||||||
|
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
|
||||||
|
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
|
||||||
|
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
|
||||||
|
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
|
||||||
|
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
|
||||||
|
else => return blk: {
|
||||||
|
std.debug.print("unhandled mknod result: {}\n", .{res});
|
||||||
|
break :blk std.fs.Dir.MakeError.Unexpected;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var fil = try std.fs.cwd().openFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
|
try fil.updateTimes(time, time);
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.chmod(self.hdr.permissions);
|
||||||
|
try fil.chown(try archive.id_table.get(alloc, self.hdr.uid_idx), try archive.id_table.get(alloc, self.hdr.gid_idx));
|
||||||
|
}
|
||||||
|
if (!options.ignore_xattr) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const Reader = @import("std").Io.Reader;
|
||||||
|
|
||||||
|
pub const Dir = packed struct {
|
||||||
|
block_start: u32,
|
||||||
|
hard_links: u32,
|
||||||
|
size: u16,
|
||||||
|
block_offset: u16,
|
||||||
|
parent_num: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !Dir {
|
||||||
|
var d: Dir = undefined;
|
||||||
|
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtDir = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
size: u32,
|
||||||
|
block_start: u32,
|
||||||
|
parent_num: u32,
|
||||||
|
idx_count: u16,
|
||||||
|
block_offset: u16,
|
||||||
|
xattr_id: u32,
|
||||||
|
// index: []DirIndex
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !ExtDir {
|
||||||
|
var d: ExtDir = undefined;
|
||||||
|
try rdr.readSliceEndian(Dir, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
pub const BlockSize = packed struct {
|
||||||
|
size: u24,
|
||||||
|
uncompressed: bool,
|
||||||
|
_: u7,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const File = struct {
|
||||||
|
block_start: u32, // bytes 0-3
|
||||||
|
frag_idx: u32, // bytes 4-7
|
||||||
|
frag_block_offset: u32, // bytes 8-11
|
||||||
|
size: u32, // bytes 12-15
|
||||||
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||||
|
var start: [16]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&start);
|
||||||
|
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 = size / block_size;
|
||||||
|
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||||
|
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||||
|
errdefer alloc.free(sizes);
|
||||||
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
|
return .{
|
||||||
|
.block_start = std.mem.readInt(u32, start[0..4], .little),
|
||||||
|
.frag_idx = frag_idx,
|
||||||
|
.frag_block_offset = std.mem.readInt(u32, start[8..12], .little),
|
||||||
|
.size = size,
|
||||||
|
.block_sizes = sizes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: File, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.block_sizes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtFile = struct {
|
||||||
|
block_start: u64, // bytes 0-7
|
||||||
|
size: u64, // bytes 8-15
|
||||||
|
sparse: u64, // bytes 16-23
|
||||||
|
hard_links: u32, // bytes 24-27
|
||||||
|
frag_idx: u32, // bytes 28-31
|
||||||
|
frag_block_offset: u32, // bytes 32-35
|
||||||
|
xattr_idx: u32, // bytes 36-39
|
||||||
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !ExtFile {
|
||||||
|
var start: [40]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&start);
|
||||||
|
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(size / block_size);
|
||||||
|
if (size % block_size != 0 and frag_idx == 0xFFFFFFFF) num_blocks += 1;
|
||||||
|
const sizes = try alloc.alloc(BlockSize, num_blocks);
|
||||||
|
errdefer alloc.free(sizes);
|
||||||
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
|
return .{
|
||||||
|
.block_start = std.mem.readInt(u64, start[0..8], .little),
|
||||||
|
.size = size,
|
||||||
|
.sparse = std.mem.readInt(u64, start[16..24], .little),
|
||||||
|
.hard_links = std.mem.readInt(u32, start[24..28], .little),
|
||||||
|
.frag_idx = frag_idx,
|
||||||
|
.frag_block_offset = std.mem.readInt(u32, start[32..36], .little),
|
||||||
|
.xattr_idx = std.mem.readInt(u32, start[36..40], .little),
|
||||||
|
.block_sizes = sizes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: ExtFile, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.block_sizes);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
pub const Symlink = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
target: []const u8,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
|
||||||
|
var start: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&start);
|
||||||
|
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
||||||
|
const target = try alloc.alloc(u8, target_size + 1);
|
||||||
|
errdefer alloc.free(target);
|
||||||
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
|
return .{
|
||||||
|
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
||||||
|
.target = target,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtSymlink = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
target: []const u8,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
|
||||||
|
var start: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&start);
|
||||||
|
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
||||||
|
const target = try alloc.alloc(u8, target_size + 1);
|
||||||
|
errdefer alloc.free(target);
|
||||||
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
|
var xattr_idx: u32 = undefined;
|
||||||
|
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
||||||
|
return .{
|
||||||
|
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
||||||
|
.target = target,
|
||||||
|
.xattr_idx = xattr_idx,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A block or character device.
|
||||||
|
pub const Dev = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
dev: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !Dev {
|
||||||
|
var d: Dev = undefined;
|
||||||
|
try rdr.readSliceEndian(Dev, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An extended block or character device.
|
||||||
|
pub const ExtDev = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
dev: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !ExtDev {
|
||||||
|
var d: ExtDev = undefined;
|
||||||
|
try rdr.readSliceEndian(ExtDev, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A socket or FIFO file.
|
||||||
|
pub const IPC = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !IPC {
|
||||||
|
var d: IPC = undefined;
|
||||||
|
try rdr.readSliceEndian(IPC, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An extended socket or FIFO file.
|
||||||
|
pub const ExtIPC = packed struct {
|
||||||
|
hard_links: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
|
||||||
|
pub fn read(rdr: *Reader) !ExtIPC {
|
||||||
|
var d: ExtIPC = undefined;
|
||||||
|
try rdr.readSliceEndian(ExtIPC, @ptrCast(&d), .little);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
};
|
||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
|
|
||||||
const DataBlock = @import("inode.zig").DataBlock;
|
|
||||||
const InodeRef = @import("inode.zig").Ref;
|
|
||||||
const DecompCache = @import("decomp_cache.zig");
|
|
||||||
const MetadataReader = @import("meta_rdr.zig");
|
|
||||||
|
|
||||||
pub fn stateless(comptime T: anytype, io: Io, cache: *DecompCache, table_start: u64, idx: u32) !T {
|
|
||||||
const PER_BLOCK = 8192 / @sizeOf(T);
|
|
||||||
|
|
||||||
const block = idx / PER_BLOCK;
|
|
||||||
const block_idx = idx % PER_BLOCK;
|
|
||||||
|
|
||||||
const offset_offset = table_start + (block * 8);
|
|
||||||
const offset: u64 = std.mem.readInt(u64, cache.map.memory[offset_offset..][0..2], .little);
|
|
||||||
|
|
||||||
var meta: MetadataReader = .init(io, cache, offset);
|
|
||||||
defer meta.deinit(io);
|
|
||||||
try meta.discardAll(block_idx * @sizeOf(T));
|
|
||||||
|
|
||||||
var new: T = undefined;
|
|
||||||
try meta.interface.readSliceEndian(T, @ptrCast(&new), .little);
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Table(comptime T: anytype) type {
|
|
||||||
return struct {
|
|
||||||
const PER_BLOCK = 8192 / @sizeOf(T);
|
|
||||||
|
|
||||||
const LookupTable = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
cache: *DecompCache,
|
|
||||||
table_start: u64,
|
|
||||||
|
|
||||||
num: u32,
|
|
||||||
values: std.AutoHashMap(u32, []T),
|
|
||||||
mut: Io.RwLock = .init,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, cache: *DecompCache, table_start: u64, num_values: u32) LookupTable {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
|
|
||||||
.cache = cache,
|
|
||||||
.table_start = table_start,
|
|
||||||
|
|
||||||
.num = num_values,
|
|
||||||
.values = .init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *LookupTable) void {
|
|
||||||
var iter = self.values.valueIterator();
|
|
||||||
while (iter.next()) |v|
|
|
||||||
self.alloc.free(v);
|
|
||||||
self.values.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *LookupTable, io: Io, idx: u32) Error!T {
|
|
||||||
const block = idx / PER_BLOCK;
|
|
||||||
const block_idx = idx % PER_BLOCK;
|
|
||||||
{
|
|
||||||
try self.mut.lockShared(io);
|
|
||||||
defer self.mut.unlockShared(io);
|
|
||||||
|
|
||||||
const val = self.values.get(block);
|
|
||||||
if (val != null) return val.*[block_idx];
|
|
||||||
}
|
|
||||||
try self.mut.lock(io);
|
|
||||||
defer self.mut.unlock(io);
|
|
||||||
|
|
||||||
const val = try self.values.getOrPut(block);
|
|
||||||
if (val.found_existing)
|
|
||||||
return val.value_ptr.*[block_idx];
|
|
||||||
errdefer self.values.removeByPtr(val.key_ptr);
|
|
||||||
|
|
||||||
const offset_offset = self.table_start + (block * 8);
|
|
||||||
const offset: u64 = std.mem.readInt(u64, self.cache.map.memory[offset_offset..][0..2], .little);
|
|
||||||
|
|
||||||
var meta: MetadataReader = .init(io, self.cache, offset);
|
|
||||||
defer meta.deinit(io);
|
|
||||||
|
|
||||||
const size = if (block == ((self.num - 1) / PER_BLOCK))
|
|
||||||
self.num % PER_BLOCK
|
|
||||||
else
|
|
||||||
PER_BLOCK;
|
|
||||||
|
|
||||||
const new_block = try self.alloc.alloc(T, size);
|
|
||||||
errdefer self.alloc.free(new_block);
|
|
||||||
try meta.interface.readSliceEndian(T, new_block, .little);
|
|
||||||
|
|
||||||
val.value_ptr.* = new_block;
|
|
||||||
|
|
||||||
return new_block[block_idx];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types
|
|
||||||
|
|
||||||
pub const Error = error{} || std.mem.Allocator.Error;
|
|
||||||
|
|
||||||
pub const FragmentEntry = extern struct {
|
|
||||||
start: u64,
|
|
||||||
size: DataBlock,
|
|
||||||
_: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const XattrEntry = extern struct {
|
|
||||||
ref: InodeRef,
|
|
||||||
count: u32,
|
|
||||||
size: u32,
|
|
||||||
};
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Io = std.Io;
|
|
||||||
const Reader = Io.Reader;
|
|
||||||
const Writer = Io.Writer;
|
|
||||||
const Limit = Io.Limit;
|
|
||||||
|
|
||||||
const DecompCache = @import("decomp_cache.zig");
|
|
||||||
|
|
||||||
const MetadataReader = @This();
|
|
||||||
|
|
||||||
io: Io,
|
|
||||||
cache: *DecompCache,
|
|
||||||
|
|
||||||
cur_offset: u64 = 0,
|
|
||||||
next_offset: u64,
|
|
||||||
|
|
||||||
interface: Reader = .{
|
|
||||||
.buffer = &[0]u8{},
|
|
||||||
.end = 0,
|
|
||||||
.seek = 0,
|
|
||||||
.vtable = &.{
|
|
||||||
.stream = stream,
|
|
||||||
.discard = discard,
|
|
||||||
.readVec = readVec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
pub fn init(io: Io, cache: *DecompCache, start: u64) MetadataReader {
|
|
||||||
return .{
|
|
||||||
.io = io,
|
|
||||||
.cache = cache,
|
|
||||||
|
|
||||||
.next_offset = start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *MetadataReader, io: Io) void {
|
|
||||||
self.cache.finished(io, self.cur_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(self: *MetadataReader) !void {
|
|
||||||
self.cache.finished(self.io, self.cur_offset);
|
|
||||||
|
|
||||||
self.interface.seek = 0;
|
|
||||||
errdefer self.interface.end = 0;
|
|
||||||
|
|
||||||
const hdr: Header = @bitCast(std.mem.readInt(u16, self.cache.map.memory[self.next_offset..][0..2], .little));
|
|
||||||
self.cur_offset = self.next_offset + 2;
|
|
||||||
self.next_offset = self.cur_offset + hdr.size;
|
|
||||||
|
|
||||||
if (hdr.uncompressed) {
|
|
||||||
self.interface.buffer = self.cache.map.memory[self.cur_offset..][0..hdr.size];
|
|
||||||
self.interface.end = hdr.size;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.interface.buffer = try self.cache.get(self.io, self.cur_offset, hdr.size, 8192);
|
|
||||||
self.interface.end = self.interface.buffer.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
|
|
||||||
if (r.seek >= r.end) {
|
|
||||||
const self: *MetadataReader = @fieldParentPtr("interface", r);
|
|
||||||
self.advance() catch |err| {
|
|
||||||
std.debug.print("error advancing metadata reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const to_write = @min(r.end - r.seek, @intFromEnum(limit));
|
|
||||||
const wrote = try w.write(r.buffer[r.seek..][0..to_write]);
|
|
||||||
r.seek += wrote;
|
|
||||||
return wrote;
|
|
||||||
}
|
|
||||||
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
|
|
||||||
if (r.seek >= r.end) {
|
|
||||||
const self: *MetadataReader = @fieldParentPtr("interface", r);
|
|
||||||
self.advance() catch |err| {
|
|
||||||
std.debug.print("error advancing metadata reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const to_discard = @min(r.end - r.seek, @intFromEnum(limit));
|
|
||||||
r.seek += to_discard;
|
|
||||||
return to_discard;
|
|
||||||
}
|
|
||||||
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
|
|
||||||
if (r.seek >= r.end) {
|
|
||||||
const self: *MetadataReader = @fieldParentPtr("interface", r);
|
|
||||||
self.advance() catch |err| {
|
|
||||||
std.debug.print("error advancing metadata reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var total: usize = 0;
|
|
||||||
for (vec) |v| {
|
|
||||||
const to_copy = @min(r.end - r.seek, v.len);
|
|
||||||
@memcpy(v[0..to_copy], r.buffer[r.seek..][0..to_copy]);
|
|
||||||
r.seek += to_copy;
|
|
||||||
total += to_copy;
|
|
||||||
if (r.seek >= r.end)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types
|
|
||||||
|
|
||||||
const Header = packed struct(u16) {
|
|
||||||
size: u15,
|
|
||||||
uncompressed: bool,
|
|
||||||
};
|
|
||||||
+10
-1
@@ -5,6 +5,8 @@ const Writer = std.Io.Writer;
|
|||||||
|
|
||||||
const ExtractionOptions = @This();
|
const ExtractionOptions = @This();
|
||||||
|
|
||||||
|
/// The amount of threads to use for extraction.
|
||||||
|
threads: usize,
|
||||||
/// Don't set the file's owner & permissions after extraction
|
/// Don't set the file's owner & permissions after extraction
|
||||||
ignore_permissions: bool = false,
|
ignore_permissions: bool = false,
|
||||||
/// Don't set xattr values. Currently xattrs are never set anyway.
|
/// Don't set xattr values. Currently xattrs are never set anyway.
|
||||||
@@ -16,9 +18,16 @@ verbose: bool = false,
|
|||||||
/// Where to print verbose log.
|
/// Where to print verbose log.
|
||||||
verbose_writer: ?*Writer = null,
|
verbose_writer: ?*Writer = null,
|
||||||
|
|
||||||
pub const default: ExtractionOptions = .{};
|
pub const SingleThreaded: ExtractionOptions = .{ .threads = 1 };
|
||||||
|
/// Use std.Thread.getCpuCount threads for extraction.
|
||||||
|
pub fn Default() !ExtractionOptions {
|
||||||
|
return .{
|
||||||
|
.threads = try std.Thread.getCpuCount(),
|
||||||
|
};
|
||||||
|
}
|
||||||
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||||
return .{
|
return .{
|
||||||
|
.threads = try std.Thread.getCpuCount(),
|
||||||
.verbose = true,
|
.verbose = true,
|
||||||
.verbose_writer = wrt,
|
.verbose_writer = wrt,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1 @@
|
|||||||
pub const Archive = @import("archive.zig");
|
pub const Archive = @import("archive.zig");
|
||||||
pub const ExtractionOptions = @import("options.zig");
|
|
||||||
|
|
||||||
test {
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
std.testing.refAllDecls(Archive);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
fil: OffsetFile,
|
||||||
|
decomp: DecompFn,
|
||||||
|
|
||||||
|
tab_start: u64,
|
||||||
|
values: u32,
|
||||||
|
|
||||||
|
pub fn init(fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) Self {
|
||||||
|
return .{
|
||||||
|
.fil = fil,
|
||||||
|
.decomp = decomp,
|
||||||
|
|
||||||
|
.tab_start = tab_start,
|
||||||
|
.values = values,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Self, alloc: std.mem.Allocator, idx: u32) !T {
|
||||||
|
const block_num = idx / VALS_PER_BLOCK;
|
||||||
|
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
||||||
|
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;
|
||||||
|
var slice: [VALS_PER_BLOCK]T = undefined;
|
||||||
|
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
|
||||||
|
var offset: u64 = 0;
|
||||||
|
try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little);
|
||||||
|
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||||
|
try meta.interface.readSliceEndian(T, slice[0..slice_size], .little);
|
||||||
|
return slice[idx_offset];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
+6
-9
@@ -9,8 +9,7 @@ const TestArchive = "testing/LinuxPATest.sfs";
|
|||||||
test "Basics" {
|
test "Basics" {
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
const sfs: Archive = try .init(fil, 0);
|
||||||
defer sfs.deinit();
|
|
||||||
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
||||||
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
||||||
return error.BadSuperblock;
|
return error.BadSuperblock;
|
||||||
@@ -24,11 +23,10 @@ test "ExtractSingleFile" {
|
|||||||
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
var sfs: Archive = try .init(fil, 0);
|
||||||
defer sfs.deinit();
|
var test_fil = try sfs.open(std.testing.allocator, TestFile);
|
||||||
var test_fil = try sfs.open(TestFile);
|
|
||||||
defer test_fil.deinit();
|
defer test_fil.deinit();
|
||||||
try test_fil.extract(TestFileExtractLocation, .Default);
|
try test_fil.extract(std.testing.allocator, TestFileExtractLocation, try .Default());
|
||||||
//TODO: validate extracted file.
|
//TODO: validate extracted file.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +36,8 @@ test "ExtractCompleteArchive" {
|
|||||||
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
var sfs: Archive = try .init(fil, 0);
|
||||||
defer sfs.deinit();
|
try sfs.extract(std.testing.allocator, TestFullExtractLocation, try .Default());
|
||||||
try sfs.extract(TestFullExtractLocation, .Default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
const LinuxPATestCorrectSuperblock: Superblock = .{
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
//! 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, frag_offset: u32, frag_entry: ?FragEntry) DataReader {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.fil = archive.fil,
|
||||||
|
.decomp = archive.decomp,
|
||||||
|
.block_size = archive.super.block_size,
|
||||||
|
.blocks = blocks,
|
||||||
|
|
||||||
|
.frag = frag_entry,
|
||||||
|
.frag_offset = frag_offset,
|
||||||
|
.size = size,
|
||||||
|
.cur_offset = start,
|
||||||
|
.interface = .{
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,181 @@
|
|||||||
|
//! 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, frag_offset: u32, frag_entry: ?FragEntry) ThreadedDataReader {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.fil = archive.fil,
|
||||||
|
.decomp = archive.decomp,
|
||||||
|
.block_size = archive.super.block_size,
|
||||||
|
.blocks = blocks,
|
||||||
|
.size = size,
|
||||||
|
.start_offset = start,
|
||||||
|
|
||||||
|
.frag_offset = frag_offset,
|
||||||
|
.frag = frag_entry,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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,97 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
const Writer = std.Io.Writer;
|
||||||
|
const Limit = std.Io.Limit;
|
||||||
|
const StreamError = std.Io.Reader.StreamError;
|
||||||
|
|
||||||
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
|
|
||||||
|
const BlockHeader = packed struct {
|
||||||
|
size: u15,
|
||||||
|
uncompressed: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const This = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
rdr: *Reader,
|
||||||
|
decomp: DecompFn,
|
||||||
|
|
||||||
|
buf: [8192]u8 = undefined,
|
||||||
|
|
||||||
|
interface: Reader,
|
||||||
|
err: ?anyerror = null,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, rdr: *Reader, decomp: DecompFn) This {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.rdr = rdr,
|
||||||
|
.decomp = decomp,
|
||||||
|
.interface = .{
|
||||||
|
.buffer = &[0]u8{},
|
||||||
|
.end = 0,
|
||||||
|
.seek = 0,
|
||||||
|
.vtable = &.{
|
||||||
|
.stream = stream,
|
||||||
|
.discard = discard,
|
||||||
|
.readVec = readVec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(self: *This) !void {
|
||||||
|
self.interface.seek = 0;
|
||||||
|
var hdr: BlockHeader = undefined;
|
||||||
|
try self.rdr.readSliceEndian(BlockHeader, @ptrCast(&hdr), .little);
|
||||||
|
if (hdr.uncompressed) {
|
||||||
|
try self.rdr.readSliceEndian(u8, self.buf[0..hdr.size], .little);
|
||||||
|
self.interface.end = hdr.size;
|
||||||
|
self.interface.buffer = self.buf[0..hdr.size];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tmp_buf: [8192]u8 = undefined;
|
||||||
|
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.buffer = self.buf[0..self.interface.end];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) StreamError!usize {
|
||||||
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return StreamError.ReadFailed;
|
||||||
|
};
|
||||||
|
if (@intFromEnum(limit) == 0) return 0;
|
||||||
|
const to_write = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||||
|
const wrote = try wrt.write(self.buf[rdr.seek .. rdr.seek + to_write]);
|
||||||
|
self.interface.seek += wrote;
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
fn discard(rdr: *Reader, limit: Limit) error{ EndOfStream, ReadFailed }!usize {
|
||||||
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return StreamError.ReadFailed;
|
||||||
|
};
|
||||||
|
if (@intFromEnum(limit) == 0) return 0;
|
||||||
|
const to_skip = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
||||||
|
rdr.seek += to_skip;
|
||||||
|
return to_skip;
|
||||||
|
}
|
||||||
|
fn readVec(rdr: *Reader, vec: [][]u8) error{ EndOfStream, ReadFailed }!usize {
|
||||||
|
const self: *This = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.end == rdr.seek) self.advance() catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return StreamError.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], self.buf[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,20 @@
|
|||||||
|
//! A File where it's meaningful (to us) content starts at a given offset.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const File = std.fs.File;
|
||||||
|
const Reader = std.fs.File.Reader;
|
||||||
|
|
||||||
|
const OffsetFile = @This();
|
||||||
|
|
||||||
|
fil: File,
|
||||||
|
offset: u64,
|
||||||
|
|
||||||
|
pub fn init(fil: File, init_offset: u64) OffsetFile {
|
||||||
|
return .{ .fil = fil, .offset = init_offset };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readerAt(self: OffsetFile, offset: u64, buffer: []u8) !Reader {
|
||||||
|
var rdr = self.fil.reader(buffer);
|
||||||
|
try rdr.seekTo(self.offset + offset);
|
||||||
|
return rdr;
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
const flate = std.compress.flate;
|
|
||||||
const zstd = std.compress.zstd;
|
|
||||||
const xz = std.compress.xz;
|
|
||||||
const lzma = std.compress.lzma;
|
|
||||||
|
|
||||||
const Error = @import("decomp.zig").Error;
|
|
||||||
|
|
||||||
pub fn zlibDecompress(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
var buf: [flate.max_window_len]u8 = undefined;
|
|
||||||
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
var decomp: flate.Decompress = .init(&rdr, .zlib, &buf);
|
|
||||||
|
|
||||||
return decomp.reader.readSliceShort(out);
|
|
||||||
}
|
|
||||||
pub fn zstdDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
const buf = try alloc.alloc(u8, in.len + zstd.block_size_max);
|
|
||||||
defer alloc.free(buf);
|
|
||||||
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
var decomp: zstd.Decompress = .init(&rdr, buf, .{ .window_len = in.len });
|
|
||||||
|
|
||||||
return decomp.reader.readSliceShort(out);
|
|
||||||
}
|
|
||||||
pub fn lzmaDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
var decomp: lzma.Decompress = .initOptions(&rdr, alloc, &[0]u8{}, .{}, 2 * out.len);
|
|
||||||
defer decomp.deinit();
|
|
||||||
|
|
||||||
return decomp.reader.readSliceShort(out);
|
|
||||||
}
|
|
||||||
pub fn xzDecompress(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
var decomp: xz.Decompress = .init(&rdr, alloc, &[0]u8{});
|
|
||||||
defer decomp.deinit();
|
|
||||||
|
|
||||||
return decomp.reader.readSliceShort(out);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user