Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21f1a304ca | |||
| cfc0e58905 | |||
| 0d5b727919 | |||
| b111859c4d | |||
| 3e97aabe53 | |||
| eec468ff17 | |||
| c7c44029c9 | |||
| e07f11d195 | |||
| d0787a5200 | |||
| 4ee15b036a | |||
| c01caba69b | |||
| 3093994ac1 | |||
| ad8222911f | |||
| a8c067e933 | |||
| 30755f7d5c | |||
| a1b9828578 | |||
| 8e4661c4c6 | |||
| 54aaf30ea5 | |||
| df22cf6529 | |||
| 6b5c830234 | |||
| 116234cf9c | |||
| 4601e8f323 | |||
| 50cae8b63d | |||
| 8b8c9a772f | |||
| f563648b20 | |||
| 8d4f3d72f8 | |||
| 0b6129f1ae | |||
| 7308faad36 | |||
| c9499251f8 | |||
| d470ca98e3 | |||
| a606f5e11a | |||
| a4e23a840d | |||
| 3a10572953 | |||
| edfe919c1b | |||
| 4515610082 | |||
| beca6a2ae6 | |||
| 760e11df0b | |||
| 5f629df47c | |||
| b0160e005b | |||
| b7b99325da | |||
| b22e4d003d | |||
| a41c37fef4 | |||
| 2d079d77f7 | |||
| 48f4235875 | |||
| 567dea8a0b | |||
| f78e5c7386 | |||
| 81e975c0d9 | |||
| fd274a8072 | |||
| 50ae79637e | |||
| ee41dc7278 | |||
| c34acebf51 | |||
| bdbda29d39 | |||
| ca4e867ddc | |||
| b066835066 | |||
| 2363bd7d10 | |||
| e619005b77 | |||
| 28b44891b3 | |||
| 4829c802a3 | |||
| 570db9632a | |||
| 0076294675 | |||
| fd9e3d595b | |||
| b8189490eb | |||
| 6adc1d5c0c | |||
| 5ec12b5786 | |||
| b892adacd7 | |||
| 2760ad6ccb | |||
| 61311433b9 | |||
| 053d64a954 | |||
| 0e0222cd02 | |||
| 9c0dfbadc2 | |||
| db2fb4b9f2 | |||
| 067eaa87c2 | |||
| b64a3ec44a | |||
| 704215e1a9 | |||
| bcfd983f8d | |||
| 75502da1d0 | |||
| a316ba569f | |||
| a0f3f45885 | |||
| f771ef7623 | |||
| 0d2576f5ee | |||
| a76803aad1 | |||
| 1ff1e91d5e | |||
| 2bcbc16613 | |||
| 3c98cf2cdb | |||
| 2c392cf250 | |||
| 5d4e7b1435 | |||
| 7aed59b5b1 | |||
| f3fb8a128f | |||
| 23bb19644b | |||
| ed14f13d9a | |||
| 428f938c3a |
@@ -0,0 +1,31 @@
|
|||||||
|
name: Release Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- uses: mlugg/setup-zig@v2
|
||||||
|
- name: Install deps
|
||||||
|
run: sudo apt update && sudo apt install -y liblzma-dev liblzo2-dev
|
||||||
|
- name: Build normal version
|
||||||
|
run: zig build --release=fast -Duse_zig_decomp=true -Dversion=${{ github.ref_name }}
|
||||||
|
- name: Move zig build out
|
||||||
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-zig-libs
|
||||||
|
- name: Rebuild with C libraries
|
||||||
|
run: zig build --release=fast -Dversion="${{ github.ref_name }}"
|
||||||
|
- name: Move C build out
|
||||||
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
files: |
|
||||||
|
unsquashfs-x86_64-zig-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", "-Duse_c_libs=true", "-Ddebug=true"],
|
||||||
|
},
|
||||||
|
|
||||||
|
"program": "zig-out/bin/unsquashfs",
|
||||||
|
"args": [
|
||||||
|
"--force",
|
||||||
|
"-d",
|
||||||
|
"testing/TestExtractUnsquashfs",
|
||||||
|
"testing/LinuxPATest.sfs",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 Caleb Gardner
|
Copyright (c) 2026 Caleb Gardner
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,7 +1,75 @@
|
|||||||
# zig-squashfs
|
# zig-squashfs
|
||||||
|
|
||||||
Messing around with zig via making a squashfs library. May amount to something. Or not.
|
This is my experiments to learn Zig. Might amount to something. Might not.
|
||||||
|
|
||||||
## Current state
|
A library and application to decompress or view squashfs archives.
|
||||||
|
|
||||||
Performance is reatively bad (when compared to the official [squashfs-tools](https://github.com/plougher/squashfs-tools), but the basics should fully work.
|
## Current State
|
||||||
|
|
||||||
|
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
||||||
|
|
||||||
|
## Build options
|
||||||
|
|
||||||
|
> `-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.
|
||||||
|
|
||||||
|
> `-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`.
|
||||||
|
|
||||||
|
> `-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.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
Most features are present except for the following:
|
||||||
|
|
||||||
|
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||||
|
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub const lzo_bytep = @compileError("unable to translate C expr: unexpected token ''");
|
||||||
|
// /usr/include/lzo/lzoconf.h:148:9
|
||||||
|
pub const lzo_charp = @compileError("unable to translate C expr: unexpected token ''");
|
||||||
|
// /usr/include/lzo/lzoconf.h:149:9
|
||||||
|
pub const lzo_voidp = @compileError("unable to translate C expr: unexpected token ''");
|
||||||
|
```
|
||||||
|
|
||||||
|
to
|
||||||
|
|
||||||
|
```zig
|
||||||
|
pub const lzo_bytep = *u8;
|
||||||
|
// /usr/include/lzo/lzoconf.h:148:9
|
||||||
|
pub const lzo_charp = @compileError("unable to translate C expr: unexpected token ''");
|
||||||
|
// /usr/include/lzo/lzoconf.h:149:9
|
||||||
|
pub const lzo_voidp = ?*anyopaque;
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,53 +1,121 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
/// version if version isn't provided during build
|
|
||||||
const def_version = "0.0.0+testing";
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const opt = b.addOptions();
|
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use zig standard library for decompression.") orelse true;
|
||||||
const ver = b.option([]const u8, "version", "sematic version") orelse def_version;
|
// const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false;
|
||||||
const sem_ver = try std.SemanticVersion.parse(ver);
|
const debug = b.option(bool, "debug", "Enable options to make debugging easier.") orelse false;
|
||||||
opt.addOption(std.SemanticVersion, "version", sem_ver);
|
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
||||||
|
|
||||||
|
const zig_squashfs_options = b.addOptions();
|
||||||
|
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
||||||
|
// zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
const lib_mod = b.createModule(.{
|
const mod = b.addModule("zig_squashfs", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = if (debug == true) .Debug else optimize,
|
||||||
});
|
.valgrind = debug,
|
||||||
const lib = b.addLibrary(.{
|
.error_tracing = debug,
|
||||||
.linkage = .static,
|
.strip = if (debug == true) false else null,
|
||||||
.name = "zig_squashfs",
|
|
||||||
.root_module = lib_mod,
|
|
||||||
.version = sem_ver,
|
|
||||||
});
|
});
|
||||||
|
mod.addOptions("config", zig_squashfs_options);
|
||||||
|
if (!use_zig_decomp) {
|
||||||
|
mod.link_libc = true;
|
||||||
|
|
||||||
const exe_mod = b.createModule(.{
|
const c_imports = b.addTranslateC(.{
|
||||||
|
.optimize = optimize,
|
||||||
|
.target = target,
|
||||||
|
.root_source_file = b.path("src/imports.c"),
|
||||||
|
});
|
||||||
|
mod.addImport("c", c_imports.createModule());
|
||||||
|
|
||||||
|
var zlib_ng = b.dependency("zlib_ng", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
mod.linkLibrary(zlib_ng.artifact("zng"));
|
||||||
|
|
||||||
|
mod.linkSystemLibrary("lzma", .{ .preferred_link_mode = .static });
|
||||||
|
|
||||||
|
var minilzo = b.dependency("minilzo", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
mod.linkLibrary(minilzo.artifact("minilzo"));
|
||||||
|
|
||||||
|
var lz4 = b.dependency("lz4", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
mod.linkLibrary(lz4.artifact("lz4"));
|
||||||
|
|
||||||
|
var zstd = b.dependency("zstd", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
mod.linkLibrary(zstd.artifact("zstd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
.root_source_file = b.path("src/bin/unsquashfs.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = if (debug == true) .Debug else optimize,
|
||||||
|
.link_libc = !use_zig_decomp,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "zig_squashfs", .module = mod },
|
||||||
|
},
|
||||||
|
.valgrind = debug,
|
||||||
|
.error_tracing = debug,
|
||||||
|
.strip = if (debug == true) false else null,
|
||||||
});
|
});
|
||||||
exe_mod.addImport("squashfs", lib_mod);
|
exe_mod.addOptions("config", unsquashfs_options);
|
||||||
exe_mod.addOptions("config", opt);
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.linkage = .static,
|
|
||||||
.name = "unsquashfs",
|
.name = "unsquashfs",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
.version = sem_ver,
|
.use_llvm = debug,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lib = b.addLibrary(.{
|
||||||
|
.name = "squashfs",
|
||||||
|
.root_module = mod,
|
||||||
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
const lib_unit_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
.root_module = lib_mod,
|
.root_module = mod,
|
||||||
|
.test_runner = .{
|
||||||
|
.mode = .simple,
|
||||||
|
.path = b.path("src/test.zig"),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const exe_unit_test = b.addTest(.{
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
|
|
||||||
|
// zls build check steps
|
||||||
|
const lib_check = b.addLibrary(.{
|
||||||
|
.name = "squashfs",
|
||||||
|
.root_module = mod,
|
||||||
|
});
|
||||||
|
const exe_check = b.addExecutable(.{
|
||||||
|
.name = "unsquashfs",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
});
|
});
|
||||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
const check = b.step("check", "Check if unsquashfs compiles");
|
||||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_test);
|
check.dependOn(&exe_check.step);
|
||||||
const test_step = b.step("test", "Run unit tests");
|
check.dependOn(&lib_check.step);
|
||||||
test_step.dependOn(&run_lib_unit_tests.step);
|
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-49
@@ -1,55 +1,32 @@
|
|||||||
.{
|
.{
|
||||||
.name = .zig_squashfs,
|
.name = .squashfs,
|
||||||
.version = "0.0.1",
|
.version = "0.0.6",
|
||||||
.fingerprint = 0x527960c72c03ffe3, // Changing this has security and trust implications.
|
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
||||||
|
.minimum_zig_version = "0.16.0",
|
||||||
.minimum_zig_version = "0.14.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#5f2f02dfb28acca2517dacbbd09e9b987f57b133",
|
||||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
// .hash = "zlib_ng-2.3.3-pre1-2HYS4ClFAABW8KlHMyBHtlNKE3V7kCS8wqfxawG7xeaa",
|
||||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
.path = "../zig-zlib-ng",
|
||||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
},
|
||||||
// // which will prevent zig from using it.
|
.zstd = .{
|
||||||
// .url = "https://example.com/foo.tar.gz",
|
.url = "git+https://github.com/allyourcodebase/zstd.git?ref=1.5.7-1#e1a501be57f42c541e8a5597e4b59a074dfd09a3",
|
||||||
//
|
.hash = "zstd-1.5.7-1-KEItkAMwAAD6OKY3m0OOmXG7aL-aLUfrDqbP5J5oYapU",
|
||||||
// // This is computed from the file contents of the directory of files that is
|
},
|
||||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
.lz4 = .{
|
||||||
// // `paths`.
|
.url = "git+https://github.com/allyourcodebase/lz4.git?ref=1.10.0-6#41f52ab227caf9d48cf88c89a4d2946caa12b102",
|
||||||
// //
|
.hash = "lz4-1.10.0-6-ewyzw-4NAAAWDpY4xpiqr4LQhZQAC0x_rGnW2iPh6jk2",
|
||||||
// // This field is the source of truth; packages do not come from a `url`; they
|
},
|
||||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
.minilzo = .{
|
||||||
// // obtain a package matching this `hash`.
|
// .url = "git+https://github.com/CalebQ42/zig-minilzo.git#7cbae997b91a44d74b7cd6c073584dc9562a6c90",
|
||||||
// //
|
// .hash = "minilzo-2.10.0-Ij7BO8wLAADeWI4Pe4jp8XTDsDaquZR14oZ7_9yKKDWP",
|
||||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
.path = "../zig-minilzo",
|
||||||
// .hash = "...",
|
},
|
||||||
//
|
// .fastlzma2 = .{
|
||||||
// // When this is provided, the package is found in a directory relative to the
|
// .url = "git+https://github.com/allyourcodebase/fast-lzma2#d7615e0c957a62fcd6691b3fe9519a091885bfa2",
|
||||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
// .hash = "fastlzma2-0.0.0-gNWHgVeLAAD0Tlak3xhNcgpPSYcjyJppq0tlGmPKCC_V",
|
||||||
// // 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,
|
|
||||||
//},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Specifies the set of files and directories that are included in this package.
|
|
||||||
// Only files and directories listed here are included in the `hash` that
|
|
||||||
// is computed for this package. Only files listed here will remain on disk
|
|
||||||
// when using the zig package manager. As a rule of thumb, one should list
|
|
||||||
// files required for compilation plus any license(s).
|
|
||||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
|
||||||
// the build root itself.
|
|
||||||
// A directory listed here means that all files within, recursively, are included.
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
"build.zig.zon",
|
"build.zig.zon",
|
||||||
|
|||||||
+184
@@ -0,0 +1,184 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
|
||||||
|
const DecompTypes = @import("decomp/types.zig");
|
||||||
|
const Decompressor = @import("decomp.zig");
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
|
const File = @import("file.zig");
|
||||||
|
const Inode = @import("inode.zig");
|
||||||
|
const BlockSize = @import("inode/file.zig").BlockSize;
|
||||||
|
const LookupTable = @import("lookup_table.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
const Utils = @import("util/utils.zig");
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
BadMagic,
|
||||||
|
BadBlockLog,
|
||||||
|
BadVersion,
|
||||||
|
BadCheck,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Archive = @This();
|
||||||
|
|
||||||
|
file: OffsetFile,
|
||||||
|
|
||||||
|
super: Superblock,
|
||||||
|
|
||||||
|
stateless_decomp: Decompressor,
|
||||||
|
|
||||||
|
/// Create an Archive from a File.
|
||||||
|
pub fn init(io: Io, fil: Io.File, offset: u64) !Archive {
|
||||||
|
var super: Superblock = undefined;
|
||||||
|
var fil_rdr = fil.reader(io, &[0]u8{});
|
||||||
|
if (offset > 0)
|
||||||
|
try fil_rdr.seekTo(offset);
|
||||||
|
try fil_rdr.interface.readSliceEndian(Superblock, @ptrCast(&super), .little);
|
||||||
|
try super.validate();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.file = .{ .fil = fil, .offset = offset },
|
||||||
|
.super = super,
|
||||||
|
.stateless_decomp = .{ .vtable = &.{ .stateless = try DecompTypes.getStatelessFn(super.compression) } },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root(self: Archive, alloc: std.mem.Allocator) !File {
|
||||||
|
return .{
|
||||||
|
.file = self.file,
|
||||||
|
.super = self.super.toMinimal(),
|
||||||
|
.decomp = self.stateless_decomp.statelessCopy(alloc),
|
||||||
|
|
||||||
|
.inode = try Utils.readInode(
|
||||||
|
alloc,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.file,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
self.super.root_ref.block_start,
|
||||||
|
self.super.root_ref.block_offset,
|
||||||
|
),
|
||||||
|
.name = "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn open(self: Archive, alloc: std.mem.Allocator, path: []const u8) !File {
|
||||||
|
if (Utils.pathIsSelf(path)) return self.root(alloc);
|
||||||
|
var root_file = self.root(alloc);
|
||||||
|
defer root_file.deinit();
|
||||||
|
return root_file.open(alloc, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fragEntry(self: Archive, idx: u32) !FragEntry {
|
||||||
|
return LookupTable.stateless(FragEntry, self.fil, &self.stateless_decomp, self.super.frag_start, idx);
|
||||||
|
}
|
||||||
|
pub fn id(self: Archive, idx: u32) !u16 {
|
||||||
|
return LookupTable.stateless(u16, self.fil, &self.stateless_decomp, self.super.id_start, idx);
|
||||||
|
}
|
||||||
|
pub fn inode(self: Archive, alloc: std.mem.Allocator, inode_num: u32) !Inode {
|
||||||
|
const ref = try LookupTable.stateless(Inode.Ref, self.file, &self.stateless_decomp, self.super.export_start, inode_num - 1);
|
||||||
|
return Utils.readInode(
|
||||||
|
alloc,
|
||||||
|
&self.stateless_decomp,
|
||||||
|
self.file,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
ref.block_start,
|
||||||
|
ref.block_offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = path;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Superblock
|
||||||
|
|
||||||
|
const SQUASHFS_MAGIC: u32 = std.mem.readInt(u32, "hsqs", .little);
|
||||||
|
|
||||||
|
pub const Superblock = packed struct {
|
||||||
|
magic: u32,
|
||||||
|
inode_count: u32,
|
||||||
|
mod_time: u32,
|
||||||
|
block_size: u32,
|
||||||
|
frag_count: u32,
|
||||||
|
compression: DecompTypes.Enum,
|
||||||
|
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: Inode.Ref,
|
||||||
|
size: u64,
|
||||||
|
id_start: u64,
|
||||||
|
xattr_start: u64,
|
||||||
|
inode_start: u64,
|
||||||
|
dir_start: u64,
|
||||||
|
frag_start: u64,
|
||||||
|
export_start: u64,
|
||||||
|
|
||||||
|
/// Validate the Superblock. If an error is returned, it's likely the archive is corrupted or not a squashfs archive.
|
||||||
|
fn validate(self: Superblock) !void {
|
||||||
|
if (self.magic != SQUASHFS_MAGIC)
|
||||||
|
return Error.BadMagic;
|
||||||
|
if (self.flags.check)
|
||||||
|
return Error.BadCheck;
|
||||||
|
if (self.ver_maj != 4 or self.ver_min != 0)
|
||||||
|
return Error.BadVersion;
|
||||||
|
if (std.math.log2(self.block_size) != self.block_log)
|
||||||
|
return Error.BadBlockLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toMinimal(self: Superblock) MinimalSuperblock {
|
||||||
|
return .{
|
||||||
|
.inode_count = self.inode_count,
|
||||||
|
.block_size = self.block_size,
|
||||||
|
.frag_count = self.frag_count,
|
||||||
|
.id_count = self.id_count,
|
||||||
|
.id_start = self.id_start,
|
||||||
|
.xattr_start = self.xattr_start,
|
||||||
|
.inode_start = self.inode_start,
|
||||||
|
.dir_start = self.dir_start,
|
||||||
|
.frag_start = self.frag_start,
|
||||||
|
.export_start = self.export_start,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MinimalSuperblock = struct {
|
||||||
|
inode_count: u32,
|
||||||
|
block_size: u32,
|
||||||
|
frag_count: u32,
|
||||||
|
id_count: u16,
|
||||||
|
id_start: u64,
|
||||||
|
xattr_start: u64,
|
||||||
|
inode_start: u64,
|
||||||
|
dir_start: u64,
|
||||||
|
frag_start: u64,
|
||||||
|
export_start: u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Frag Entry
|
||||||
|
|
||||||
|
pub const FragEntry = packed struct {
|
||||||
|
block_start: u64,
|
||||||
|
size: BlockSize,
|
||||||
|
_: u32,
|
||||||
|
};
|
||||||
+113
-124
@@ -1,146 +1,135 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const config = @import("config");
|
const Io = std.Io;
|
||||||
const squashfs = @import("squashfs");
|
const Writer = Io.Writer;
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const help_msg =
|
const config = @import("config");
|
||||||
\\Basic Usage: zig-unsquashfs [Options] SQUASHFS_FILE <EXTRACT_LOCATION>
|
const squashfs = @import("zig_squashfs");
|
||||||
|
|
||||||
|
//TODO: Add more options
|
||||||
|
const help_mgs =
|
||||||
\\
|
\\
|
||||||
\\General options:
|
\\Usage: unsquashfs [options] <archive>
|
||||||
\\ -e <path> Path to a file or directory inside the archive to extract instead of the whole archive.
|
|
||||||
\\ Can be given multiple times.
|
|
||||||
\\ -o <bytes> Skip <bytes> before reading from the archive.
|
|
||||||
\\ -v Verbose output.
|
|
||||||
\\
|
\\
|
||||||
\\Extraction options:
|
\\Options:
|
||||||
\\ --unbreak-symlinks Attempt extract symlink targets along with symlinks. Will not place files outside of the extraction location.
|
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
||||||
\\ -us Same as --unbreak-symlinks
|
|
||||||
\\ --deref-symlinks Replace symlink files with their target.
|
|
||||||
\\ -ds Same as --deref-symlinks
|
|
||||||
\\ -p <#> Use at most # of processors. Defaults to logical core count.
|
|
||||||
\\
|
\\
|
||||||
\\Listing Options:
|
\\ -o <offset> Start reading the archive at the given offset.
|
||||||
\\ -l List files instead of extracting. When used, you do not need to specify an extraction location.
|
\\ -dx Don't set xattr values
|
||||||
\\ -ll Similiar to -l, but with file attributes.
|
\\ -dp Don't set permissions (includes setting uid & gid owner)
|
||||||
\\ -lln Similiar to -ll, but with numeric uids and gids.
|
|
||||||
\\
|
\\
|
||||||
\\Other:
|
\\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
|
||||||
\\ --help Prints this help message.
|
\\ -v Verbose
|
||||||
\\ -h Same as --help
|
\\
|
||||||
\\ --version Print version number.
|
\\ --force Force extraction. If the destination already exists, it will be deleted.
|
||||||
|
\\
|
||||||
|
\\ --help Display this messages
|
||||||
|
\\ --version Display the version
|
||||||
\\
|
\\
|
||||||
;
|
;
|
||||||
|
|
||||||
const stdout = std.io.getStdOut();
|
const errors = error{InvalidArguments};
|
||||||
|
|
||||||
var extr_files: std.ArrayList([]const u8) = undefined;
|
var archive: []const u8 = "";
|
||||||
|
var extLoc: []const u8 = "squashfs-root";
|
||||||
var offset: u64 = 0;
|
var offset: u64 = 0;
|
||||||
|
var threads: u32 = 0;
|
||||||
var verbose: bool = false;
|
var verbose: bool = false;
|
||||||
var unbreak: bool = false;
|
var ignore_xattrs: bool = false;
|
||||||
var deref: bool = false;
|
var ignore_permissions: bool = false;
|
||||||
var processors: u16 = 0;
|
var force: bool = false;
|
||||||
var list: ListTypes = .None;
|
|
||||||
|
|
||||||
var filename: []const u8 = "";
|
pub fn main(init: std.process.Init) !void {
|
||||||
var extr_location: []const u8 = "";
|
const alloc = init.gpa;
|
||||||
|
const io = init.io;
|
||||||
|
|
||||||
const ListTypes = enum {
|
var stdout = std.Io.File.stdout();
|
||||||
None,
|
var out = stdout.writer(io, &[0]u8{});
|
||||||
List,
|
defer out.interface.flush() catch {};
|
||||||
ListAttr,
|
try handleArgs(init.minimal.args, &out.interface);
|
||||||
ListNumeric,
|
if (archive.len == 0) {
|
||||||
};
|
try out.interface.print("You must provide a squashfs archive\n", .{});
|
||||||
|
try out.interface.print(help_mgs, .{});
|
||||||
pub fn main() !void {
|
|
||||||
const alloc = std.heap.smp_allocator;
|
|
||||||
extr_files = .init(alloc);
|
|
||||||
defer extr_files.deinit();
|
|
||||||
var args = std.process.argsWithAllocator(alloc) catch {
|
|
||||||
_ = try stdout.writeAll("Unable to allocate memory");
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
var fil: Io.File = try Io.Dir.cwd().openFile(io, archive, .{}); //TODO: Handle error gracefully.
|
||||||
|
defer fil.close(io);
|
||||||
|
var arc: squashfs.Archive = try .init(io, fil, offset); //TODO: Handle error gracefully.
|
||||||
|
const options: squashfs.ExtractionOptions = .{
|
||||||
|
.threads = if (threads == 0) try std.Thread.getCpuCount() else threads,
|
||||||
|
.verbose = verbose,
|
||||||
|
.verbose_writer = if (verbose) &out.interface else null,
|
||||||
|
.ignore_xattr = ignore_xattrs,
|
||||||
|
.ignore_permissions = ignore_permissions,
|
||||||
};
|
};
|
||||||
defer args.deinit();
|
if (force)
|
||||||
_ = args.next();
|
try Io.Dir.cwd().deleteTree(io, extLoc);
|
||||||
while (args.next()) |arg| {
|
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
|
||||||
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
}
|
||||||
_ = try stdout.writeAll(help_msg);
|
|
||||||
return;
|
fn handleArgs(args: std.process.Args, out: *Writer) !void {
|
||||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
var arg_iter = args.iterate();
|
||||||
try config.version.format("", .{}, stdout.writer());
|
defer arg_iter.deinit();
|
||||||
_ = try stdout.write("\n");
|
_ = arg_iter.next(); // args[0] is the application launch command.
|
||||||
return;
|
while (arg_iter.next()) |arg| {
|
||||||
|
if (std.mem.eql(u8, arg, "-o")) {
|
||||||
|
const nxt = arg_iter.next();
|
||||||
|
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 = arg_iter.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 = arg_iter.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")) {
|
} else if (std.mem.eql(u8, arg, "-v")) {
|
||||||
verbose = true;
|
verbose = true;
|
||||||
} else if (std.mem.eql(u8, arg, "--unbreak-symlinks") or std.mem.eql(u8, arg, "-us")) {
|
continue;
|
||||||
unbreak = true;
|
} else if (std.mem.eql(u8, arg, "-dx")) {
|
||||||
} else if (std.mem.eql(u8, arg, "--deref-symlinks") or std.mem.eql(u8, arg, "-ds")) {
|
ignore_xattrs = true;
|
||||||
deref = true;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-l")) {
|
} else if (std.mem.eql(u8, arg, "-dp")) {
|
||||||
list = .List;
|
ignore_permissions = true;
|
||||||
} else if (std.mem.eql(u8, arg, "-ll")) {
|
continue;
|
||||||
list = .ListAttr;
|
} else if (std.mem.eql(u8, arg, "--force")) {
|
||||||
} else if (std.mem.eql(u8, arg, "-lln")) {
|
force = true;
|
||||||
list = .ListNumeric;
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "-e")) {
|
} else if (std.mem.eql(u8, arg, "--version")) {
|
||||||
const next = args.next();
|
try out.print("zig-unsquashfs v", .{});
|
||||||
if (next == null) {
|
try config.version.format(out);
|
||||||
_ = try stdout.writeAll("path required after -e\n");
|
try out.print("\nBuilt using Zig {s} in {} mode\n", .{ builtin.zig_version_string, builtin.mode });
|
||||||
return;
|
std.process.exit(0);
|
||||||
}
|
return;
|
||||||
try extr_files.append(next.?);
|
} else if (std.mem.eql(u8, arg, "--help")) {
|
||||||
} else if (std.mem.eql(u8, arg, "-o")) {
|
try out.print(help_mgs, .{});
|
||||||
const next = args.next();
|
std.process.exit(0);
|
||||||
if (next == null) {
|
|
||||||
_ = try stdout.writeAll("offset required after -o\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
offset = try std.fmt.parseInt(u64, next.?, 10);
|
|
||||||
} else if (std.mem.eql(u8, arg, "-p")) {
|
|
||||||
const next = args.next();
|
|
||||||
if (next == null) {
|
|
||||||
_ = try stdout.writeAll("number required after -p\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processors = try std.fmt.parseInt(u16, next.?, 10);
|
|
||||||
} else if (filename.len == 0) {
|
|
||||||
filename = arg;
|
|
||||||
} else if (extr_location.len == 0) {
|
|
||||||
extr_location = arg;
|
|
||||||
} else {
|
|
||||||
_ = try stdout.writeAll("invalid or too many arguments\n");
|
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
if (filename.len == 0) {
|
|
||||||
_ = try stdout.writeAll("no archive given\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (list == .None and extr_location.len == 0) {
|
|
||||||
_ = try stdout.writeAll("no extract location given\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fil = try std.fs.cwd().openFile(filename, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var th_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = std.heap.smp_allocator };
|
|
||||||
var rdr = squashfs.SfsFile.init(
|
|
||||||
th_alloc.allocator(),
|
|
||||||
fil,
|
|
||||||
offset,
|
|
||||||
) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error opening {s} as squashfs: {any}\n", .{ filename, err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer rdr.deinit();
|
|
||||||
//TODO: list and extr_files;
|
|
||||||
var op: squashfs.ExtractionOptions = squashfs.ExtractionOptions.init() catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error setting extraction options: {any}\n", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
op.verbose = verbose;
|
|
||||||
op.dereference_symlinks = deref;
|
|
||||||
op.unbreak_symlinks = unbreak;
|
|
||||||
if (processors != 0) op.thread_count = processors;
|
|
||||||
rdr.extract(op, extr_location) catch |err| {
|
|
||||||
try std.fmt.format(stdout.writer(), "Error extracting archive: {any}\n", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const StatelessDecomp = *const fn (std.mem.Allocator, in: []u8, out: []u8) Error!usize;
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
OutOfMemory,
|
||||||
|
EndOfStream,
|
||||||
|
ReadFailed,
|
||||||
|
WriteFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Decompressor = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator = std.heap.smp_allocator,
|
||||||
|
vtable: *const struct {
|
||||||
|
decompress: *const fn (*const Decompressor, in: []u8, out: []u8) Error!usize = defaultDecompress,
|
||||||
|
stateless: StatelessDecomp,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Create a copy of the decompressor using it's stateless function and the new allocator.
|
||||||
|
pub fn statelessCopy(self: Decompressor, alloc: std.mem.Allocator) Decompressor {
|
||||||
|
return &.{ .alloc = alloc, .vtable = &.{ .stateless = self.vtable.stateless } };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
|
||||||
|
return self.vtable.decompress(self, in, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn defaultDecompress(self: *const Decompressor, in: []u8, out: []u8) Error!usize {
|
||||||
|
return self.vtable.stateless(self.alloc, in, out);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c");
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
|
||||||
|
|
||||||
|
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
const res = c.LZ4_decompress_safe(in.ptr, out.ptr, @intCast(in.len), @intCast(out.len));
|
||||||
|
if (res > 0) return @abs(res);
|
||||||
|
return Decompressor.Error.ReadFailed; // TOOD: Find out what errors can be returned.
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c");
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
const Lzma = @This();
|
||||||
|
|
||||||
|
streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream),
|
||||||
|
|
||||||
|
interface: Decompressor,
|
||||||
|
|
||||||
|
err: ?Error = null,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !Lzma {
|
||||||
|
return .{
|
||||||
|
.streams = try .init(alloc),
|
||||||
|
.interface = &.{
|
||||||
|
.alloc = alloc,
|
||||||
|
.vtable = .{ .decompress = decompress, .stateless = stateless },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Lzma) void {
|
||||||
|
var values = self.streams.valueIterator();
|
||||||
|
while (values.next()) |val| {
|
||||||
|
c.lzma_end(val);
|
||||||
|
}
|
||||||
|
self.streams.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getOrCreate(self: *Lzma) !*c.lzma_stream {
|
||||||
|
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
|
||||||
|
if (res.found_existing) return res.value_ptr;
|
||||||
|
res.value_ptr.* = .{
|
||||||
|
.alloc = .{
|
||||||
|
.alloc = lzmaAlloc,
|
||||||
|
.free = lzmaFree,
|
||||||
|
.@"opaque" = &self.interface.alloc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return res.value_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var self: *Lzma = @fieldParentPtr("interface", decomp);
|
||||||
|
|
||||||
|
const stream = try self.getOrCreate();
|
||||||
|
stream.next_in = in.ptr;
|
||||||
|
stream.avail_in = in.len;
|
||||||
|
stream.next_out = out.ptr;
|
||||||
|
stream.avail_out = out.len;
|
||||||
|
var res = c.lzma_alone_decoder(stream, out.len);
|
||||||
|
decodeResult(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return lzmaErrorToDecompError(err);
|
||||||
|
};
|
||||||
|
while (true) {
|
||||||
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
|
if (res == c.LZMA_OK) continue;
|
||||||
|
if (res == c.LZMA_STREAM_END) break;
|
||||||
|
decodeResult(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return lzmaErrorToDecompError(err);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return stream.total_out;
|
||||||
|
}
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var stream: c.lzma_stream = .{
|
||||||
|
.next_in = in.ptr,
|
||||||
|
.avail_in = in.len,
|
||||||
|
.next_out = out.ptr,
|
||||||
|
.avail_out = out.len,
|
||||||
|
.allocator = &.{
|
||||||
|
.alloc = lzmaAlloc,
|
||||||
|
.free = lzmaFree,
|
||||||
|
.@"opaque" = @ptrCast(@constCast(&alloc)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var res = c.lzma_alone_decoder(&stream, out.len);
|
||||||
|
decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
|
||||||
|
while (true) {
|
||||||
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
|
if (res == c.LZMA_OK) continue;
|
||||||
|
if (res == c.LZMA_STREAM_END) break;
|
||||||
|
decodeResult(res) catch |err| return lzmaErrorToDecompError(err);
|
||||||
|
}
|
||||||
|
return stream.total_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn decodeResult(res: c_uint) Error!void {
|
||||||
|
return switch (res) {
|
||||||
|
c.LZMA_OK => {},
|
||||||
|
c.LZMA_STREAM_END => {},
|
||||||
|
c.LZMA_NO_CHECK => {},
|
||||||
|
c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck,
|
||||||
|
c.LZMA_MEM_ERROR => Error.OutOfMemory,
|
||||||
|
c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory,
|
||||||
|
c.LZMA_FORMAT_ERROR => Error.Format,
|
||||||
|
c.LZMA_OPTIONS_ERROR => Error.Options,
|
||||||
|
c.LZMA_DATA_ERROR => Error.Data,
|
||||||
|
c.LZMA_BUF_ERROR => Error.BufferExhausted,
|
||||||
|
c.LZMA_PROG_ERROR => Error.Programming,
|
||||||
|
c.LZMA_SEEK_NEEDED => Error.SeekNeeded,
|
||||||
|
else => Error.Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn lzmaErrorToDecompError(err: Error) Decompressor.Error {
|
||||||
|
switch (err) {
|
||||||
|
Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
|
||||||
|
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Format => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Options => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Data => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.BufferExhausted => return Decompressor.Error.WriteFailed,
|
||||||
|
Error.Programming => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.SeekNeeded => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Unknown => return Decompressor.Error.ReadFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
|
||||||
|
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||||
|
return alloc.rawAlloc(size, .@"1", 0);
|
||||||
|
}
|
||||||
|
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
|
||||||
|
if (alloc_ptr == null) return;
|
||||||
|
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||||
|
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
OutOfMemory,
|
||||||
|
UnsupportedCheck,
|
||||||
|
Format,
|
||||||
|
Options,
|
||||||
|
Data,
|
||||||
|
BufferExhausted,
|
||||||
|
Programming,
|
||||||
|
SeekNeeded,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c");
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
|
||||||
|
|
||||||
|
pub fn stateless(_: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var out_len = out.len;
|
||||||
|
const res = c.lzo1x_decompress(in.ptr, in.len, out.ptr, &out_len, null);
|
||||||
|
return switch (res) {
|
||||||
|
c.LZO_E_OK => out_len,
|
||||||
|
c.LZO_E_ERROR => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_OUT_OF_MEMORY => Decompressor.Error.OutOfMemory,
|
||||||
|
c.LZO_E_NOT_COMPRESSIBLE => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_INPUT_OVERRUN => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_OUTPUT_OVERRUN => Decompressor.Error.WriteFailed,
|
||||||
|
c.LZO_E_LOOKBEHIND_OVERRUN => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_EOF_NOT_FOUND => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_INPUT_NOT_CONSUMED => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_NOT_YET_IMPLEMENTED => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_INVALID_ARGUMENT => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_INVALID_ALIGNMENT => Decompressor.Error.ReadFailed,
|
||||||
|
c.LZO_E_OUTPUT_NOT_CONSUMED => Decompressor.Error.WriteFailed,
|
||||||
|
c.LZO_E_INTERNAL_ERROR => Decompressor.Error.ReadFailed,
|
||||||
|
else => Decompressor.Error.ReadFailed,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c");
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
const Xz = @This();
|
||||||
|
|
||||||
|
streams: std.AutoHashMap(std.Thread.Id, c.lzma_stream),
|
||||||
|
|
||||||
|
interface: Decompressor,
|
||||||
|
|
||||||
|
err: ?Error = null,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !Xz {
|
||||||
|
return .{
|
||||||
|
.streams = try .init(alloc),
|
||||||
|
.interface = &.{
|
||||||
|
.alloc = alloc,
|
||||||
|
.vtable = .{ .decompress = decompress, .stateless = stateless },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Xz) void {
|
||||||
|
var values = self.streams.valueIterator();
|
||||||
|
while (values.next()) |val| {
|
||||||
|
c.xz_end(val);
|
||||||
|
}
|
||||||
|
self.streams.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getOrCreate(self: *Xz) !*c.xz_stream {
|
||||||
|
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
|
||||||
|
if (res.found_existing) return res.value_ptr;
|
||||||
|
res.value_ptr.* = .{
|
||||||
|
.alloc = .{
|
||||||
|
.alloc = lzmaAlloc,
|
||||||
|
.free = lzmaFree,
|
||||||
|
.@"opaque" = &self.interface.alloc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return res.value_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var self: *Xz = @fieldParentPtr("interface", decomp);
|
||||||
|
|
||||||
|
const stream = try self.getOrCreate();
|
||||||
|
stream.next_in = in.ptr;
|
||||||
|
stream.avail_in = in.len;
|
||||||
|
stream.next_out = out.ptr;
|
||||||
|
stream.avail_out = out.len;
|
||||||
|
var res = c.lzma_alone_decoder(stream, out.len);
|
||||||
|
decodeResult(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return xzErrorToDecompError(err);
|
||||||
|
};
|
||||||
|
while (true) {
|
||||||
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
|
if (res == c.LZMA_OK) continue;
|
||||||
|
if (res == c.LZMA_STREAM_END) break;
|
||||||
|
decodeResult(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return xzErrorToDecompError(err);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return stream.total_out;
|
||||||
|
}
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var stream: c.lzma_stream = .{
|
||||||
|
.next_in = in.ptr,
|
||||||
|
.avail_in = in.len,
|
||||||
|
.next_out = out.ptr,
|
||||||
|
.avail_out = out.len,
|
||||||
|
.allocator = &.{
|
||||||
|
.alloc = lzmaAlloc,
|
||||||
|
.free = lzmaFree,
|
||||||
|
.@"opaque" = @ptrCast(@constCast(&alloc)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var res = c.lzma_alone_decoder(&stream, out.len);
|
||||||
|
decodeResult(res) catch |err| return xzErrorToDecompError(err);
|
||||||
|
while (true) {
|
||||||
|
res = c.lzma_code(&stream, c.LZMA_RUN);
|
||||||
|
if (res == c.LZMA_OK) continue;
|
||||||
|
if (res == c.LZMA_STREAM_END) break;
|
||||||
|
decodeResult(res) catch |err| return xzErrorToDecompError(err);
|
||||||
|
}
|
||||||
|
return stream.total_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn decodeResult(res: c_uint) Error!void {
|
||||||
|
return switch (res) {
|
||||||
|
c.LZMA_OK => {},
|
||||||
|
c.LZMA_STREAM_END => {},
|
||||||
|
c.LZMA_NO_CHECK => {},
|
||||||
|
c.LZMA_UNSUPPORTED_CHECK => Error.UnsupportedCheck,
|
||||||
|
c.LZMA_MEM_ERROR => Error.OutOfMemory,
|
||||||
|
c.LZMA_MEMLIMIT_ERROR => Error.OutOfMemory,
|
||||||
|
c.LZMA_FORMAT_ERROR => Error.Format,
|
||||||
|
c.LZMA_OPTIONS_ERROR => Error.Options,
|
||||||
|
c.LZMA_DATA_ERROR => Error.Data,
|
||||||
|
c.LZMA_BUF_ERROR => Error.BufferExhausted,
|
||||||
|
c.LZMA_PROG_ERROR => Error.Programming,
|
||||||
|
c.LZMA_SEEK_NEEDED => Error.SeekNeeded,
|
||||||
|
else => Error.Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn xzErrorToDecompError(err: Error) Decompressor.Error {
|
||||||
|
switch (err) {
|
||||||
|
Error.OutOfMemory => return Decompressor.Error.OutOfMemory,
|
||||||
|
Error.UnsupportedCheck => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Format => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Options => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Data => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.BufferExhausted => return Decompressor.Error.WriteFailed,
|
||||||
|
Error.Programming => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.SeekNeeded => return Decompressor.Error.ReadFailed,
|
||||||
|
Error.Unknown => return Decompressor.Error.ReadFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lzmaAlloc(ptr: ?*anyopaque, _: usize, size: usize) callconv(.c) ?*anyopaque {
|
||||||
|
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||||
|
return alloc.rawAlloc(size, .@"1", 0);
|
||||||
|
}
|
||||||
|
fn lzmaFree(ptr: ?*anyopaque, alloc_ptr: ?*anyopaque) callconv(.c) void {
|
||||||
|
if (alloc_ptr == null) return;
|
||||||
|
var alloc: *std.mem.Allocator = @ptrCast(@alignCast(ptr));
|
||||||
|
alloc.rawFree(@ptrCast(alloc_ptr), .@"1", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
OutOfMemory,
|
||||||
|
UnsupportedCheck,
|
||||||
|
Format,
|
||||||
|
Options,
|
||||||
|
Data,
|
||||||
|
BufferExhausted,
|
||||||
|
Programming,
|
||||||
|
SeekNeeded,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c");
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
const Zlib = @This();
|
||||||
|
|
||||||
|
streams: std.AutoHashMap(std.Thread.Id, c.zng_stream),
|
||||||
|
|
||||||
|
interface: Decompressor,
|
||||||
|
|
||||||
|
err: ?Error = null,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !Zlib {
|
||||||
|
return .{
|
||||||
|
.streams = try .init(alloc),
|
||||||
|
.interface = &.{
|
||||||
|
.alloc = alloc,
|
||||||
|
.vtable = .{ .decompress = decompress, .stateless = stateless },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Zlib) void {
|
||||||
|
var values = self.streams.valueIterator();
|
||||||
|
while (values.next()) |val| {
|
||||||
|
_ = c.zng_deflateEnd(val);
|
||||||
|
}
|
||||||
|
self.streams.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getOrCreate(self: *Zlib) !*c.zng_stream {
|
||||||
|
const res = try self.streams.getOrPut(std.Thread.getCurrentId());
|
||||||
|
if (res.found_existing) return res.value_ptr;
|
||||||
|
res.value_ptr.* = .{
|
||||||
|
.@"opaque" = self,
|
||||||
|
.zalloc = zalloc,
|
||||||
|
.zfree = zfree,
|
||||||
|
};
|
||||||
|
return res.value_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var self: *Zlib = @fieldParentPtr("interface", decomp);
|
||||||
|
|
||||||
|
var stream = try self.getOrCreate();
|
||||||
|
stream.next_in = in.ptr;
|
||||||
|
stream.avail_in = in.len;
|
||||||
|
stream.next_out = out.ptr;
|
||||||
|
stream.avail_out = out.len;
|
||||||
|
var res = c.zng_inflateReset(stream);
|
||||||
|
decodeError(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return Decompressor.Error.ReadFailed;
|
||||||
|
};
|
||||||
|
res = c.zng_inflate(stream, c.Z_FINISH);
|
||||||
|
decodeError(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return switch (err) {
|
||||||
|
Error.OutOfMemory => err,
|
||||||
|
else => Decompressor.Error.ReadFailed,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
_ = alloc;
|
||||||
|
var out_len = out.len;
|
||||||
|
const res = c.zng_uncompress(out.ptr, &out_len, in.ptr, in.len);
|
||||||
|
return switch (res) {
|
||||||
|
c.Z_OK => out_len,
|
||||||
|
c.Z_MEM_ERROR => Decompressor.Error.OutOfMemory,
|
||||||
|
c.Z_BUF_ERROR => Decompressor.Error.WriteFailed,
|
||||||
|
else => Decompressor.Error.ReadFailed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn decodeError(res: i32) Error!void {
|
||||||
|
return switch (res) {
|
||||||
|
c.Z_OK => {},
|
||||||
|
c.Z_STREAM_ERROR => Error.Stream,
|
||||||
|
c.Z_BUF_ERROR => Error.Buffer,
|
||||||
|
c.Z_MEM_ERROR => Error.OutOfMemory,
|
||||||
|
c.Z_DATA_ERROR => Error.Data,
|
||||||
|
c.Z_VERSION_ERROR => Error.Version,
|
||||||
|
else => Error.Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zalloc(ptr: ?*anyopaque, items: c_uint, size: c_uint) callconv(.c) ?*anyopaque {
|
||||||
|
var self: *Zlib = @ptrCast(ptr);
|
||||||
|
return self.interface.alloc.rawAlloc(items * size, .@"1", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zfree(ptr: ?*anyopaque, addr: ?*anyopaque) callconv(.c) void {
|
||||||
|
var self: *Zlib = @ptrCast(ptr);
|
||||||
|
self.interface.alloc.rawFree(@ptrCast(addr), .@"1", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
OutOfMemory,
|
||||||
|
Stream,
|
||||||
|
Buffer,
|
||||||
|
Data,
|
||||||
|
Version,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c");
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
const Zstd = @This();
|
||||||
|
|
||||||
|
context: std.AutoHashMap(std.Thread.Id, ?*c.ZSTD_DCtx),
|
||||||
|
|
||||||
|
interface: Decompressor,
|
||||||
|
|
||||||
|
err: ?Error = null,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !Zstd {
|
||||||
|
return .{
|
||||||
|
.streams = try .init(alloc),
|
||||||
|
.interface = &.{
|
||||||
|
.alloc = alloc,
|
||||||
|
.vtable = .{ .decompress = decompress, .stateless = stateless },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Zstd) void {
|
||||||
|
var values = self.context.valueIterator();
|
||||||
|
while (values.next()) |val| {
|
||||||
|
_ = c.ZSTD_freeDCtx(val.*);
|
||||||
|
}
|
||||||
|
self.context.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getOrCreate(self: *Zstd) !*c.ZSTD_DCtx {
|
||||||
|
const res = try self.context.getOrPut(std.Thread.getCurrentId());
|
||||||
|
if (res.found_existing) return res.value_ptr;
|
||||||
|
res.value_ptr.* = c.ZSTD_createDCtx();
|
||||||
|
if (res.value_ptr.* == null) return Error.OutOfMemory;
|
||||||
|
return res.value_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress(decomp: *const Decompressor, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var self: *Zstd = @fieldParentPtr("interface", decomp);
|
||||||
|
|
||||||
|
const ctx = self.getOrCreate();
|
||||||
|
const res = c.ZSTD_decompressDCtx(ctx, out.ptr, out.len, in.ptr, in.len);
|
||||||
|
decodeError(res) catch |err| {
|
||||||
|
self.err = err;
|
||||||
|
return ZstdErrorToDecompError(err);
|
||||||
|
};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
_ = alloc;
|
||||||
|
const res = c.ZSTD_decompress(out.ptr, out.len, in.ptr, in.len);
|
||||||
|
decodeError(res) catch |err| return ZstdErrorToDecompError(err);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn decodeError(res: usize) Error!void {
|
||||||
|
if (c.ZSTD_isError(res) == 0) return;
|
||||||
|
return switch (c.ZSTD_getErrorCode(res)) {
|
||||||
|
c.ZSTD_error_prefix_unknown => Error.PrefixUnknown,
|
||||||
|
c.ZSTD_error_version_unsupported => Error.VersionUnsupported,
|
||||||
|
c.ZSTD_error_frameParameter_unsupported => Error.FrameParameterUnsupported,
|
||||||
|
c.ZSTD_error_frameParameter_windowTooLarge => Error.FrameParameterWindowTooLarge,
|
||||||
|
c.ZSTD_error_corruption_detected => Error.CorruptionDetected,
|
||||||
|
c.ZSTD_error_checksum_wrong => Error.ChecksumWrong,
|
||||||
|
c.ZSTD_error_literals_headerWrong => Error.LiteralsHeaderWrong,
|
||||||
|
c.ZSTD_error_dictionary_corrupted => Error.DictionaryCorrupted,
|
||||||
|
c.ZSTD_error_dictionary_wrong => Error.DictionaryWrong,
|
||||||
|
c.ZSTD_error_dictionaryCreation_failed => Error.DictionaryCreationFailed,
|
||||||
|
c.ZSTD_error_parameter_unsupported => Error.ParameterUnsupported,
|
||||||
|
c.ZSTD_error_parameter_combination_unsupported => Error.ParameterCombinationUnsupported,
|
||||||
|
c.ZSTD_error_parameter_outOfBound => Error.ParameterOutOfBound,
|
||||||
|
c.ZSTD_error_tableLog_tooLarge => Error.TableLogTooLarge,
|
||||||
|
c.ZSTD_error_maxSymbolValue_tooLarge => Error.MaxSymbolValueTooLarge,
|
||||||
|
c.ZSTD_error_maxSymbolValue_tooSmall => Error.MaxSymbolValueTooSmall,
|
||||||
|
c.ZSTD_error_cannotProduce_uncompressedBlock => Error.CannotProduceUncompressedBlock,
|
||||||
|
c.ZSTD_error_stabilityCondition_notRespected => Error.StabilityConditionNotRespected,
|
||||||
|
c.ZSTD_error_stage_wrong => Error.StageWrong,
|
||||||
|
c.ZSTD_error_init_missing => Error.InitMissing,
|
||||||
|
c.ZSTD_error_memory_allocation => Error.MemoryAllocation,
|
||||||
|
c.ZSTD_error_workSpace_tooSmall => Error.WorkSpaceTooSmall,
|
||||||
|
c.ZSTD_error_dstSize_tooSmall => Error.DstSizeTooSmall,
|
||||||
|
c.ZSTD_error_srcSize_wrong => Error.SrcSizeWrong,
|
||||||
|
c.ZSTD_error_dstBuffer_null => Error.DstBufferNull,
|
||||||
|
c.ZSTD_error_noForwardProgress_destFull => Error.NoForwardProgressDestFull,
|
||||||
|
c.ZSTD_error_noForwardProgress_inputEmpty => Error.NoForwardProgressInputEmpty,
|
||||||
|
c.ZSTD_error_frameIndex_tooLarge => Error.FrameIndexTooLarge,
|
||||||
|
c.ZSTD_error_seekableIO => Error.SeekableIo,
|
||||||
|
c.ZSTD_error_dstBuffer_wrong => Error.DstBufferWrong,
|
||||||
|
c.ZSTD_error_srcBuffer_wrong => Error.SrcBufferWrong,
|
||||||
|
c.ZSTD_error_sequenceProducer_failed => Error.SequenceProducerFailed,
|
||||||
|
c.ZSTD_error_externalSequences_invalid => Error.ExternalSequencesInvalid,
|
||||||
|
else => Error.Generic,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
inline fn ZstdErrorToDecompError(err: Error) Decompressor.Error {
|
||||||
|
return switch (err) {
|
||||||
|
Error.OutOfMemory => Decompressor.Error.OutOfMemory,
|
||||||
|
Error.Generic => Decompressor.Error.ReadFailed,
|
||||||
|
Error.PrefixUnknown => Decompressor.Error.ReadFailed,
|
||||||
|
Error.VersionUnsupported => Decompressor.Error.ReadFailed,
|
||||||
|
Error.FrameParameterUnsupported => Decompressor.Error.ReadFailed,
|
||||||
|
Error.FrameParameterWindowTooLarge => Decompressor.Error.ReadFailed,
|
||||||
|
Error.CorruptionDetected => Decompressor.Error.ReadFailed,
|
||||||
|
Error.ChecksumWrong => Decompressor.Error.ReadFailed,
|
||||||
|
Error.LiteralsHeaderWrong => Decompressor.Error.ReadFailed,
|
||||||
|
Error.DictionaryCorrupted => Decompressor.Error.ReadFailed,
|
||||||
|
Error.DictionaryWrong => Decompressor.Error.ReadFailed,
|
||||||
|
Error.DictionaryCreationFailed => Decompressor.Error.ReadFailed,
|
||||||
|
Error.ParameterUnsupported => Decompressor.Error.ReadFailed,
|
||||||
|
Error.ParameterCombinationUnsupported => Decompressor.Error.ReadFailed,
|
||||||
|
Error.ParameterOutOfBound => Decompressor.Error.ReadFailed,
|
||||||
|
Error.TableLogTooLarge => Decompressor.Error.ReadFailed,
|
||||||
|
Error.MaxSymbolValueTooLarge => Decompressor.Error.ReadFailed,
|
||||||
|
Error.MaxSymbolValueTooSmall => Decompressor.Error.ReadFailed,
|
||||||
|
Error.CannotProduceUncompressedBlock => Decompressor.Error.ReadFailed,
|
||||||
|
Error.StabilityConditionNotRespected => Decompressor.Error.ReadFailed,
|
||||||
|
Error.StageWrong => Decompressor.Error.ReadFailed,
|
||||||
|
Error.InitMissing => Decompressor.Error.ReadFailed,
|
||||||
|
Error.MemoryAllocation => Decompressor.Error.OutOfMemory,
|
||||||
|
Error.WorkSpaceTooSmall => Decompressor.Error.WriteFailed,
|
||||||
|
Error.DstSizeTooSmall => Decompressor.Error.WriteFailed,
|
||||||
|
Error.SrcSizeWrong => Decompressor.Error.ReadFailed,
|
||||||
|
Error.DstBufferNull => Decompressor.Error.WriteFailed,
|
||||||
|
Error.NoForwardProgressDestFull => Decompressor.Error.WriteFailed,
|
||||||
|
Error.NoForwardProgressInputEmpty => Decompressor.Error.ReadFailed,
|
||||||
|
Error.FrameIndexTooLarge => Decompressor.Error.ReadFailed,
|
||||||
|
Error.SeekableIo => Decompressor.Error.ReadFailed,
|
||||||
|
Error.DstBufferWrong => Decompressor.Error.WriteFailed,
|
||||||
|
Error.SrcBufferWrong => Decompressor.Error.ReadFailed,
|
||||||
|
Error.SequenceProducerFailed => Decompressor.Error.ReadFailed,
|
||||||
|
Error.ExternalSequencesInvalid => Decompressor.Error.ReadFailed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
OutOfMemory,
|
||||||
|
Generic,
|
||||||
|
PrefixUnknown,
|
||||||
|
VersionUnsupported,
|
||||||
|
FrameParameterUnsupported,
|
||||||
|
FrameParameterWindowTooLarge,
|
||||||
|
CorruptionDetected,
|
||||||
|
ChecksumWrong,
|
||||||
|
LiteralsHeaderWrong,
|
||||||
|
DictionaryCorrupted,
|
||||||
|
DictionaryWrong,
|
||||||
|
DictionaryCreationFailed,
|
||||||
|
ParameterUnsupported,
|
||||||
|
ParameterCombinationUnsupported,
|
||||||
|
ParameterOutOfBound,
|
||||||
|
TableLogTooLarge,
|
||||||
|
MaxSymbolValueTooLarge,
|
||||||
|
MaxSymbolValueTooSmall,
|
||||||
|
CannotProduceUncompressedBlock,
|
||||||
|
StabilityConditionNotRespected,
|
||||||
|
StageWrong,
|
||||||
|
InitMissing,
|
||||||
|
MemoryAllocation,
|
||||||
|
WorkSpaceTooSmall,
|
||||||
|
DstSizeTooSmall,
|
||||||
|
SrcSizeWrong,
|
||||||
|
DstBufferNull,
|
||||||
|
NoForwardProgressDestFull,
|
||||||
|
NoForwardProgressInputEmpty,
|
||||||
|
FrameIndexTooLarge,
|
||||||
|
SeekableIo,
|
||||||
|
DstBufferWrong,
|
||||||
|
SrcBufferWrong,
|
||||||
|
SequenceProducerFailed,
|
||||||
|
ExternalSequencesInvalid,
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
const config = @import("config");
|
||||||
|
|
||||||
|
const Decompressor = @import("../decomp.zig");
|
||||||
|
|
||||||
|
pub fn getStatelessFn(decomp: Enum) !Decompressor.StatelessDecomp {
|
||||||
|
if (config.use_zig_decomp) {
|
||||||
|
return switch (decomp) {
|
||||||
|
.gzip => @import("zig/zlib.zig").stateless,
|
||||||
|
.lzma => @import("zig/lzma.zig").stateless,
|
||||||
|
.xz => @import("zig/xz.zig").stateless,
|
||||||
|
.zstd => @import("zig/zstd.zig").stateless,
|
||||||
|
.lz4 => error.ZigLz4Unsupported,
|
||||||
|
.lzo => error.ZigLzoUnsupported,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return switch (decomp) {
|
||||||
|
.gzip => @import("c/zlib.zig").stateless,
|
||||||
|
.lzma => @import("c/lzma.zig").stateless,
|
||||||
|
.lzo => @import("c/lzo.zig").stateless,
|
||||||
|
.xz => @import("c/xz.zig").stateless,
|
||||||
|
.lz4 => @import("c/lz4.zig").stateless,
|
||||||
|
.zstd => @import("c/zstd.zig").stateless,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Enum = enum(u16) {
|
||||||
|
gzip = 1, // Though officially named gzip, it actually uses zlib.
|
||||||
|
lzma,
|
||||||
|
lzo,
|
||||||
|
xz,
|
||||||
|
lz4,
|
||||||
|
zstd,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Decomp = if (config.use_zig_decomp)
|
||||||
|
union(enum) {
|
||||||
|
gzip: @import("zig/zlib.zig"),
|
||||||
|
lzma: @import("zig/lzma.zig"),
|
||||||
|
xz: @import("zig/xz.zig"),
|
||||||
|
zstd: @import("zig/zstd.zig"),
|
||||||
|
|
||||||
|
pub fn deinit(_: *Decomp) void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
union(enum) {
|
||||||
|
gzip: @import("c/zlib.zig"),
|
||||||
|
lzma: @import("c/lzma.zig"),
|
||||||
|
lzo: @import("c/lzo.zig"),
|
||||||
|
xz: @import("c/xz.zig"),
|
||||||
|
lz4: @import("c/lz4.zig"),
|
||||||
|
zstd: @import("c/zstd.zig"),
|
||||||
|
|
||||||
|
pub fn deinit(self: *Decomp) void {
|
||||||
|
switch (self) {
|
||||||
|
.gzip => self.gzip.deinit(),
|
||||||
|
.lzma => self.lzma.deinit(),
|
||||||
|
.xz => self.xz.deinit(),
|
||||||
|
.zstd => self.zstd.deinit(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const lzma = std.compress.lzma;
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
|
||||||
|
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
const buf = try alloc.alloc(u8, in.len);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var decomp = lzma.Decompress.initOptions(&rdr, alloc, buf, .{}, out.len) catch |err|
|
||||||
|
return switch (err) {
|
||||||
|
error.Overflow => Decompressor.Error.ReadFailed,
|
||||||
|
error.CorruptInput => Decompressor.Error.ReadFailed,
|
||||||
|
error.InvalidRangeCode => Decompressor.Error.ReadFailed,
|
||||||
|
else => @errorCast(err),
|
||||||
|
};
|
||||||
|
defer decomp.deinit();
|
||||||
|
return decomp.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const xz = std.compress.xz;
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
|
||||||
|
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
const buf = try alloc.alloc(u8, in.len);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var decomp = xz.Decompress.init(&rdr, alloc, buf) catch |err|
|
||||||
|
return switch (err) {
|
||||||
|
error.WrongChecksum => Decompressor.Error.ReadFailed,
|
||||||
|
error.NotXzStream => Decompressor.Error.ReadFailed,
|
||||||
|
else => @errorCast(err),
|
||||||
|
};
|
||||||
|
defer decomp.deinit();
|
||||||
|
return decomp.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const flate = std.compress.flate;
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
|
||||||
|
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
const buf = try alloc.alloc(u8, out.len);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var decomp = flate.Decompress.init(&rdr, .zlib, buf);
|
||||||
|
return decomp.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const zstd = std.compress.zstd;
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
const Decompressor = @import("../../decomp.zig");
|
||||||
|
|
||||||
|
interface: Decompressor = .{ .vtable = &.{ .stateless = stateless } },
|
||||||
|
|
||||||
|
pub fn stateless(alloc: std.mem.Allocator, in: []u8, out: []u8) Decompressor.Error!usize {
|
||||||
|
var rdr: Reader = .fixed(in);
|
||||||
|
const buf = try alloc.alloc(u8, out.len * 2);
|
||||||
|
defer alloc.free(buf);
|
||||||
|
|
||||||
|
var decomp = zstd.Decompress.init(&rdr, buf, .{ .window_len = @truncate(out.len) });
|
||||||
|
return decomp.reader.readSliceShort(out);
|
||||||
|
}
|
||||||
+49
-56
@@ -1,70 +1,63 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
const InodeType = @import("inode.zig").Type;
|
const InodeType = @import("inode.zig").Type;
|
||||||
const Compression = @import("superblock.zig").Compression;
|
|
||||||
|
|
||||||
const Header = extern struct { //use extern instead of packed, due to bit alignment
|
pub fn readDirectory(alloc: std.mem.Allocator, rdr: *Reader, size: u32) []Entry {
|
||||||
count: u32,
|
var read: u32 = 3;
|
||||||
block: u32,
|
var hdr: Header = undefined;
|
||||||
num: u32,
|
var raw: RawEntry = undefined;
|
||||||
};
|
var out: std.ArrayList(Entry) = .initCapacity(alloc, 50);
|
||||||
|
errdefer {
|
||||||
const RawEntry = struct {
|
for (out.items) |i|
|
||||||
offset: u16,
|
alloc.free(i.name);
|
||||||
num_offset: i16,
|
out.deinit(alloc);
|
||||||
type: InodeType,
|
|
||||||
size: u16,
|
|
||||||
name: []const u8,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: anytype) !RawEntry {
|
|
||||||
var fixed: [8]u8 = undefined;
|
|
||||||
_ = try rdr.read(&fixed);
|
|
||||||
const size = std.mem.readInt(u16, fixed[6..8], .little);
|
|
||||||
const name = try alloc.alloc(u8, size + 1);
|
|
||||||
_ = try rdr.read(name);
|
|
||||||
return .{
|
|
||||||
.offset = std.mem.readInt(u16, fixed[0..2], .little),
|
|
||||||
.num_offset = std.mem.readInt(i16, fixed[2..4], .little),
|
|
||||||
.type = @enumFromInt(std.mem.readInt(u16, fixed[4..6], .little)),
|
|
||||||
.size = size,
|
|
||||||
.name = name,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
while (read < size) {
|
||||||
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
|
try out.ensureUnusedCapacity(alloc, hdr.count + 1);
|
||||||
|
read += @sizeOf(Header);
|
||||||
|
for (0..hdr.count + 1) |_| {
|
||||||
|
try rdr.readSliceEndian(RawEntry, @ptrCast(&raw), .little);
|
||||||
|
read += @sizeOf(RawEntry) + raw.name_size + 1;
|
||||||
|
const new = out.addOneAssumeCapacity();
|
||||||
|
new.* = .{
|
||||||
|
.block_start = hdr.block_start,
|
||||||
|
.block_offset = raw.block_offset,
|
||||||
|
.num = @abs(hdr.num + raw.num_offset),
|
||||||
|
.inode_type = raw.inode_type,
|
||||||
|
.name = try alloc.alloc(u8, raw.name_size + 1),
|
||||||
|
};
|
||||||
|
try rdr.readSliceEndian(u8, new.name, .little);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toOwnedSlice(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
pub const Entry = struct {
|
pub const Entry = struct {
|
||||||
block: u32,
|
block_start: u32,
|
||||||
offset: u16,
|
block_offset: u16,
|
||||||
num: u32,
|
num: u32,
|
||||||
type: InodeType,
|
inode_type: InodeType,
|
||||||
name: []const u8,
|
name: []u8,
|
||||||
|
|
||||||
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: Entry, alloc: std.mem.Allocator) void {
|
||||||
alloc.free(self.name);
|
alloc.free(self.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn readDirectory(alloc: std.mem.Allocator, rdr: anytype, size: u32) ![]Entry {
|
// extern instead of packed due to alignment issues (packed will read it as 16 bytes instead of 12).
|
||||||
var entries: std.ArrayList(Entry) = .init(alloc);
|
const Header = extern struct {
|
||||||
errdefer entries.deinit();
|
count: u32,
|
||||||
var cur_red: u32 = 3; // dir size includes "." & "..", so its actual size is off by 3.
|
block_start: u32,
|
||||||
var hdr: Header = undefined;
|
num: u32,
|
||||||
while (cur_red < size) {
|
};
|
||||||
_ = try rdr.read(std.mem.asBytes(&hdr));
|
|
||||||
cur_red += 12;
|
const RawEntry = packed struct {
|
||||||
try entries.ensureUnusedCapacity(hdr.count + 1);
|
block_offset: u16,
|
||||||
for (0..hdr.count + 1) |_| {
|
num_offset: i16,
|
||||||
const raw_ent: RawEntry = try .init(alloc, rdr);
|
inode_type: InodeType,
|
||||||
cur_red += 9 + raw_ent.size;
|
name_size: u16,
|
||||||
errdefer alloc.free(raw_ent.name);
|
};
|
||||||
entries.appendAssumeCapacity(.{
|
|
||||||
.block = hdr.block,
|
|
||||||
.offset = raw_ent.offset,
|
|
||||||
.num = @truncate(@abs(@as(i64, hdr.num) + raw_ent.num_offset)),
|
|
||||||
.type = raw_ent.type,
|
|
||||||
.name = raw_ent.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries.toOwnedSlice();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
/// Replace symlinks with their targets
|
|
||||||
dereference_symlinks: bool = false,
|
|
||||||
/// Always extract a symlink's target if it's part of the archive.
|
|
||||||
/// May result in the symlink's target being changed.
|
|
||||||
unbreak_symlinks: bool = false,
|
|
||||||
/// Do not set file's permissions & owner when extracted.
|
|
||||||
ignore_permissions: bool = false,
|
|
||||||
/// Verbose logging
|
|
||||||
verbose: bool = false,
|
|
||||||
/// Verbose logging writer. If not set, stdout is used.
|
|
||||||
verbose_logger: std.io.AnyWriter = std.io.getStdOut().writer().any(),
|
|
||||||
/// Number of threads used during extraction. Defualts to std.Thread.getCpuCount().
|
|
||||||
thread_count: usize,
|
|
||||||
|
|
||||||
pub fn init() !Self {
|
|
||||||
return .{
|
|
||||||
.thread_count = try std.Thread.getCpuCount(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+108
-345
@@ -1,355 +1,118 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const Io = std.Io;
|
||||||
|
|
||||||
const dir = @import("directory.zig");
|
const Archive = @import("archive.zig");
|
||||||
|
const Decompressor = @import("decomp.zig");
|
||||||
const DirEntry = dir.Entry;
|
const Directory = @import("directory.zig");
|
||||||
|
const ExtractionOptions = @import("options.zig");
|
||||||
const Inode = @import("inode.zig");
|
const Inode = @import("inode.zig");
|
||||||
const SfsReader = @import("reader.zig").SfsReader;
|
const DataReader = @import("util/data_reader.zig");
|
||||||
const ToReader = @import("reader/to_read.zig").ToRead;
|
const FileIter = @import("util/iter.zig");
|
||||||
const ExtractionOptions = @import("extract_options.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
const DataReader = @import("reader/data.zig").DataReader;
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
const Compression = @import("superblock.zig").Compression;
|
const Utils = @import("util/utils.zig");
|
||||||
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
|
||||||
|
|
||||||
pub fn File(comptime T: type) type {
|
pub const Error = error{
|
||||||
return struct {
|
NotDirectory,
|
||||||
pub const FileError = error{
|
NotRegularFile,
|
||||||
NotRegular,
|
};
|
||||||
NotDirectory,
|
|
||||||
NotFound,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
const File = @This();
|
||||||
|
|
||||||
rdr: *SfsReader(T),
|
file: OffsetFile,
|
||||||
// parent: *File(T),
|
super: Archive.MinimalSuperblock,
|
||||||
|
decomp: Decompressor,
|
||||||
|
|
||||||
inode: Inode,
|
name: []const u8,
|
||||||
name: []const u8,
|
inode: Inode,
|
||||||
|
|
||||||
/// Directory entries. Only populated on directories.
|
pub fn init(alloc: std.mem.Allocator, archive: Archive, entry: Directory.Entry) !File {
|
||||||
entries: ?[]DirEntry = null,
|
const new_name = try alloc.alloc(u8, entry.name.len);
|
||||||
/// File reader. Only populated on regular files.
|
errdefer alloc.free(new_name);
|
||||||
data_reader: ?DataReader(T) = null,
|
@memcpy(new_name, entry.name);
|
||||||
|
return .{
|
||||||
pub fn init(rdr: *SfsReader(T), inode: Inode, name: []const u8) !Self {
|
.file = archive.file,
|
||||||
const name_cpy: []u8 = try rdr.alloc.alloc(u8, name.len);
|
.super = archive.super,
|
||||||
@memcpy(name_cpy, name);
|
.decomp = archive.stateless_decomp.statelessCopy(alloc),
|
||||||
var out = Self{
|
.name = new_name,
|
||||||
.rdr = rdr,
|
.inode = try Utils.readInode(
|
||||||
.inode = inode,
|
alloc,
|
||||||
.name = name_cpy,
|
&archive.decomp,
|
||||||
};
|
archive.file,
|
||||||
switch (inode.data) {
|
archive.super.inode_start,
|
||||||
.dir => |d| {
|
archive.super.block_size,
|
||||||
var meta = MetadataReader(T).init(
|
entry.block_start,
|
||||||
rdr.alloc,
|
entry.block_offset,
|
||||||
rdr.super.comp,
|
),
|
||||||
rdr.rdr,
|
|
||||||
d.block + rdr.super.dir_start,
|
|
||||||
);
|
|
||||||
try meta.skip(d.offset);
|
|
||||||
out.entries = try dir.readDirectory(rdr.alloc, &meta, d.size);
|
|
||||||
},
|
|
||||||
.ext_dir => |d| {
|
|
||||||
var meta = MetadataReader(T).init(
|
|
||||||
rdr.alloc,
|
|
||||||
rdr.super.comp,
|
|
||||||
rdr.rdr,
|
|
||||||
d.block + rdr.super.dir_start,
|
|
||||||
);
|
|
||||||
try meta.skip(d.offset);
|
|
||||||
out.entries = try dir.readDirectory(rdr.alloc, &meta, d.size);
|
|
||||||
},
|
|
||||||
.file => |f| {
|
|
||||||
out.data_reader = try .init(rdr, inode);
|
|
||||||
_ = f;
|
|
||||||
//TODO: fragments
|
|
||||||
// if (f.hasFragment()) {
|
|
||||||
// try out.data_reader.?.addFragment(
|
|
||||||
// try rdr.frag_table.get(f.frag_idx),
|
|
||||||
// f.frag_offset,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
.ext_file => |f| {
|
|
||||||
out.data_reader = try .init(rdr, inode);
|
|
||||||
_ = f;
|
|
||||||
//TODO: Fragments
|
|
||||||
// if (f.hasFragment()) {
|
|
||||||
// try out.data_reader.?.addFragment(
|
|
||||||
// try rdr.frag_table.get(f.frag_idx),
|
|
||||||
// f.frag_offset,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
pub fn initFromRef(rdr: *SfsReader(T), ref: Inode.Ref, name: []const u8) !Self {
|
|
||||||
var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ref.block + rdr.super.inode_start);
|
|
||||||
try meta.skip(ref.offset);
|
|
||||||
const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
|
|
||||||
return .init(rdr, inode, name);
|
|
||||||
}
|
|
||||||
pub fn initFromEntry(rdr: *SfsReader(T), ent: DirEntry) !Self {
|
|
||||||
var meta: MetadataReader(T) = .init(rdr.alloc, rdr.super.comp, rdr.rdr, ent.block + rdr.super.inode_start);
|
|
||||||
try meta.skip(ent.offset);
|
|
||||||
const inode: Inode = try .init(&meta, rdr.alloc, rdr.super.block_size);
|
|
||||||
return .init(rdr, inode, ent.name);
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.rdr.alloc.free(self.name);
|
|
||||||
self.inode.deinit(self.rdr.alloc);
|
|
||||||
if (self.entries != null) {
|
|
||||||
for (self.entries.?) |e| {
|
|
||||||
e.deinit(self.rdr.alloc);
|
|
||||||
}
|
|
||||||
self.rdr.alloc.free(self.entries.?);
|
|
||||||
}
|
|
||||||
if (self.data_reader != null) {
|
|
||||||
self.data_reader.?.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uid(self: Self) !u32 {
|
|
||||||
return self.rdr.id_table.get(self.inode.hdr.uid_idx);
|
|
||||||
}
|
|
||||||
pub fn gid(self: Self) !u32 {
|
|
||||||
return self.rdr.id_table.get(self.inode.hdr.uid_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Reader = std.io.GenericReader(*DataReader(T), anyerror, DataReader(T).read);
|
|
||||||
|
|
||||||
pub fn read(self: *Self, buf: []u8) !usize {
|
|
||||||
if (self.data_reader == null) return FileError.NotRegular;
|
|
||||||
return self.data_reader.?.read(buf);
|
|
||||||
}
|
|
||||||
pub fn reader(self: *Self) !Reader {
|
|
||||||
if (self.data_reader == null) return FileError.NotRegular;
|
|
||||||
return self.data_reader.?.reader();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open(self: Self, path: []const u8) !Self {
|
|
||||||
if (self.entries == null) return FileError.NotDirectory;
|
|
||||||
if (path.len == 0) return self;
|
|
||||||
const idx = std.mem.indexOf(u8, path, "/") orelse path.len;
|
|
||||||
if (idx == 0) return self.open(path[1..]);
|
|
||||||
const name = path[0..idx];
|
|
||||||
for (self.entries.?) |e| {
|
|
||||||
if (std.mem.eql(u8, e.name, name)) {
|
|
||||||
var fil: Self = try .initFromEntry(self.rdr, e);
|
|
||||||
if (idx >= path.len - 1) return fil;
|
|
||||||
defer fil.deinit();
|
|
||||||
return fil.open(path[idx + 1 ..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FileError.NotFound;
|
|
||||||
}
|
|
||||||
pub fn iterate(self: Self) Iterator {
|
|
||||||
return .{
|
|
||||||
.rdr = self.rdr,
|
|
||||||
.entries = self.entries.?,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const Iterator = struct {
|
|
||||||
rdr: *SfsReader(T),
|
|
||||||
entries: []DirEntry,
|
|
||||||
|
|
||||||
idx: u32 = 0,
|
|
||||||
|
|
||||||
pub fn next(self: *Iterator) !?File(T) {
|
|
||||||
if (self.idx >= self.entries.len) return null;
|
|
||||||
const out = try Self.initFromEntry(self.rdr, self.entries[self.idx]);
|
|
||||||
self.idx += 1;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
pub fn reset(self: *Iterator) void {
|
|
||||||
self.idx = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
|
||||||
const Pool = std.Thread.Pool;
|
|
||||||
const Mutex = std.Thread.Mutex;
|
|
||||||
|
|
||||||
pub const ExtractError = error{FileExists};
|
|
||||||
|
|
||||||
pub fn extract(self: *Self, op: ExtractionOptions, path: []const u8) !void {
|
|
||||||
var wg: WaitGroup = .{};
|
|
||||||
var pol: Pool = undefined;
|
|
||||||
try pol.init(.{
|
|
||||||
.n_jobs = op.thread_count,
|
|
||||||
.allocator = self.rdr.alloc,
|
|
||||||
});
|
|
||||||
defer pol.deinit();
|
|
||||||
var errs: std.ArrayList(anyerror) = .init(self.rdr.alloc);
|
|
||||||
defer errs.deinit();
|
|
||||||
try self.extractInode(op, &wg, &errs, &pol, self.inode, path);
|
|
||||||
wg.wait();
|
|
||||||
if (errs.items.len > 0) return errs.items[0];
|
|
||||||
}
|
|
||||||
fn extractInode(
|
|
||||||
self: *Self,
|
|
||||||
op: ExtractionOptions,
|
|
||||||
wg: *WaitGroup,
|
|
||||||
errs: *std.ArrayList(anyerror),
|
|
||||||
pol: *Pool,
|
|
||||||
inode: Inode,
|
|
||||||
path: []const u8,
|
|
||||||
) !void {
|
|
||||||
wg.start();
|
|
||||||
defer wg.finish(); //TODO: When everthing is threaded, this will need to be handled by the threads, not here.
|
|
||||||
switch (inode.hdr.type) {
|
|
||||||
.file, .ext_file => {
|
|
||||||
var fil = try std.fs.cwd().createFile(path, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var data: DataReader(T) = try .init(self.rdr, inode);
|
|
||||||
defer data.deinit();
|
|
||||||
try data.writeTo(fil); // TODO: Thread
|
|
||||||
const fil_uid = self.rdr.id_table.get(inode.hdr.uid_idx) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error getting uid {} from table: {}\n", .{ inode.hdr.uid_idx, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const fil_gid = self.rdr.id_table.get(inode.hdr.gid_idx) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error getting gid {} from table: {}\n", .{ inode.hdr.gid_idx, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
fil.chmod(inode.hdr.perm) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
fil.chown(fil_uid, fil_gid) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
//TODO: update mtime.
|
|
||||||
},
|
|
||||||
.dir, .ext_dir => {
|
|
||||||
std.fs.cwd().makeDir(path) catch |err| {
|
|
||||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var dir_block: u32 = 0;
|
|
||||||
var dir_offset: u16 = 0;
|
|
||||||
var dir_size: u32 = 0;
|
|
||||||
switch (inode.data) {
|
|
||||||
.dir => |d| {
|
|
||||||
dir_block = d.block;
|
|
||||||
dir_offset = d.offset;
|
|
||||||
dir_size = d.size;
|
|
||||||
},
|
|
||||||
.ext_dir => |d| {
|
|
||||||
dir_block = d.block;
|
|
||||||
dir_offset = d.offset;
|
|
||||||
dir_size = d.size;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
var meta: MetadataReader(T) = .init(self.rdr.alloc, self.rdr.super.comp, self.rdr.rdr, dir_block + self.rdr.super.dir_start);
|
|
||||||
try meta.skip(dir_offset);
|
|
||||||
const entries = try dir.readDirectory(self.rdr.alloc, &meta, dir_size);
|
|
||||||
defer self.rdr.alloc.free(entries);
|
|
||||||
for (entries) |ent| {
|
|
||||||
defer ent.deinit(self.rdr.alloc);
|
|
||||||
var new_path: []u8 = undefined;
|
|
||||||
if (path[path.len - 1] == '/') {
|
|
||||||
new_path = self.rdr.alloc.alloc(u8, path.len + ent.name.len) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error allocating memory: {}\n", .{err}) catch {};
|
|
||||||
}
|
|
||||||
errs.append(err) catch {};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
@memcpy(new_path[0..path.len], path);
|
|
||||||
@memcpy(new_path[path.len..], ent.name);
|
|
||||||
} else {
|
|
||||||
new_path = self.rdr.alloc.alloc(u8, path.len + ent.name.len + 1) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error allocating memory: {}\n", .{err}) catch {};
|
|
||||||
}
|
|
||||||
errs.append(err) catch {};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
@memcpy(new_path[0..path.len], path);
|
|
||||||
new_path[path.len] = '/';
|
|
||||||
@memcpy(new_path[path.len + 1 ..], ent.name);
|
|
||||||
}
|
|
||||||
defer self.rdr.alloc.free(new_path);
|
|
||||||
|
|
||||||
meta = .init(self.rdr.alloc, self.rdr.super.comp, self.rdr.rdr, ent.block + self.rdr.super.inode_start);
|
|
||||||
meta.skip(ent.offset) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error reading inode: {}\n", .{err}) catch {};
|
|
||||||
}
|
|
||||||
errs.append(err) catch {};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
const new_inode = Inode.init(&meta, self.rdr.alloc, self.rdr.super.block_size) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error reading inode: {}\n", .{err}) catch {};
|
|
||||||
}
|
|
||||||
errs.append(err) catch {};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
defer new_inode.deinit(self.rdr.alloc);
|
|
||||||
self.extractInode(op, wg, errs, pol, new_inode, new_path) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error extracting {s}: {}\n", .{ new_path, err }) catch {};
|
|
||||||
}
|
|
||||||
errs.append(err) catch {};
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var fil = std.fs.cwd().openDir(path, .{ .iterate = true }) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error openning {s} to set permissions: {}\n", .{ path, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const fil_uid = self.rdr.id_table.get(inode.hdr.uid_idx) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error getting uid {} from table: {}\n", .{ inode.hdr.uid_idx, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const fil_gid = self.rdr.id_table.get(inode.hdr.gid_idx) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error getting gid {} from table: {}\n", .{ inode.hdr.gid_idx, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
fil.chmod(inode.hdr.perm) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
fil.chown(fil_uid, fil_gid) catch |err| {
|
|
||||||
if (op.verbose) {
|
|
||||||
std.fmt.format(op.verbose_logger, "error chmod {s}: {}\n", .{ path, err }) catch {};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
// .symlink, .ext_symlink => {},
|
|
||||||
else => {
|
|
||||||
std.debug.print("TODO: {}\n", .{inode.hdr.type});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
pub fn deinit(self: File) void {
|
||||||
|
self.decomp.alloc.free(self.name);
|
||||||
|
self.inode.deinit(self.decomp.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory functions
|
||||||
|
|
||||||
|
pub fn isDir(self: File) bool {
|
||||||
|
return switch (self.inode.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// Opens a sub-file. If the given path is "" or "." (after trimming /) a copy of the File is returned.
|
||||||
|
pub fn open(self: File, alloc: std.mem.Allocator, filepath: []const u8) !File {
|
||||||
|
var res = try self.inode.findInode(
|
||||||
|
alloc,
|
||||||
|
&self.decomp,
|
||||||
|
self.file,
|
||||||
|
self.super.dir_start,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
filepath,
|
||||||
|
);
|
||||||
|
if (res.name.len == 0) {
|
||||||
|
res.name = try alloc.alloc(u8, self.name.len);
|
||||||
|
@memcpy(res.name, self.name);
|
||||||
|
}
|
||||||
|
return .{
|
||||||
|
.file = self.file,
|
||||||
|
.super = self.super,
|
||||||
|
.decomp = self.decomp.statelessCopy(alloc),
|
||||||
|
.name = res.name,
|
||||||
|
.inode = res.inode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn iter(self: File, alloc: std.mem.Allocator) !FileIter {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.entries = try self.inode.readDirectory(alloc, &self.decomp, self.file, self.super.dir_start),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular file functions
|
||||||
|
|
||||||
|
pub fn isRegularFile(self: File) bool {
|
||||||
|
return switch (self.inode.hdr.inode_type) {
|
||||||
|
.file, .ext_file => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// a std.Io.Reader compatible reader that reads a regular file's data.
|
||||||
|
pub fn dataReader(self: File, alloc: std.mem.Allocator) !DataReader {
|
||||||
|
return self.inode.dataReader(
|
||||||
|
&self.decomp.statelessCopy(alloc),
|
||||||
|
self.file,
|
||||||
|
self.super.frag_start,
|
||||||
|
self.super.block_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Universal functions
|
||||||
|
|
||||||
|
pub fn extract(self: File, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = alloc;
|
||||||
|
_ = path;
|
||||||
|
_ = options;
|
||||||
|
return error.TODO;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
const BlockSize = @import("inode/file.zig").BlockSize;
|
|
||||||
|
|
||||||
pub const FragEntry = packed struct {
|
|
||||||
block: u64,
|
|
||||||
size: BlockSize,
|
|
||||||
_: u32,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#include <zlib-ng.h>
|
||||||
|
#include <lzo/minilzo.h>
|
||||||
|
#include <lz4.h>
|
||||||
|
#include <zstd.h>
|
||||||
|
#include <lzma.h>
|
||||||
+335
-73
@@ -1,88 +1,52 @@
|
|||||||
|
//! This is the raw squashfs representation of a file/directory.
|
||||||
|
//! Most of the time using File is a better experience and using Inodes directory
|
||||||
|
//! is only required for more technical use cases.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
const dir = @import("inode/dir.zig");
|
const Decompressor = @import("decomp.zig");
|
||||||
const file = @import("inode/file.zig");
|
const Directory = @import("directory.zig");
|
||||||
const misc = @import("inode/misc.zig");
|
const FragEntry = @import("archive.zig").FragEntry;
|
||||||
|
const Dir = @import("inode/dir.zig");
|
||||||
|
const File = @import("inode/file.zig");
|
||||||
|
const Misc = @import("inode/misc.zig");
|
||||||
|
const Sym = @import("inode/sym.zig");
|
||||||
|
const LookupTable = @import("lookup_table.zig");
|
||||||
|
const MinimalSuperblock = @import("archive.zig").MinimalSuperblock;
|
||||||
|
const DataReader = @import("util/data_reader.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
pub const Ref = packed struct {
|
const Inode = @This();
|
||||||
offset: u16,
|
|
||||||
block: u32,
|
|
||||||
_: u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Type = enum(u16) {
|
|
||||||
dir = 1,
|
|
||||||
file,
|
|
||||||
symlink,
|
|
||||||
block_dev,
|
|
||||||
char_dev,
|
|
||||||
fifo,
|
|
||||||
socket,
|
|
||||||
ext_dir,
|
|
||||||
ext_file,
|
|
||||||
ext_symlink,
|
|
||||||
ext_block_dev,
|
|
||||||
ext_char_dev,
|
|
||||||
ext_fifo,
|
|
||||||
ext_socket,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Header = packed struct {
|
|
||||||
type: Type,
|
|
||||||
perm: u16,
|
|
||||||
uid_idx: u16,
|
|
||||||
gid_idx: u16,
|
|
||||||
mod_time: u32,
|
|
||||||
num: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Data = union(enum) {
|
|
||||||
dir: dir.Dir,
|
|
||||||
file: file.File,
|
|
||||||
symlink: misc.Symlink,
|
|
||||||
block_dev: misc.Dev,
|
|
||||||
char_dev: misc.Dev,
|
|
||||||
fifo: misc.IPC,
|
|
||||||
socket: misc.IPC,
|
|
||||||
ext_dir: dir.ExtDir,
|
|
||||||
ext_file: file.ExtFile,
|
|
||||||
ext_symlink: misc.ExtSymlink,
|
|
||||||
ext_block_dev: misc.ExtDev,
|
|
||||||
ext_char_dev: misc.ExtDev,
|
|
||||||
ext_fifo: misc.ExtIPC,
|
|
||||||
ext_socket: misc.ExtIPC,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
hdr: Header,
|
hdr: Header,
|
||||||
data: Data,
|
data: Data,
|
||||||
|
|
||||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !Self {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
||||||
var hdr: Header = undefined;
|
var hdr: Header = undefined;
|
||||||
_ = try rdr.read(std.mem.asBytes(&hdr));
|
try rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
const data: Data = switch (hdr.type) {
|
|
||||||
.dir => .{ .dir = try .init(rdr) },
|
|
||||||
.file => .{ .file = try .init(rdr, alloc, block_size) },
|
|
||||||
.symlink => .{ .symlink = try .init(rdr, alloc) },
|
|
||||||
.block_dev => .{ .block_dev = try .init(rdr) },
|
|
||||||
.char_dev => .{ .char_dev = try .init(rdr) },
|
|
||||||
.fifo => .{ .fifo = try .init(rdr) },
|
|
||||||
.socket => .{ .socket = try .init(rdr) },
|
|
||||||
.ext_dir => .{ .ext_dir = try .init(rdr) },
|
|
||||||
.ext_file => .{ .ext_file = try .init(rdr, alloc, block_size) },
|
|
||||||
.ext_symlink => .{ .ext_symlink = try .init(rdr, alloc) },
|
|
||||||
.ext_block_dev => .{ .ext_block_dev = try .init(rdr) },
|
|
||||||
.ext_char_dev => .{ .ext_char_dev = try .init(rdr) },
|
|
||||||
.ext_fifo => .{ .ext_fifo = try .init(rdr) },
|
|
||||||
.ext_socket => .{ .ext_socket = try .init(rdr) },
|
|
||||||
};
|
|
||||||
return .{
|
return .{
|
||||||
.hdr = hdr,
|
.hdr = hdr,
|
||||||
.data = data,
|
.data = switch (hdr.inode_type) {
|
||||||
|
.dir => .{ .dir = .read(rdr) },
|
||||||
|
.file => .{ .file = .read(alloc, rdr, block_size) },
|
||||||
|
.symlink => .{ .symlink = .read(alloc, rdr) },
|
||||||
|
.block => .{ .block = .read(rdr) },
|
||||||
|
.char => .{ .char = .read(rdr) },
|
||||||
|
.fifo => .{ .fifo = .read(rdr) },
|
||||||
|
.sock => .{ .sock = .read(rdr) },
|
||||||
|
.ext_dir => .{ .ext_dir = .read(rdr) },
|
||||||
|
.ext_file => .{ .ext_file = .read(alloc, rdr, block_size) },
|
||||||
|
.ext_symlink => .{ .ext_symlink = .read(alloc, rdr) },
|
||||||
|
.ext_block => .{ .ext_block = .read(rdr) },
|
||||||
|
.ext_char => .{ .ext_char = .read(rdr) },
|
||||||
|
.ext_fifo => .{ .ext_fifo = .read(rdr) },
|
||||||
|
.ext_sock => .{ .ext_sock = .read(rdr) },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: Self, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
||||||
switch (self.data) {
|
switch (self.data) {
|
||||||
.file => |f| alloc.free(f.block_sizes),
|
.file => |f| alloc.free(f.block_sizes),
|
||||||
.ext_file => |f| alloc.free(f.block_sizes),
|
.ext_file => |f| alloc.free(f.block_sizes),
|
||||||
@@ -91,3 +55,301 @@ pub fn deinit(self: Self, alloc: std.mem.Allocator) void {
|
|||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn copy(self: Inode, alloc: std.mem.Allocator) !Inode {
|
||||||
|
switch (self.data) {
|
||||||
|
.dir,
|
||||||
|
.ext_dir,
|
||||||
|
.block,
|
||||||
|
.ext_block,
|
||||||
|
.char,
|
||||||
|
.ext_char,
|
||||||
|
.fifo,
|
||||||
|
.ext_fifo,
|
||||||
|
.sock,
|
||||||
|
.ext_sock,
|
||||||
|
=> return self,
|
||||||
|
.file => |f| {
|
||||||
|
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
|
||||||
|
@memcpy(new_sizes, f.block_sizes);
|
||||||
|
return .{
|
||||||
|
.hdr = self.hdr,
|
||||||
|
.data = .{ .file = .{
|
||||||
|
.block_start = f.block_start,
|
||||||
|
.frag_idx = f.frag_idx,
|
||||||
|
.block_offset = f.block_offset,
|
||||||
|
.size = f.size,
|
||||||
|
.block_sizes = new_sizes,
|
||||||
|
} },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.ext_file => |f| {
|
||||||
|
const new_sizes = try alloc.alloc(File.BlockSize, f.block_sizes.len);
|
||||||
|
@memcpy(new_sizes, f.block_sizes);
|
||||||
|
return .{
|
||||||
|
.hdr = self.hdr,
|
||||||
|
.data = .{ .ext_file = .{
|
||||||
|
.block_start = self.block_start,
|
||||||
|
.size = self.size,
|
||||||
|
.sparse = self.sparse,
|
||||||
|
.hard_links = self.hard_links,
|
||||||
|
.frag_idx = self.frag_idx,
|
||||||
|
.block_offset = self.block_offset,
|
||||||
|
.xattr_idx = self.xattr_idx,
|
||||||
|
.block_sizes = new_sizes,
|
||||||
|
} },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.symlink => |s| {
|
||||||
|
const new_target = try alloc.alloc(u8, s.target.len);
|
||||||
|
@memcpy(new_target, s.target);
|
||||||
|
return .{
|
||||||
|
.hdr = self.hdr,
|
||||||
|
.data = .{ .symlink = .{
|
||||||
|
.hard_links = s.hard_links,
|
||||||
|
.target = new_target,
|
||||||
|
} },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.ext_symlink => |s| {
|
||||||
|
const new_target = try alloc.alloc(u8, s.target.len);
|
||||||
|
@memcpy(new_target, s.target);
|
||||||
|
return .{
|
||||||
|
.hdr = self.hdr,
|
||||||
|
.data = .{ .ext_symlink = .{
|
||||||
|
.hard_links = s.hard_links,
|
||||||
|
.xattr_idx = s.xattr_idx,
|
||||||
|
.target = new_target,
|
||||||
|
} },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
pub const Ref = packed struct {
|
||||||
|
block_offset: u16,
|
||||||
|
block_start: u32,
|
||||||
|
_: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Type = enum(u16) {
|
||||||
|
dir = 1,
|
||||||
|
file,
|
||||||
|
symlink,
|
||||||
|
block,
|
||||||
|
char,
|
||||||
|
fifo,
|
||||||
|
sock,
|
||||||
|
ext_dir,
|
||||||
|
ext_file,
|
||||||
|
ext_symlink,
|
||||||
|
ext_block,
|
||||||
|
ext_char,
|
||||||
|
ext_fifo,
|
||||||
|
ext_sock,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = packed struct {
|
||||||
|
inode_type: Type,
|
||||||
|
permission: u16,
|
||||||
|
uid_idx: u16,
|
||||||
|
gid_idx: u16,
|
||||||
|
mod_time: u32,
|
||||||
|
num: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Data = union(Type) {
|
||||||
|
dir: Dir.Dir,
|
||||||
|
file: File.File,
|
||||||
|
symlink: Sym.Symlink,
|
||||||
|
block: Misc.Device,
|
||||||
|
char: Misc.Device,
|
||||||
|
fifo: Misc.Ipc,
|
||||||
|
sock: Misc.Ipc,
|
||||||
|
ext_dir: Dir.ExtDir,
|
||||||
|
ext_file: File.ExtFile,
|
||||||
|
ext_symlink: Sym.ExtSymlink,
|
||||||
|
ext_block: Misc.ExtDevice,
|
||||||
|
ext_char: Misc.ExtDevice,
|
||||||
|
ext_fifo: Misc.ExtIpc,
|
||||||
|
ext_sock: Misc.ExtIpc,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
NotDirectory,
|
||||||
|
NotFound,
|
||||||
|
NotRegularFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utils functions
|
||||||
|
|
||||||
|
// Universal
|
||||||
|
|
||||||
|
pub fn uid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 {
|
||||||
|
return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.uid_idx);
|
||||||
|
}
|
||||||
|
pub fn uidCached(self: Inode, table: LookupTable.CachedTable(u16)) !u16 {
|
||||||
|
return table.get(self.hdr.uid_idx);
|
||||||
|
}
|
||||||
|
pub fn gid(self: Inode, decomp: *const Decompressor, fil: OffsetFile, id_start: u64) !u16 {
|
||||||
|
return LookupTable.stateless(u16, fil, decomp, id_start, self.hdr.gid_idx);
|
||||||
|
}
|
||||||
|
pub fn gidCached(self: Inode, table: LookupTable.CachedTable(u16)) !u16 {
|
||||||
|
return table.get(self.hdr.gid_idx);
|
||||||
|
}
|
||||||
|
pub fn xattr(self: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, xattr_start: u64) !?LookupTable.XattrValues {
|
||||||
|
if (@intFromEnum(self.hdr.inode_type) < 8) return null;
|
||||||
|
const idx: u32 = switch (self.data) {
|
||||||
|
.ext_dir => |d| d.xattr_idx,
|
||||||
|
.ext_file => |f| f.xattr_idx,
|
||||||
|
.ext_symlink => |s| s.xattr_idx,
|
||||||
|
.ext_block, .ext_char => |d| d.xattr_idx,
|
||||||
|
.ext_fifo, .ext_sock => |d| d.xattr_idx,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
if (idx == 0xFFFFFFFF) return null;
|
||||||
|
return LookupTable.statelessXattr(alloc, fil, decomp, xattr_start, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir inodes
|
||||||
|
|
||||||
|
/// For directory inodes, tries to find the inode at the given path. Returns both the inode, and it's file name.
|
||||||
|
/// If the path is empty or "." then a copy of this inode is returned with no name ("").
|
||||||
|
pub fn findInode(
|
||||||
|
inode: Inode,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
decomp: *const Decompressor,
|
||||||
|
fil: OffsetFile,
|
||||||
|
dir_start: u64,
|
||||||
|
inode_start: u64,
|
||||||
|
block_size: u32,
|
||||||
|
filepath: []const u8,
|
||||||
|
) !struct { inode: Inode, name: []const u8 } {
|
||||||
|
switch (inode.data) {
|
||||||
|
.dir => |d| {
|
||||||
|
const path: []const u8 = std.mem.trim(u8, filepath, "/");
|
||||||
|
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
|
||||||
|
return .{ .inode = inode.copy(alloc), .name = "" };
|
||||||
|
return findInodeRaw(
|
||||||
|
alloc,
|
||||||
|
decomp,
|
||||||
|
fil,
|
||||||
|
dir_start,
|
||||||
|
inode_start,
|
||||||
|
block_size,
|
||||||
|
path,
|
||||||
|
d,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
.ext_dir => |d| {
|
||||||
|
const path: []const u8 = std.mem.trim(u8, filepath, "/");
|
||||||
|
if (path.len == 0 or (path.len == 1 and path[0] == '.'))
|
||||||
|
return .{ .inode = inode.copy(alloc), .name = "" };
|
||||||
|
return findInodeRaw(
|
||||||
|
alloc,
|
||||||
|
decomp,
|
||||||
|
fil,
|
||||||
|
dir_start,
|
||||||
|
inode_start,
|
||||||
|
block_size,
|
||||||
|
path,
|
||||||
|
d,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
else => return Error.NotDirectory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline fn findInodeRaw(
|
||||||
|
inode: Inode,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
decomp: *const Decompressor,
|
||||||
|
fil: OffsetFile,
|
||||||
|
dir_start: u64,
|
||||||
|
inode_start: u64,
|
||||||
|
block_size: u32,
|
||||||
|
path: []const u8,
|
||||||
|
dat: anytype,
|
||||||
|
) !struct { inode: Inode, name: []const u8 } {
|
||||||
|
const first_element: []const u8 = std.mem.sliceTo(path, '/');
|
||||||
|
|
||||||
|
const dirs = try readDirRaw(alloc, decomp, fil, dir_start, dat);
|
||||||
|
defer {
|
||||||
|
for (dirs) |dir|
|
||||||
|
dir.deinit(alloc);
|
||||||
|
alloc.free(dirs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directories are stored ASCIIbetically, so we can use binary search.
|
||||||
|
var cur_slice = dirs;
|
||||||
|
var idx: usize = 0;
|
||||||
|
while (cur_slice.len > 0) {
|
||||||
|
idx = cur_slice.len / 2;
|
||||||
|
const mid_name = cur_slice[idx].name;
|
||||||
|
switch (std.mem.order(u8, first_element, mid_name)) {
|
||||||
|
.gt => {
|
||||||
|
cur_slice = cur_slice[idx + 1 ..];
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
.lt => {
|
||||||
|
cur_slice = cur_slice[0..idx];
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
.eq => break,
|
||||||
|
}
|
||||||
|
} else return Error.NotFound;
|
||||||
|
const entry = cur_slice[idx];
|
||||||
|
var rdr = try fil.readerAt(inode_start + entry.block_start, &[0]u8{});
|
||||||
|
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
|
||||||
|
try meta_rdr.interface.discardAll(entry.block_offset);
|
||||||
|
const ret_inode: Inode = try .read(alloc, &meta_rdr.interface, block_size);
|
||||||
|
if (first_element.len == path.len) {
|
||||||
|
const name_copy = try alloc.alloc(u8, entry.name.len);
|
||||||
|
@memcpy(name_copy, entry.name.len);
|
||||||
|
return .{ .inode = ret_inode, .name = name_copy };
|
||||||
|
}
|
||||||
|
return inode.findInode(alloc, decomp, fil, dir_start, inode_start, block_size, path[first_element.len..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the directory entries for a directory inode.
|
||||||
|
pub fn readDirectory(inode: Inode, alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64) ![]Directory.Entry {
|
||||||
|
return switch (inode.data) {
|
||||||
|
.dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
|
||||||
|
.ext_dir => |d| readDirRaw(alloc, decomp, fil, dir_start, d),
|
||||||
|
else => Error.NotDirectory,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
inline fn readDirRaw(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, dir_start: u64, dat: anytype) ![]Directory.Entry {
|
||||||
|
var rdr = try fil.readerAt(dir_start + dat.block_start, &[0]u8{});
|
||||||
|
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
|
||||||
|
try meta_rdr.interface.discardAll(dat.block_offset);
|
||||||
|
return Directory.readDirectory(alloc, meta_rdr, dat.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// file inodes
|
||||||
|
|
||||||
|
/// Gets the data reader for a file inode.
|
||||||
|
pub fn dataReader(inode: Inode, decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32) !DataReader {
|
||||||
|
return switch (inode.data) {
|
||||||
|
.file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f),
|
||||||
|
.ext_file => |f| dataReaderRaw(decomp, fil, frag_start, block_size, f),
|
||||||
|
else => Error.NotRegularFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
inline fn dataReaderRaw(decomp: *const Decompressor, fil: OffsetFile, frag_start: u64, block_size: u32, dat: anytype) !DataReader {
|
||||||
|
return .init(
|
||||||
|
decomp,
|
||||||
|
fil,
|
||||||
|
block_size,
|
||||||
|
dat.block_sizes,
|
||||||
|
dat.size,
|
||||||
|
dat.block_start,
|
||||||
|
if (dat.frag_idx != 0xFFFFFFFF)
|
||||||
|
try LookupTable.stateless(FragEntry, fil, decomp, frag_start, dat.frag_idx)
|
||||||
|
else
|
||||||
|
null,
|
||||||
|
dat.frag_offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+19
-15
@@ -1,31 +1,35 @@
|
|||||||
const std = @import("std");
|
const Reader = @import("std").Io.Reader;
|
||||||
|
|
||||||
pub const Dir = packed struct {
|
pub const Dir = packed struct {
|
||||||
block: u32,
|
block_start: u32,
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
size: u16,
|
size: u16,
|
||||||
offset: u16,
|
block_offset: u16,
|
||||||
parent_num: u32,
|
parent_num: u32,
|
||||||
|
|
||||||
pub fn init(rdr: anytype) !Dir {
|
const Self = @This();
|
||||||
var out: Dir = undefined;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&out));
|
pub fn read(rdr: *Reader) !Self {
|
||||||
return out;
|
var new: Self = undefined;
|
||||||
|
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ExtDir = packed struct {
|
pub const ExtDir = packed struct {
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
block: u32,
|
block_start: u32,
|
||||||
parent_num: u32,
|
parent_num: u32,
|
||||||
idx_count: u16,
|
idx_count: u16,
|
||||||
offset: u16,
|
block_offset: u16,
|
||||||
xattr_idx: u32,
|
xattr_idx: u32,
|
||||||
|
|
||||||
pub fn init(rdr: anytype) !ExtDir {
|
const Self = @This();
|
||||||
var out: ExtDir = undefined;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&out));
|
pub fn read(rdr: *Reader) !Self {
|
||||||
return out;
|
var new: Self = undefined;
|
||||||
|
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+36
-44
@@ -1,77 +1,69 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
pub const BlockSize = packed struct {
|
pub const BlockSize = packed struct {
|
||||||
size: u24,
|
size: u31,
|
||||||
uncompressed: bool,
|
uncompressed: bool,
|
||||||
_: u7,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const File = struct {
|
pub const File = struct {
|
||||||
block: u32,
|
block_start: u32,
|
||||||
frag_idx: u32,
|
frag_idx: u32,
|
||||||
frag_offset: u32,
|
frag_offset: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
block_sizes: []BlockSize,
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !File {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||||
var fixed: [16]u8 = undefined;
|
var buf: [16]u8 = undefined;
|
||||||
_ = try rdr.read(&fixed);
|
try rdr.readSliceAll(&buf);
|
||||||
const frag_idx = std.mem.readInt(u32, fixed[4..8], .little);
|
const frag_idx = std.mem.readVarInt(u32, buf[4..8], .little);
|
||||||
const size = std.mem.readInt(u32, fixed[12..16], .little);
|
const size = std.mem.readVarInt(u32, buf[12..], .little);
|
||||||
var blocks: u32 = size / block_size;
|
const sizes_len = size / block_size;
|
||||||
if (size % block_size > 0 and frag_idx == 0xffffffff) {
|
if (frag_idx != 0xFFFFFFFF and size % block_size > 0)
|
||||||
blocks += 1;
|
sizes_len += 1;
|
||||||
}
|
const sizes = try alloc.alloc(BlockSize, sizes_len);
|
||||||
const block_sizes = try alloc.alloc(BlockSize, blocks);
|
errdefer alloc.free(sizes);
|
||||||
errdefer alloc.free(block_sizes);
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
|
|
||||||
return .{
|
return .{
|
||||||
.block = std.mem.readInt(u32, fixed[0..4], .little),
|
.block_start = std.mem.readVarInt(u32, buf[0..4], .little),
|
||||||
.frag_idx = frag_idx,
|
.frag_idx = frag_idx,
|
||||||
.frag_offset = std.mem.readInt(u32, fixed[8..12], .little),
|
.frag_offset = std.mem.readVarInt(u32, buf[8..12], .little),
|
||||||
.size = size,
|
.size = size,
|
||||||
.block_sizes = block_sizes,
|
.block_sizes = sizes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn hasFragment(self: File) bool {
|
|
||||||
return self.frag_idx != 0xffffffff;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ExtFile = struct {
|
pub const ExtFile = struct {
|
||||||
block: u64,
|
block_start: u64,
|
||||||
size: u64,
|
size: u64,
|
||||||
sparse: u64,
|
sparse: u64,
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
frag_idx: u32,
|
frag_idx: u32,
|
||||||
frag_offset: u32,
|
frag_offset: u32,
|
||||||
xattr_idx: u32,
|
xattr_idx: u32,
|
||||||
block_sizes: []BlockSize,
|
block_sizes: []BlockSize,
|
||||||
|
|
||||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator, block_size: u32) !ExtFile {
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !File {
|
||||||
var fixed: [40]u8 = undefined;
|
var buf: [40]u8 = undefined;
|
||||||
_ = try rdr.read(&fixed);
|
try rdr.readSliceAll(&buf);
|
||||||
const size = std.mem.readInt(u64, fixed[8..16], .little);
|
const frag_idx = std.mem.readVarInt(u32, buf[28..32], .little);
|
||||||
const frag_idx = std.mem.readInt(u32, fixed[28..32], .little);
|
const size = std.mem.readVarInt(u64, buf[8..16], .little);
|
||||||
var blocks: u32 = @truncate(size / block_size);
|
const sizes_len = size / block_size;
|
||||||
if (size % block_size > 0 and frag_idx == 0xffffffff) {
|
if (frag_idx != 0xFFFFFFFF and size % block_size > 0)
|
||||||
blocks += 1;
|
sizes_len += 1;
|
||||||
}
|
const sizes = try alloc.alloc(BlockSize, sizes_len);
|
||||||
const block_sizes = try alloc.alloc(BlockSize, blocks);
|
errdefer alloc.free(sizes);
|
||||||
errdefer alloc.free(block_sizes);
|
try rdr.readSliceEndian(BlockSize, sizes, .little);
|
||||||
_ = try rdr.read(std.mem.sliceAsBytes(block_sizes));
|
|
||||||
return .{
|
return .{
|
||||||
.block = std.mem.readInt(u64, fixed[0..8], .little),
|
.block_start = std.mem.readVarInt(u64, buf[0..8], .little),
|
||||||
.size = size,
|
.size = size,
|
||||||
.sparse = std.mem.readInt(u64, fixed[16..24], .little),
|
.sparse = std.mem.readVarInt(u64, buf[16..24], .little),
|
||||||
.hard_link = std.mem.readInt(u32, fixed[24..28], .little),
|
.hard_links = std.mem.readVarInt(u32, buf[24..28], .little),
|
||||||
.frag_idx = frag_idx,
|
.frag_idx = frag_idx,
|
||||||
.frag_offset = std.mem.readInt(u32, fixed[32..36], .little),
|
.frag_offset = std.mem.readVarInt(u32, buf[32..36], .little),
|
||||||
.xattr_idx = std.mem.readInt(u32, fixed[36..40], .little),
|
.xattr_idx = std.mem.readVarInt(u32, buf[36..40], .little),
|
||||||
.block_sizes = block_sizes,
|
.block_sizes = sizes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn hasFragment(self: ExtFile) bool {
|
|
||||||
return self.frag_idx != 0xffffffff;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
+33
-67
@@ -1,87 +1,53 @@
|
|||||||
const std = @import("std");
|
const Reader = @import("std").Io.Reader;
|
||||||
|
|
||||||
pub const Symlink = struct {
|
pub const Device = packed struct {
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
// size: u32,
|
|
||||||
target: []const u8,
|
|
||||||
|
|
||||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !Symlink {
|
|
||||||
var fixed: [8]u8 = undefined;
|
|
||||||
_ = try rdr.read(&fixed);
|
|
||||||
const size = std.mem.readInt(u32, fixed[4..8], .little);
|
|
||||||
const target = try alloc.alloc(u8, size);
|
|
||||||
errdefer alloc.free(target);
|
|
||||||
_ = try rdr.read(target);
|
|
||||||
return .{
|
|
||||||
.hard_link = std.mem.readInt(u32, fixed[0..4], .little),
|
|
||||||
.target = target,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ExtSymlink = struct {
|
|
||||||
hard_link: u32,
|
|
||||||
// size: u32,
|
|
||||||
target: []const u8,
|
|
||||||
xattr_idx: u32,
|
|
||||||
|
|
||||||
pub fn init(rdr: anytype, alloc: std.mem.Allocator) !ExtSymlink {
|
|
||||||
var fixed: [8]u8 = undefined;
|
|
||||||
_ = try rdr.read(&fixed);
|
|
||||||
const size = std.mem.readInt(u32, fixed[4..8], .little);
|
|
||||||
const target = try alloc.alloc(u8, size);
|
|
||||||
errdefer alloc.free(target);
|
|
||||||
_ = try rdr.read(target);
|
|
||||||
var xattr_idx: u32 = 0;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&xattr_idx));
|
|
||||||
return .{
|
|
||||||
.hard_link = std.mem.readInt(u32, fixed[0..4], .little),
|
|
||||||
.target = target,
|
|
||||||
.xattr_idx = xattr_idx,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Dev = packed struct {
|
|
||||||
hard_link: u32,
|
|
||||||
device: u32,
|
device: u32,
|
||||||
|
|
||||||
pub fn init(rdr: anytype) !Dev {
|
const Self = @This();
|
||||||
var out: Dev = undefined;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&out));
|
pub fn read(rdr: *Reader) !Self {
|
||||||
return out;
|
var new: Self = undefined;
|
||||||
|
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ExtDev = packed struct {
|
pub const ExtDevice = packed struct {
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
device: u32,
|
device: u32,
|
||||||
xattr_idx: u32,
|
xattr_idx: u32,
|
||||||
|
|
||||||
pub fn init(rdr: anytype) !ExtDev {
|
const Self = @This();
|
||||||
var out: ExtDev = undefined;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&out));
|
pub fn read(rdr: *Reader) !Self {
|
||||||
return out;
|
var new: Self = undefined;
|
||||||
|
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const IPC = packed struct {
|
pub const Ipc = packed struct {
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
|
|
||||||
pub fn init(rdr: anytype) !IPC {
|
const Self = @This();
|
||||||
var out: IPC = undefined;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&out));
|
pub fn read(rdr: *Reader) !Self {
|
||||||
return out;
|
var new: Self = undefined;
|
||||||
|
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ExtIPC = packed struct {
|
pub const ExtIpc = packed struct {
|
||||||
hard_link: u32,
|
hard_links: u32,
|
||||||
xattr_idx: u32,
|
xattr_idx: u32,
|
||||||
|
|
||||||
pub fn init(rdr: anytype) !ExtIPC {
|
const Self = @This();
|
||||||
var out: ExtIPC = undefined;
|
|
||||||
_ = try rdr.read(std.mem.asBytes(&out));
|
pub fn read(rdr: *Reader) !Self {
|
||||||
return out;
|
var new: Self = undefined;
|
||||||
|
try rdr.readSliceEndian(Self, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
|
||||||
|
pub const Symlink = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
target: []const u8,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !Symlink {
|
||||||
|
var buf: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&buf);
|
||||||
|
const size = std.mem.readVarInt(u32, buf[4..], .little);
|
||||||
|
const target = try alloc.alloc(u8, size);
|
||||||
|
errdefer alloc.free(target);
|
||||||
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
|
return .{
|
||||||
|
.hard_links = std.mem.readVarInt(u32, buf[0..4], .little),
|
||||||
|
.target = target,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ExtSymlink = struct {
|
||||||
|
hard_links: u32,
|
||||||
|
xattr_idx: u32,
|
||||||
|
target: []const u8,
|
||||||
|
|
||||||
|
pub fn read(alloc: std.mem.Allocator, rdr: *Reader) !ExtSymlink {
|
||||||
|
var buf: [8]u8 = undefined;
|
||||||
|
try rdr.readSliceAll(&buf);
|
||||||
|
const size = std.mem.readVarInt(u32, buf[4..], .little);
|
||||||
|
const target = try alloc.alloc(u8, size);
|
||||||
|
errdefer alloc.free(target);
|
||||||
|
try rdr.readSliceEndian(u8, target, .little);
|
||||||
|
var xattr_idx: u32 = undefined;
|
||||||
|
try rdr.readSliceEndian(u32, @ptrCast(&xattr_idx), .little);
|
||||||
|
return .{
|
||||||
|
.hard_links = std.mem.readVarInt(u32, buf[0..4], .little),
|
||||||
|
.target = target,
|
||||||
|
.xattr_idx = xattr_idx,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Decompressor = @import("decomp.zig");
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
|
pub fn stateless(comptime T: anytype, fil: OffsetFile, decomp: *const Decompressor, table_start: u64, idx: u32) !T {
|
||||||
|
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||||
|
const block = idx / VALS_PER_BLOCK;
|
||||||
|
const block_idx = idx % VALS_PER_BLOCK;
|
||||||
|
|
||||||
|
const offset = try fil.valueAt(u64, table_start + (8 * block));
|
||||||
|
var buf: [8192]u8 = undefined;
|
||||||
|
var rdr = try fil.readerAt(offset, &buf);
|
||||||
|
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
|
||||||
|
try meta_rdr.interface.discardAll(@sizeOf(T) * block_idx);
|
||||||
|
|
||||||
|
var out: T = undefined;
|
||||||
|
try meta_rdr.interface.readSliceEndian(T, @ptrCast(&out), .little);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InodeRef = @import("inode.zig").Ref;
|
||||||
|
|
||||||
|
const XattrLookup = packed struct {
|
||||||
|
// This isn't actuall an inode ref, but is stored that exact same way.
|
||||||
|
ref: InodeRef,
|
||||||
|
kv_count: u32,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const XattrKey = packed struct {
|
||||||
|
type: enum(u2) {
|
||||||
|
user,
|
||||||
|
trusted,
|
||||||
|
security,
|
||||||
|
},
|
||||||
|
out_of_line: bool,
|
||||||
|
_: u13,
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const XattrValues = std.AutoHashMap([:0]u8, []u8);
|
||||||
|
|
||||||
|
pub fn statelessXattr(alloc: std.mem.Allocator, fil: OffsetFile, decomp: *const Decompressor, table_start: u64, idx: u32) !XattrValues {
|
||||||
|
const xattr_start = try fil.valueAt(u64, table_start);
|
||||||
|
const block = idx / 512;
|
||||||
|
const block_idx = idx % 512;
|
||||||
|
|
||||||
|
const block_start = try fil.valueAt(u64, table_start + 8 + (block * 8));
|
||||||
|
var rdr = try fil.readerAt(block_start, &[0]u8{});
|
||||||
|
var meta_rdr: MetadataReader = .init(&rdr.interface, decomp);
|
||||||
|
try meta_rdr.interface.discardAll(16 * block_idx);
|
||||||
|
|
||||||
|
var lookup: XattrLookup = undefined;
|
||||||
|
try meta_rdr.interface.readSliceEndian(XattrLookup, @ptrCast(&lookup), .little);
|
||||||
|
|
||||||
|
rdr = try fil.readerAt(xattr_start + lookup.ref.block_start, &[0]u8{});
|
||||||
|
meta_rdr = .init(&rdr.interface, decomp);
|
||||||
|
try meta_rdr.interface.discardAll(lookup.ref.block_offset);
|
||||||
|
|
||||||
|
var out: XattrValues = try .init(alloc);
|
||||||
|
for (0..lookup.kv_count) |_| {
|
||||||
|
var key: XattrKey = undefined;
|
||||||
|
try meta_rdr.interface.readSliceEndian(XattrKey, @ptrCast(&key), .little);
|
||||||
|
const prefix_size = switch (key.type) {
|
||||||
|
.user => 4,
|
||||||
|
.trusted => 7,
|
||||||
|
.security => 8,
|
||||||
|
};
|
||||||
|
const name: [:0]u8 = try alloc.alloc(u8, prefix_size + key.name_size + 1);
|
||||||
|
name[prefix_size + key.name_size] = 0;
|
||||||
|
try meta_rdr.interface.readSliceEndian(u8, name[prefix_size .. prefix_size + key.name_size], .little);
|
||||||
|
switch (key.type) {
|
||||||
|
.user => @memcpy(name[0..4], "user"),
|
||||||
|
.trusted => @memcpy(name[0..7], "trusted"),
|
||||||
|
.security => @memcpy(name[0..8], "security"),
|
||||||
|
}
|
||||||
|
if (key.out_of_line) {
|
||||||
|
try meta_rdr.interface.discardAll(4);
|
||||||
|
var value_offset: InodeRef = undefined;
|
||||||
|
try meta_rdr.interface.readSliceEndian(InodeRef, @ptrCast(&value_offset), .little);
|
||||||
|
|
||||||
|
var value_rdr = try fil.readerAt(xattr_start + value_offset.block_start, &[0]u8{});
|
||||||
|
var value_meta: MetadataReader = .init(&value_rdr.interface, decomp);
|
||||||
|
try value_meta.interface.discardAll(value_offset.block_offset);
|
||||||
|
|
||||||
|
var val_size: u32 = undefined;
|
||||||
|
try value_meta.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
|
||||||
|
const value = try alloc.alloc(u8, val_size);
|
||||||
|
try value_meta.interface.readSliceEndian(u8, value, .little);
|
||||||
|
try out.put(name, value);
|
||||||
|
} else {
|
||||||
|
var val_size: u32 = undefined;
|
||||||
|
try meta_rdr.interface.readSliceEndian(u32, @ptrCast(&val_size), .little);
|
||||||
|
const value = try alloc.alloc(u8, val_size);
|
||||||
|
try meta_rdr.interface.readSliceEndian(u8, value, .little);
|
||||||
|
try out.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn CachedTable(comptime T: anytype) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const VALS_PER_BLOCK = 8192 / @sizeOf(T);
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
decomp: *const Decompressor,
|
||||||
|
|
||||||
|
fil: OffsetFile,
|
||||||
|
table_start: u64,
|
||||||
|
num: u32,
|
||||||
|
|
||||||
|
cache: std.AutoHashMap(u32, []T),
|
||||||
|
cache_mut: std.Thread.Mutex = .{},
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, table_offset: u64, num: u32) !Self {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.decomp = decomp,
|
||||||
|
|
||||||
|
.fil = fil,
|
||||||
|
.table_start = table_offset,
|
||||||
|
.num = num,
|
||||||
|
|
||||||
|
.cache = .init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
var values = self.cache.valueIterator();
|
||||||
|
while (values.next()) |val|
|
||||||
|
self.alloc.free(val);
|
||||||
|
self.cache.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Self, idx: u32) !T {
|
||||||
|
const block = idx / VALS_PER_BLOCK;
|
||||||
|
const block_idx = idx % VALS_PER_BLOCK;
|
||||||
|
|
||||||
|
if (self.cache.get(block)) |val|
|
||||||
|
return val[block_idx];
|
||||||
|
|
||||||
|
self.cache_mut.lock();
|
||||||
|
defer self.cache_mut.unlock();
|
||||||
|
|
||||||
|
// Double check in case another thread was doing your work.
|
||||||
|
if (self.cache.get(block)) |val|
|
||||||
|
return val[block_idx];
|
||||||
|
|
||||||
|
const offset = try self.fil.valueAt(u64, self.table_start + (8 * block));
|
||||||
|
var buf: [8192]u8 = undefined;
|
||||||
|
var rdr = try self.fil.readerAt(offset, &buf);
|
||||||
|
var meta_rdr: MetadataReader = .init(&rdr.interface, self.decomp);
|
||||||
|
const block_size = if (block == (self.num - 1) / VALS_PER_BLOCK)
|
||||||
|
self.num % VALS_PER_BLOCK
|
||||||
|
else
|
||||||
|
VALS_PER_BLOCK;
|
||||||
|
const new_block = try self.alloc.alloc(T, block_size);
|
||||||
|
errdefer self.alloc.free(new_block);
|
||||||
|
try meta_rdr.interface.readSliceEndian(T, new_block, .little);
|
||||||
|
try self.cache.put(block, new_block);
|
||||||
|
return new_block[block_idx];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//! Options for file/directory extraction.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Writer = std.Io.Writer;
|
||||||
|
|
||||||
|
const ExtractionOptions = @This();
|
||||||
|
|
||||||
|
/// The number of threads used for extraction. 0 implies single threaded.
|
||||||
|
threads: usize = 1,
|
||||||
|
/// 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,
|
||||||
|
/// Where to print verbose log.
|
||||||
|
verbose_writer: ?*Writer = null,
|
||||||
|
|
||||||
|
pub const SingleThreadedDefault: ExtractionOptions = .{};
|
||||||
|
pub fn Default() !ExtractionOptions {
|
||||||
|
return .{
|
||||||
|
.threads = try std.Thread.getCpuCount(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||||
|
return .{
|
||||||
|
.verbose = true,
|
||||||
|
.verbose_writer = wrt,
|
||||||
|
.threads = try std.Thread.getCpuCount(),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Inode = @import("inode.zig");
|
|
||||||
const File = @import("file.zig").File;
|
|
||||||
const Table = @import("table.zig").Table;
|
|
||||||
const PRead = @import("reader/p_read.zig").PRead;
|
|
||||||
const FragEntry = @import("fragment.zig").FragEntry;
|
|
||||||
const Superblock = @import("superblock.zig").Superblock;
|
|
||||||
const ExtractionOptions = @import("extract_options.zig");
|
|
||||||
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
|
||||||
|
|
||||||
pub const SfsError = error{
|
|
||||||
NotExportable,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn SfsReader(comptime T: type) type {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
rdr: PRead(T),
|
|
||||||
|
|
||||||
super: Superblock = undefined,
|
|
||||||
/// ID table. Can be accessed directly
|
|
||||||
id_table: Table(u32, T) = undefined,
|
|
||||||
/// Fragment table. Can be accessed directly
|
|
||||||
frag_table: Table(FragEntry, T) = undefined,
|
|
||||||
/// Export table. Each element is an inode referce.
|
|
||||||
/// If accessing directly, keep in mind, the table starts at inode 1, as such it's recommended to use the InodeAt function instead.
|
|
||||||
export_table: Table(Inode.Ref, T) = undefined,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: T, offset: u64) !Self {
|
|
||||||
var out: Self = .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.rdr = .init(rdr, offset),
|
|
||||||
};
|
|
||||||
_ = try rdr.pread(std.mem.asBytes(&out.super), 0);
|
|
||||||
out.frag_table = .init(alloc, out.rdr, out.super.comp, out.super.frag_start, out.super.frag_count);
|
|
||||||
out.id_table = .init(alloc, out.rdr, out.super.comp, out.super.id_start, out.super.id_count);
|
|
||||||
out.export_table = .init(alloc, out.rdr, out.super.comp, out.super.export_start, out.super.inode_count);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.id_table.deinit();
|
|
||||||
self.frag_table.deinit();
|
|
||||||
self.export_table.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A representation of the archives root folder.
|
|
||||||
pub fn root(self: *Self) !File(T) {
|
|
||||||
return .initFromRef(self, self.super.root_ref, "");
|
|
||||||
}
|
|
||||||
/// Get the file at path. Equivelent to calling open on the root File.
|
|
||||||
pub fn open(self: *Self, path: []const u8) !File(T) {
|
|
||||||
var rt = try self.root();
|
|
||||||
if (path.len == 0 or (path.len == 1 and path[0] == '/') or path.len == 1 and path[0] == '.') return rt;
|
|
||||||
defer rt.deinit();
|
|
||||||
return rt.open(path);
|
|
||||||
}
|
|
||||||
/// Extract the entire archive to the given path & with the given options.
|
|
||||||
/// Equivelent to calling extract on the root File.
|
|
||||||
pub fn extract(self: *Self, op: ExtractionOptions, path: []const u8) !void {
|
|
||||||
var rt = try self.root();
|
|
||||||
defer rt.deinit();
|
|
||||||
return rt.extract(op, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the Inode with the given Inode Number.
|
|
||||||
/// Requires the archive to have an export table.
|
|
||||||
pub fn inodeAt(self: Self, num: u32) !Inode {
|
|
||||||
if (!self.super.flags.has_export) return SfsError.NotExportable;
|
|
||||||
const ref = try self.export_table.get(num - 1);
|
|
||||||
var meta = MetadataReader(T).init(
|
|
||||||
self.alloc,
|
|
||||||
self.super.comp,
|
|
||||||
self.rdr,
|
|
||||||
self.super.inode_start + ref.block,
|
|
||||||
);
|
|
||||||
try meta.skip(ref.offset);
|
|
||||||
return .init(meta, self.alloc, self.super.block_size);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Inode = @import("../inode.zig");
|
|
||||||
const PRead = @import("p_read.zig").PRead;
|
|
||||||
const SfsReader = @import("../reader.zig").SfsReader;
|
|
||||||
const FragEntry = @import("../fragment.zig").FragEntry;
|
|
||||||
const BlockSize = @import("../inode/file.zig").BlockSize;
|
|
||||||
const Compression = @import("../superblock.zig").Compression;
|
|
||||||
|
|
||||||
const DataReaderError = error{
|
|
||||||
EOF,
|
|
||||||
InvalidIndex,
|
|
||||||
ExtractionActive,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DecompCompletion = struct {
|
|
||||||
errs: std.ArrayList(anyerror),
|
|
||||||
map: std.AutoArrayHashMap(usize, []u8),
|
|
||||||
mut: std.Thread.Mutex = .{},
|
|
||||||
cond: std.Thread.Condition = .{},
|
|
||||||
|
|
||||||
fn init(alloc: std.mem.Allocator) DecompCompletion {
|
|
||||||
return .{
|
|
||||||
.errs = .init(alloc),
|
|
||||||
.map = .init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn deinit(self: *DecompCompletion) void {
|
|
||||||
self.errs.deinit();
|
|
||||||
self.map.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(self: *DecompCompletion) void {
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
self.errs.clearAndFree();
|
|
||||||
self.map.clearAndFree();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add(self: *DecompCompletion, idx: usize, data: []u8) !void {
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
defer self.cond.signal();
|
|
||||||
try self.map.put(idx, data);
|
|
||||||
}
|
|
||||||
fn addErr(self: *DecompCompletion, err: anyerror) void {
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
defer self.cond.signal();
|
|
||||||
self.errs.append(err) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getBlock(self: *DecompCompletion, idx: usize) ?[]u8 {
|
|
||||||
const res = self.map.fetchSwapRemove(idx);
|
|
||||||
if (res == null) return null;
|
|
||||||
return res.?.value;
|
|
||||||
}
|
|
||||||
fn hasErrs(self: DecompCompletion) bool {
|
|
||||||
return self.errs.items.len > 0;
|
|
||||||
}
|
|
||||||
fn condWait(self: *DecompCompletion) void {
|
|
||||||
self.cond.wait(&self.mut);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn DataReader(comptime T: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
rdr: PRead(T),
|
|
||||||
comp: Compression,
|
|
||||||
block_size: u32,
|
|
||||||
|
|
||||||
sizes: []BlockSize,
|
|
||||||
offsets: []u64,
|
|
||||||
file_size: u64,
|
|
||||||
|
|
||||||
frag: ?[]u8 = null,
|
|
||||||
|
|
||||||
completion: DecompCompletion,
|
|
||||||
|
|
||||||
pub fn init(rdr: *SfsReader(T), inode: Inode) !Self {
|
|
||||||
var sizes: []BlockSize = undefined;
|
|
||||||
var file_size: u64 = 0;
|
|
||||||
var offsets: []u64 = undefined;
|
|
||||||
switch (inode.data) {
|
|
||||||
.file => |f| {
|
|
||||||
sizes = f.block_sizes;
|
|
||||||
file_size = f.size;
|
|
||||||
offsets = try rdr.alloc.alloc(u64, sizes.len);
|
|
||||||
if (sizes.len > 0) offsets[0] = f.block;
|
|
||||||
},
|
|
||||||
.ext_file => |f| {
|
|
||||||
sizes = f.block_sizes;
|
|
||||||
file_size = f.size;
|
|
||||||
offsets = try rdr.alloc.alloc(u64, sizes.len);
|
|
||||||
if (sizes.len > 0) offsets[0] = f.block;
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
if (offsets.len > 1) {
|
|
||||||
for (1..offsets.len) |i| {
|
|
||||||
offsets[i] = offsets[i - 1] + sizes[i - 1].size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .{
|
|
||||||
.alloc = rdr.alloc,
|
|
||||||
.rdr = rdr.rdr,
|
|
||||||
.comp = rdr.super.comp,
|
|
||||||
.block_size = rdr.super.block_size,
|
|
||||||
.sizes = sizes,
|
|
||||||
.offsets = offsets,
|
|
||||||
.file_size = file_size,
|
|
||||||
.completion = .init(rdr.alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.alloc.free(self.offsets);
|
|
||||||
self.completion.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addFragment(self: *Self, data: []u8) void {
|
|
||||||
self.frag = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeTo(self: *Self, wrt: anytype) !void {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(@TypeOf(wrt), "write") or std.meta.hasFn(@TypeOf(wrt), "pwrite"));
|
|
||||||
var write_thr = try std.Thread.spawn(
|
|
||||||
.{ .allocator = self.alloc },
|
|
||||||
writeThread,
|
|
||||||
.{ self, wrt, null, null },
|
|
||||||
);
|
|
||||||
defer self.completion.clear();
|
|
||||||
for (0..self.numBlocks()) |i| {
|
|
||||||
var thr = std.Thread.spawn(
|
|
||||||
.{ .allocator = self.alloc },
|
|
||||||
decompThread,
|
|
||||||
.{ self, i },
|
|
||||||
) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
thr.detach();
|
|
||||||
}
|
|
||||||
write_thr.join();
|
|
||||||
if (self.completion.hasErrs()) return self.completion.errs.items[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeToNoBlock(self: *Self, wrt: anytype, comptime finish: anytype, finish_args: anytype) !void {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(@TypeOf(wrt), "write") or std.meta.hasFn(@TypeOf(wrt), "pwrite"));
|
|
||||||
errdefer self.completion.clear();
|
|
||||||
var write_thr = try std.Thread.spawn(
|
|
||||||
.{ .allocator = self.alloc },
|
|
||||||
writeThread,
|
|
||||||
.{ self, wrt, finish, finish_args },
|
|
||||||
);
|
|
||||||
write_thr.detach();
|
|
||||||
for (0..self.numBlocks()) |i| {
|
|
||||||
var thr = std.Thread.spawn(
|
|
||||||
.{ .allocator = self.alloc },
|
|
||||||
decompThread,
|
|
||||||
.{ self, i },
|
|
||||||
) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
thr.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn numBlocks(self: Self) usize {
|
|
||||||
var out = self.sizes.len;
|
|
||||||
if (self.frag != null) out += 1;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
/// Returns the decompressed data block at the given idx.
|
|
||||||
/// If the block is sparse (filled with 0s), a zero length slice is returned.
|
|
||||||
fn blockAt(self: Self, idx: usize) ![]u8 {
|
|
||||||
if (idx >= self.numBlocks()) return DataReaderError.InvalidIndex;
|
|
||||||
const size = self.sizes[idx];
|
|
||||||
if (size.size == 0) return &[0]u8{};
|
|
||||||
const block = try self.alloc.alloc(u8, blk: {
|
|
||||||
if (idx == self.numBlocks() - 1) break :blk self.file_size % self.block_size;
|
|
||||||
break :blk self.block_size;
|
|
||||||
});
|
|
||||||
if (idx == self.sizes.len and self.frag != null) {
|
|
||||||
@memcpy(block, self.frag.?);
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
if (size.uncompressed) {
|
|
||||||
_ = try self.rdr.pread(block, self.offsets[idx]);
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
_ = try self.comp.decompress(
|
|
||||||
1024 * 1024,
|
|
||||||
self.alloc,
|
|
||||||
self.rdr.readerAt(self.offsets[idx]).reader(),
|
|
||||||
block,
|
|
||||||
);
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn writeThread(
|
|
||||||
self: *Self,
|
|
||||||
wrt: anytype,
|
|
||||||
comptime finish: anytype,
|
|
||||||
finish_args: anytype,
|
|
||||||
) void {
|
|
||||||
var cur_idx: usize = 0;
|
|
||||||
self.completion.mut.lock();
|
|
||||||
defer self.completion.mut.unlock();
|
|
||||||
while (cur_idx < self.numBlocks() and !self.completion.hasErrs()) {
|
|
||||||
self.completion.condWait();
|
|
||||||
if (self.completion.hasErrs()) break;
|
|
||||||
if (comptime std.meta.hasFn(@TypeOf(wrt), "pwrite")) {
|
|
||||||
for (self.completion.map.keys()) |_| {
|
|
||||||
const k = self.completion.map.keys()[0];
|
|
||||||
const blk = self.completion.getBlock(k).?;
|
|
||||||
defer self.alloc.free(blk);
|
|
||||||
if (blk.len > 0) {
|
|
||||||
_ = wrt.pwrite(blk, self.block_size * k) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
_ = wrt.pwrite(&[1]u8{0}, (self.block_size * (k + 1)) - 1) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
cur_idx += 1;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
while (self.completion.getBlock(cur_idx)) |blk| {
|
|
||||||
defer self.alloc.free(blk);
|
|
||||||
if (blk.len > 0) {
|
|
||||||
_ = wrt.write(blk) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const blank: [1024 * 1024]u8 = [1]u8{0} ** (1024 * 1024);
|
|
||||||
_ = wrt.write(blank[0..self.block_size]) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
cur_idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (comptime @TypeOf(finish) != @TypeOf(null) and @TypeOf(finish_args) != @TypeOf(null)) @call(.auto, finish, finish_args);
|
|
||||||
}
|
|
||||||
fn decompThread(
|
|
||||||
self: *Self,
|
|
||||||
idx: usize,
|
|
||||||
) void {
|
|
||||||
if (self.completion.hasErrs()) return;
|
|
||||||
defer self.completion.cond.signal();
|
|
||||||
const block = self.blockAt(idx) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.completion.add(idx, block) catch |err| {
|
|
||||||
self.completion.addErr(err);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const PRead = @import("p_read.zig").PRead;
|
|
||||||
const Compression = @import("../superblock.zig").Compression;
|
|
||||||
|
|
||||||
const MetaHeader = packed struct {
|
|
||||||
size: u15,
|
|
||||||
uncompressed: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn MetadataReader(comptime T: type) type {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(T, "read"));
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
comp: Compression,
|
|
||||||
rdr: PRead(T),
|
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
block: [8192]u8 = undefined,
|
|
||||||
block_size: usize = 0,
|
|
||||||
block_offset: u32 = 0,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, comp: Compression, rdr: PRead(T), offset: u64) Self {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.comp = comp,
|
|
||||||
.rdr = rdr,
|
|
||||||
.offset = offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readNextBlock(self: *Self) !void {
|
|
||||||
var hdr: MetaHeader = undefined;
|
|
||||||
_ = try self.rdr.pread(std.mem.asBytes(&hdr), self.offset);
|
|
||||||
self.offset += 2;
|
|
||||||
if (hdr.uncompressed) {
|
|
||||||
self.block_size = try self.rdr.pread(self.block[0..hdr.size], self.offset);
|
|
||||||
} else {
|
|
||||||
self.block_size = try self.comp.decompress(
|
|
||||||
8192,
|
|
||||||
self.alloc,
|
|
||||||
self.rdr.readerAt(self.offset).reader(),
|
|
||||||
&self.block,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.offset += hdr.size;
|
|
||||||
self.block_offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(self: *Self, offset: u32) !void {
|
|
||||||
var skipped: u32 = 0;
|
|
||||||
var hdr: MetaHeader = undefined;
|
|
||||||
while (offset - skipped >= 8192) {
|
|
||||||
_ = try self.rdr.pread(std.mem.asBytes(&hdr), self.offset);
|
|
||||||
self.offset += 2 + hdr.size;
|
|
||||||
skipped += 8192;
|
|
||||||
}
|
|
||||||
var to_skip: u32 = 0;
|
|
||||||
while (skipped < offset) {
|
|
||||||
if (self.block_offset >= self.block_size) try self.readNextBlock();
|
|
||||||
to_skip = @min(self.block_size - self.block_offset, offset - skipped);
|
|
||||||
self.block_offset += to_skip;
|
|
||||||
skipped += to_skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *Self, buf: []u8) !usize {
|
|
||||||
var cur_red: usize = 0;
|
|
||||||
var to_read: usize = 0;
|
|
||||||
while (cur_red < buf.len) {
|
|
||||||
if (self.block_offset >= self.block_size) try self.readNextBlock();
|
|
||||||
to_read = @min(buf.len - cur_red, self.block_size - self.block_offset);
|
|
||||||
@memcpy(buf[cur_red .. cur_red + to_read], self.block[self.block_offset .. self.block_offset + to_read]);
|
|
||||||
cur_red += to_read;
|
|
||||||
self.block_offset += @truncate(to_read);
|
|
||||||
}
|
|
||||||
return cur_red;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const ToRead = @import("to_read.zig").ToRead;
|
|
||||||
|
|
||||||
/// A simple wrapper around a type with the pread([]u8, u64) function.
|
|
||||||
/// Provides a couple useful utility functions.
|
|
||||||
pub fn PRead(comptime T: type) type {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
rdr: T,
|
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
pub fn init(rdr: T, offset: u64) Self {
|
|
||||||
return .{
|
|
||||||
.rdr = rdr,
|
|
||||||
.offset = offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pread(self: Self, buf: []u8, offset: u64) !usize {
|
|
||||||
return self.rdr.pread(buf, self.offset + offset);
|
|
||||||
}
|
|
||||||
pub fn readerAt(self: Self, offset: u64) ToRead(T) {
|
|
||||||
return .init(self.rdr, self.offset + offset);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn ToRead(comptime T: type) type {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(T, "pread"));
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Error = anyerror;
|
|
||||||
|
|
||||||
rdr: T,
|
|
||||||
offset: u64,
|
|
||||||
|
|
||||||
pub fn init(rdr: T, init_offset: u64) Self {
|
|
||||||
return .{
|
|
||||||
.rdr = rdr,
|
|
||||||
.offset = init_offset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *Self, buf: []u8) !usize {
|
|
||||||
const red = try self.rdr.pread(buf, self.offset);
|
|
||||||
self.offset += red;
|
|
||||||
return red;
|
|
||||||
}
|
|
||||||
pub fn readAll(self: *Self, buf: []u8) !usize {
|
|
||||||
var cur_red = try self.read(buf);
|
|
||||||
if (cur_red == 0) return cur_red;
|
|
||||||
var res: usize = 0;
|
|
||||||
while (cur_red < buf.len) {
|
|
||||||
res = try self.read(buf[cur_red..]);
|
|
||||||
if (res == 0) break;
|
|
||||||
cur_red += res;
|
|
||||||
}
|
|
||||||
return cur_red;
|
|
||||||
}
|
|
||||||
const Reader = std.io.GenericReader(*Self, anyerror, read);
|
|
||||||
pub fn reader(self: anytype) Reader {
|
|
||||||
return .{
|
|
||||||
.context = @constCast(self),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+2
-61
@@ -1,62 +1,3 @@
|
|||||||
const std = @import("std");
|
pub const Archive = @import("archive.zig");
|
||||||
|
|
||||||
pub const SfsReader = @import("reader.zig").SfsReader;
|
pub const ExtractionOptions = @import("options.zig");
|
||||||
pub const ExtractionOptions = @import("extract_options.zig");
|
|
||||||
|
|
||||||
pub const SfsFile = SfsReader(std.fs.File);
|
|
||||||
|
|
||||||
const test_archive = "testing/LinuxPATest.sfs";
|
|
||||||
|
|
||||||
test "OpenFile" {
|
|
||||||
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
|
|
||||||
defer sfs_fil.close();
|
|
||||||
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
|
|
||||||
defer rdr.deinit();
|
|
||||||
_ = try rdr.frag_table.get(rdr.super.frag_count - 1);
|
|
||||||
_ = try rdr.id_table.get(rdr.super.id_count - 1);
|
|
||||||
_ = try rdr.export_table.get(rdr.super.inode_count - 1);
|
|
||||||
std.debug.print("{}\n", .{rdr.super});
|
|
||||||
var root = try rdr.root();
|
|
||||||
defer root.deinit();
|
|
||||||
var iter = root.iterate();
|
|
||||||
while (true) {
|
|
||||||
var f = try iter.next();
|
|
||||||
if (f == null) break;
|
|
||||||
defer f.?.deinit();
|
|
||||||
std.debug.print("{s}\n", .{f.?.name});
|
|
||||||
}
|
|
||||||
std.debug.print("Finished OpenFile test\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
test "ExtractSingleFile" {
|
|
||||||
const single_file = "PortableApps/Notepad++Portable/App/Notepad++/doLocalConf.xml";
|
|
||||||
const single_file_extr_loc = "testing/doLocalConf.xml";
|
|
||||||
|
|
||||||
std.fs.cwd().deleteFile(single_file_extr_loc) catch {};
|
|
||||||
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
|
|
||||||
defer sfs_fil.close();
|
|
||||||
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
|
|
||||||
defer rdr.deinit();
|
|
||||||
var fil = try rdr.open(single_file);
|
|
||||||
defer fil.deinit();
|
|
||||||
var op: ExtractionOptions = try .init();
|
|
||||||
op.verbose = true;
|
|
||||||
try fil.extract(op, single_file_extr_loc);
|
|
||||||
|
|
||||||
std.debug.print("Finished ExtractSingleFile test\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
test "ExtractAll" {
|
|
||||||
const extr_dir = "testing/testExtract";
|
|
||||||
|
|
||||||
std.fs.cwd().deleteTree(extr_dir) catch {};
|
|
||||||
const sfs_fil = try std.fs.cwd().openFile(test_archive, .{});
|
|
||||||
defer sfs_fil.close();
|
|
||||||
var rdr: SfsFile = try .init(std.testing.allocator, sfs_fil, 0);
|
|
||||||
defer rdr.deinit();
|
|
||||||
const op: ExtractionOptions = try .init();
|
|
||||||
// op.verbose = true;
|
|
||||||
try rdr.extract(op, extr_dir);
|
|
||||||
|
|
||||||
std.debug.print("Finished ExtractAll test\n", .{});
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const InodeRef = @import("inode.zig").Ref;
|
|
||||||
|
|
||||||
pub const Superblock = packed struct {
|
|
||||||
magic: u32,
|
|
||||||
inode_count: u32,
|
|
||||||
mod_time: u32,
|
|
||||||
block_size: u32,
|
|
||||||
frag_count: u32,
|
|
||||||
comp: Compression,
|
|
||||||
block_log: u16,
|
|
||||||
flags: packed struct {
|
|
||||||
_: u4,
|
|
||||||
id_uncomp: bool,
|
|
||||||
comp_options: bool,
|
|
||||||
no_xattr: bool,
|
|
||||||
xattr_uncomp: bool,
|
|
||||||
has_export: bool,
|
|
||||||
de_dupe: bool,
|
|
||||||
frag_always: bool,
|
|
||||||
no_frag: bool,
|
|
||||||
frag_uncomp: bool,
|
|
||||||
check: bool,
|
|
||||||
data_uncomp: bool,
|
|
||||||
inode_uncomp: bool,
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DecompressError = error{
|
|
||||||
LzoUnavailable,
|
|
||||||
Lz4Unavailable,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Compression = enum(u16) {
|
|
||||||
gzip = 1,
|
|
||||||
lzma,
|
|
||||||
lzo,
|
|
||||||
xz,
|
|
||||||
lz4,
|
|
||||||
zstd,
|
|
||||||
|
|
||||||
pub fn decompress(self: Compression, comptime max_size: u32, alloc: std.mem.Allocator, source: anytype, dest: []u8) !usize {
|
|
||||||
switch (self) {
|
|
||||||
.gzip => {
|
|
||||||
var decomp = std.compress.zlib.decompressor(source);
|
|
||||||
return decomp.read(dest);
|
|
||||||
},
|
|
||||||
.lzma => {
|
|
||||||
var decomp = try std.compress.lzma.decompress(alloc, source);
|
|
||||||
defer decomp.deinit();
|
|
||||||
return decomp.read(dest);
|
|
||||||
},
|
|
||||||
.lzo => return DecompressError.LzoUnavailable,
|
|
||||||
.xz => {
|
|
||||||
var decomp = try std.compress.xz.decompress(alloc, source);
|
|
||||||
defer decomp.deinit();
|
|
||||||
return decomp.read(dest);
|
|
||||||
},
|
|
||||||
.lz4 => return DecompressError.Lz4Unavailable,
|
|
||||||
.zstd => {
|
|
||||||
var window: [max_size]u8 = undefined;
|
|
||||||
var decomp = std.compress.zstd.decompressor(source, .{ .window_buffer = &window });
|
|
||||||
return decomp.read(dest);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const PRead = @import("reader/p_read.zig").PRead;
|
|
||||||
const Compression = @import("superblock.zig").Compression;
|
|
||||||
const MetadataReader = @import("reader/metadata.zig").MetadataReader;
|
|
||||||
|
|
||||||
pub const TableError = error{
|
|
||||||
InvalidIndex,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn Table(comptime T: type, comptime R: type) type {
|
|
||||||
comptime std.debug.assert(std.meta.hasFn(R, "pread"));
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
rdr: PRead(R),
|
|
||||||
comp: Compression,
|
|
||||||
|
|
||||||
offset: u64,
|
|
||||||
table_count: u32,
|
|
||||||
mut: std.Thread.RwLock = .{},
|
|
||||||
|
|
||||||
table: []T = &[0]T{},
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, rdr: PRead(R), comp: Compression, offset: u64, table_count: u32) Self {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.rdr = rdr,
|
|
||||||
.comp = comp,
|
|
||||||
.offset = offset,
|
|
||||||
.table_count = table_count,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: Self) void {
|
|
||||||
self.alloc.free(self.table);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resize(self: *Self, to_add: usize) !void {
|
|
||||||
if (!self.alloc.resize(self.table, self.table.len + to_add)) {
|
|
||||||
const new_table = try self.alloc.alloc(T, self.table.len + to_add);
|
|
||||||
@memcpy(new_table[0..self.table.len], self.table);
|
|
||||||
self.alloc.free(self.table);
|
|
||||||
self.table = new_table;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Self, idx: u32) !T {
|
|
||||||
if (idx >= self.table_count) return TableError.InvalidIndex;
|
|
||||||
self.mut.lockShared();
|
|
||||||
defer self.mut.unlockShared();
|
|
||||||
if (idx >= self.table.len) {
|
|
||||||
return self.getAndFill(idx);
|
|
||||||
}
|
|
||||||
return self.table[idx];
|
|
||||||
}
|
|
||||||
fn getAndFill(self: *Self, idx: u32) !T {
|
|
||||||
self.mut.unlockShared();
|
|
||||||
defer self.mut.lockShared();
|
|
||||||
self.mut.lock();
|
|
||||||
defer self.mut.unlock();
|
|
||||||
var to_read: usize = 0;
|
|
||||||
var offset: u64 = 0;
|
|
||||||
while (idx >= self.table.len) {
|
|
||||||
to_read = @min(self.table_count - self.table.len, comptime 8192 / @sizeOf(T));
|
|
||||||
try self.resize(to_read);
|
|
||||||
_ = try self.rdr.pread(std.mem.asBytes(&offset), self.offset);
|
|
||||||
self.offset += 8;
|
|
||||||
var meta: MetadataReader(R) = .init(self.alloc, self.comp, self.rdr, offset);
|
|
||||||
_ = try meta.read(std.mem.sliceAsBytes(self.table[self.table.len - to_read ..]));
|
|
||||||
}
|
|
||||||
return self.table[idx];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
const Writer = std.Io.Writer;
|
||||||
|
const Limit = std.Io.Limit;
|
||||||
|
|
||||||
|
const FragEntry = @import("../archive.zig").FragEntry;
|
||||||
|
const Decompressor = @import("../decomp.zig");
|
||||||
|
const BlockSize = @import("../inode/file.zig").BlockSize;
|
||||||
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
|
const DataReader = @This();
|
||||||
|
|
||||||
|
decomp: *const Decompressor,
|
||||||
|
file: OffsetFile,
|
||||||
|
block_size: u32,
|
||||||
|
blocks: []BlockSize,
|
||||||
|
size: u64,
|
||||||
|
frag: ?FragEntry,
|
||||||
|
frag_offset: u32,
|
||||||
|
|
||||||
|
offset: u64,
|
||||||
|
idx: usize = 0,
|
||||||
|
sparse: bool = false,
|
||||||
|
|
||||||
|
interface: Reader,
|
||||||
|
|
||||||
|
pub fn init(decomp: *const Decompressor, file: OffsetFile, block_size: u32, blocks: []BlockSize, size: u64, init_offset: u64, frag: ?FragEntry, frag_offset: u32) DataReader {
|
||||||
|
return .{
|
||||||
|
.decomp = decomp,
|
||||||
|
.file = file,
|
||||||
|
.block_size = block_size,
|
||||||
|
.blocks = blocks,
|
||||||
|
.size = size,
|
||||||
|
.frag = frag,
|
||||||
|
.frag_offset = frag_offset,
|
||||||
|
|
||||||
|
.offset = init_offset,
|
||||||
|
|
||||||
|
.interface = .{
|
||||||
|
.buffer = &[1]u8{undefined} ** (1024 * 1024),
|
||||||
|
.end = 0,
|
||||||
|
.seek = 0,
|
||||||
|
.vtable = &.{ .stream = stream, .discard = discard, .readVec = readVec },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn numBlocks(self: *DataReader) usize {
|
||||||
|
return if (self.frag == null)
|
||||||
|
self.blocks.len
|
||||||
|
else
|
||||||
|
self.blocks.len + 1;
|
||||||
|
}
|
||||||
|
fn advanceBuffer(self: *DataReader) Reader.Error!void {
|
||||||
|
if (self.idx >= self.numBlocks()) return Reader.Error.EndOfStream;
|
||||||
|
defer self.idx += 1;
|
||||||
|
self.sparse = false;
|
||||||
|
self.interface.end = 0; // If we error out and the error is ignored, we'll stil end up back here to error again.
|
||||||
|
self.interface.seek = 0;
|
||||||
|
if (self.idx == self.blocks.len) { // Fragment
|
||||||
|
var rdr = self.file.readerAt(self.frag.?.block_start, &[0]u8{}) catch return Reader.Error.ReadFailed;
|
||||||
|
const size = self.size % self.block_size;
|
||||||
|
if (self.frag.?.size.uncompressed) {
|
||||||
|
try rdr.interface.discardAll(self.frag_offset);
|
||||||
|
try rdr.interface.readSliceAll(self.interface.buffer[0..size]);
|
||||||
|
self.interface.end = size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const raw_loc = self.interface.buffer.len - self.frag.?.size.size;
|
||||||
|
try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]);
|
||||||
|
_ = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch
|
||||||
|
return Reader.Error.ReadFailed;
|
||||||
|
@memmove(self.interface.buffer[0..size], self.interface.buffer[self.frag_offset .. self.frag_offset + size]);
|
||||||
|
self.interface.end = size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const block = self.blocks[self.idx];
|
||||||
|
if (block.size == 0) {
|
||||||
|
self.interface.end = if (self.idx == self.numBlocks() - 1)
|
||||||
|
self.size % self.block_size
|
||||||
|
else
|
||||||
|
self.block_size;
|
||||||
|
self.sparse = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defer self.offset += block.size;
|
||||||
|
var rdr = try self.file.readerAt(self.offset, &[0]u8{});
|
||||||
|
if (block.uncompressed) {
|
||||||
|
try rdr.interface.readSliceAll(self.interface.buffer[0..block.size]);
|
||||||
|
self.interface.end = block.size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const raw_loc = self.interface.buffer.len - block.size;
|
||||||
|
try rdr.interface.readSliceAll(self.interface.buffer[raw_loc..]);
|
||||||
|
self.interface.end = self.decomp.decompress(self.interface.buffer[raw_loc..], self.interface.buffer) catch
|
||||||
|
return Reader.Error.ReadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(r: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
|
||||||
|
var self: *DataReader = @fieldParentPtr("interface", r);
|
||||||
|
if (r.seek == r.end) try self.advanceBuffer();
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
|
||||||
|
const to_write = @min(r.end - r.seek, @intFromEnum(limit));
|
||||||
|
const wrote = if (self.sparse)
|
||||||
|
try wrt.splatByte(0, to_write)
|
||||||
|
else
|
||||||
|
try wrt.write(r.buffer[r.seek .. r.seek + to_write]);
|
||||||
|
r.seek += wrote;
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
fn discard(r: *Reader, limit: Limit) Reader.Error!usize {
|
||||||
|
var self: *DataReader = @fieldParentPtr("interface", r);
|
||||||
|
if (r.seek == r.end) try self.advanceBuffer();
|
||||||
|
if (limit == .nothing) return 0;
|
||||||
|
|
||||||
|
const adv = @min(r.end - r.seek, @intFromEnum(limit));
|
||||||
|
r.seek += adv;
|
||||||
|
return adv;
|
||||||
|
}
|
||||||
|
fn readVec(r: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||||
|
var self: *DataReader = @fieldParentPtr("interface", r);
|
||||||
|
if (r.seek == r.end) try self.advanceBuffer();
|
||||||
|
|
||||||
|
var wrote: usize = 0;
|
||||||
|
for (vec) |slice| {
|
||||||
|
if (r.seek == r.end) break;
|
||||||
|
const to_copy = @min(r.end - r.seek, slice.len);
|
||||||
|
if (self.sparse) {
|
||||||
|
@memset(slice[0..to_copy], 0);
|
||||||
|
} else {
|
||||||
|
@memcpy(slice[0..to_copy], r.buffer[r.seek .. r.seek + to_copy]);
|
||||||
|
}
|
||||||
|
r.seek += to_copy;
|
||||||
|
wrote += to_copy;
|
||||||
|
}
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const MinimalSuperblock = @import("../archive.zig").MinimalSuperblock;
|
||||||
|
const Decompressor = @import("../decomp.zig");
|
||||||
|
const DirEntry = @import("../directory.zig").Entry;
|
||||||
|
const File = @import("../file.zig");
|
||||||
|
const Inode = @import("../inode.zig");
|
||||||
|
const MetadataReader = @import("metadata.zig");
|
||||||
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
const Utils = @import("utils.zig");
|
||||||
|
|
||||||
|
const Iter = @This();
|
||||||
|
|
||||||
|
file: OffsetFile,
|
||||||
|
super: MinimalSuperblock,
|
||||||
|
decomp: Decompressor,
|
||||||
|
|
||||||
|
entries: []DirEntry,
|
||||||
|
idx: usize = 0,
|
||||||
|
|
||||||
|
pub fn deinit(self: Iter) void {
|
||||||
|
for (self.entries) |ent|
|
||||||
|
ent.deinit(self.decomp.alloc);
|
||||||
|
self.decomp.alloc.free(self.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *Iter) !?File {
|
||||||
|
if (self.idx >= self.entries.len) return null;
|
||||||
|
defer self.idx += 1;
|
||||||
|
|
||||||
|
const entry = self.entries[self.idx];
|
||||||
|
|
||||||
|
const new_name = try self.decomp.alloc.alloc(u8, entry.name.len);
|
||||||
|
@memcpy(new_name, entry.name);
|
||||||
|
return .{
|
||||||
|
.file = self.file,
|
||||||
|
.super = self.super,
|
||||||
|
.decomp = self.decomp,
|
||||||
|
|
||||||
|
.name = new_name,
|
||||||
|
.inode = Utils.readInode(
|
||||||
|
self.decomp.alloc,
|
||||||
|
&self.decomp,
|
||||||
|
self.file,
|
||||||
|
self.super.inode_start,
|
||||||
|
self.super.block_size,
|
||||||
|
entry.block_start,
|
||||||
|
entry.block_offset,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn reset(self: *Iter) void {
|
||||||
|
self.idx = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Reader = std.Io.Reader;
|
||||||
|
const Writer = std.Io.Writer;
|
||||||
|
const Limit = std.Io.Limit;
|
||||||
|
|
||||||
|
const Decompressor = @import("../decomp.zig");
|
||||||
|
|
||||||
|
const Header = packed struct {
|
||||||
|
size: u15,
|
||||||
|
uncompressed: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MetadataReader = @This();
|
||||||
|
|
||||||
|
rdr: *Reader,
|
||||||
|
decomp: *const Decompressor,
|
||||||
|
|
||||||
|
read_buf: [8192]u8 = undefined,
|
||||||
|
interface: Reader = .{
|
||||||
|
.buffer = &([1]u8{undefined} ** 8192),
|
||||||
|
.end = 0,
|
||||||
|
.seek = 0,
|
||||||
|
.vtable = &.{
|
||||||
|
.stream = stream,
|
||||||
|
.discard = discard,
|
||||||
|
.readVec = readVec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
pub fn init(rdr: *Reader, decomp: *const Decompressor) MetadataReader {
|
||||||
|
return .{ .rdr = rdr, .decomp = decomp };
|
||||||
|
}
|
||||||
|
fn advanceBuffer(self: *MetadataReader) Reader.Error!void {
|
||||||
|
self.interface.seek = 0;
|
||||||
|
var hdr: Header = undefined;
|
||||||
|
try self.rdr.readSliceEndian(Header, @ptrCast(&hdr), .little);
|
||||||
|
try self.rdr.readSliceAll(self.read_buf[0..hdr.size]);
|
||||||
|
if (hdr.uncompressed) {
|
||||||
|
@memcpy(self.interface.buffer[0..hdr.size], self.read_buf[0..hdr.size]);
|
||||||
|
self.interface.end = hdr.size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.interface.end = self.decomp.decompress(self.read_buf[0..hdr.size], self.interface.buffer) catch |err|
|
||||||
|
return switch (err) {
|
||||||
|
error.OutOfMemory => error.ReadFailed,
|
||||||
|
else => err,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stream(rdr: *Reader, wrt: *Writer, limit: Limit) Reader.StreamError!usize {
|
||||||
|
var self: *MetadataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.seek == rdr.end) try self.advanceBuffer();
|
||||||
|
const to_write = @min(@intFromEnum(limit), rdr.end - rdr.seek);
|
||||||
|
const wrote = try wrt.write(rdr.buffer[rdr.seek .. rdr.seek + to_write]);
|
||||||
|
rdr.seek += wrote;
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
|
fn discard(rdr: *Reader, limit: Limit) Reader.Error!usize {
|
||||||
|
var self: *MetadataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.seek == rdr.end) try self.advanceBuffer();
|
||||||
|
const to_adv = @min(@intFromEnum(limit), rdr.end - rdr.seek);
|
||||||
|
rdr.seek += to_adv;
|
||||||
|
return to_adv;
|
||||||
|
}
|
||||||
|
fn readVec(rdr: *Reader, vec: [][]u8) Reader.Error!usize {
|
||||||
|
var self: *MetadataReader = @fieldParentPtr("interface", rdr);
|
||||||
|
if (rdr.seek == rdr.end) try self.advanceBuffer();
|
||||||
|
var wrote = 0;
|
||||||
|
for (vec) |v| {
|
||||||
|
if (rdr.seek == rdr.end) break;
|
||||||
|
const to_write = @min(v.len, rdr.end - rdr.seek);
|
||||||
|
@memcpy(v[0..to_write], rdr.buffer[rdr.seek .. rdr.seek + to_write]);
|
||||||
|
wrote += to_write;
|
||||||
|
rdr.seek += to_write;
|
||||||
|
}
|
||||||
|
return wrote;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Io = std.Io;
|
||||||
|
const FileReader = Io.File.Reader;
|
||||||
|
|
||||||
|
const OffsetFile = @This();
|
||||||
|
|
||||||
|
fil: Io.File,
|
||||||
|
offset: u64 = 0,
|
||||||
|
|
||||||
|
pub fn readerAt(self: OffsetFile, io: Io, offset: u64, buf: []u8) !FileReader {
|
||||||
|
var rdr = self.fil.reader(io, buf);
|
||||||
|
try rdr.seekTo(self.offset + offset);
|
||||||
|
return rdr;
|
||||||
|
}
|
||||||
|
pub fn valueAt(self: OffsetFile, comptime T: type, io: Io, offset: u64) !T {
|
||||||
|
var rdr = self.fil.reader(io, &[0]u8{});
|
||||||
|
try rdr.seekTo(self.offset + offset);
|
||||||
|
var new: T = undefined;
|
||||||
|
try rdr.interface.readSliceEndian(T, @ptrCast(&new), .little);
|
||||||
|
return new;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Decompressor = @import("../decomp.zig");
|
||||||
|
const DirEntry = @import("../directory.zig").Entry;
|
||||||
|
const Inode = @import("../inode.zig");
|
||||||
|
const MetadataReader = @import("metadata.zig");
|
||||||
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
|
pub fn pathIsSelf(path: []const u8) bool {
|
||||||
|
if (path.len == 0) return true;
|
||||||
|
if (path.len == 1) {
|
||||||
|
return switch (path[0]) {
|
||||||
|
'.', '/' => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return std.mem.eql(u8, path, "./");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readInode(alloc: std.mem.Allocator, decomp: *const Decompressor, fil: OffsetFile, inode_start: u64, block_size: u32, block_start: u32, block_offset: u16) !Inode {
|
||||||
|
var rdr = try fil.readerAt(inode_start + block_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(&rdr.interface, decomp);
|
||||||
|
try meta.interface.discardAll(block_offset);
|
||||||
|
return .read(alloc, &meta.interface, block_size);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user