Compare commits
36
Commits
v0.0.3
...
more-redux
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d00acb03b9 | ||
|
|
7f09a8ff69 | ||
|
|
68d1a9d26d | ||
|
|
2e4364b863 | ||
|
|
2e33e19cdd | ||
|
|
173e76469f | ||
|
|
c5d75d3839 | ||
|
|
e0b697dd0b | ||
|
|
9f2a4bcc1e | ||
|
|
78dbaaed7e | ||
|
|
0039b19b4b | ||
|
|
4b2b7021c7 | ||
|
|
a1b9828578 | ||
|
|
8e4661c4c6 | ||
|
|
54aaf30ea5 | ||
|
|
df22cf6529 | ||
|
|
6b5c830234 | ||
|
|
116234cf9c | ||
|
|
4601e8f323 | ||
|
|
50cae8b63d | ||
|
|
8b8c9a772f | ||
|
|
f563648b20 | ||
|
|
8d4f3d72f8 | ||
|
|
0b6129f1ae | ||
|
|
7308faad36 | ||
|
|
c9499251f8 | ||
|
|
d470ca98e3 | ||
|
|
a606f5e11a | ||
|
|
a4e23a840d | ||
|
|
3a10572953 | ||
|
|
edfe919c1b | ||
|
|
4515610082 | ||
|
|
beca6a2ae6 | ||
|
|
760e11df0b | ||
|
|
5f629df47c | ||
|
|
b0160e005b |
@@ -13,19 +13,19 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- uses: mlugg/setup-zig@v2
|
- uses: mlugg/setup-zig@v2
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
run: sudo apt update && sudo apt install -y liblzma-dev liblzo2-dev
|
||||||
- name: Build normal version
|
- name: Build normal version
|
||||||
run: zig build -Drelease=true -Dversion=${{ github.ref_name }}
|
run: zig build --release=fast -Duse_zig_decomp=true -Dversion=${{ github.ref_name }}
|
||||||
- name: Move normal build out
|
- name: Move zig build out
|
||||||
run: mv zig-out/bin/unsquashfs ./
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-zig-libs
|
||||||
- name: Rebuild with C libraries
|
- name: Rebuild with C libraries
|
||||||
run: zig build -Drelease=true -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
|
run: zig build --release=fast -Dversion="${{ github.ref_name }}"
|
||||||
- name: Move C build out
|
- name: Move C build out
|
||||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
unsquashfs
|
unsquashfs-x86_64-zig-libs
|
||||||
unsquashfs-c-libs
|
unsquashfs-x86_64-c-libs
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ testing/
|
|||||||
|
|
||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
|
zig-pkg/
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Project-local debug tasks
|
||||||
|
//
|
||||||
|
// For more documentation on how to configure debug tasks,
|
||||||
|
// see: https://zed.dev/docs/debugger
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Build & Run",
|
||||||
|
|
||||||
|
"adapter": "CodeLLDB",
|
||||||
|
"request": "launch",
|
||||||
|
|
||||||
|
"build": {
|
||||||
|
"command": "zig",
|
||||||
|
"args": ["build", "-Ddebug=true", "-Dstatic=true"],
|
||||||
|
},
|
||||||
|
|
||||||
|
"program": "zig-out/bin/unsquashfs",
|
||||||
|
"args": [
|
||||||
|
"--force",
|
||||||
|
"-d",
|
||||||
|
"testing/TestExtractUnsquashfs",
|
||||||
|
"testing/LinuxPATest.sfs",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -6,19 +6,23 @@ A library and application to decompress or view squashfs archives.
|
|||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
Overall works, but currently is missing some features (see below). Extraction is a bit slow compared to the normal `unsquashfs` (from my _very_ basic testing it's about ~3x slower). Only properly work on Linux, any other OSes probably won't work fully and are untested.
|
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
||||||
|
|
||||||
## Build options
|
## Build options
|
||||||
|
|
||||||
> `-Duse_c_libs`
|
> `-Duse_c_libs=true`
|
||||||
|
|
||||||
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
Instead of using 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`
|
> `-Dallow_lzo=true`
|
||||||
|
|
||||||
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
||||||
|
|
||||||
> `-Dversion`
|
> `-Ddebug=true`
|
||||||
|
|
||||||
|
Sets various build options that make debugging easier. Specifically, debug optimization is forced, valgrind support is enabled, error tracing is enabled, stipping is disabled, and copmilation uses LLVM (this is due to some linking issues when on Debug optimization and is required for debugging tools such as `lldb`. In the future this may be removed from the debug flag).
|
||||||
|
|
||||||
|
> `-Dversion=0.0.0`
|
||||||
|
|
||||||
Sets the version of `unsquashfs` shown when `--version` is passed.
|
Sets the version of `unsquashfs` shown when `--version` is passed.
|
||||||
|
|
||||||
@@ -26,11 +30,29 @@ Sets the version of `unsquashfs` shown when `--version` is passed.
|
|||||||
|
|
||||||
Most features are present except for the following:
|
Most features are present except for the following:
|
||||||
|
|
||||||
* mod_time is not set on extraction
|
|
||||||
* xattrs are not applied on extraction
|
|
||||||
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||||
|
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
|
||||||
|
|
||||||
## Building considerations
|
## Performance
|
||||||
|
|
||||||
|
This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) 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,79 +1,116 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
|
const zig_decomp = b.option(bool, "zig_decomp", "Use zig standard library for decompression.") orelse false;
|
||||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support");
|
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support.") orelse false;
|
||||||
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
const static = b.option(bool, "static", "Statically link libraries.") orelse false;
|
||||||
|
const debug = b.option(bool, "debug", "Enable options to make debugging easier.") orelse false;
|
||||||
const zig_squashfs_options = b.addOptions();
|
const version_string_option = b.option([]const u8, "version", "Version of the library/binary") orelse "0.0.0-testing";
|
||||||
zig_squashfs_options.addOption(bool, "use_c_libs", use_c_libs_option orelse false);
|
|
||||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false);
|
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
const optimize: std.builtin.OptimizeMode = if (debug) .Debug else b.standardOptimizeOption(.{});
|
||||||
const mod = b.addModule("zig_squashfs", .{
|
|
||||||
.root_source_file = b.path("src/root.zig"),
|
const version: std.SemanticVersion = try .parse(if (version_string_option[0] == 'v') version_string_option[1..] else version_string_option);
|
||||||
|
|
||||||
|
const build_config = b.addOptions();
|
||||||
|
build_config.addOption(bool, "zig_decomp", zig_decomp);
|
||||||
|
build_config.addOption(bool, "allow_lzo", allow_lzo);
|
||||||
|
build_config.addOption(bool, "debug", debug);
|
||||||
|
build_config.addOption(std.SemanticVersion, "version", version);
|
||||||
|
|
||||||
|
const c_h = b.addTranslateC(.{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.link_libc = if (use_c_libs_option == true) true else false,
|
.root_source_file = b.path("src/c.h"),
|
||||||
});
|
});
|
||||||
mod.addOptions("config", zig_squashfs_options);
|
if (allow_lzo) c_h.defineCMacro("ALLOW_LZO", null);
|
||||||
if (use_c_libs_option == true) {
|
const c = c_h.createModule();
|
||||||
mod.linkSystemLibrary("zlib", .{});
|
|
||||||
mod.linkSystemLibrary("lzma", .{});
|
if (static) {
|
||||||
if (allow_lzo == true)
|
const zng = b.dependency("zlib_ng", .{
|
||||||
mod.linkSystemLibrary("minilzo", .{});
|
.target = target,
|
||||||
mod.linkSystemLibrary("lz4", .{});
|
.optimize = optimize,
|
||||||
mod.linkSystemLibrary("zstd", .{});
|
.zlib_compat = true,
|
||||||
|
});
|
||||||
|
c.linkLibrary(zng.artifact("zng"));
|
||||||
|
|
||||||
|
const zstd = b.dependency("zstd", .{ .optimize = optimize, .target = target });
|
||||||
|
c.linkLibrary(zstd.artifact("zstd"));
|
||||||
|
|
||||||
|
const lz4 = b.dependency("lz4", .{ .optimize = optimize, .target = target });
|
||||||
|
c.linkLibrary(lz4.artifact("lz4"));
|
||||||
|
|
||||||
|
const xz = b.dependency("xz", .{ .optimize = optimize, .target = target });
|
||||||
|
c.linkLibrary(xz.artifact("lzma"));
|
||||||
|
|
||||||
|
if (allow_lzo) {
|
||||||
|
const lzo = b.dependency("minilzo", .{ .optimize = optimize, .target = target });
|
||||||
|
c.linkLibrary(lzo.artifact("minilzo"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.linkSystemLibrary("z", .{});
|
||||||
|
c.linkSystemLibrary("zstd", .{});
|
||||||
|
c.linkSystemLibrary("lz4", .{});
|
||||||
|
c.linkSystemLibrary("lzma", .{});
|
||||||
|
if (allow_lzo)
|
||||||
|
c.linkSystemLibrary("minilzo", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
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",
|
||||||
.root_module = mod,
|
.root_module = b.addModule("zig_squashfs", .{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.valgrind = debug,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "c", .module = c },
|
||||||
|
.{ .name = "build_config", .module = build_config.createModule() },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
.use_llvm = debug,
|
||||||
|
.version = version,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "squashfs", .module = lib.root_module },
|
||||||
|
.{ .name = "build_config", .module = build_config.createModule() },
|
||||||
|
},
|
||||||
|
.valgrind = debug,
|
||||||
|
}),
|
||||||
|
.use_llvm = debug,
|
||||||
|
.version = version,
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
const run_step = b.step("run", "Run the app");
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
|
||||||
run_step.dependOn(&run_cmd.step);
|
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
|
||||||
if (b.args) |args| {
|
|
||||||
run_cmd.addArgs(args);
|
|
||||||
}
|
|
||||||
const mod_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
.root_module = mod,
|
.root_module = lib.root_module,
|
||||||
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
const exe_tests = b.addTest(.{
|
|
||||||
.root_module = exe.root_module,
|
|
||||||
});
|
|
||||||
const run_exe_tests = b.addRunArtifact(exe_tests);
|
|
||||||
const test_step = b.step("test", "Run tests");
|
const test_step = b.step("test", "Run tests");
|
||||||
test_step.dependOn(&run_mod_tests.step);
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
test_step.dependOn(&run_exe_tests.step);
|
|
||||||
|
// zls build check steps
|
||||||
|
const lib_check = b.addLibrary(.{
|
||||||
|
.name = "squashfs",
|
||||||
|
.root_module = lib.root_module,
|
||||||
|
.use_llvm = debug,
|
||||||
|
});
|
||||||
|
const exe_check = b.addExecutable(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
|
.root_module = exe.root_module,
|
||||||
|
.use_llvm = debug,
|
||||||
|
});
|
||||||
|
const check = b.step("check", "Check if unsquashfs compiles");
|
||||||
|
check.dependOn(&lib_check.step);
|
||||||
|
check.dependOn(&exe_check.step);
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-36
@@ -1,43 +1,25 @@
|
|||||||
.{
|
.{
|
||||||
.name = .squashfs,
|
.name = .squashfs,
|
||||||
.version = "0.0.1",
|
.version = "0.0.6",
|
||||||
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.16.0",
|
||||||
// This field is optional.
|
|
||||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
|
||||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
|
||||||
// Once all dependencies are fetched, `zig build` no longer requires
|
|
||||||
// internet connectivity.
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
.zlib_ng = .{
|
||||||
//.example = .{
|
.url = "git+https://github.com/CalebQ42/zig-zlib-ng#915db2dbd4a540d8a933336d67520b786f168f01",
|
||||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
.hash = "zlib_ng-2.3.3-pre1-2HYS4MlFAADc-eSZv4_xjkxZRCdGUVOYZKdtqhybstgk",
|
||||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
},
|
||||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
.zstd = .{
|
||||||
// // which will prevent zig from using it.
|
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-2#4ccb01e6fceae86fb9fe36c60b8a103cdc058155",
|
||||||
// .url = "https://example.com/foo.tar.gz",
|
.hash = "zstd-1.5.7-2-KEItkAMwAADNhCbGaImKrjXjnSA51jcqEbGCA3RasVZ9",
|
||||||
//
|
},
|
||||||
// // This is computed from the file contents of the directory of files that is
|
.lz4 = .{
|
||||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
.url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102",
|
||||||
// // `paths`.
|
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
|
||||||
// //
|
},
|
||||||
// // This field is the source of truth; packages do not come from a `url`; they
|
.minilzo = .{
|
||||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
.url = "git+https://github.com/CalebQ42/zig-minilzo.git#f72a0e20a30fad38cd10104f393c8f3440478697",
|
||||||
// // obtain a package matching this `hash`.
|
.hash = "minilzo-2.10.0-Ij7BO7YLAABd6oK2YdimXwvFTOC8wD1sNm0PHGoRNEwR",
|
||||||
// //
|
},
|
||||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
|
||||||
// .hash = "...",
|
|
||||||
//
|
|
||||||
// // When this is provided, the package is found in a directory relative to the
|
|
||||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
|
||||||
// // computed. This field and `url` are mutually exclusive.
|
|
||||||
// .path = "foo",
|
|
||||||
//
|
|
||||||
// // When this is set to `true`, a package is declared to be lazily
|
|
||||||
// // fetched. This makes the dependency only get fetched if it is
|
|
||||||
// // actually used.
|
|
||||||
// .lazy = false,
|
|
||||||
//},
|
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
zig test \
|
|
||||||
-lc \
|
|
||||||
-lz \
|
|
||||||
-llzma \
|
|
||||||
-lminilzo \
|
|
||||||
-llz4 \
|
|
||||||
-lzstd \
|
|
||||||
src/test.zig
|
|
||||||
+127
-144
@@ -1,168 +1,151 @@
|
|||||||
//! A squashfs archive read from a file.
|
|
||||||
//! Can be used to directly access File's contents or extract to the filesystem.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const Io = std.Io;
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const Decomp = @import("decomp.zig");
|
const util = @import("utils/util.zig");
|
||||||
const ExtractionOptions = @import("options.zig");
|
|
||||||
|
const File = @import("file.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const InodeRef = Inode.Ref;
|
const Decomp = @import("decomp.zig");
|
||||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
const Options = @import("options.zig");
|
||||||
const SfsFile = @import("file.zig");
|
const Decompress = @import("utils/decompress.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();
|
||||||
|
|
||||||
// 4 Gigs
|
map: Io.File.MemoryMap,
|
||||||
const DEFAULT_MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
|
||||||
|
|
||||||
parent_alloc: std.mem.Allocator,
|
super: Super,
|
||||||
alloc: std.heap.ThreadSafeAllocator,
|
root_inode_ref: Inode.Reference,
|
||||||
// alloc: std.heap.FixedBufferAllocator,
|
|
||||||
// fixed_buf: []u8,
|
|
||||||
thread_count: usize,
|
|
||||||
|
|
||||||
fil: OffsetFile,
|
pub fn init(io: Io, file: Io.File, offset: u64) !Archive {
|
||||||
|
var map = try file.createMemoryMap(io, .{
|
||||||
|
.len = try file.length(io) - offset,
|
||||||
|
.offset = offset,
|
||||||
|
.protection = .{ .read = true },
|
||||||
|
});
|
||||||
|
|
||||||
super: Superblock,
|
var superblock = util.readValue(Superblock, map.memory[0..@sizeOf(Superblock)]);
|
||||||
|
const super = try superblock.checkAndMinimize();
|
||||||
|
|
||||||
setup: bool = false,
|
|
||||||
|
|
||||||
decomp: Decomp.DecompFn,
|
|
||||||
|
|
||||||
frag_table: Table(FragEntry) = undefined,
|
|
||||||
id_table: Table(u16) = undefined,
|
|
||||||
export_table: Table(InodeRef) = undefined,
|
|
||||||
|
|
||||||
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
|
||||||
return initAdvanced(
|
|
||||||
alloc,
|
|
||||||
fil,
|
|
||||||
0,
|
|
||||||
try std.Thread.getCpuCount(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/// Create the Archive dictating the amount of threads used for extraction.
|
|
||||||
/// If you're planning on only interacting with a small number of files, it should be fine to use few (or one) threads.
|
|
||||||
pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: usize) !Archive {
|
|
||||||
var super: Superblock = undefined;
|
|
||||||
const red = try fil.pread(@ptrCast(&super), offset);
|
|
||||||
std.debug.assert(red == @sizeOf(Superblock));
|
|
||||||
try super.validate();
|
|
||||||
// const fixed_buf = try alloc.alloc(u8, mem);
|
|
||||||
return .{
|
return .{
|
||||||
.parent_alloc = alloc,
|
.map = map,
|
||||||
.alloc = .{ .child_allocator = alloc },
|
|
||||||
// .fixed_buf = fixed_buf,
|
|
||||||
.thread_count = if (threads > 0) threads else try std.Thread.getCpuCount(),
|
|
||||||
.fil = .init(fil, offset),
|
|
||||||
.decomp = switch (super.compression) {
|
|
||||||
.gzip => Decomp.gzipDecompress,
|
|
||||||
.lzma => Decomp.lzmaDecompress,
|
|
||||||
.xz => Decomp.xzDecompress,
|
|
||||||
.zstd => Decomp.zstdDecompress,
|
|
||||||
.lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported,
|
|
||||||
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
|
||||||
},
|
|
||||||
|
|
||||||
.super = super,
|
.super = super,
|
||||||
|
.root_inode_ref = superblock.root_inode_ref,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *Archive) void {
|
pub fn deinit(self: *Archive, io: Io) void {
|
||||||
// self.parent_alloc.free(self.fixed_buf);
|
self.map.destroy(io);
|
||||||
if (self.setup) {
|
|
||||||
self.frag_table.deinit();
|
|
||||||
self.export_table.deinit();
|
|
||||||
self.id_table.deinit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocator(self: *Archive) std.mem.Allocator {
|
pub fn root(self: *Archive, alloc: std.mem.Allocator) !File {
|
||||||
return self.alloc.allocator();
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.data = self.map.memory,
|
||||||
|
.super = self.super,
|
||||||
|
|
||||||
|
.inode = try .readLocation(
|
||||||
|
alloc,
|
||||||
|
self.map.memory,
|
||||||
|
self.root_inode_ref.start,
|
||||||
|
self.root_inode_ref.offset,
|
||||||
|
self.super,
|
||||||
|
),
|
||||||
|
.name = "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn open(self: *Archive, alloc: std.mem.Allocator, filepath: []const u8) !File {
|
||||||
|
var root_file = try self.root(alloc);
|
||||||
|
defer root_file.deinit();
|
||||||
|
|
||||||
|
return root.open(alloc, filepath);
|
||||||
|
}
|
||||||
|
pub fn extract(self: *Archive, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: Options) !void {
|
||||||
|
const root_inode: Inode = try .readLocation(
|
||||||
|
alloc,
|
||||||
|
self.map.memory,
|
||||||
|
self.root_inode_ref.start,
|
||||||
|
self.root_inode_ref.offset,
|
||||||
|
self.super,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.single_threaded)
|
||||||
|
return Decompress.single(alloc, io, self.map.memory, self.super, root_inode, ext_loc, options);
|
||||||
|
return Decompress.multi(alloc, io, self.map.memory, self.super, root_inode, ext_loc, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setupValues(self: *Archive) !void {
|
// Superblock
|
||||||
const alloc = self.allocator();
|
|
||||||
self.frag_table = try .init(alloc, self.fil, self.decomp, self.super.frag_start, self.super.frag_count);
|
|
||||||
self.id_table = try .init(alloc, self.fil, self.decomp, self.super.id_start, self.super.id_count);
|
|
||||||
self.export_table = try .init(alloc, self.fil, self.decomp, self.super.export_start, self.super.inode_count);
|
|
||||||
self.setup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(self: *Archive, idx: u32) !u16 {
|
const Superblock = extern struct {
|
||||||
if (!self.setup) try self.setupValues();
|
pub const MAGIC: u32 = 0x73717368;
|
||||||
return self.id_table.get(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn frag(self: *Archive, idx: u32) !FragEntry {
|
magic: u32,
|
||||||
if (!self.setup) try self.setupValues();
|
inode_count: u32,
|
||||||
return self.frag_table.get(idx);
|
mod_time: u32,
|
||||||
}
|
block_size: u32,
|
||||||
|
frag_count: u32,
|
||||||
|
compression: Decomp.Enum,
|
||||||
|
block_log: u16,
|
||||||
|
flags: packed struct(u16) {
|
||||||
|
inode_uncompressed: bool,
|
||||||
|
data_uncompressed: bool,
|
||||||
|
check: bool,
|
||||||
|
fragment_uncompressed: bool,
|
||||||
|
fragment_never: bool,
|
||||||
|
fragment_always: bool,
|
||||||
|
de_duplicate: bool,
|
||||||
|
exportable: bool,
|
||||||
|
xattr_uncompressed: bool,
|
||||||
|
xattr_never: bool,
|
||||||
|
compression_options: bool,
|
||||||
|
id_uncompressed: bool,
|
||||||
|
_: u4,
|
||||||
|
},
|
||||||
|
id_count: u16,
|
||||||
|
version_major: u16,
|
||||||
|
version_minor: u16,
|
||||||
|
root_inode_ref: Inode.Reference,
|
||||||
|
size: u64,
|
||||||
|
id_table_start: u64,
|
||||||
|
xattr_table_start: u64,
|
||||||
|
inode_table_start: u64,
|
||||||
|
dir_table_start: u64,
|
||||||
|
frag_table_start: u64,
|
||||||
|
export_table_start: u64,
|
||||||
|
|
||||||
pub fn inode(self: *Archive, num: u32) !Inode {
|
fn checkAndMinimize(self: Superblock) !Super {
|
||||||
if (!self.setup) try self.setupValues();
|
if (self.magic != MAGIC)
|
||||||
const ref = try self.export_table.get(num - 1);
|
return error.InvalidMagic;
|
||||||
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
if (self.version_major != 4 or self.version_minor != 0)
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
|
return error.IncompatibleVersion;
|
||||||
try meta.interface.discardAll(ref.block_offset);
|
if (std.math.log2(self.block_size) != self.block_log)
|
||||||
return try .read(self.allocator(), &meta.interface, self.super.block_size);
|
return error.BadBlockLog;
|
||||||
}
|
if (self.flags.check)
|
||||||
|
return error.BadCheckFlag;
|
||||||
|
|
||||||
pub fn root(self: *Archive) !SfsFile {
|
return .{
|
||||||
if (!self.setup) try self.setupValues();
|
.block_size = self.block_size,
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
.frag_count = self.frag_count,
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
.decomp_fn = try self.compression.func(),
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
.id_count = self.id_count,
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
.id_table_start = self.id_table_start,
|
||||||
return .init(self, in, "");
|
.xattr_table_start = self.xattr_table_start,
|
||||||
}
|
.inode_table_start = self.inode_table_start,
|
||||||
|
.dir_table_start = self.dir_table_start,
|
||||||
pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
.frag_table_start = self.frag_table_start,
|
||||||
if (!self.setup) try self.setupValues();
|
.export_table_start = self.export_table_start,
|
||||||
var root_fil = try self.root();
|
};
|
||||||
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
|
||||||
return root_fil.open(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
var alloc = self.allocator();
|
|
||||||
var ext_path: []u8 = undefined;
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind == .directory) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else return error.ExtractionPathExists;
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else {
|
|
||||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
|
||||||
try in.extractToThreaded(self, ext_path, options, self.thread_count);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
pub const Super = struct {
|
||||||
|
block_size: u32,
|
||||||
|
frag_count: u32,
|
||||||
|
decomp_fn: Decomp.Fn,
|
||||||
|
id_count: u16,
|
||||||
|
id_table_start: u64,
|
||||||
|
xattr_table_start: u64,
|
||||||
|
inode_table_start: u64,
|
||||||
|
dir_table_start: u64,
|
||||||
|
frag_table_start: u64,
|
||||||
|
export_table_start: u64,
|
||||||
|
};
|
||||||
|
|||||||
+41
-81
@@ -1,9 +1,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Writer = std.Io.Writer;
|
const Io = std.Io;
|
||||||
const builtin = @import("builtin");
|
const Writer = Io.Writer;
|
||||||
|
|
||||||
const config = @import("config");
|
const config = @import("build_config");
|
||||||
const squashfs = @import("zig_squashfs");
|
const squashfs = @import("squashfs");
|
||||||
|
const Archive = squashfs.Archive;
|
||||||
|
const Options = squashfs.Options;
|
||||||
|
|
||||||
//TODO: Add more options
|
//TODO: Add more options
|
||||||
const help_mgs =
|
const help_mgs =
|
||||||
@@ -14,95 +16,53 @@ 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
|
||||||
\\
|
\\
|
||||||
;
|
;
|
||||||
|
|
||||||
const errors = error{InvalidArguments};
|
var arc_loc: []const u8 = "";
|
||||||
|
var ext_loc: []const u8 = "squashfs-root";
|
||||||
|
|
||||||
var archive: []const u8 = "";
|
|
||||||
var extLoc: []const u8 = "squashfs-root";
|
|
||||||
var offset: u64 = 0;
|
var offset: u64 = 0;
|
||||||
var threads: u32 = 0;
|
var threads: usize = 0;
|
||||||
var verbose: bool = false;
|
var force: bool = false;
|
||||||
|
|
||||||
pub fn main() !void {
|
var options: Options = .default;
|
||||||
const alloc = std.heap.smp_allocator;
|
|
||||||
var stdout = std.fs.File.stdout();
|
pub fn main(init: std.process.Init) !void {
|
||||||
var out = stdout.writer(&[0]u8{});
|
var io = init.io;
|
||||||
defer out.interface.flush() catch {};
|
const alloc = init.gpa;
|
||||||
try handleArgs(alloc, &out.interface);
|
|
||||||
if (archive.len == 0) {
|
// TODO: process args
|
||||||
try out.interface.print("You must provide a squashfs archive\n", .{});
|
|
||||||
try out.interface.print(help_mgs, .{});
|
var limited_io: Io.Threaded = undefined;
|
||||||
return;
|
if (threads != 0) {
|
||||||
}
|
limited_io = if (threads == 1)
|
||||||
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
Io.Threaded.init_single_threaded
|
||||||
defer fil.close();
|
else
|
||||||
var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, threads); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
Io.Threaded.init(alloc, .{
|
||||||
defer arc.deinit();
|
.async_limit = .limited(threads),
|
||||||
try arc.extract(extLoc, if (verbose) .VerboseDefault(&out.interface) else .Default); //TODO: Handle error gracefully.
|
.concurrent_limit = .limited(threads),
|
||||||
|
.argv0 = .init(init.minimal.args),
|
||||||
|
.environ = init.minimal.environ,
|
||||||
|
});
|
||||||
|
io = limited_io.io();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
var fil = try Io.Dir.cwd().openFile(io, arc_loc, .{});
|
||||||
var args = try std.process.argsWithAllocator(alloc);
|
defer fil.close(io);
|
||||||
defer args.deinit();
|
|
||||||
_ = args.next(); // args[0] is the application launch command.
|
var arc: Archive = try .open(io, fil, offset);
|
||||||
while (args.next()) |arg| {
|
defer arc.close(io);
|
||||||
if (std.mem.eql(u8, arg, "-o")) {
|
|
||||||
const nxt = args.next();
|
try arc.extract(alloc, io, ext_loc, options);
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
|
||||||
try out.print("-o must be followed by a number\n", .{});
|
|
||||||
return errors.InvalidArguments;
|
|
||||||
}
|
|
||||||
offset = std.fmt.parseInt(u64, nxt.?, 10) catch {
|
|
||||||
try out.print("-o must be followed by a number\n", .{});
|
|
||||||
return errors.InvalidArguments;
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-d")) {
|
|
||||||
const nxt = args.next();
|
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
|
||||||
try out.print("-d must be followed by a location\n", .{});
|
|
||||||
return errors.InvalidArguments;
|
|
||||||
}
|
|
||||||
extLoc = nxt.?;
|
|
||||||
continue;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
|
||||||
const nxt = args.next();
|
|
||||||
if (nxt == null or nxt.?.len == 0) {
|
|
||||||
try out.print("-p must be followed by a number\n", .{});
|
|
||||||
return errors.InvalidArguments;
|
|
||||||
}
|
|
||||||
threads = std.fmt.parseInt(u32, nxt.?, 10) catch {
|
|
||||||
try out.print("-p must be followed by a number\n", .{});
|
|
||||||
return errors.InvalidArguments;
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
} else if (std.mem.eql(u8, arg, "-v")) {
|
|
||||||
verbose = true;
|
|
||||||
continue;
|
|
||||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
|
||||||
try out.print("zig-unsquashfs v", .{});
|
|
||||||
try config.version.format(out);
|
|
||||||
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
|
|
||||||
std.process.exit(0);
|
|
||||||
return;
|
|
||||||
} else if (std.mem.eql(u8, arg, "--help")) {
|
|
||||||
try out.print(help_mgs, .{});
|
|
||||||
std.process.exit(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (archive.len > 0) {
|
|
||||||
try out.print("you can only provide one file at a time\n", .{});
|
|
||||||
try out.print(help_mgs, .{});
|
|
||||||
return errors.InvalidArguments;
|
|
||||||
}
|
|
||||||
archive = arg;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <lz4.h>
|
||||||
|
#include <lzma.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
|
#ifdef ALLOW_LZO
|
||||||
|
#include <lzo/minilzo.h>
|
||||||
|
#endif
|
||||||
+103
-215
@@ -1,260 +1,148 @@
|
|||||||
//! Implementations for decompression.
|
|
||||||
//! TODO: change to vtable interface to allow for shared decompressors for better performance/resource usage.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Reader = std.Io.Reader;
|
const Io = std.Io;
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const config = if (builtin.is_test) .{
|
const c = @import("c");
|
||||||
.use_c_libs = builtin.link_libc == true,
|
const build = @import("build_config");
|
||||||
.allow_lzo = false, // Change once LZO compilation is fixed
|
|
||||||
} else @import("config");
|
|
||||||
|
|
||||||
const c = @cImport({
|
pub const Enum = enum(u16) {
|
||||||
@cInclude("zlib.h");
|
|
||||||
@cInclude("lzma.h");
|
|
||||||
@cInclude("lz4.h");
|
|
||||||
@cInclude("zstd.h");
|
|
||||||
@cInclude("zstd_errors.h");
|
|
||||||
if (config.allow_lzo)
|
|
||||||
@cInclude("lzo/minilzo.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const CompressionType = enum(u16) {
|
|
||||||
gzip = 1,
|
gzip = 1,
|
||||||
lzma,
|
lzma,
|
||||||
lzo,
|
lzo,
|
||||||
xz,
|
xz,
|
||||||
lz4,
|
lz4,
|
||||||
zstd,
|
zstd,
|
||||||
|
|
||||||
|
pub fn func(self: Enum) !Fn {
|
||||||
|
return switch (self) {
|
||||||
|
.gzip => if (build.zig_decomp) zigZlib else cZlib,
|
||||||
|
.lzma => if (build.zig_decomp) zigLzma else cLzma,
|
||||||
|
.lzo => if (build.zig_decomp or !build.allow_lzo) error.LzoUnsupported else cLzo,
|
||||||
|
.xz => if (build.zig_decomp) zigXz else cXz,
|
||||||
|
.lz4 => if (build.zig_decomp) error.Lz4Unsupported else cLz4,
|
||||||
|
.zstd => if (build.zig_decomp) zigZstd else cZstd,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pub const Fn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize;
|
||||||
|
|
||||||
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
pub const Error = Io.Reader.Error || std.mem.Allocator.Error;
|
||||||
|
|
||||||
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
// Actual decomp functions
|
||||||
|
|
||||||
|
fn zigZlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
var rdr: Io.Reader = .fixed(in);
|
||||||
|
var decomp: std.compress.flate.Decompress = .init(&rdr, .zlib, &[0]u8{});
|
||||||
|
|
||||||
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|
||||||
var rdr: Reader = .fixed(in);
|
|
||||||
const buf = try alloc.alloc(u8, out.len);
|
|
||||||
defer alloc.free(buf);
|
|
||||||
var decomp = std.compress.flate.Decompress.init(&rdr, .zlib, buf);
|
|
||||||
return decomp.reader.readSliceShort(out);
|
return decomp.reader.readSliceShort(out);
|
||||||
}
|
}
|
||||||
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
fn cZlib(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
_ = alloc;
|
var stream: c.z_stream = .{
|
||||||
var out_len: usize = out.len;
|
.avail_in = @truncate(in.len),
|
||||||
const res = c.uncompress(out.ptr, &out_len, in.ptr, in.len);
|
|
||||||
return switch (res) {
|
|
||||||
c.Z_OK => out_len,
|
|
||||||
c.Z_MEM_ERROR => error.NotEnoughMemory,
|
|
||||||
c.Z_BUF_ERROR => error.OutBufferTooSmall,
|
|
||||||
c.Z_DATA_ERROR => error.BadData,
|
|
||||||
else => error.UnknownResult,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma;
|
|
||||||
|
|
||||||
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,
|
.next_in = in.ptr,
|
||||||
.avail_in = in.len,
|
.avail_out = @truncate(out.len),
|
||||||
.next_out = out.ptr,
|
.next_out = out.ptr,
|
||||||
.avail_out = out.len,
|
|
||||||
};
|
};
|
||||||
var res = c.lzma_alone_decoder(&stream, in.len * 2);
|
const res = c.inflate(&stream, c.Z_FULL_FLUSH);
|
||||||
switch (res) {
|
if (res != c.Z_OK)
|
||||||
c.LZMA_OK => {},
|
return Error.ReadFailed;
|
||||||
c.LZMA_MEM_ERROR => return error.LzmaMemoryError,
|
return stream.total_out;
|
||||||
c.LZMA_PROG_ERROR => return error.LzmaProgramError,
|
|
||||||
else => return error.UnknownResult,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
var rdr: Io.Reader = .fixed(in);
|
||||||
|
|
||||||
|
var decomp: std.compress.lzma.Decompress = .initOptions(&rdr, alloc, &[0]u8{}, .{}, 0);
|
||||||
|
defer decomp.deinit();
|
||||||
|
|
||||||
|
return decomp.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
|
fn cLzma(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
var stream: c.lzma_stream = .{
|
||||||
|
.avail_in = in.len,
|
||||||
|
.next_in = in.ptr,
|
||||||
|
.avail_out = out.len,
|
||||||
|
.next_out = out.ptr,
|
||||||
|
};
|
||||||
defer c.lzma_end(&stream);
|
defer c.lzma_end(&stream);
|
||||||
|
|
||||||
|
var res = c.lzma_alone_decoder(&stream, 0);
|
||||||
|
if (res != c.LZMA_OK)
|
||||||
|
return Error.ReadFailed;
|
||||||
|
|
||||||
while (res == c.LZMA_OK)
|
while (res == c.LZMA_OK)
|
||||||
res = c.lzma_code(&stream, c.LZMA_RUN);
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
return switch (res) {
|
if (res != c.LZMA_FINISH)
|
||||||
c.LZMA_STREAM_END => stream.total_out,
|
return Error.ReadFailed;
|
||||||
c.LZMA_MEM_ERROR => error.LzmaMemoryError,
|
|
||||||
c.LZMA_MEMLIMIT_ERROR => error.LzmaMemoryLimit,
|
return stream.total_out;
|
||||||
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 cLzo(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
|
||||||
// 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();
|
var res = c.lzo_init();
|
||||||
if (res != 0) return error.LzoInitFailed;
|
if (res != c.LZO_E_OK)
|
||||||
var out_len: usize = out.len;
|
return Error.ReadFailed;
|
||||||
res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null);
|
|
||||||
return switch (res) {
|
var len = out.len;
|
||||||
c.LZO_E_OK => out_len,
|
res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &len, null);
|
||||||
c.LZO_E_ERROR => error.LzoError,
|
if (res != c.LZO_E_OK)
|
||||||
c.LZO_E_OUT_OF_MEMORY => error.LzoOutOfMemory,
|
return Error.ReadFailed;
|
||||||
c.LZO_E_NOT_COMPRESSIBLE => error.LzoNotCompressible,
|
|
||||||
c.LZO_E_INPUT_OVERRUN => error.LzoInputOverrun,
|
return Error.ReadFailed;
|
||||||
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) Error!usize {
|
||||||
|
var rdr: Io.Reader = .fixed(in);
|
||||||
|
|
||||||
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
var decomp: std.compress.xz.Decompress = .init(&rdr, alloc, &[0]u8{});
|
||||||
var rdr: Reader = .fixed(in);
|
defer decomp.deinit();
|
||||||
var decomp = try std.compress.xz.decompress(alloc, rdr.adaptToOldInterface());
|
|
||||||
return decomp.read(out);
|
return decomp.reader.readSliceShort(out);
|
||||||
}
|
}
|
||||||
fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
fn cXz(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
_ = alloc;
|
|
||||||
var stream: c.lzma_stream = .{
|
var stream: c.lzma_stream = .{
|
||||||
.next_in = in.ptr,
|
|
||||||
.avail_in = in.len,
|
.avail_in = in.len,
|
||||||
.next_out = out.ptr,
|
.next_in = in.ptr,
|
||||||
.avail_out = out.len,
|
.avail_out = out.len,
|
||||||
|
.next_out = out.ptr,
|
||||||
};
|
};
|
||||||
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);
|
defer c.lzma_end(&stream);
|
||||||
|
|
||||||
|
var res = c.lzma_stream_decoder(&stream, 0, 0);
|
||||||
|
if (res != c.LZMA_OK)
|
||||||
|
return Error.ReadFailed;
|
||||||
|
|
||||||
while (res == c.LZMA_OK)
|
while (res == c.LZMA_OK)
|
||||||
res = c.lzma_code(&stream, c.LZMA_RUN);
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
return switch (res) {
|
if (res != c.LZMA_FINISH)
|
||||||
c.LZMA_STREAM_END => stream.total_out,
|
return Error.ReadFailed;
|
||||||
c.LZMA_MEM_ERROR => error.LzmaMemoryError,
|
|
||||||
c.LZMA_MEMLIMIT_ERROR => error.LzmaMemoryLimit,
|
return stream.total_out;
|
||||||
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 cLz4(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
|
||||||
// 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));
|
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.
|
if (res < 0) return Error.ReadFailed;
|
||||||
return error.Lz4DecompressFailed;
|
|
||||||
|
return @abs(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const zstdDecompress = if (config.use_c_libs) cZstd else zigZstd;
|
fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
|
var rdr: Io.Reader = .fixed(in);
|
||||||
|
|
||||||
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);
|
const buf = try alloc.alloc(u8, 1024 * 1024);
|
||||||
defer alloc.free(buf);
|
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{
|
var decomp: std.compress.zstd.Decompress = .init(&rdr, buf, .{
|
||||||
Generic,
|
.window_len = 1024 * 1024,
|
||||||
PrefixUnknown,
|
});
|
||||||
VersionUnsupported,
|
|
||||||
FrameParameterUnsupported,
|
return decomp.reader.readSliceShort(out);
|
||||||
FrameParameterWindowTooLarge,
|
}
|
||||||
CorruptionDetected,
|
fn cZstd(_: std.mem.Allocator, in: []u8, out: []u8) Error!usize {
|
||||||
ChecksumWrong,
|
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
|
||||||
LiteralsHeaderWrong,
|
if (c.ZSTD_isError(res) == 1) {
|
||||||
DictionaryCorrupted,
|
std.debug.print("{s}\n", .{c.ZSTD_getErrorName(res)});
|
||||||
DictionaryWrong,
|
return Error.ReadFailed;
|
||||||
DictionaryCreationFailed,
|
}
|
||||||
ParameterUnsupported,
|
return res;
|
||||||
ParameterCombinationUnsupported,
|
}
|
||||||
ParameterOutOfBound,
|
|
||||||
TableLogTooLarge,
|
|
||||||
MaxSymbolValueTooLarge,
|
|
||||||
MaxSymbolValueTooSmall,
|
|
||||||
CannotProduceUncompressedBlock,
|
|
||||||
StabilityConditionNotRespected,
|
|
||||||
StageWrong,
|
|
||||||
InitMissing,
|
|
||||||
MemoryAllocation,
|
|
||||||
WorkSpaceTooSmall,
|
|
||||||
DstSizeTooSmall,
|
|
||||||
SrcSizeWrong,
|
|
||||||
DstBufferNull,
|
|
||||||
NoForwardProgressDestFull,
|
|
||||||
NoForwardProgressInputEmpty,
|
|
||||||
FrameIndexTooLarge,
|
|
||||||
SeekableIo,
|
|
||||||
DstBufferWrong,
|
|
||||||
SrcBufferWrong,
|
|
||||||
SequenceProducerFailed,
|
|
||||||
ExternalSequencesInvalid,
|
|
||||||
MaxCode,
|
|
||||||
};
|
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const utils = @import("utils/util.zig");
|
||||||
|
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
|
||||||
|
const Directory = @This();
|
||||||
|
|
||||||
|
entries: []Entry,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, size: u32) !Directory {
|
||||||
|
var red: u32 = 3;
|
||||||
|
|
||||||
|
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 10);
|
||||||
|
errdefer out.deinit(alloc);
|
||||||
|
|
||||||
|
while (red < size) {
|
||||||
|
const hdr: Header = try utils.readValueRdr(Header, rdr);
|
||||||
|
red += @sizeOf(Header);
|
||||||
|
|
||||||
|
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||||
|
|
||||||
|
for (0..hdr.count + 1) |_| {
|
||||||
|
const raw: RawEntry = try utils.readValueRdr(RawEntry, rdr);
|
||||||
|
|
||||||
|
const name = try alloc.alloc(u8, raw.name_size + 1);
|
||||||
|
errdefer alloc.free(name);
|
||||||
|
|
||||||
|
try rdr.readSliceAll(name);
|
||||||
|
|
||||||
|
out.addOneAssumeCapacity().* = .{
|
||||||
|
.start = hdr.start,
|
||||||
|
.offset = raw.offset,
|
||||||
|
.type = raw.type,
|
||||||
|
.name = name,
|
||||||
|
};
|
||||||
|
|
||||||
|
red += @sizeOf(RawEntry) + raw.name_size + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .entries = try out.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 Entry = struct {
|
||||||
|
start: u32,
|
||||||
|
offset: u16,
|
||||||
|
type: Inode.Type,
|
||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Header = extern struct {
|
||||||
|
count: u32,
|
||||||
|
start: u32,
|
||||||
|
inode_num: u32,
|
||||||
|
};
|
||||||
|
const RawEntry = extern struct {
|
||||||
|
offset: u16,
|
||||||
|
inode_num_offset: i16,
|
||||||
|
type: Inode.Type,
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//! Directory entry from the directory table.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
|
|
||||||
const InodeType = @import("inode.zig").InodeType;
|
|
||||||
|
|
||||||
const Entry = @This();
|
|
||||||
|
|
||||||
const Header = extern struct { // use extern due to bad alignment with packed.
|
|
||||||
count: u32,
|
|
||||||
block_start: u32,
|
|
||||||
num: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
const RawEntry = packed struct {
|
|
||||||
offset: u16,
|
|
||||||
inode_offset: i16,
|
|
||||||
inode_type: InodeType,
|
|
||||||
name_size: u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
block_start: u32,
|
|
||||||
block_offset: u16,
|
|
||||||
num: u32,
|
|
||||||
inode_type: InodeType,
|
|
||||||
name: []const u8,
|
|
||||||
|
|
||||||
pub fn readDir(alloc: std.mem.Allocator, rdr: *Reader, size: u32) ![]Entry {
|
|
||||||
var cur_red: u32 = 3; // start at 3 due to "." & ".." being counted in the dir size.
|
|
||||||
var hdr: Header = undefined;
|
|
||||||
var raw: RawEntry = undefined;
|
|
||||||
var out: std.ArrayList(Entry) = try .initCapacity(alloc, 100); // Start out with a decent capacity instead of needing to allocate per header.
|
|
||||||
errdefer out.deinit(alloc);
|
|
||||||
while (cur_red < size) {
|
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
|
||||||
cur_red += @sizeOf(Header);
|
|
||||||
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
|
||||||
for (0..hdr.count + 1) |_| {
|
|
||||||
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
|
||||||
const name = try alloc.alloc(u8, raw.name_size + 1);
|
|
||||||
errdefer alloc.free(name);
|
|
||||||
try rdr.readSliceEndian(u8, name, .little);
|
|
||||||
const val = out.addOneAssumeCapacity();
|
|
||||||
val.* = .{
|
|
||||||
.block_start = hdr.block_start,
|
|
||||||
.block_offset = raw.offset,
|
|
||||||
.num = @abs(hdr.num + raw.offset),
|
|
||||||
.inode_type = raw.inode_type,
|
|
||||||
.name = name,
|
|
||||||
};
|
|
||||||
cur_red += @sizeOf(RawEntry) + raw.name_size + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out.toOwnedSlice(alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.name);
|
|
||||||
}
|
|
||||||
+131
-203
@@ -1,230 +1,158 @@
|
|||||||
//! A file/directory within the squashfs archive.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const File = std.fs.File;
|
const Io = std.Io;
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
|
||||||
const Mutex = std.Thread.Mutex;
|
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
|
||||||
const DirEntry = @import("dir_entry.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 Directory = @import("dir.zig");
|
||||||
const DataReader = @import("util/data.zig");
|
const MetadataReader = @import("utils/meta.zig");
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const Options = @import("options.zig");
|
||||||
|
const DataReader = @import("utils/data_reader.zig");
|
||||||
|
const Decompress = @import("utils/decompress.zig");
|
||||||
|
const Super = @import("archive.zig").Super;
|
||||||
|
|
||||||
const FileError = error{
|
const File = @This();
|
||||||
NotDirectory,
|
|
||||||
NotRegularFile,
|
|
||||||
NotSymlink,
|
|
||||||
NotDevice,
|
|
||||||
NotFound,
|
|
||||||
ExtractionPathExists,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SfsFile = @This();
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
archive: *Archive,
|
data: []u8,
|
||||||
|
super: Super,
|
||||||
|
|
||||||
inode: Inode,
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
inode: Inode,
|
||||||
|
|
||||||
|
pub fn copy(self: File, alloc: std.mem.Allocator) !File {
|
||||||
|
const new_name = try alloc.dupe(u8, self.name);
|
||||||
|
errdefer alloc.free(new_name);
|
||||||
|
|
||||||
/// Initialize a new File.
|
|
||||||
/// name is copied to the File so can be safely freed afterwards.
|
|
||||||
pub fn init(archive: *Archive, inode: Inode, name: []const u8) !SfsFile {
|
|
||||||
const new_name = try archive.allocator().alloc(u8, name.len);
|
|
||||||
@memcpy(new_name, name);
|
|
||||||
return .{
|
return .{
|
||||||
.archive = archive,
|
.alloc = alloc,
|
||||||
.inode = inode,
|
|
||||||
|
.data = self.data,
|
||||||
|
.super = self.super,
|
||||||
|
|
||||||
.name = new_name,
|
.name = new_name,
|
||||||
|
.inode = try self.inode.copy(alloc),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn fromEntry(archive: *Archive, entry: DirEntry) !SfsFile {
|
pub fn deinit(self: File) void {
|
||||||
var rdr = try archive.fil.readerAt(entry.block_start + archive.super.inode_start, &[0]u8{});
|
self.inode.deinit(self.alloc);
|
||||||
var meta: MetadataReader = .init(archive.allocator(), &rdr.interface, archive.decomp);
|
self.alloc.free(self.name);
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
|
||||||
const inode: Inode = try .read(archive.allocator(), &meta.interface, archive.super.block_size);
|
|
||||||
errdefer inode.deinit(archive.allocator());
|
|
||||||
return .init(archive, inode, entry.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: SfsFile) void {
|
/// Reader interface to read a regular file's contents.
|
||||||
var alloc = self.archive.allocator();
|
pub fn reader(self: File, alloc: std.mem.Allocator) !DataReader {
|
||||||
alloc.free(self.name);
|
return switch (self.inode.header.type) {
|
||||||
self.inode.deinit(alloc);
|
.file => |f| .init(
|
||||||
}
|
alloc,
|
||||||
|
self.data,
|
||||||
fn getEntries(self: SfsFile) ![]DirEntry {
|
self.super.decomp_fn,
|
||||||
return self.inode.dirEntries(self.archive.allocator(), self.archive.*);
|
self.super.block_size,
|
||||||
}
|
f.start,
|
||||||
|
f.size,
|
||||||
pub fn ownerUid(self: SfsFile) !u16 {
|
f.blocks,
|
||||||
return self.archive.id(self.inode.hdr.uid_idx);
|
),
|
||||||
}
|
.ext_file => |f| .init(
|
||||||
pub fn ownerGid(self: SfsFile) !u16 {
|
alloc,
|
||||||
return self.archive.id(self.inode.hdr.gid_idx);
|
self.data,
|
||||||
}
|
self.super.decomp_fn,
|
||||||
pub fn permissions(self: SfsFile) u16 {
|
self.super.block_size,
|
||||||
return self.inode.hdr.permissions;
|
f.start,
|
||||||
}
|
f.size,
|
||||||
|
f.blocks,
|
||||||
pub fn isRegular(self: SfsFile) bool {
|
),
|
||||||
return switch (self.inode.hdr.inode_type) {
|
else => error.NotRegularFile,
|
||||||
.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 open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
|
||||||
pub fn dataReader(self: SfsFile) !DataReader {
|
switch (self.inode.header.type) {
|
||||||
return self.inode.dataReader(self.archive);
|
.dir, .ext_dir => {},
|
||||||
|
else => return error.NotDirectory,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isDir(self: SfsFile) bool {
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
return switch (self.inode.hdr.inode_type) {
|
|
||||||
.dir, .ext_dir => true,
|
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
|
||||||
else => false,
|
return self.copy(alloc);
|
||||||
};
|
|
||||||
|
const first_element: []const u8 = std.mem.sliceTo(path, '/');
|
||||||
|
const final = first_element.len == path.len;
|
||||||
|
|
||||||
|
var dir_rdr: MetadataReader = .init(alloc, self.data[self.inode.data.dir.start + self.super.dir_table_start ..], self.super.decomp_fn);
|
||||||
|
try dir_rdr.interface.discardAll(self.inode.data.dir.offset);
|
||||||
|
|
||||||
|
const dir_size = self.inode.data.dir.size;
|
||||||
|
|
||||||
|
const entry: Directory.Entry = blk: {
|
||||||
|
var dir: Directory = try .read(alloc, &dir_rdr.interface, dir_size);
|
||||||
|
defer dir.deinit(alloc);
|
||||||
|
|
||||||
|
var entries = dir.entries;
|
||||||
|
var idx: usize = undefined;
|
||||||
|
|
||||||
|
while (entries.len > 1) {
|
||||||
|
idx = entries.len / 2;
|
||||||
|
const val = entries[idx];
|
||||||
|
|
||||||
|
switch (std.mem.order(u8, first_element, val.name)) {
|
||||||
|
.eq => break,
|
||||||
|
.lt => entries = entries[0..idx],
|
||||||
|
.gt => entries = entries[idx..],
|
||||||
}
|
}
|
||||||
pub fn iterate(self: SfsFile) !Iterator {
|
} else {
|
||||||
if (!self.isDir()) return FileError.NotDirectory;
|
if (!std.mem.eql(u8, first_element, entries[0].name))
|
||||||
|
return error.NotFound;
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!final) break :blk entries[idx];
|
||||||
|
|
||||||
|
const inode: Inode = try .readLocation(
|
||||||
|
alloc,
|
||||||
|
self.data,
|
||||||
|
entries[idx].start,
|
||||||
|
entries[idx].offset,
|
||||||
|
self.super,
|
||||||
|
);
|
||||||
|
errdefer inode.deinit(alloc);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.entries = try self.getEntries(),
|
.alloc = alloc,
|
||||||
.archive = self.archive,
|
|
||||||
|
.data = self.data,
|
||||||
|
.super = self.super,
|
||||||
|
|
||||||
|
.inode = inode,
|
||||||
|
.name = try alloc.dupe(u8, entries[idx].name),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
/// Open a sub-file/folder within a directory at the given path.
|
|
||||||
/// If path is "", ".", "/", or "./", this File is returned.
|
|
||||||
pub fn open(self: SfsFile, path: []const u8) !SfsFile {
|
|
||||||
if (!self.isDir()) return FileError.NotDirectory;
|
|
||||||
if (pathIsSelf(path)) return self;
|
|
||||||
|
|
||||||
// Recursively stip ending & leading path separators.
|
|
||||||
if (path[0] == '/') return self.open(path[1..]);
|
|
||||||
if (path[path.len - 1] == '/') return self.open(path[0 .. path.len - 1]);
|
|
||||||
|
|
||||||
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
|
|
||||||
const first_element = path[0..idx];
|
|
||||||
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]);
|
|
||||||
const entries = try self.getEntries();
|
|
||||||
defer {
|
|
||||||
var alloc = self.archive.allocator();
|
|
||||||
for (entries) |e| {
|
|
||||||
e.deinit(alloc);
|
|
||||||
}
|
|
||||||
alloc.free(entries);
|
|
||||||
}
|
|
||||||
var cur_slice = entries;
|
|
||||||
var split = cur_slice.len / 2;
|
|
||||||
while (cur_slice.len > 0) {
|
|
||||||
split = cur_slice.len / 2;
|
|
||||||
const comp = std.mem.order(u8, first_element, cur_slice[split].name);
|
|
||||||
switch (comp) {
|
|
||||||
.eq => {
|
|
||||||
var fil: SfsFile = try .fromEntry(self.archive, cur_slice[split]);
|
|
||||||
if (idx == path.len) {
|
|
||||||
return fil;
|
|
||||||
}
|
|
||||||
defer fil.deinit();
|
|
||||||
return fil.open(path[idx + 1 ..]);
|
|
||||||
},
|
|
||||||
.lt => cur_slice = cur_slice[0..split],
|
|
||||||
.gt => cur_slice = cur_slice[split + 1 ..],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FileError.NotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isSymlink(self: SfsFile) bool {
|
|
||||||
return switch (self.inode.hdr.inode_type) {
|
|
||||||
.symlink, .ext_symlink => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn symlinkPath(self: SfsFile) ![]const u8 {
|
|
||||||
if (!self.isSymlink()) return FileError.NotSymlink;
|
|
||||||
return switch (self.inode.data) {
|
|
||||||
.symlink => |s| s.target,
|
|
||||||
.ext_symlink => |s| s.target,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (entry.type != .dir)
|
||||||
|
return error.NotFound;
|
||||||
|
|
||||||
|
const transient_file: File = .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.data = self.data,
|
||||||
|
.super = self.super,
|
||||||
|
|
||||||
|
.inode = try .readLocation(
|
||||||
|
alloc,
|
||||||
|
self.data,
|
||||||
|
entry.start,
|
||||||
|
entry.offset,
|
||||||
|
self.super,
|
||||||
|
),
|
||||||
|
.name = "",
|
||||||
|
}; // We don't care about deiniting this file since dir inodes don't need to be deinited and we don't have an allocd name.
|
||||||
|
|
||||||
|
return transient_file.open(alloc, path[first_element.len + 1 ..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the File is a block or character device.
|
/// Extract the given File to the location.
|
||||||
pub fn isDevice(self: SfsFile) bool {
|
pub fn extract(self: File, alloc: std.mem.Allocator, io: Io, ext_loc: []const u8, options: Options) !void {
|
||||||
return switch (self.inode.hdr.inode_type) {
|
// TODO: do some basic processing to check if ext_loc is a folder & if self is regular file and adjust accordingly.
|
||||||
.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 (options.single_threaded)
|
||||||
/// If the gievn path is a folder, the File's contents will be extracted within.
|
return Decompress.single(alloc, io, self.map.memory, self.super, self.inode, ext_loc, options);
|
||||||
pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !void {
|
return Decompress.multi(alloc, io, self.map.memory, self.super, self.inode, ext_loc, options);
|
||||||
var alloc = self.archive.allocator();
|
|
||||||
var ext_path: []u8 = undefined;
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind == .directory) {
|
|
||||||
if (!self.isDir()) {
|
|
||||||
const has_end_sep = path[path.len - 1] == '/';
|
|
||||||
const alloc_size = if (has_end_sep)
|
|
||||||
path.len + self.name.len
|
|
||||||
else
|
|
||||||
path.len + self.name.len + 1;
|
|
||||||
ext_path = try alloc.alloc(u8, alloc_size);
|
|
||||||
@memcpy(ext_path[0..path.len], path);
|
|
||||||
@memcpy(ext_path[ext_path.len - self.name.len ..], self.name);
|
|
||||||
if (!has_end_sep) ext_path[path.len] = '/';
|
|
||||||
} else {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
}
|
}
|
||||||
} else return FileError.ExtractionPathExists;
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else {
|
|
||||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
||||||
return self.inode.extractToThreaded(self.archive, path, options, self.archive.thread_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function.
|
|
||||||
pub fn pathIsSelf(path: []const u8) bool {
|
|
||||||
if (path.len == 0) return true;
|
|
||||||
if (path.len == 1 and (path[0] == '/' or path[0] == '.')) return true;
|
|
||||||
if (path.len == 2 and (path[0] == '.' and path[1] == '/')) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Iterator = struct {
|
|
||||||
entries: []DirEntry,
|
|
||||||
archive: *Archive,
|
|
||||||
|
|
||||||
idx: u32 = 0,
|
|
||||||
|
|
||||||
pub fn next(self: *Iterator) !?SfsFile {
|
|
||||||
if (self.idx >= self.entries.len) return null;
|
|
||||||
defer self.idx += 1;
|
|
||||||
return try SfsFile.fromEntry(self.archive, self.entries[self.idx]);
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Iterator) void {
|
|
||||||
var alloc = self.archive.allocator();
|
|
||||||
for (self.entries) |e| {
|
|
||||||
e.deinit(alloc);
|
|
||||||
}
|
|
||||||
alloc.free(self.entries);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Lookup = @import("lookup.zig");
|
||||||
|
const Decomp = @import("decomp.zig");
|
||||||
|
const BlockSize = @import("inode.zig").BlockSize;
|
||||||
|
|
||||||
|
const FragCache = @This();
|
||||||
|
|
||||||
|
block_size: u32,
|
||||||
|
|
||||||
|
entries: Lookup.Table(Entry),
|
||||||
|
|
||||||
|
cache: std.AutoHashMapUnmanaged(u32, CacheData) = .empty,
|
||||||
|
|
||||||
|
mut: Io.Mutex = .init,
|
||||||
|
|
||||||
|
pub fn init(data: []u8, decomp: Decomp.Fn, block_size: u32, start: u64, count: u32) !FragCache {
|
||||||
|
return .{
|
||||||
|
.block_size = block_size,
|
||||||
|
|
||||||
|
.entries = try .init(data, decomp, start, count),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *FragCache, alloc: std.mem.Allocator) void {
|
||||||
|
self.entries.deinit(alloc);
|
||||||
|
var iter = self.cache.valueIterator();
|
||||||
|
while (iter.next()) |cache| {
|
||||||
|
if (cache.allocd)
|
||||||
|
alloc.free(cache.data);
|
||||||
|
}
|
||||||
|
self.cache.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *FragCache, alloc: std.mem.Allocator, io: Io, idx: u32) ![]u8 {
|
||||||
|
const cache = self.cache.get(idx);
|
||||||
|
if (cache != null) return cache.?.data;
|
||||||
|
|
||||||
|
const entry = try self.entries.get(alloc, io, idx);
|
||||||
|
|
||||||
|
const data = self.entries.data[entry.start..][0..entry.size.size];
|
||||||
|
|
||||||
|
if (entry.size.uncompressed)
|
||||||
|
return data;
|
||||||
|
|
||||||
|
try self.mut.lock(io);
|
||||||
|
defer self.mut.unlock(io);
|
||||||
|
|
||||||
|
const cache_data = try alloc.alloc(u8, self.block_size);
|
||||||
|
errdefer alloc.free(cache_data);
|
||||||
|
|
||||||
|
_ = try self.entries.decomp(alloc, data, cache_data);
|
||||||
|
|
||||||
|
try self.cache.put(alloc, idx, .{
|
||||||
|
.data = cache_data,
|
||||||
|
.allocd = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cache_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
pub const Entry = extern struct {
|
||||||
|
start: u64,
|
||||||
|
size: BlockSize,
|
||||||
|
_: u32,
|
||||||
|
};
|
||||||
|
const CacheData = struct {
|
||||||
|
data: []u8,
|
||||||
|
allocd: bool,
|
||||||
|
};
|
||||||
+248
-484
@@ -1,28 +1,74 @@
|
|||||||
//! A file-system object. Represents a File or directory.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Reader = std.Io.Reader;
|
const Io = std.Io;
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
|
||||||
const Pool = std.Thread.Pool;
|
|
||||||
const Mutex = std.Thread.Mutex;
|
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
const util = @import("utils/util.zig");
|
||||||
const DirEntry = @import("dir_entry.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");
|
|
||||||
|
|
||||||
pub const Ref = packed struct {
|
const MetadataReader = @import("utils/meta.zig");
|
||||||
block_offset: u16,
|
const Super = @import("archive.zig").Super;
|
||||||
block_start: u32,
|
|
||||||
|
const Inode = @This();
|
||||||
|
|
||||||
|
header: Header,
|
||||||
|
data: Data,
|
||||||
|
|
||||||
|
pub fn readLocation(alloc: std.mem.Allocator, data: []u8, start: u32, offset: u16, super: Super) !Inode {
|
||||||
|
var meta: MetadataReader = .init(alloc, data[super.inode_table_start + start ..], super.decomp_fn);
|
||||||
|
try meta.interface.discardAll(offset);
|
||||||
|
|
||||||
|
return read(alloc, &meta.interface, super.block_size);
|
||||||
|
}
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Io.Reader, block_size: u32) !Inode {
|
||||||
|
const hdr = try util.readValueRdr(Header, rdr);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.header = hdr,
|
||||||
|
.data = switch (hdr.type) {
|
||||||
|
.dir => .{ .dir = try .readBasic(rdr) },
|
||||||
|
.ext_dir => .{ .dir = try .readExt(rdr) },
|
||||||
|
.file => .{ .file = try .readBasic(rdr, alloc, block_size) },
|
||||||
|
.ext_file => .{ .file = try .readExt(rdr, alloc, block_size) },
|
||||||
|
.symlink, .ext_symlink => .{ .symlink = try .read(rdr, alloc) },
|
||||||
|
.block_dev, .char_dev => .{ .dev = try .readBasic(rdr) },
|
||||||
|
.ext_block_dev, .ext_char_dev => .{ .dev = try .readExt(rdr) },
|
||||||
|
.fifo, .socket => .{ .fifo = try .readBasic(rdr) },
|
||||||
|
.ext_fifo, .ext_socket => .{ .fifo = try .readExt(rdr) },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
|
||||||
|
var new_inode = self;
|
||||||
|
|
||||||
|
switch (new_inode.data) {
|
||||||
|
.file => |*f| f.blocks = try alloc.dupe(BlockSize, f.blocks),
|
||||||
|
.symlink => |*f| f.target = try alloc.dupe(u8, f.target),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_inode;
|
||||||
|
}
|
||||||
|
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||||
|
switch (self.data) {
|
||||||
|
.file => |f| alloc.free(f.blocks),
|
||||||
|
.symlink => |s| alloc.free(s.target),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
pub const Reference = packed struct(u64) {
|
||||||
|
offset: u16,
|
||||||
|
start: u32,
|
||||||
_: u16,
|
_: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const InodeType = enum(u16) {
|
pub const BlockSize = packed struct(u32) {
|
||||||
|
size: u23,
|
||||||
|
uncompressed: bool,
|
||||||
|
_: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Type = enum(u16) {
|
||||||
dir = 1,
|
dir = 1,
|
||||||
file,
|
file,
|
||||||
symlink,
|
symlink,
|
||||||
@@ -39,500 +85,218 @@ pub const InodeType = enum(u16) {
|
|||||||
ext_socket,
|
ext_socket,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const InodeData = union(InodeType) {
|
pub const Header = extern struct {
|
||||||
dir: dir.Dir,
|
type: Type,
|
||||||
file: file.File,
|
permission: u16,
|
||||||
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,
|
inode_num: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Inode = @This();
|
pub const Data = union(enum) {
|
||||||
|
dir: Dir,
|
||||||
|
file: File,
|
||||||
|
symlink: Symlink,
|
||||||
|
dev: Dev,
|
||||||
|
fifo: Fifo,
|
||||||
|
};
|
||||||
|
|
||||||
hdr: Header,
|
// Inode data types
|
||||||
data: InodeData,
|
|
||||||
|
pub const Dir = struct {
|
||||||
|
//RegularDir structure:
|
||||||
|
// start: u32,
|
||||||
|
// hard_links: u32,
|
||||||
|
// size: u16,
|
||||||
|
// offset: u16,
|
||||||
|
// parent_inode_num: u32,
|
||||||
|
// ExtDir structure
|
||||||
|
// hard_links: u32,
|
||||||
|
// size: u32,
|
||||||
|
// start: u32,
|
||||||
|
// parent_inode_num: u32,
|
||||||
|
// idx_count: u16,
|
||||||
|
// offset: u16,
|
||||||
|
// xattr_idx: u32,
|
||||||
|
// dir_indexes: []DirIndex,
|
||||||
|
|
||||||
|
hard_links: u32,
|
||||||
|
size: u32,
|
||||||
|
start: u32,
|
||||||
|
offset: u16,
|
||||||
|
xattr_idx: ?u32,
|
||||||
|
|
||||||
|
pub fn readBasic(rdr: *Io.Reader) !Dir {
|
||||||
|
var raw: [12]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&raw);
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
|
||||||
var hdr: Header = undefined;
|
|
||||||
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
|
||||||
return .{
|
return .{
|
||||||
.hdr = hdr,
|
.start = util.readValue(u32, raw[0..4]),
|
||||||
.data = switch (hdr.inode_type) {
|
.hard_links = util.readValue(u32, raw[4..8]),
|
||||||
.dir => .{ .dir = try .read(rdr) },
|
.size = util.readValue(u16, raw[8..10]),
|
||||||
.file => .{ .file = try .read(alloc, rdr, block_size) },
|
.offset = util.readValue(u16, raw[10..12]),
|
||||||
.symlink => .{ .symlink = try .read(alloc, rdr) },
|
.xattr_idx = null,
|
||||||
.block_dev => .{ .block_dev = try .read(rdr) },
|
|
||||||
.char_dev => .{ .char_dev = try .read(rdr) },
|
|
||||||
.fifo => .{ .fifo = try .read(rdr) },
|
|
||||||
.socket => .{ .socket = try .read(rdr) },
|
|
||||||
.ext_dir => .{ .ext_dir = try .read(rdr) },
|
|
||||||
.ext_file => .{ .ext_file = try .read(alloc, rdr, block_size) },
|
|
||||||
.ext_symlink => .{ .ext_symlink = try .read(alloc, rdr) },
|
|
||||||
.ext_block_dev => .{ .ext_block_dev = try .read(rdr) },
|
|
||||||
.ext_char_dev => .{ .ext_char_dev = try .read(rdr) },
|
|
||||||
.ext_fifo => .{ .ext_fifo = try .read(rdr) },
|
|
||||||
.ext_socket => .{ .ext_socket = try .read(rdr) },
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn readFromEntry(alloc: std.mem.Allocator, archive: *Archive, entry: DirEntry) !Inode {
|
pub fn readExt(rdr: *Io.Reader) !Dir {
|
||||||
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
var raw: [24]u8 = undefined;
|
||||||
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
try rdr.readSliceAll(&raw);
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
|
||||||
return read(alloc, &meta.interface, archive.super.block_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
return .{
|
||||||
switch (self.data) {
|
.hard_links = util.readValue(u32, raw[0..4]),
|
||||||
.file => |f| alloc.free(f.block_sizes),
|
.size = util.readValue(u32, raw[4..8]),
|
||||||
.ext_file => |f| alloc.free(f.block_sizes),
|
.start = util.readValue(u32, raw[8..12]),
|
||||||
.symlink => |s| alloc.free(s.target),
|
.offset = util.readValue(u16, raw[18..20]),
|
||||||
.ext_symlink => |s| alloc.free(s.target),
|
.xattr_idx = util.readValue(u32, raw[20..24]),
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the data reader for a file inode.
|
|
||||||
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !DataReader {
|
|
||||||
return switch (self.hdr.inode_type) {
|
|
||||||
.file => readerFromData(alloc, archive, self.data.file),
|
|
||||||
.ext_file => readerFromData(alloc, archive, self.data.ext_file),
|
|
||||||
else => error.NotRegularFile,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn readerFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !DataReader {
|
|
||||||
var out: DataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
|
||||||
if (data.frag_idx != 0xFFFFFFFF)
|
|
||||||
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
/// Get a threaded data reader for a file inode.
|
|
||||||
pub fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !ThreadedDataReader {
|
|
||||||
return switch (self.hdr.inode_type) {
|
|
||||||
.file => threadedReaderFromData(alloc, archive, self.data.file),
|
|
||||||
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
|
|
||||||
else => error.NotRegularFile,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !ThreadedDataReader {
|
|
||||||
var out: ThreadedDataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
|
||||||
if (data.frag_idx != 0xFFFFFFFF)
|
|
||||||
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the directory entries for a directory inode.
|
|
||||||
pub fn dirEntries(self: Inode, alloc: std.mem.Allocator, archive: Archive) ![]DirEntry {
|
|
||||||
return switch (self.hdr.inode_type) {
|
|
||||||
.dir => entriesFromData(alloc, archive, self.data.dir),
|
|
||||||
.ext_dir => entriesFromData(alloc, archive, self.data.ext_dir),
|
|
||||||
else => error.NotDirectory,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the inode to the given path. Single threaded.
|
|
||||||
pub fn extractTo(self: Inode, 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.extractTo(archive, path[0 .. path.len - 1], options);
|
|
||||||
std.fs.cwd().makeDir(path) catch |err| {
|
|
||||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
|
|
||||||
};
|
|
||||||
var alloc = archive.allocator();
|
|
||||||
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);
|
|
||||||
|
|
||||||
var inode: Inode = try readFromEntry(alloc, archive, entry);
|
|
||||||
defer inode.deinit(alloc);
|
|
||||||
try inode.extractTo(archive, new_path, options);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.file, .ext_file => try self.extractRegFile(archive.allocator(), archive, path, options),
|
|
||||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
|
||||||
else => try self.extractDevice(archive, path, options),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Parent = struct {
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
path: []const u8,
|
|
||||||
uid: u16,
|
|
||||||
gid: u16,
|
|
||||||
perm: u16,
|
|
||||||
mod_time: u32,
|
|
||||||
|
|
||||||
ignore_permissions: bool,
|
|
||||||
ignore_xattr: bool,
|
|
||||||
|
|
||||||
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(hdr.uid_idx),
|
|
||||||
.gid = try archive.id(hdr.gid_idx),
|
|
||||||
.perm = hdr.permissions,
|
|
||||||
.mod_time = hdr.mod_time,
|
|
||||||
|
|
||||||
.ignore_permissions = options.ignore_permissions,
|
|
||||||
.ignore_xattr = options.ignore_xattr,
|
|
||||||
|
|
||||||
.wg = .{ .state = .init(dir_size) },
|
|
||||||
};
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(p: *Parent) !void {
|
|
||||||
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 = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Extract the inode to the given path. Multi-threaded.
|
pub const File = struct {
|
||||||
/// Functions identically to extractTo on all but regular files and directories.
|
//RegularFile structure:
|
||||||
///
|
// start: u32,
|
||||||
/// If threads <= 1, then this just calls extractTo.
|
// frag_idx: u32,
|
||||||
pub fn extractToThreaded(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, threads: usize) !void {
|
// frag_offset: u32,
|
||||||
if (threads <= 1) return self.extractTo(archive, path, options);
|
// size: u32,
|
||||||
switch (self.hdr.inode_type) {
|
// block_sizes: []BlockSize,
|
||||||
.dir, .ext_dir => {
|
//ExtFile structure:
|
||||||
// Removing any trailing separators since that's the easiest path forward.
|
// start: u64,
|
||||||
if (path[path.len - 1] == '/') return self.extractToThreaded(archive, path[0 .. path.len - 1], options, threads);
|
// size: u64,
|
||||||
|
// sparse: u64,
|
||||||
|
// hard_links: u32,
|
||||||
|
// frag_idx: u32,
|
||||||
|
// frag_offset: u32,
|
||||||
|
// xattr_idx: u32,
|
||||||
|
// block_sizes: []BlockSize,
|
||||||
|
|
||||||
// Fixed Allocator
|
start: u64,
|
||||||
// const mem_buf = archive.allocator().alloc(u8, 2 * 1024 * 1024 * 1024);
|
frag_idx: u32,
|
||||||
// defer archive.allocator().free(mem_buf);
|
frag_offset: u32,
|
||||||
// var fixed_alloc: std.heap.FixedBufferAllocator = .init(mem_buf);
|
size: u64,
|
||||||
// const alloc = fixed_alloc.threadSafeAllocator();
|
blocks: []BlockSize,
|
||||||
|
xattr_idx: ?u32,
|
||||||
|
|
||||||
// Arena Allocator
|
pub fn readBasic(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File {
|
||||||
var arena_alloc: std.heap.ArenaAllocator = .init(archive.allocator());
|
var raw: [16]u8 = undefined;
|
||||||
defer arena_alloc.deinit();
|
try rdr.readSliceAll(&raw);
|
||||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
|
||||||
const alloc = thread_alloc.allocator();
|
|
||||||
|
|
||||||
var wg: WaitGroup = .{};
|
const frag_idx = util.readValue(u32, raw[4..8]);
|
||||||
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
|
const size = util.readValue(u32, raw[12..16]);
|
||||||
var pool: Pool = undefined;
|
|
||||||
try pool.init(.{ .allocator = alloc, .n_jobs = threads - 1 });
|
|
||||||
defer pool.deinit();
|
|
||||||
var out_err: ?anyerror = null;
|
|
||||||
|
|
||||||
wg.start();
|
const block_num = if (frag_idx == 0xFFFFFFFF)
|
||||||
self.extractThread(alloc, archive, path, options, &wg, &pool, &out_err, null);
|
try std.math.divCeil(usize, size, block_size)
|
||||||
pool.waitAndWork(&wg);
|
else
|
||||||
if (out_err != null) return out_err.?;
|
size / block_size;
|
||||||
|
|
||||||
var fil = try std.fs.cwd().openFile(path, .{});
|
const sizes = try alloc.alloc(BlockSize, block_num);
|
||||||
defer fil.close();
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
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(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.file, .ext_file => {
|
|
||||||
const alloc = archive.allocator();
|
|
||||||
|
|
||||||
var pool: Pool = undefined;
|
return .{
|
||||||
try pool.init(.{ .allocator = alloc, .n_jobs = threads });
|
.start = util.readValue(u32, raw[0..4]),
|
||||||
defer pool.deinit();
|
.frag_idx = frag_idx,
|
||||||
|
.frag_offset = util.readValue(u32, raw[8..12]),
|
||||||
try self.extractRegFileThreaded(alloc, archive, path, options, &pool);
|
.size = size,
|
||||||
|
.blocks = sizes,
|
||||||
var fil = try std.fs.cwd().openFile(path, .{});
|
.xattr_idx = null,
|
||||||
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(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
|
||||||
else => try self.extractDevice(archive, path, options),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract threadedly the inode to the path.
|
|
||||||
fn extractThread(
|
|
||||||
self: Inode,
|
|
||||||
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", .{ 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 entries = self.dirEntries(alloc, archive.*) catch |err| {
|
|
||||||
if (options.verbose)
|
|
||||||
options.verbose_writer.?.print("Error getting directory entries for inode #{} (extracting to {s}): {}\n", .{ self.hdr.num, path, err }) catch {};
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const p = Parent.create(alloc, self.hdr, archive, path, options, entries.len) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
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();
|
|
||||||
if (options.verbose)
|
|
||||||
options.verbose_writer.?.print("Error starting extraction thread: {}\n", .{err}) catch {};
|
|
||||||
out_err.* = err;
|
|
||||||
continue;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
pub fn readExt(rdr: *Io.Reader, alloc: std.mem.Allocator, block_size: u32) !File {
|
||||||
.file, .ext_file => {
|
var raw: [40]u8 = undefined;
|
||||||
self.extractRegFileThreaded(alloc, archive, path, options, pool) catch |err| {
|
try rdr.readSliceAll(&raw);
|
||||||
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(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;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Creates and writes the inode file contents to the given path.
|
|
||||||
/// Optionally set owner & permissions.
|
|
||||||
///
|
|
||||||
/// Assumes the inode is a file or ext_file type.
|
|
||||||
fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
|
||||||
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 time = @as(i128, self.hdr.mod_time) * 1000000000;
|
const frag_idx = util.readValue(u32, raw[28..32]);
|
||||||
try fil.updateTimes(time, time);
|
const size = util.readValue(u64, raw[8..16]);
|
||||||
if (!options.ignore_permissions) {
|
|
||||||
try fil.chmod(self.hdr.permissions);
|
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Extract the inode file contents to the given path threadedly.
|
|
||||||
/// pool is used to spawn threads.
|
|
||||||
///
|
|
||||||
/// Assumes the inode is a file or ext_file type.
|
|
||||||
fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions, pool: *Pool) !void {
|
|
||||||
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;
|
const block_num = if (frag_idx == 0xFFFFFFFF)
|
||||||
try fil.updateTimes(time, time);
|
try std.math.divCeil(usize, size, block_size)
|
||||||
if (!options.ignore_permissions) {
|
else
|
||||||
try fil.chmod(self.hdr.permissions);
|
size / block_size;
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
const sizes = try alloc.alloc(BlockSize, block_num);
|
||||||
}
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
/// Creates the symlink described by the inode.
|
|
||||||
///
|
return .{
|
||||||
/// Assumes the inode is a symlink or ext_symlink type.
|
.start = util.readValue(u64, raw[0..8]),
|
||||||
fn extractSymlink(self: Inode, path: []const u8) !void {
|
.size = size,
|
||||||
const target = switch (self.data) {
|
.frag_idx = frag_idx,
|
||||||
.symlink => |s| s.target,
|
.frag_offset = util.readValue(u32, raw[32..36]),
|
||||||
.ext_symlink => |s| s.target,
|
.blocks = sizes,
|
||||||
else => unreachable,
|
.xattr_idx = util.readValue(u32, raw[36..40]),
|
||||||
};
|
};
|
||||||
try std.fs.cwd().symLink(target, path, .{});
|
|
||||||
}
|
}
|
||||||
/// Creates the device described by the inode.
|
};
|
||||||
///
|
|
||||||
/// Optionally set owner & permissions.
|
pub const Symlink = struct {
|
||||||
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
|
hard_links: u32,
|
||||||
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
target: []const u8,
|
||||||
var mode: u32 = undefined;
|
// xattr_idx is ignored on extended symlinks as you can't apply them to symlinks anyway.
|
||||||
var dev: u32 = 0;
|
|
||||||
switch (self.data) {
|
pub fn read(rdr: *Io.Reader, alloc: std.mem.Allocator) !Symlink {
|
||||||
.char_dev => |d| {
|
var raw: [8]u8 = undefined;
|
||||||
mode = std.posix.S.IFCHR;
|
try rdr.readSliceAll(&raw);
|
||||||
dev = d.dev;
|
|
||||||
},
|
const target = try alloc.alloc(u8, std.mem.readInt(u32, raw[4..], .little));
|
||||||
.ext_char_dev => |d| {
|
errdefer alloc.free(target);
|
||||||
mode = std.posix.S.IFCHR;
|
|
||||||
dev = d.dev;
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
},
|
|
||||||
.block_dev => |d| {
|
return .{
|
||||||
mode = std.posix.S.IFBLK;
|
.hard_links = std.mem.readInt(u32, raw[0..4], .little),
|
||||||
dev = d.dev;
|
.target = target,
|
||||||
},
|
};
|
||||||
.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 => {},
|
pub const Dev = struct {
|
||||||
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
|
hard_links: u32,
|
||||||
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
|
device: u32,
|
||||||
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
|
// xattr_idx omitted on basic device inodes.
|
||||||
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
|
xattr_idx: ?u32,
|
||||||
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
|
|
||||||
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
|
pub fn readBasic(rdr: *Io.Reader) !Dev {
|
||||||
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
|
var raw: [8]u8 = undefined;
|
||||||
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
|
try rdr.readSliceAll(&raw);
|
||||||
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
|
|
||||||
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
|
return .{
|
||||||
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
|
.hard_links = util.readValue(u32, raw[0..4]),
|
||||||
else => return blk: {
|
.device = util.readValue(u32, raw[4..8]),
|
||||||
std.debug.print("unhandled mknod result: {}\n", .{res});
|
.xattr_idx = null,
|
||||||
break :blk std.fs.Dir.MakeError.Unexpected;
|
};
|
||||||
},
|
|
||||||
}
|
}
|
||||||
var fil = try std.fs.cwd().openFile(path, .{});
|
pub fn readExt(rdr: *Io.Reader) !Dev {
|
||||||
defer fil.close();
|
var raw: [12]u8 = undefined;
|
||||||
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
try rdr.readSliceAll(&raw);
|
||||||
try fil.updateTimes(time, time);
|
|
||||||
if (!options.ignore_permissions) {
|
return .{
|
||||||
try fil.chmod(self.hdr.permissions);
|
.hard_links = util.readValue(u32, raw[0..4]),
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
.device = util.readValue(u32, raw[4..8]),
|
||||||
|
.xattr_idx = util.readValue(u32, raw[8..12]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (!options.ignore_xattr) {
|
};
|
||||||
// TODO
|
|
||||||
|
pub const Fifo = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
// Xattr_idx omitted on basic fifo inodes.
|
||||||
|
xattr_idx: ?u32,
|
||||||
|
|
||||||
|
pub fn readBasic(rdr: *Io.Reader) !Fifo {
|
||||||
|
var raw: [4]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&raw);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.hard_links = util.readValue(u32, raw[0..4]),
|
||||||
|
.xattr_idx = null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
pub fn readExt(rdr: *Io.Reader) !Fifo {
|
||||||
|
var raw: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&raw);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.hard_links = util.readValue(u32, raw[0..4]),
|
||||||
|
.xattr_idx = util.readValue(u32, raw[4..8]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
|
|
||||||
pub const Symlink = struct {
|
|
||||||
hard_links: u32,
|
|
||||||
target: []const u8,
|
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
|
|
||||||
var start: [8]u8 = undefined;
|
|
||||||
try rdr.readSliceAll(&start);
|
|
||||||
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
|
||||||
const target = try alloc.alloc(u8, target_size + 1);
|
|
||||||
errdefer alloc.free(target);
|
|
||||||
try rdr.readSliceEndian(u8, target, .little);
|
|
||||||
return .{
|
|
||||||
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
|
||||||
.target = target,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: Symlink, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ExtSymlink = struct {
|
|
||||||
hard_links: u32,
|
|
||||||
target: []const u8,
|
|
||||||
xattr_idx: u32,
|
|
||||||
|
|
||||||
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
|
|
||||||
var start: [8]u8 = undefined;
|
|
||||||
try rdr.readSliceAll(&start);
|
|
||||||
const target_size = std.mem.readInt(u32, start[4..8], .little);
|
|
||||||
const target = try alloc.alloc(u8, target_size + 1);
|
|
||||||
errdefer alloc.free(target);
|
|
||||||
try rdr.readSliceEndian(u8, target, .little);
|
|
||||||
var xattr_idx: u32 = undefined;
|
|
||||||
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
|
||||||
return .{
|
|
||||||
.hard_links = std.mem.readInt(u32, start[0..4], .little),
|
|
||||||
.target = target,
|
|
||||||
.xattr_idx = xattr_idx,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: ExtSymlink, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A block or character device.
|
|
||||||
pub const Dev = 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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Decomp = @import("decomp.zig");
|
||||||
|
const Reference = @import("inode.zig").Reference;
|
||||||
|
const MetadataReader = @import("utils/meta.zig");
|
||||||
|
const util = @import("utils/util.zig");
|
||||||
|
|
||||||
|
pub fn Value(comptime T: type, alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, table_start: u64, idx: u32) !T {
|
||||||
|
const ITEMS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||||
|
|
||||||
|
const block_idx = idx / ITEMS_PER_BLOCK;
|
||||||
|
const value_idx = idx % ITEMS_PER_BLOCK;
|
||||||
|
|
||||||
|
const start = util.readValue(u64, data[table_start + (block_idx * 8) ..][0..8]);
|
||||||
|
|
||||||
|
var meta: MetadataReader = .init(alloc, data[start..], decomp);
|
||||||
|
try meta.interface.discardAll(value_idx * @sizeOf(T));
|
||||||
|
|
||||||
|
return util.readValueRdr(T, &meta.interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Table(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const ITEMS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||||
|
|
||||||
|
data: []u8,
|
||||||
|
decomp: Decomp.Fn,
|
||||||
|
|
||||||
|
start: u64,
|
||||||
|
count: u32,
|
||||||
|
block_count: u32,
|
||||||
|
|
||||||
|
table: std.hash_map.AutoHashMapUnmanaged(u32, []T) = .empty,
|
||||||
|
mut: Io.Mutex = .init,
|
||||||
|
|
||||||
|
pub fn init(data: []u8, decomp: Decomp.Fn, start: u64, count: u32) !Self {
|
||||||
|
return .{
|
||||||
|
.data = data,
|
||||||
|
.decomp = decomp,
|
||||||
|
|
||||||
|
.start = start,
|
||||||
|
.count = count,
|
||||||
|
.block_count = try std.math.divCeil(u32, count, ITEMS_PER_BLOCK),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
|
||||||
|
var iter = self.table.valueIterator();
|
||||||
|
while (iter.next()) |block|
|
||||||
|
alloc.free(block.*);
|
||||||
|
|
||||||
|
self.table.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Self, alloc: std.mem.Allocator, io: Io, idx: u32) !T {
|
||||||
|
if (idx >= self.count) return error.InvalidIndex;
|
||||||
|
|
||||||
|
const block_idx = idx / ITEMS_PER_BLOCK;
|
||||||
|
const value_idx = idx % ITEMS_PER_BLOCK;
|
||||||
|
|
||||||
|
const init_get = self.table.get(block_idx);
|
||||||
|
if (init_get != null) return init_get.?[value_idx];
|
||||||
|
|
||||||
|
try self.mut.lock(io);
|
||||||
|
defer self.mut.unlock(io);
|
||||||
|
|
||||||
|
const start = util.readValue(u64, self.data[self.start + (block_idx * 8) ..][0..8]);
|
||||||
|
|
||||||
|
const len = if (block_idx == self.block_count - 1) self.count % ITEMS_PER_BLOCK else ITEMS_PER_BLOCK;
|
||||||
|
|
||||||
|
const new_block = try alloc.alloc(T, len);
|
||||||
|
errdefer alloc.free(new_block);
|
||||||
|
|
||||||
|
var meta: MetadataReader = .init(alloc, self.data[start..], self.decomp);
|
||||||
|
|
||||||
|
try meta.interface.readSliceEndian(T, new_block, .little);
|
||||||
|
|
||||||
|
try self.table.put(alloc, block_idx, new_block);
|
||||||
|
|
||||||
|
return new_block[value_idx];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Xattr = struct {
|
||||||
|
kv_start: u64,
|
||||||
|
lookup: Table(XattrLookup),
|
||||||
|
|
||||||
|
pub fn init(data: []u8, decomp: Decomp.Fn, start: u64) !Xattr {
|
||||||
|
const kv_start = util.readValue(u64, data[start..][0..8]);
|
||||||
|
const count = util.readValue(u32, data[start..][8..12]);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.kv_start = kv_start,
|
||||||
|
.lookup = try .init(data, decomp, start + 16, count),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Xattr, alloc: std.mem.Allocator) void {
|
||||||
|
self.lookup.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Xattr, alloc: std.mem.Allocator, io: Io, idx: u32) ![]KV {
|
||||||
|
const lookup = try self.lookup.get(alloc, io, idx);
|
||||||
|
|
||||||
|
var meta: MetadataReader = .init(alloc, self.lookup.data[self.kv_start + lookup.ref.start ..], self.lookup.decomp);
|
||||||
|
try meta.interface.discardAll(lookup.ref.offset);
|
||||||
|
|
||||||
|
const kvs = try alloc.alloc(KV, lookup.count);
|
||||||
|
errdefer alloc.free(kvs);
|
||||||
|
|
||||||
|
for (kvs) |*kv| {
|
||||||
|
const entry = try util.readValueRdr(KeyEntry, &meta.interface);
|
||||||
|
|
||||||
|
const prefix = switch (entry.type.type) {
|
||||||
|
.user => "user.",
|
||||||
|
.trusted => "trusted.",
|
||||||
|
.security => "security.",
|
||||||
|
};
|
||||||
|
|
||||||
|
kv.name = try alloc.allocSentinel(u8, entry.name_size + prefix.len, 0);
|
||||||
|
errdefer alloc.free(kv.name);
|
||||||
|
|
||||||
|
try meta.interface.readSliceEndian(u8, kv.name[prefix.len..], .little);
|
||||||
|
|
||||||
|
@memcpy(kv.name[0..prefix.len], prefix);
|
||||||
|
|
||||||
|
var value_meta = if (entry.type.out_of_line) blk: {
|
||||||
|
const ool_ref = try util.readValueRdr(Reference, &meta.interface);
|
||||||
|
|
||||||
|
var ool_meta: MetadataReader = .init(alloc, self.lookup.data[self.kv_start + ool_ref.start ..], self.lookup.decomp);
|
||||||
|
try ool_meta.interface.discardAll(ool_ref.offset);
|
||||||
|
|
||||||
|
break :blk ool_meta;
|
||||||
|
} else meta;
|
||||||
|
|
||||||
|
const value_len = try util.readValueRdr(u32, &value_meta.interface);
|
||||||
|
|
||||||
|
kv.value = try alloc.alloc(u8, value_len);
|
||||||
|
errdefer alloc.free(kv.value);
|
||||||
|
|
||||||
|
try value_meta.interface.readSliceEndian(u8, kv.value, .little);
|
||||||
|
}
|
||||||
|
|
||||||
|
return kvs;
|
||||||
|
}
|
||||||
|
|
||||||
|
//types
|
||||||
|
|
||||||
|
pub const KV = struct {
|
||||||
|
name: [:0]u8,
|
||||||
|
value: []u8,
|
||||||
|
|
||||||
|
pub fn deinit(self: KV, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.name);
|
||||||
|
alloc.free(self.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const XattrLookup = extern struct {
|
||||||
|
ref: Reference,
|
||||||
|
count: u32,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const KeyEntry = extern struct {
|
||||||
|
type: packed struct(u16) {
|
||||||
|
type: enum(u8) {
|
||||||
|
user,
|
||||||
|
trusted,
|
||||||
|
security,
|
||||||
|
},
|
||||||
|
out_of_line: bool,
|
||||||
|
_: u7,
|
||||||
|
},
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
|
};
|
||||||
+11
-13
@@ -1,23 +1,21 @@
|
|||||||
//! Options for file/directory extraction.
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Writer = std.Io.Writer;
|
const Writer = std.Io.Writer;
|
||||||
|
|
||||||
const ExtractionOptions = @This();
|
const Options = @This();
|
||||||
|
|
||||||
/// Don't set the file's owner & permissions after extraction
|
|
||||||
ignore_permissions: bool = false,
|
|
||||||
/// Don't set xattr values. Currently xattrs are never set anyway.
|
|
||||||
ignore_xattr: bool = false,
|
|
||||||
/// Replace symlinks with their target.
|
|
||||||
dereference_symlinks: bool = false,
|
|
||||||
/// Verbose logging. If true, verbose_writer must be set
|
|
||||||
verbose: bool = false,
|
verbose: bool = false,
|
||||||
/// Where to print verbose log.
|
|
||||||
verbose_writer: ?*Writer = null,
|
verbose_writer: ?*Writer = null,
|
||||||
|
|
||||||
pub const Default: ExtractionOptions = .{};
|
ignore_permissions: bool = false,
|
||||||
pub fn VerboseDefault(wrt: *Writer) ExtractionOptions {
|
ignore_xattr: bool = false,
|
||||||
|
|
||||||
|
dereference_symlink: bool = false,
|
||||||
|
|
||||||
|
single_threaded: bool = false,
|
||||||
|
|
||||||
|
pub const default: Options = .{};
|
||||||
|
pub const single_threaded_default: Options = .{ .single_threaded = true };
|
||||||
|
pub fn verboseDefault(wrt: *Writer) Options {
|
||||||
return .{
|
return .{
|
||||||
.verbose = true,
|
.verbose = true,
|
||||||
.verbose_writer = wrt,
|
.verbose_writer = wrt,
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
pub const Archive = @import("archive.zig");
|
pub const Archive = @import("archive.zig");
|
||||||
|
pub const Options = @import("options.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@import("tests.zig"));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const math = std.math;
|
|
||||||
|
|
||||||
const CompressionType = @import("decomp.zig").CompressionType;
|
|
||||||
const InodeRef = @import("inode.zig").Ref;
|
|
||||||
|
|
||||||
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
|
||||||
|
|
||||||
const SuperblockError = error{
|
|
||||||
InvalidMagic,
|
|
||||||
InvalidBlockLog,
|
|
||||||
InvalidVersion,
|
|
||||||
InvalidCheck,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A squashfs Superblock
|
|
||||||
pub const Superblock = packed struct {
|
|
||||||
magic: u32,
|
|
||||||
inode_count: u32,
|
|
||||||
mod_time: u32,
|
|
||||||
block_size: u32,
|
|
||||||
frag_count: u32,
|
|
||||||
compression: CompressionType,
|
|
||||||
block_log: u16,
|
|
||||||
flags: packed struct {
|
|
||||||
inode_uncompressed: bool,
|
|
||||||
data_uncompressed: bool,
|
|
||||||
check: bool,
|
|
||||||
frag_uncompressed: bool,
|
|
||||||
fragment_never: bool,
|
|
||||||
fragment_always: bool,
|
|
||||||
duplicates: bool,
|
|
||||||
exportable: bool,
|
|
||||||
xattr_uncompressed: bool,
|
|
||||||
xattr_never: bool,
|
|
||||||
compression_options: bool,
|
|
||||||
ids_uncompressed: bool,
|
|
||||||
_: u4,
|
|
||||||
},
|
|
||||||
id_count: u16,
|
|
||||||
ver_maj: u16,
|
|
||||||
ver_min: u16,
|
|
||||||
root_ref: InodeRef,
|
|
||||||
size: u64,
|
|
||||||
id_start: u64,
|
|
||||||
xattr_start: u64,
|
|
||||||
inode_start: u64,
|
|
||||||
dir_start: u64,
|
|
||||||
frag_start: u64,
|
|
||||||
export_start: u64,
|
|
||||||
|
|
||||||
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive.
|
|
||||||
pub fn validate(self: Superblock) !void {
|
|
||||||
if (self.magic != SQUASHFS_MAGIC)
|
|
||||||
return SuperblockError.InvalidMagic;
|
|
||||||
if (self.flags.check)
|
|
||||||
return SuperblockError.InvalidCheck;
|
|
||||||
if (self.ver_maj != 4 or self.ver_min != 0)
|
|
||||||
return SuperblockError.InvalidVersion;
|
|
||||||
if (math.log2(self.block_size) != self.block_log)
|
|
||||||
return SuperblockError.InvalidBlockLog;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Mutex = std.Thread.Mutex;
|
|
||||||
|
|
||||||
const DecompFn = @import("decomp.zig").DecompFn;
|
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
|
||||||
|
|
||||||
const TableError = error{
|
|
||||||
InvalidIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A two-layer metadata table.
|
|
||||||
pub fn Table(T: anytype) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
fil: OffsetFile,
|
|
||||||
decomp: DecompFn,
|
|
||||||
tab_start: u64,
|
|
||||||
|
|
||||||
tab: std.AutoHashMap(u32, []T),
|
|
||||||
values: u32,
|
|
||||||
|
|
||||||
mut: Mutex = .{},
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, tab_start: u64, values: u32) !Self {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.fil = fil,
|
|
||||||
.decomp = decomp,
|
|
||||||
.tab_start = tab_start,
|
|
||||||
|
|
||||||
.tab = .init(alloc),
|
|
||||||
.values = values,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var iter = self.tab.valueIterator();
|
|
||||||
while (iter.next()) |s| {
|
|
||||||
self.alloc.free(s.*);
|
|
||||||
}
|
|
||||||
self.tab.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Self, idx: u32) !T {
|
|
||||||
if (idx >= self.values) return TableError.InvalidIndex;
|
|
||||||
const block_num = idx / VALS_PER_BLOCK;
|
|
||||||
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
|
||||||
if (self.tab.contains(block_num)) {
|
|
||||||
const block = self.tab.get(block_num).?;
|
|
||||||
return block[idx_offset];
|
|
||||||
}
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
// Double check in case of race condition..
|
|
||||||
if (self.tab.contains(block_num)) {
|
|
||||||
const block = self.tab.get(block_num).?;
|
|
||||||
return block[idx_offset];
|
|
||||||
}
|
|
||||||
const is_last = (self.values - 1) / VALS_PER_BLOCK == block_num;
|
|
||||||
const slice_size = if (is_last) self.values - (block_num * VALS_PER_BLOCK) else VALS_PER_BLOCK;
|
|
||||||
const slice = try self.alloc.alloc(T, slice_size);
|
|
||||||
var rdr = try self.fil.readerAt(self.tab_start + (8 * block_num), &[0]u8{});
|
|
||||||
var offset: u64 = 0;
|
|
||||||
try rdr.interface.readSliceEndian(u64, @ptrCast(&offset), .little);
|
|
||||||
rdr = try self.fil.readerAt(offset, &[0]u8{});
|
|
||||||
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
|
||||||
try meta.interface.readSliceEndian(T, @ptrCast(slice), .little);
|
|
||||||
try self.tab.put(block_num, slice);
|
|
||||||
return slice[idx_offset];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const stuff = @import("builtin");
|
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
|
||||||
const Superblock = @import("super.zig").Superblock;
|
|
||||||
|
|
||||||
const TestArchive = "testing/LinuxPATest.sfs";
|
|
||||||
|
|
||||||
test "Basics" {
|
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
|
||||||
if (sfs.super != LinuxPATestCorrectSuperblock) {
|
|
||||||
std.debug.print("Superblock wrong\nShould be: {}\n\nis: {}\n", .{ LinuxPATestCorrectSuperblock, sfs.super });
|
|
||||||
return error.BadSuperblock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestFile = "Start.exe";
|
|
||||||
const TestFileExtractLocation = "testing/Start.exe";
|
|
||||||
|
|
||||||
test "ExtractSingleFile" {
|
|
||||||
std.fs.cwd().deleteFile(TestFileExtractLocation) catch {};
|
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
|
||||||
var test_fil = try sfs.open(TestFile);
|
|
||||||
defer test_fil.deinit();
|
|
||||||
try test_fil.extract(TestFileExtractLocation, .Default);
|
|
||||||
//TODO: validate extracted file.
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestFullExtractLocation = "testing/TestExtract";
|
|
||||||
|
|
||||||
test "ExtractCompleteArchive" {
|
|
||||||
std.fs.cwd().deleteTree(TestFullExtractLocation) catch {};
|
|
||||||
var fil = try std.fs.cwd().openFile(TestArchive, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var sfs: Archive = try .init(std.testing.allocator, fil);
|
|
||||||
defer sfs.deinit();
|
|
||||||
try sfs.extract(TestFullExtractLocation, .Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
const LinuxPATestCorrectSuperblock: Superblock = .{
|
|
||||||
.magic = std.mem.readInt(u32, "hsqs", .little),
|
|
||||||
.inode_count = 2974,
|
|
||||||
.mod_time = 1632696724,
|
|
||||||
.block_size = 131072,
|
|
||||||
.frag_count = 264,
|
|
||||||
.compression = .zstd,
|
|
||||||
.block_log = 17,
|
|
||||||
.flags = .{
|
|
||||||
.inode_uncompressed = false,
|
|
||||||
.data_uncompressed = false,
|
|
||||||
.check = false,
|
|
||||||
.frag_uncompressed = false,
|
|
||||||
.fragment_never = false,
|
|
||||||
.fragment_always = false,
|
|
||||||
.duplicates = true,
|
|
||||||
.exportable = true,
|
|
||||||
.xattr_uncompressed = false,
|
|
||||||
.xattr_never = false,
|
|
||||||
.compression_options = false,
|
|
||||||
.ids_uncompressed = false,
|
|
||||||
._ = 0,
|
|
||||||
},
|
|
||||||
.id_count = 1,
|
|
||||||
.ver_maj = 4,
|
|
||||||
.ver_min = 0,
|
|
||||||
.root_ref = .{
|
|
||||||
.block_offset = 1363,
|
|
||||||
.block_start = 29237,
|
|
||||||
._ = 0,
|
|
||||||
},
|
|
||||||
.size = 106841744,
|
|
||||||
.id_start = 106841632,
|
|
||||||
.xattr_start = 106841720,
|
|
||||||
.inode_start = 106778274,
|
|
||||||
.dir_start = 106807998,
|
|
||||||
.frag_start = 106837747,
|
|
||||||
.export_start = 106841602,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
var alloc = std.testing.allocator;
|
||||||
|
var io = std.testing.io;
|
||||||
|
|
||||||
|
const Archive = @import("archive.zig");
|
||||||
|
|
||||||
|
const TEST_ARCHIVE = "testing/LinuxPATest.sfs";
|
||||||
|
|
||||||
|
const OPEN_TEST_LOCATION = "PortableApps/gVimPortable/GVim-v8.2.2965.glibc2.15-x86_64.AppImage";
|
||||||
|
|
||||||
|
test "Open" {
|
||||||
|
var archive_file = try Io.Dir.cwd().openFile(io, TEST_ARCHIVE, .{});
|
||||||
|
defer archive_file.close(io);
|
||||||
|
|
||||||
|
var arc: Archive = try .init(io, archive_file, 0);
|
||||||
|
defer arc.deinit(io);
|
||||||
|
|
||||||
|
var root = try arc.root(alloc);
|
||||||
|
defer root.deinit();
|
||||||
|
|
||||||
|
var test_open = try root.open(alloc, OPEN_TEST_LOCATION);
|
||||||
|
defer test_open.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FULL_EXTRACT_LOCATION = "testing/TestExtractST";
|
||||||
|
|
||||||
|
test "FullExtractSingleThreaded" {
|
||||||
|
Io.Dir.cwd().deleteTree(io, FULL_EXTRACT_LOCATION) catch {};
|
||||||
|
|
||||||
|
var archive_file = try Io.Dir.cwd().openFile(io, TEST_ARCHIVE, .{});
|
||||||
|
defer archive_file.close(io);
|
||||||
|
|
||||||
|
var arc: Archive = try .init(io, archive_file, 0);
|
||||||
|
defer arc.deinit(io);
|
||||||
|
|
||||||
|
try arc.extract(alloc, io, FULL_EXTRACT_LOCATION, .default);
|
||||||
|
}
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
//! A reader for a regular file.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
const Writer = std.Io.Writer;
|
|
||||||
const Limit = std.Io.Limit;
|
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
|
||||||
const OffsetFile = @import("offset_file.zig");
|
|
||||||
|
|
||||||
const DataReader = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
fil: OffsetFile,
|
|
||||||
decomp: DecompFn,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
blocks: []BlockSize,
|
|
||||||
|
|
||||||
frag: ?FragEntry = null, // TODO: do something better?
|
|
||||||
frag_offset: u32 = 0,
|
|
||||||
size: u64,
|
|
||||||
|
|
||||||
interface: Reader,
|
|
||||||
|
|
||||||
cur_offset: u64,
|
|
||||||
block_idx: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) DataReader {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.fil = archive.fil,
|
|
||||||
.decomp = archive.decomp,
|
|
||||||
.block_size = archive.super.block_size,
|
|
||||||
.blocks = blocks,
|
|
||||||
.size = size,
|
|
||||||
.cur_offset = start,
|
|
||||||
.interface = .{
|
|
||||||
.end = 0,
|
|
||||||
.seek = 0,
|
|
||||||
.buffer = &[0]u8{},
|
|
||||||
.vtable = &.{
|
|
||||||
.stream = stream,
|
|
||||||
.discard = discard,
|
|
||||||
.readVec = readVec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *DataReader) void {
|
|
||||||
self.alloc.free(self.interface.buffer);
|
|
||||||
self.interface.end = 0;
|
|
||||||
self.interface.seek = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *DataReader, entry: FragEntry, frag_offset: u32) void {
|
|
||||||
self.frag = entry;
|
|
||||||
self.frag_offset = frag_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn numBlocks(self: DataReader) usize {
|
|
||||||
var res = self.blocks.len;
|
|
||||||
if (self.frag != null) res += 1;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(self: *DataReader) !void {
|
|
||||||
if (self.block_idx > self.blocks.len or (self.block_idx == self.blocks.len and self.frag == null)) {
|
|
||||||
if (self.interface.buffer.len > 0) {
|
|
||||||
self.alloc.free(self.interface.buffer);
|
|
||||||
self.interface.buffer = &[0]u8{};
|
|
||||||
self.interface.end = 0;
|
|
||||||
self.interface.seek = 0;
|
|
||||||
}
|
|
||||||
return Reader.Error.EndOfStream;
|
|
||||||
}
|
|
||||||
defer self.block_idx += 1;
|
|
||||||
const cur_block_size = if (self.block_idx == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
|
||||||
try self.resizeBuffer(cur_block_size);
|
|
||||||
self.interface.seek = 0;
|
|
||||||
self.interface.end = cur_block_size;
|
|
||||||
if (self.block_idx == self.blocks.len) { // fragment
|
|
||||||
var rdr = try self.fil.readerAt(self.frag.?.start, &[0]u8{});
|
|
||||||
if (self.frag.?.size.uncompressed) {
|
|
||||||
try rdr.interface.discardAll(self.frag_offset);
|
|
||||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp_buf = try self.alloc.alloc(u8, self.frag.?.size.size);
|
|
||||||
defer self.alloc.free(tmp_buf);
|
|
||||||
try rdr.interface.readSliceAll(tmp_buf);
|
|
||||||
const needed_block = try self.alloc.alloc(u8, self.block_size);
|
|
||||||
defer self.alloc.free(needed_block);
|
|
||||||
_ = try self.decomp(self.alloc, tmp_buf, needed_block);
|
|
||||||
@memcpy(self.interface.buffer, needed_block[self.frag_offset .. self.frag_offset + cur_block_size]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const block = self.blocks[self.block_idx];
|
|
||||||
if (block.size == 0) {
|
|
||||||
@memset(self.interface.buffer, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rdr = try self.fil.readerAt(self.cur_offset, &[0]u8{});
|
|
||||||
self.cur_offset += block.size;
|
|
||||||
if (block.uncompressed) {
|
|
||||||
try rdr.interface.readSliceAll(self.interface.buffer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp_buf = try self.alloc.alloc(u8, block.size);
|
|
||||||
defer self.alloc.free(tmp_buf);
|
|
||||||
try rdr.interface.readSliceAll(tmp_buf);
|
|
||||||
_ = try self.decomp(self.alloc, tmp_buf, self.interface.buffer);
|
|
||||||
}
|
|
||||||
/// Does not guarentee that data currently in the buffer is retained.
|
|
||||||
fn resizeBuffer(self: *DataReader, size: usize) !void {
|
|
||||||
if (self.interface.buffer.len == size) return;
|
|
||||||
if (!self.alloc.resize(self.interface.buffer, size)) {
|
|
||||||
self.alloc.free(self.interface.buffer);
|
|
||||||
self.interface.buffer = self.alloc.alloc(u8, size) catch |err| {
|
|
||||||
self.interface.buffer = &[0]u8{};
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
self.interface.buffer.len = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
|
|
||||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
|
||||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
|
||||||
if (err == error.EndOfStream) return error.EndOfStream;
|
|
||||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
if (limit == .nothing) return 0;
|
|
||||||
const to_read = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
|
||||||
const res = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_read]);
|
|
||||||
rdr.seek += res;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
|
||||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
|
||||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
|
||||||
if (err == error.EndOfStream) return error.EndOfStream;
|
|
||||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
if (limit == .nothing) return 0;
|
|
||||||
const to_adv = @min(rdr.end - rdr.seek, @intFromEnum(limit));
|
|
||||||
rdr.seek += to_adv;
|
|
||||||
return to_adv;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
|
||||||
var self: *DataReader = @alignCast(@fieldParentPtr("interface", rdr));
|
|
||||||
if (rdr.seek >= rdr.end) self.advance() catch |err| {
|
|
||||||
if (err == error.EndOfStream) return error.EndOfStream;
|
|
||||||
std.log.err("Error advancing data reader: {}\n", .{err});
|
|
||||||
return Reader.Error.ReadFailed;
|
|
||||||
};
|
|
||||||
var cur_red: usize = 0;
|
|
||||||
for (vec) |s| {
|
|
||||||
const to_copy: usize = @min(rdr.end - rdr.seek, s.len);
|
|
||||||
@memcpy(s[0..to_copy], rdr.buffer[rdr.seek .. rdr.seek + to_copy]);
|
|
||||||
rdr.seek += to_copy;
|
|
||||||
cur_red += to_copy;
|
|
||||||
if (rdr.end == rdr.seek) break;
|
|
||||||
}
|
|
||||||
return cur_red;
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
//! Similiar to DataReader, but set-up for threaded writing to files.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Reader = std.Io.Reader;
|
|
||||||
const Writer = std.Io.Writer;
|
|
||||||
const Limit = std.Io.Limit;
|
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
|
||||||
const Pool = std.Thread.Pool;
|
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
|
||||||
const OffsetFile = @import("offset_file.zig");
|
|
||||||
|
|
||||||
const ThreadedDataReader = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
fil: OffsetFile,
|
|
||||||
decomp: DecompFn,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
blocks: []BlockSize,
|
|
||||||
|
|
||||||
frag: ?FragEntry = null, // TODO: do something better?
|
|
||||||
frag_offset: u32 = 0,
|
|
||||||
size: u64,
|
|
||||||
|
|
||||||
start_offset: u64,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, start: u64, size: u64) ThreadedDataReader {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.fil = archive.fil,
|
|
||||||
.decomp = archive.decomp,
|
|
||||||
.block_size = archive.super.block_size,
|
|
||||||
.blocks = blocks,
|
|
||||||
.size = size,
|
|
||||||
.start_offset = start,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void {
|
|
||||||
self.frag = entry;
|
|
||||||
self.frag_offset = frag_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn numBlocks(self: ThreadedDataReader) usize {
|
|
||||||
var res = self.blocks.len;
|
|
||||||
if (self.frag != null) res += 1;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract the data to the file threadedly, using pool to spawn threads.
|
|
||||||
/// If multiple errors occur, thread spawning errors will have, then the last decompression error that occurs;
|
|
||||||
///
|
|
||||||
/// The function must be called from an unused DataReader. The DataReader is still usable afterwards.
|
|
||||||
/// If only extractThreaded is used, there is no need to call deinit() afterwards.
|
|
||||||
///
|
|
||||||
/// The file will always be written to starting at 0.
|
|
||||||
pub fn extractThreaded(self: ThreadedDataReader, file: std.fs.File, pool: *Pool) !void {
|
|
||||||
var wg: WaitGroup = .{};
|
|
||||||
wg.startMany(self.numBlocks());
|
|
||||||
var out_err: ?anyerror = null;
|
|
||||||
|
|
||||||
var cur_write_offset: u64 = 0;
|
|
||||||
var cur_read_offset: u64 = self.start_offset;
|
|
||||||
for (0..self.blocks.len) |i| {
|
|
||||||
const cur_block_size = if (i == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
|
||||||
try pool.spawn(workThreadBlocks, .{ self, file, cur_write_offset, cur_read_offset, self.blocks[i], cur_block_size, &wg, &out_err });
|
|
||||||
cur_write_offset += cur_block_size;
|
|
||||||
cur_read_offset += self.blocks[i].size;
|
|
||||||
}
|
|
||||||
if (self.frag != null) {
|
|
||||||
try pool.spawn(workThreadFragment, .{ self, file, cur_write_offset, &wg, &out_err });
|
|
||||||
}
|
|
||||||
pool.waitAndWork(&wg);
|
|
||||||
if (out_err != null) return out_err.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn workThreadBlocks(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, read_offset: u64, block: BlockSize, cur_block_size: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
|
||||||
defer wg.finish();
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
|
||||||
wrt.seekTo(write_offset) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer wrt.interface.flush() catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
if (block.size == 0) {
|
|
||||||
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var rdr = self.fil.readerAt(read_offset, &[0]u8{}) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if (block.uncompressed) {
|
|
||||||
rdr.interface.streamExact(&wrt.interface, block.size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: shared buffers
|
|
||||||
const read_buf = self.alloc.alloc(u8, block.size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(read_buf);
|
|
||||||
rdr.interface.readSliceAll(read_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// TODO: shared buffers
|
|
||||||
const res_buf = self.alloc.alloc(u8, cur_block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(res_buf);
|
|
||||||
_ = self.decomp(self.alloc, read_buf, res_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
wrt.interface.writeAll(res_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn workThreadFragment(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
|
||||||
defer wg.finish();
|
|
||||||
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
|
||||||
wrt.seekTo(write_offset) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer wrt.interface.flush() catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
|
|
||||||
var rdr = self.fil.readerAt(self.frag.?.start, &[0]u8{}) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if (self.frag.?.size.uncompressed) {
|
|
||||||
rdr.interface.discardAll(self.frag_offset) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
rdr.interface.streamExact(&wrt.interface, self.size % self.block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tmp_buf = self.alloc.alloc(u8, self.frag.?.size.size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(tmp_buf);
|
|
||||||
rdr.interface.readSliceAll(tmp_buf) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const needed_block = self.alloc.alloc(u8, self.block_size) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(needed_block);
|
|
||||||
_ = self.decomp(self.alloc, tmp_buf, needed_block) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
wrt.interface.writeAll(needed_block[self.frag_offset .. self.frag_offset + (self.size % self.block_size)]) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//! 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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Decomp = @import("../decomp.zig");
|
||||||
|
const BlockSize = @import("../inode.zig").BlockSize;
|
||||||
|
|
||||||
|
const DataReader = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
data: []u8,
|
||||||
|
decomp: Decomp.Fn,
|
||||||
|
block_size: u32,
|
||||||
|
|
||||||
|
size: u64,
|
||||||
|
blocks: []BlockSize,
|
||||||
|
|
||||||
|
frag: ?[]u8 = null,
|
||||||
|
|
||||||
|
idx: u32 = 0,
|
||||||
|
buf: []u8,
|
||||||
|
|
||||||
|
interface: Io.Reader = .{
|
||||||
|
.buffer = &[0]u8{},
|
||||||
|
.seek = 0,
|
||||||
|
.end = 0,
|
||||||
|
.vtable = &.{
|
||||||
|
.stream = stream,
|
||||||
|
.discard = discard,
|
||||||
|
.readVec = readVec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn, block_size: u32, size: u64, blocks: []BlockSize) !DataReader {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.data = data,
|
||||||
|
.decomp = decomp,
|
||||||
|
.block_size = block_size,
|
||||||
|
|
||||||
|
.size = size,
|
||||||
|
.blocks = blocks,
|
||||||
|
.buf = if (blocks.len > 0) try alloc.alloc(u8, 1024 * 1024) else &[0]u8{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: DataReader) void {
|
||||||
|
self.alloc.free(self.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addFrag(self: *DataReader, frag: []u8) void {
|
||||||
|
self.frag = frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(self: *DataReader) Io.Reader.Error!void {
|
||||||
|
if (self.idx > self.blocks.len) return error.EndOfStream;
|
||||||
|
defer self.idx += 1;
|
||||||
|
|
||||||
|
self.interface.seek = 0;
|
||||||
|
errdefer self.interface.end = 0;
|
||||||
|
|
||||||
|
if (self.idx == self.blocks.len) {
|
||||||
|
if (self.frag == null) return error.EndOfStream;
|
||||||
|
|
||||||
|
self.interface.buffer = self.frag.?;
|
||||||
|
self.interface.end = self.frag.?.len;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interface.end = if (self.frag == null and self.idx == self.blocks.len - 1)
|
||||||
|
self.size % self.block_size
|
||||||
|
else
|
||||||
|
self.block_size;
|
||||||
|
|
||||||
|
const block = self.blocks[self.idx];
|
||||||
|
|
||||||
|
if (block.size == 0) {
|
||||||
|
@memset(self.buf[0..self.interface.end], 0);
|
||||||
|
self.interface.buffer = self.buf;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.uncompressed) {
|
||||||
|
self.interface.buffer = self.data;
|
||||||
|
self.data = self.data[self.interface.end..];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = self.decomp(self.alloc, self.data[0..block.size], self.buf[0..self.interface.end]) catch return error.ReadFailed;
|
||||||
|
self.data = self.data[block.size..];
|
||||||
|
|
||||||
|
self.interface.buffer = self.buf;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(rdr: *Io.Reader, wrt: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
||||||
|
if (rdr.seek >= rdr.end) {
|
||||||
|
var meta: *DataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
try meta.advance();
|
||||||
|
}
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
|
||||||
|
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
|
||||||
|
|
||||||
|
const wrote = try wrt.write(rdr.buffer[rdr.seek..][0..size]);
|
||||||
|
rdr.seek += wrote;
|
||||||
|
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
fn discard(rdr: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
|
||||||
|
if (rdr.seek >= rdr.end) {
|
||||||
|
var meta: *DataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
try meta.advance();
|
||||||
|
}
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
|
||||||
|
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
|
||||||
|
rdr.seek += size;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
fn readVec(rdr: *Io.Reader, vec: [][]u8) Io.Reader.Error!usize {
|
||||||
|
if (rdr.seek >= rdr.end) {
|
||||||
|
var meta: *DataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
try meta.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrote: usize = 0;
|
||||||
|
for (vec) |s| {
|
||||||
|
const size = @min(rdr.end - rdr.seek, s.len);
|
||||||
|
|
||||||
|
@memcpy(s[0..size], rdr.buffer[rdr.seek..][0..size]);
|
||||||
|
|
||||||
|
wrote += size;
|
||||||
|
rdr.seek += size;
|
||||||
|
|
||||||
|
if (rdr.seek >= rdr.end) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const Super = @import("../archive.zig").Super;
|
||||||
|
const Decomp = @import("../decomp.zig");
|
||||||
|
const Directory = @import("../dir.zig");
|
||||||
|
const FragCache = @import("../frag_cache.zig");
|
||||||
|
const Inode = @import("../inode.zig");
|
||||||
|
const Lookup = @import("../lookup.zig");
|
||||||
|
const Options = @import("../options.zig");
|
||||||
|
const DataReader = @import("data_reader.zig");
|
||||||
|
const MetadataReader = @import("meta.zig");
|
||||||
|
|
||||||
|
// TODO: Add verbose logging.
|
||||||
|
|
||||||
|
pub fn single(alloc: std.mem.Allocator, io: Io, data: []u8, super: Super, inode: Inode, filepath: []const u8, options: Options) !void {
|
||||||
|
const path = std.mem.trim(u8, filepath, "/");
|
||||||
|
|
||||||
|
var id_table: Lookup.Table(u16) = try .init(data, super.decomp_fn, super.id_table_start, super.id_count);
|
||||||
|
defer id_table.deinit(alloc);
|
||||||
|
var xattr_table: Lookup.Xattr = try .init(data, super.decomp_fn, super.xattr_table_start);
|
||||||
|
defer xattr_table.deinit(alloc);
|
||||||
|
var frag_cache: FragCache = try .init(data, super.decomp_fn, super.block_size, super.frag_table_start, super.frag_count);
|
||||||
|
defer frag_cache.deinit(alloc);
|
||||||
|
|
||||||
|
var pool: std.ArrayList(InodeAndPath) = try .initCapacity(alloc, 15);
|
||||||
|
pool.appendAssumeCapacity(.{
|
||||||
|
.inode = inode,
|
||||||
|
.path = path,
|
||||||
|
.root = true,
|
||||||
|
});
|
||||||
|
defer {
|
||||||
|
while (pool.pop()) |in|
|
||||||
|
in.deinit(alloc);
|
||||||
|
pool.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirs: std.ArrayList(InodeAndPath) = .empty;
|
||||||
|
defer {
|
||||||
|
while (dirs.pop()) |dir|
|
||||||
|
alloc.free(dir.path);
|
||||||
|
dirs.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cwd = Io.Dir.cwd();
|
||||||
|
|
||||||
|
while (pool.pop()) |in| {
|
||||||
|
switch (in.inode.data) {
|
||||||
|
.dir => |d| {
|
||||||
|
try cwd.createDirPath(io, in.path);
|
||||||
|
|
||||||
|
if (d.size <= 3) {
|
||||||
|
defer if (in.path.len != path.len)
|
||||||
|
in.deinit(alloc);
|
||||||
|
|
||||||
|
var fil = try cwd.openFile(io, in.path, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
|
||||||
|
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, d.xattr_idx, fil, options);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta: MetadataReader = .init(alloc, data[d.start..], super.decomp_fn);
|
||||||
|
try meta.interface.discardAll(d.offset);
|
||||||
|
|
||||||
|
var dir: Directory = try .read(alloc, &meta.interface, d.size);
|
||||||
|
defer dir.deinit(alloc);
|
||||||
|
|
||||||
|
for (dir.entries) |entry| {
|
||||||
|
const new_inode: Inode = try .readLocation(alloc, data, entry.start, entry.offset, super);
|
||||||
|
|
||||||
|
const new_path = std.mem.concat(alloc, u8, &.{ in.path, "/", entry.name }) catch |err| {
|
||||||
|
new_inode.deinit(alloc);
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
try pool.append(alloc, .{
|
||||||
|
.inode = new_inode,
|
||||||
|
.path = new_path,
|
||||||
|
.root = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try dirs.append(alloc, in);
|
||||||
|
},
|
||||||
|
.file => |f| {
|
||||||
|
defer if (in.path.len != path.len)
|
||||||
|
in.deinit(alloc);
|
||||||
|
|
||||||
|
var atomic = try cwd.createFileAtomic(io, in.path, .{});
|
||||||
|
defer atomic.deinit(io);
|
||||||
|
|
||||||
|
var rdr: DataReader = try .init(alloc, data[f.start..], super.decomp_fn, super.block_size, f.size, f.blocks);
|
||||||
|
defer rdr.deinit();
|
||||||
|
|
||||||
|
if (f.frag_idx != 0xFFFFFFFF) {
|
||||||
|
const frag = try frag_cache.get(alloc, io, f.frag_idx);
|
||||||
|
rdr.addFrag(frag);
|
||||||
|
}
|
||||||
|
var buf: [1024 * 50]u8 = undefined; // TODO: find a resonable buffer size.
|
||||||
|
var writer = atomic.file.writer(io, &buf);
|
||||||
|
_ = try rdr.interface.streamRemaining(&writer.interface);
|
||||||
|
|
||||||
|
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, f.xattr_idx, atomic.file, options);
|
||||||
|
|
||||||
|
try atomic.link(io);
|
||||||
|
},
|
||||||
|
.symlink => |s| {
|
||||||
|
defer if (in.path.len != path.len)
|
||||||
|
in.deinit(alloc);
|
||||||
|
|
||||||
|
try cwd.symLink(io, s.target, in.path, .{});
|
||||||
|
},
|
||||||
|
.dev => |d| {
|
||||||
|
defer if (in.path.len != path.len)
|
||||||
|
in.deinit(alloc);
|
||||||
|
|
||||||
|
const mode: u32 = switch (in.inode.header.type) {
|
||||||
|
.char_dev, .ext_char_dev => std.os.linux.DT.CHR,
|
||||||
|
.block_dev, .ext_block_dev => std.os.linux.DT.BLK,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sent_path = try alloc.dupeSentinel(u8, in.path, 0);
|
||||||
|
|
||||||
|
const res = std.os.linux.mknod(sent_path, mode, d.device);
|
||||||
|
alloc.free(sent_path);
|
||||||
|
if (res != 0)
|
||||||
|
return error.MkNodError;
|
||||||
|
|
||||||
|
var fil = try cwd.openFile(io, in.path, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
|
||||||
|
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, d.xattr_idx, fil, options);
|
||||||
|
},
|
||||||
|
.fifo => |f| {
|
||||||
|
defer if (in.path.len != path.len)
|
||||||
|
in.deinit(alloc);
|
||||||
|
|
||||||
|
const mode: u32 = switch (in.inode.header.type) {
|
||||||
|
.fifo, .ext_fifo => std.os.linux.DT.FIFO,
|
||||||
|
.socket, .ext_socket => std.os.linux.DT.SOCK,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sent_path = try alloc.dupeSentinel(u8, in.path, 0);
|
||||||
|
|
||||||
|
const res = std.os.linux.mknod(sent_path, mode, 0);
|
||||||
|
alloc.free(sent_path);
|
||||||
|
if (res != 0)
|
||||||
|
return error.MkNodError;
|
||||||
|
|
||||||
|
var fil = try cwd.openFile(io, in.path, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
|
||||||
|
try applyPermissions(alloc, io, &id_table, &xattr_table, in.inode.header, f.xattr_idx, fil, options);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (dirs.pop()) |dir| {
|
||||||
|
defer if (dir.path.len != path.len)
|
||||||
|
alloc.free(dir.path);
|
||||||
|
|
||||||
|
var fil = try cwd.openFile(io, dir.path, .{});
|
||||||
|
defer fil.close(io);
|
||||||
|
|
||||||
|
try applyPermissions(alloc, io, &id_table, &xattr_table, dir.inode.header, dir.inode.data.dir.xattr_idx, fil, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn multi(alloc: std.mem.Allocator, io: Io, data: []const u8, super: Super, inode: Inode, filepath: []const u8, options: Options) !void {
|
||||||
|
_ = alloc;
|
||||||
|
_ = io;
|
||||||
|
_ = data;
|
||||||
|
_ = super;
|
||||||
|
_ = inode;
|
||||||
|
_ = filepath;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
|
||||||
|
const InodeAndPath = struct {
|
||||||
|
inode: Inode,
|
||||||
|
path: []const u8,
|
||||||
|
root: bool,
|
||||||
|
|
||||||
|
fn deinit(self: InodeAndPath, alloc: std.mem.Allocator) void {
|
||||||
|
if (self.root) return;
|
||||||
|
self.inode.deinit(alloc);
|
||||||
|
alloc.free(self.path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn applyPermissions(alloc: std.mem.Allocator, io: Io, id_table: *Lookup.Table(u16), xattr_table: *Lookup.Xattr, header: Inode.Header, xattr_idx: ?u32, fil: Io.File, options: Options) !void {
|
||||||
|
if (!options.ignore_permissions) {
|
||||||
|
try fil.setPermissions(io, @enumFromInt(header.permission));
|
||||||
|
|
||||||
|
try fil.setTimestamps(io, .{ .modify_timestamp = .{ .new = .fromNanoseconds(@as(i96, @intCast(header.mod_time)) * std.time.ns_per_ms) } });
|
||||||
|
|
||||||
|
try fil.setOwner(io, try id_table.get(alloc, io, header.uid_idx), try id_table.get(alloc, io, header.gid_idx));
|
||||||
|
}
|
||||||
|
if (!options.ignore_xattr and xattr_idx != null) {
|
||||||
|
const xattrs = try xattr_table.get(alloc, io, xattr_idx.?);
|
||||||
|
defer {
|
||||||
|
for (xattrs) |kv|
|
||||||
|
kv.deinit(alloc);
|
||||||
|
alloc.free(xattrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (xattrs) |kv| {
|
||||||
|
const res = std.os.linux.fsetxattr(fil.handle, kv.name, kv.value.ptr, kv.value.len, 0);
|
||||||
|
if (res != 0)
|
||||||
|
return error.FSetXattrError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
const Decomp = @import("../decomp.zig");
|
||||||
|
|
||||||
|
const MetadataReader = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
data: []u8,
|
||||||
|
decomp: Decomp.Fn,
|
||||||
|
|
||||||
|
decomp_block: [8192]u8 = undefined,
|
||||||
|
|
||||||
|
interface: Io.Reader = .{
|
||||||
|
.buffer = &[0]u8{},
|
||||||
|
.end = 0,
|
||||||
|
.seek = 0,
|
||||||
|
.vtable = &.{
|
||||||
|
.stream = stream,
|
||||||
|
.discard = discard,
|
||||||
|
.readVec = readVec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, data: []u8, decomp: Decomp.Fn) MetadataReader {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.data = data,
|
||||||
|
.decomp = decomp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(self: *MetadataReader) Io.Reader.Error!void {
|
||||||
|
self.interface.seek = 0;
|
||||||
|
errdefer self.interface.end = 0;
|
||||||
|
|
||||||
|
const hdr = util.readValue(Header, self.data[0..2]);
|
||||||
|
|
||||||
|
const block = self.data[2..][0..hdr.size];
|
||||||
|
|
||||||
|
self.data = self.data[hdr.size + 2 ..];
|
||||||
|
|
||||||
|
if (hdr.uncompressed) {
|
||||||
|
self.interface.end = hdr.size;
|
||||||
|
self.interface.buffer = block;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interface.end = self.decomp(self.alloc, block, &self.decomp_block) catch return error.ReadFailed;
|
||||||
|
self.interface.buffer = self.decomp_block[0..self.interface.end];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(rdr: *Io.Reader, wrt: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
|
||||||
|
if (rdr.seek >= rdr.end) {
|
||||||
|
var meta: *MetadataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
try meta.advance();
|
||||||
|
}
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
|
||||||
|
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
|
||||||
|
|
||||||
|
const wrote = try wrt.write(rdr.buffer[rdr.seek..][0..size]);
|
||||||
|
rdr.seek += wrote;
|
||||||
|
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
fn discard(rdr: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
|
||||||
|
if (rdr.seek >= rdr.end) {
|
||||||
|
var meta: *MetadataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
try meta.advance();
|
||||||
|
}
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
|
||||||
|
const size = @min(@intFromEnum(limit), rdr.end - rdr.seek);
|
||||||
|
rdr.seek += size;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
fn readVec(rdr: *Io.Reader, vec: [][]u8) Io.Reader.Error!usize {
|
||||||
|
if (rdr.seek >= rdr.end) {
|
||||||
|
var meta: *MetadataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
try meta.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrote: usize = 0;
|
||||||
|
for (vec) |s| {
|
||||||
|
const size = @min(rdr.end - rdr.seek, s.len);
|
||||||
|
|
||||||
|
@memcpy(s[0..size], rdr.buffer[rdr.seek..][0..size]);
|
||||||
|
|
||||||
|
wrote += size;
|
||||||
|
rdr.seek += size;
|
||||||
|
|
||||||
|
if (rdr.seek >= rdr.end) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
const Header = packed struct(u16) {
|
||||||
|
size: u15,
|
||||||
|
uncompressed: bool,
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
pub fn readValue(comptime T: type, bytes: []u8) T {
|
||||||
|
var res = std.mem.bytesToValue(T, bytes);
|
||||||
|
if (comptime builtin.cpu.arch.endian() != .little) {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.@"enum" => res = std.mem.nativeToLittle(@typeInfo(T).@"enum".tag_type, res),
|
||||||
|
.@"struct" => |s| {
|
||||||
|
if (s.layout == .@"packed") {
|
||||||
|
res = @bitCast(std.mem.nativeToLittle(s.backing_integer.?, @bitCast(res)));
|
||||||
|
} else {
|
||||||
|
std.mem.byteSwapAllFields(T, &res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.int => res = std.mem.nativeToLittle(T, res),
|
||||||
|
.array => |a| std.mem.byteSwapAllElements(a.child, res),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
pub fn readValueRdr(comptime T: type, rdr: *std.Io.Reader) !T {
|
||||||
|
var tmp: [@sizeOf(T)]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&tmp);
|
||||||
|
|
||||||
|
return readValue(T, &tmp);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user