Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8002e745e0 | |||
| b5742bc282 | |||
| 51305a1a80 | |||
| 1dc85c62fc | |||
| 8b8c9a772f | |||
| f563648b20 | |||
| 8d4f3d72f8 | |||
| 0b6129f1ae | |||
| 7308faad36 | |||
| c9499251f8 | |||
| d470ca98e3 | |||
| a606f5e11a | |||
| a4e23a840d | |||
| 3a10572953 | |||
| edfe919c1b | |||
| 4515610082 | |||
| beca6a2ae6 | |||
| 760e11df0b | |||
| 5f629df47c | |||
| b0160e005b | |||
| b7b99325da |
@@ -15,17 +15,17 @@ jobs:
|
|||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
run: sudo apt update && sudo apt install -y zlib1g-dev libzstd-dev liblzma-dev liblz4-dev liblzo2-dev
|
||||||
- name: Build normal version
|
- name: Build normal version
|
||||||
run: zig build -Drelease=true -Dversion=${{ github.ref_name }}
|
run: zig build --release=fast -Dversion=${{ github.ref_name }}
|
||||||
- name: Move normal build out
|
- name: Move normal build out
|
||||||
run: mv zig-out/bin/unsquashfs ./
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-zig-libs
|
||||||
- name: Rebuild with C libraries
|
- name: Rebuild with C libraries
|
||||||
run: zig build -Drelease=true -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
|
run: zig build --release=fast -Duse_c_libs=true -Dversion="${{ github.ref_name }}"
|
||||||
- name: Move C build out
|
- name: Move C build out
|
||||||
run: mv zig-out/bin/unsquashfs ./unsquashfs-c-libs
|
run: mv zig-out/bin/unsquashfs ./unsquashfs-x86_64-c-libs
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
files: |
|
files: |
|
||||||
unsquashfs
|
unsquashfs-x86_64-zig-libs
|
||||||
unsquashfs-c-libs
|
unsquashfs-x86_64-c-libs
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "extern/zstd"]
|
||||||
|
path = extern/zstd
|
||||||
|
url = https://github.com/facebook/zstd
|
||||||
@@ -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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -6,19 +6,23 @@ A library and application to decompress or view squashfs archives.
|
|||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
Overall works, but currently is missing some features (see below). Extraction is a bit slow compared to the normal `unsquashfs` (from my _very_ basic testing it's about ~3x slower). Only properly work on Linux, any other OSes probably won't work fully and are untested.
|
Overall works, but currently is missing some features ([see below](#capabilities)) and has significantly slow performance compared to `unsquashfs` ([see below](#performance)).
|
||||||
|
|
||||||
## Build options
|
## Build options
|
||||||
|
|
||||||
> `-Duse_c_libs`
|
> `-Duse_c_libs=true`
|
||||||
|
|
||||||
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
Instead of using Zig's standard library for decompression, use the system's C libraries. Has the benefit of being much faster and enabling LZO and LZ4 decompression.
|
||||||
|
|
||||||
> `-Dallow_lzo`
|
> `-Dallow_lzo=true`
|
||||||
|
|
||||||
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
Enable compiling with LZO decompression support. The LZO library currently has some issues with Zig when imported so it's easier to just disable it by default. Only has an effect when using `-Duse_c_libs=true`.
|
||||||
|
|
||||||
> `-Dversion`
|
> `-Ddebug=true`
|
||||||
|
|
||||||
|
Sets various build options that make debugging easier. Specifically, debug optimization is forced, valgrind support is enabled, error tracing is enabled, stipping is disabled, and copmilation uses LLVM (this is due to some linking issues when on Debug optimization and is required for debugging tools such as `lldb`. In the future this may be removed from the debug flag).
|
||||||
|
|
||||||
|
> `-Dversion=0.0.0`
|
||||||
|
|
||||||
Sets the version of `unsquashfs` shown when `--version` is passed.
|
Sets the version of `unsquashfs` shown when `--version` is passed.
|
||||||
|
|
||||||
@@ -26,11 +30,29 @@ Sets the version of `unsquashfs` shown when `--version` is passed.
|
|||||||
|
|
||||||
Most features are present except for the following:
|
Most features are present except for the following:
|
||||||
|
|
||||||
* mod_time is not set on extraction
|
|
||||||
* xattrs are not applied on extraction
|
|
||||||
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
* When using Zig decompression libraries then lzo and lz4 compression types are unavailable. I don't _currently_ plan on spending the time to find and validate a library since neither is popular.
|
||||||
|
* When using C decompression libraries, lzo is not supported by default due to [some issues](#build-considerations). If it's needed it's trivial to fix, but it's easiest to just leave it disabled.
|
||||||
|
|
||||||
## Building considerations
|
## Performance
|
||||||
|
|
||||||
|
This is some basic observation's I've made about this library's performance when compared to `unsquashfs`. Unless otherwise stated, most observations were made when extracting my test archive (which is fairly small and uses zstd compression) and with `--release=fast`.
|
||||||
|
|
||||||
|
Currently, my only performance checks are checking execution time, nothing deeper.
|
||||||
|
|
||||||
|
* Under ideal circumstances, my library is ~70% slower (.12s vs .20s).
|
||||||
|
* Using Zig decompression libraries *significantly* increases decompression time by ~600%. Under ideal circumstances.
|
||||||
|
* Performance improvements/regressions will be common. I'm still learning Zig.
|
||||||
|
|
||||||
|
Example Times:
|
||||||
|
|
||||||
|
* *unsquashfs, multi-threaded*: .12s
|
||||||
|
* *unsquashfs, single-threaded*: .13s
|
||||||
|
* *C-libs, single-threaded*: .45s
|
||||||
|
* *C-libs, multi-threaded*: .20s
|
||||||
|
* *Zig-libs, single-threaded*: 5.78s
|
||||||
|
* *Zig-libs, multi-threaded*: 1.08s
|
||||||
|
|
||||||
|
## Build considerations
|
||||||
|
|
||||||
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
|
Compilation without `use_c_libs` works completely fine, but Zig has issues with some symbols from the lzo library that needs to be manually fixed. In particular you need to fix the definitions for `lzo_bytep` and `lzo_voidp` to be `*u8` and `?*anyopaque` respectively. Due to this, you have to manually enable LZO decompression using `-Dallow_lzo=true` when building.
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,44 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Compile = std.Build.Step.Compile;
|
||||||
|
const ResolvedTarget = std.Build.ResolvedTarget;
|
||||||
|
const OptimizeMode = std.builtin.OptimizeMode;
|
||||||
|
const Module = std.Build.Module;
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const use_c_libs_option = b.option(bool, "use_c_libs", "Use C versions of decompression libraries instead of the Zig standard library ones");
|
const use_zig_decomp = b.option(bool, "use_zig_decomp", "Use Zig standard library for decompression instead of C libraries.") orelse false;
|
||||||
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support");
|
const allow_lzo = b.option(bool, "allow_lzo", "Compile with lzo support") orelse false;
|
||||||
|
const debug = b.option(bool, "debug", "Enable options to make debugging easier.");
|
||||||
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
const version_string_option = b.option([]const u8, "version", "Version of the library/binary");
|
||||||
|
|
||||||
const zig_squashfs_options = b.addOptions();
|
const zig_squashfs_options = b.addOptions();
|
||||||
zig_squashfs_options.addOption(bool, "use_c_libs", use_c_libs_option orelse false);
|
zig_squashfs_options.addOption(bool, "use_zig_decomp", use_zig_decomp);
|
||||||
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo orelse false);
|
zig_squashfs_options.addOption(bool, "allow_lzo", allow_lzo);
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
const mod = b.addModule("zig_squashfs", .{
|
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,
|
||||||
.link_libc = if (use_c_libs_option == true) true else false,
|
.link_libc = !use_zig_decomp,
|
||||||
|
.valgrind = debug,
|
||||||
|
.error_tracing = debug,
|
||||||
|
.strip = if (debug == true) false else null,
|
||||||
});
|
});
|
||||||
mod.addOptions("config", zig_squashfs_options);
|
mod.addOptions("config", zig_squashfs_options);
|
||||||
if (use_c_libs_option == true) {
|
|
||||||
mod.linkSystemLibrary("zlib", .{});
|
if (!use_zig_decomp) {
|
||||||
mod.linkSystemLibrary("lzma", .{});
|
var zlib = b.dependency("zlib_ng", .{});
|
||||||
|
mod.linkLibrary(zlib.artifact("zng"));
|
||||||
|
|
||||||
|
mod.linkSystemLibrary("lzma", .{ .preferred_link_mode = .static });
|
||||||
if (allow_lzo == true)
|
if (allow_lzo == true)
|
||||||
mod.linkSystemLibrary("minilzo", .{});
|
mod.linkSystemLibrary("minilzo", .{ .preferred_link_mode = .static });
|
||||||
mod.linkSystemLibrary("lz4", .{});
|
mod.linkSystemLibrary("lz4", .{ .preferred_link_mode = .static });
|
||||||
mod.linkSystemLibrary("zstd", .{});
|
|
||||||
|
const zstd_lib = buildZstdLibrary(b, target, optimize, debug);
|
||||||
|
mod.linkLibrary(zstd_lib);
|
||||||
|
mod.addIncludePath(b.path("extern/zstd/lib/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = version_string_option orelse "0.0.0-testing";
|
var version = version_string_option orelse "0.0.0-testing";
|
||||||
@@ -39,32 +53,31 @@ pub fn build(b: *std.Build) !void {
|
|||||||
var exe_mod = b.createModule(.{
|
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 = if (use_c_libs_option == true) true else false,
|
.link_libc = !use_zig_decomp,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "zig_squashfs", .module = mod },
|
.{ .name = "zig_squashfs", .module = mod },
|
||||||
},
|
},
|
||||||
|
.valgrind = debug,
|
||||||
|
.error_tracing = debug,
|
||||||
|
.strip = if (debug == true) false else null,
|
||||||
});
|
});
|
||||||
exe_mod.addOptions("config", unsquashfs_options);
|
exe_mod.addOptions("config", unsquashfs_options);
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "unsquashfs",
|
.name = "unsquashfs",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
const lib = b.addLibrary(.{
|
const lib = b.addLibrary(.{
|
||||||
.name = "squashfs",
|
.name = "squashfs",
|
||||||
.root_module = mod,
|
.root_module = mod,
|
||||||
|
.use_llvm = debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
const run_step = b.step("run", "Run the app");
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
|
||||||
run_step.dependOn(&run_cmd.step);
|
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
|
||||||
if (b.args) |args| {
|
|
||||||
run_cmd.addArgs(args);
|
|
||||||
}
|
|
||||||
const mod_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
.root_module = mod,
|
.root_module = mod,
|
||||||
});
|
});
|
||||||
@@ -76,4 +89,75 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const test_step = b.step("test", "Run tests");
|
const test_step = b.step("test", "Run tests");
|
||||||
test_step.dependOn(&run_mod_tests.step);
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
test_step.dependOn(&run_exe_tests.step);
|
test_step.dependOn(&run_exe_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,
|
||||||
|
});
|
||||||
|
const check = b.step("check", "Check if unsquashfs compiles");
|
||||||
|
check.dependOn(&lib_check.step);
|
||||||
|
check.dependOn(&exe_check.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buildZstdLibrary(b: *std.Build, target: ResolvedTarget, optimize: OptimizeMode, debug: ?bool) *Compile {
|
||||||
|
var zstd_lib = b.addLibrary(.{
|
||||||
|
.name = "zstd",
|
||||||
|
.linkage = .static,
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = if (debug == true) .Debug else optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
}),
|
||||||
|
.use_llvm = debug,
|
||||||
|
});
|
||||||
|
zstd_lib.root_module.addCSourceFiles(.{
|
||||||
|
.root = b.path("extern/zstd/lib/"),
|
||||||
|
.files = &.{
|
||||||
|
"common/debug.c",
|
||||||
|
"common/entropy_common.c",
|
||||||
|
"common/error_private.c",
|
||||||
|
"common/fse_decompress.c",
|
||||||
|
"common/pool.c",
|
||||||
|
"common/threading.c",
|
||||||
|
"common/xxhash.c",
|
||||||
|
"common/zstd_common.c",
|
||||||
|
"compress/fse_compress.c",
|
||||||
|
"compress/hist.c",
|
||||||
|
"compress/huf_compress.c",
|
||||||
|
"compress/zstd_compress.c",
|
||||||
|
"compress/zstd_compress_literals.c",
|
||||||
|
"compress/zstd_compress_sequences.c",
|
||||||
|
"compress/zstd_compress_superblock.c",
|
||||||
|
"compress/zstd_double_fast.c",
|
||||||
|
"compress/zstd_fast.c",
|
||||||
|
"compress/zstd_lazy.c",
|
||||||
|
"compress/zstd_ldm.c",
|
||||||
|
"compress/zstdmt_compress.c",
|
||||||
|
"compress/zstd_opt.c",
|
||||||
|
"compress/zstd_preSplit.c",
|
||||||
|
"decompress/huf_decompress.c",
|
||||||
|
"decompress/zstd_ddict.c",
|
||||||
|
"decompress/zstd_decompress_block.c",
|
||||||
|
"decompress/zstd_decompress.c",
|
||||||
|
"dictBuilder/cover.c",
|
||||||
|
"dictBuilder/divsufsort.c",
|
||||||
|
"dictBuilder/fastcover.c",
|
||||||
|
"dictBuilder/zdict.c",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
zstd_lib.root_module.addCSourceFiles(.{
|
||||||
|
.root = b.path("extern/zstd/lib/decompress"),
|
||||||
|
.files = &.{"huf_decompress_amd64.S"},
|
||||||
|
});
|
||||||
|
zstd_lib.installHeadersDirectory(b.path("extern/zstd/lib/"), &.{}, .{});
|
||||||
|
zstd_lib.installHeadersDirectory(b.path("extern/zstd/lib/common/"), &.{}, .{});
|
||||||
|
zstd_lib.installHeadersDirectory(b.path("extern/zstd/lib/compress/"), &.{}, .{});
|
||||||
|
zstd_lib.installHeadersDirectory(b.path("extern/zstd/lib/dictBuilder/"), &.{}, .{});
|
||||||
|
zstd_lib.installHeadersDirectory(b.path("extern/zstd/lib/"), &.{}, .{});
|
||||||
|
return zstd_lib;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-35
@@ -1,43 +1,14 @@
|
|||||||
.{
|
.{
|
||||||
.name = .squashfs,
|
.name = .squashfs,
|
||||||
.version = "0.0.1",
|
.version = "0.0.6",
|
||||||
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
.fingerprint = 0x37ba29474b87f145, // Changing this has security and trust implications.
|
||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
// This field is optional.
|
|
||||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
|
||||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
|
||||||
// Once all dependencies are fetched, `zig build` no longer requires
|
|
||||||
// internet connectivity.
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
.zlib_ng = .{
|
||||||
//.example = .{
|
// .url = "https://github.com/CalebQ42/zig-zlib-ng/archive/refs/tags/2.3.3.tar.gz",
|
||||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
// .hash = "zlib_ng-2.3.3-2HYS4Bw_AADjgv7tkrqjjz2fVz5kRTvha8wN9LEcjYNp",
|
||||||
// // `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.
|
|
||||||
// .url = "https://example.com/foo.tar.gz",
|
|
||||||
//
|
|
||||||
// // 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
|
|
||||||
// // `paths`.
|
|
||||||
// //
|
|
||||||
// // 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
|
|
||||||
// // obtain a package matching this `hash`.
|
|
||||||
// //
|
|
||||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
|
||||||
// .hash = "...",
|
|
||||||
//
|
|
||||||
// // When this is provided, the package is found in a directory relative to the
|
|
||||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
|
||||||
// // computed. This field and `url` are mutually exclusive.
|
|
||||||
// .path = "foo",
|
|
||||||
//
|
|
||||||
// // When this is set to `true`, a package is declared to be lazily
|
|
||||||
// // fetched. This makes the dependency only get fetched if it is
|
|
||||||
// // actually used.
|
|
||||||
// .lazy = false,
|
|
||||||
//},
|
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
+1
Submodule extern/zstd added at f8745da6ff
+36
-107
@@ -12,157 +12,86 @@ const InodeRef = Inode.Ref;
|
|||||||
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
||||||
const SfsFile = @import("file.zig");
|
const SfsFile = @import("file.zig");
|
||||||
const Superblock = @import("super.zig").Superblock;
|
const Superblock = @import("super.zig").Superblock;
|
||||||
const Table = @import("table.zig").Table;
|
const Tables = @import("tables.zig");
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
const XattrTable = @import("xattr.zig");
|
||||||
|
|
||||||
const config = if (builtin.is_test) .{
|
const config = if (builtin.is_test) .{
|
||||||
.use_c_libs = true,
|
.use_zig_decomp = builtin.link_libc != true,
|
||||||
.allow_lzo = false,
|
.allow_lzo = false,
|
||||||
} else @import("config");
|
} else @import("config");
|
||||||
|
|
||||||
/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry.
|
|
||||||
/// The offset into the block and fragment size is stored in the file's inode.
|
|
||||||
pub const FragEntry = packed struct {
|
|
||||||
start: u64,
|
|
||||||
size: BlockSize,
|
|
||||||
_: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Archive = @This();
|
const Archive = @This();
|
||||||
|
|
||||||
// 4 Gigs
|
alloc: std.mem.Allocator,
|
||||||
const DEFAULT_MEM_SIZE = 4 * 1024 * 1024 * 1024;
|
|
||||||
|
|
||||||
parent_alloc: std.mem.Allocator,
|
|
||||||
alloc: std.heap.ThreadSafeAllocator,
|
|
||||||
// alloc: std.heap.FixedBufferAllocator,
|
|
||||||
// fixed_buf: []u8,
|
|
||||||
thread_count: usize,
|
|
||||||
|
|
||||||
fil: OffsetFile,
|
fil: OffsetFile,
|
||||||
|
decomp: Decomp.DecompFn,
|
||||||
|
|
||||||
super: Superblock,
|
super: Superblock,
|
||||||
|
|
||||||
setup: bool = false,
|
tables: ?Tables = null,
|
||||||
|
|
||||||
decomp: Decomp.DecompFn,
|
|
||||||
|
|
||||||
frag_table: Table(FragEntry) = undefined,
|
|
||||||
id_table: Table(u16) = undefined,
|
|
||||||
export_table: Table(InodeRef) = undefined,
|
|
||||||
|
|
||||||
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
/// Default settings using std.Thread.getCpuCount() threads and the minimum of 4gb or half of system memory for memory usage.
|
||||||
pub fn init(alloc: std.mem.Allocator, fil: File) !Archive {
|
pub fn init(alloc: std.mem.Allocator, fil: File, offset: u64) !Archive {
|
||||||
return initAdvanced(
|
|
||||||
alloc,
|
|
||||||
fil,
|
|
||||||
0,
|
|
||||||
try std.Thread.getCpuCount(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/// Create the Archive dictating the amount of threads used for extraction.
|
|
||||||
/// If you're planning on only interacting with a small number of files, it should be fine to use few (or one) threads.
|
|
||||||
pub fn initAdvanced(alloc: std.mem.Allocator, fil: File, offset: u64, threads: usize) !Archive {
|
|
||||||
var super: Superblock = undefined;
|
var super: Superblock = undefined;
|
||||||
const red = try fil.pread(@ptrCast(&super), offset);
|
const red = try fil.pread(@ptrCast(&super), offset);
|
||||||
std.debug.assert(red == @sizeOf(Superblock));
|
std.debug.assert(red == @sizeOf(Superblock));
|
||||||
try super.validate();
|
try super.validate();
|
||||||
// const fixed_buf = try alloc.alloc(u8, mem);
|
const off_fil: OffsetFile = .init(fil, offset);
|
||||||
return .{
|
const decomp: Decomp.DecompFn = switch (super.compression) {
|
||||||
.parent_alloc = alloc,
|
|
||||||
.alloc = .{ .child_allocator = alloc },
|
|
||||||
// .fixed_buf = fixed_buf,
|
|
||||||
.thread_count = if (threads > 0) threads else try std.Thread.getCpuCount(),
|
|
||||||
.fil = .init(fil, offset),
|
|
||||||
.decomp = switch (super.compression) {
|
|
||||||
.gzip => Decomp.gzipDecompress,
|
.gzip => Decomp.gzipDecompress,
|
||||||
.lzma => Decomp.lzmaDecompress,
|
.lzma => Decomp.lzmaDecompress,
|
||||||
.xz => Decomp.xzDecompress,
|
.xz => Decomp.xzDecompress,
|
||||||
.zstd => Decomp.zstdDecompress,
|
.zstd => Decomp.zstdDecompress,
|
||||||
.lz4 => if (config.use_c_libs) Decomp.cLz4 else return error.Lz4Unsupported,
|
.lz4 => if (!config.use_zig_decomp) Decomp.cLz4 else return error.Lz4Unsupported,
|
||||||
.lzo => if (config.use_c_libs and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
.lzo => if (!config.use_zig_decomp and config.allow_lzo) Decomp.lzoDecompress else return error.LzoUnsupported,
|
||||||
},
|
};
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.fil = off_fil,
|
||||||
|
.decomp = decomp,
|
||||||
.super = super,
|
.super = super,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn deinit(self: *Archive) void {
|
pub fn deinit(self: *Archive) void {
|
||||||
// self.parent_alloc.free(self.fixed_buf);
|
if (self.tables != null)
|
||||||
if (self.setup) {
|
self.tables.?.deinit();
|
||||||
self.frag_table.deinit();
|
|
||||||
self.export_table.deinit();
|
|
||||||
self.id_table.deinit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocator(self: *Archive) std.mem.Allocator {
|
pub fn inode(self: *Archive, alloc: std.mem.Allocator, num: u32) !Inode {
|
||||||
return self.alloc.allocator();
|
if (self.tables == null)
|
||||||
}
|
self.tables = try .init(alloc, self);
|
||||||
|
|
||||||
fn setupValues(self: *Archive) !void {
|
|
||||||
const alloc = self.allocator();
|
|
||||||
self.frag_table = try .init(alloc, self.fil, self.decomp, self.super.frag_start, self.super.frag_count);
|
|
||||||
self.id_table = try .init(alloc, self.fil, self.decomp, self.super.id_start, self.super.id_count);
|
|
||||||
self.export_table = try .init(alloc, self.fil, self.decomp, self.super.export_start, self.super.inode_count);
|
|
||||||
self.setup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(self: *Archive, idx: u32) !u16 {
|
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
return self.id_table.get(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn frag(self: *Archive, idx: u32) !FragEntry {
|
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
return self.frag_table.get(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inode(self: *Archive, num: u32) !Inode {
|
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
const ref = try self.export_table.get(num - 1);
|
const ref = try self.export_table.get(num - 1);
|
||||||
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
var rdr = try self.fil.readerAt(ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, &self.decomp);
|
var meta: MetadataReader = .init(alloc, &rdr.interface, &self.decomp);
|
||||||
try meta.interface.discardAll(ref.block_offset);
|
try meta.interface.discardAll(ref.block_offset);
|
||||||
return try .read(self.allocator(), &meta.interface, self.super.block_size);
|
return try .read(alloc, &meta.interface, self.super.block_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root(self: *Archive) !SfsFile {
|
pub fn root(self: *Archive, alloc: std.mem.Allocator) !SfsFile {
|
||||||
if (!self.setup) try self.setupValues();
|
if (self.tables == null)
|
||||||
|
self.tables = try .init(alloc, self);
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
const in: Inode = try .read(alloc, &meta.interface, self.super.block_size);
|
||||||
return .init(self, in, "");
|
return .init(self, in, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(self: *Archive, path: []const u8) !SfsFile {
|
pub fn open(self: *Archive, alloc: std.mem.Allocator, path: []const u8) !SfsFile {
|
||||||
if (!self.setup) try self.setupValues();
|
if (self.tables == null)
|
||||||
var root_fil = try self.root();
|
self.tables = try .init(alloc, self);
|
||||||
|
var root_fil = try self.root(alloc);
|
||||||
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
defer if (!SfsFile.pathIsSelf(path)) root_fil.deinit();
|
||||||
return root_fil.open(path);
|
return root_fil.open(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract(self: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
pub fn extract(self: Archive, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||||
if (!self.setup) try self.setupValues();
|
|
||||||
var alloc = self.allocator();
|
|
||||||
var ext_path: []u8 = undefined;
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind == .directory) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else return error.ExtractionPathExists;
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else {
|
|
||||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
||||||
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
var rdr = try self.fil.readerAt(self.super.root_ref.block_start + self.super.inode_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(self.allocator(), &rdr.interface, self.decomp);
|
var meta: MetadataReader = .init(self.alloc, &rdr.interface, self.decomp);
|
||||||
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
try meta.interface.discardAll(self.super.root_ref.block_offset);
|
||||||
const in: Inode = try .read(self.allocator(), &meta.interface, self.super.block_size);
|
const in: Inode = try .read(self.alloc, &meta.interface, self.super.block_size);
|
||||||
try in.extractToThreaded(self, ext_path, options, self.thread_count);
|
try in.extractTo(alloc, self, path, options);
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-2
@@ -14,8 +14,13 @@ const help_mgs =
|
|||||||
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
\\ -d <location> Extract to the given location instead of "squashfs-root"
|
||||||
\\
|
\\
|
||||||
\\ -o <offset> Start reading the archive at the given offset.
|
\\ -o <offset> Start reading the archive at the given offset.
|
||||||
|
\\ -dx Don't set xattr values
|
||||||
|
\\ -dp Don't set permissions (includes setting uid & gid owner)
|
||||||
\\
|
\\
|
||||||
\\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
|
\\ -p <threads> Specify how many threads to use. If no present or zero, the system's logical cores count is used.
|
||||||
|
\\ -v Verbose
|
||||||
|
\\
|
||||||
|
\\ --force Force extraction. If the destination already exists, it will be deleted.
|
||||||
\\
|
\\
|
||||||
\\ --help Display this messages
|
\\ --help Display this messages
|
||||||
\\ --version Display the version
|
\\ --version Display the version
|
||||||
@@ -28,6 +33,10 @@ var archive: []const u8 = "";
|
|||||||
var extLoc: []const u8 = "squashfs-root";
|
var extLoc: []const u8 = "squashfs-root";
|
||||||
var offset: u64 = 0;
|
var offset: u64 = 0;
|
||||||
var threads: u32 = 0;
|
var threads: u32 = 0;
|
||||||
|
var verbose: bool = false;
|
||||||
|
var ignore_xattrs: bool = false;
|
||||||
|
var ignore_permissions: bool = false;
|
||||||
|
var force: bool = false;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const alloc = std.heap.smp_allocator;
|
const alloc = std.heap.smp_allocator;
|
||||||
@@ -42,9 +51,18 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
var fil: std.fs.File = try std.fs.cwd().openFile(archive, .{}); //TODO: Handle error gracefully.
|
||||||
defer fil.close();
|
defer fil.close();
|
||||||
var arc: squashfs.Archive = try .initAdvanced(alloc, fil, offset, threads); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
var arc: squashfs.Archive = try .init(alloc, fil, offset); //TODO: Update when memory size matters. //TODO: Handle error gracefully.
|
||||||
defer arc.deinit();
|
defer arc.deinit();
|
||||||
try arc.extract(extLoc, .Default); //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,
|
||||||
|
};
|
||||||
|
if (force)
|
||||||
|
try std.fs.cwd().deleteTree(extLoc);
|
||||||
|
try arc.extract(alloc, extLoc, options); //TODO: Handle error gracefully.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
||||||
@@ -82,6 +100,18 @@ fn handleArgs(alloc: std.mem.Allocator, out: *Writer) !void {
|
|||||||
return errors.InvalidArguments;
|
return errors.InvalidArguments;
|
||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "-v")) {
|
||||||
|
verbose = true;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "-dx")) {
|
||||||
|
ignore_xattrs = true;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "-dp")) {
|
||||||
|
ignore_permissions = true;
|
||||||
|
continue;
|
||||||
|
} else if (std.mem.eql(u8, arg, "--force")) {
|
||||||
|
force = true;
|
||||||
|
continue;
|
||||||
} else if (std.mem.eql(u8, arg, "--version")) {
|
} else if (std.mem.eql(u8, arg, "--version")) {
|
||||||
try out.print("zig-unsquashfs v", .{});
|
try out.print("zig-unsquashfs v", .{});
|
||||||
try config.version.format(out);
|
try config.version.format(out);
|
||||||
|
|||||||
+10
-9
@@ -6,12 +6,12 @@ const Reader = std.Io.Reader;
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const config = if (builtin.is_test) .{
|
const config = if (builtin.is_test) .{
|
||||||
.use_c_libs = builtin.link_libc == true,
|
.use_zig_decomp = builtin.link_libc != true,
|
||||||
.allow_lzo = false, // Change once LZO compilation is fixed
|
.allow_lzo = false, // Change once LZO compilation is fixed
|
||||||
} else @import("config");
|
} else @import("config");
|
||||||
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("zlib.h");
|
@cInclude("zlib-ng.h");
|
||||||
@cInclude("lzma.h");
|
@cInclude("lzma.h");
|
||||||
@cInclude("lz4.h");
|
@cInclude("lz4.h");
|
||||||
@cInclude("zstd.h");
|
@cInclude("zstd.h");
|
||||||
@@ -31,7 +31,7 @@ pub const CompressionType = enum(u16) {
|
|||||||
|
|
||||||
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
pub const DecompFn = *const fn (alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize; // TODO: replace anyerror to definitive error types.
|
||||||
|
|
||||||
pub const gzipDecompress = if (config.use_c_libs) cGzip else zigGzip;
|
pub const gzipDecompress = if (!config.use_zig_decomp) cGzip else zigGzip;
|
||||||
|
|
||||||
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
var rdr: Reader = .fixed(in);
|
var rdr: Reader = .fixed(in);
|
||||||
@@ -43,7 +43,7 @@ fn zigGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
_ = alloc;
|
_ = alloc;
|
||||||
var out_len: usize = out.len;
|
var out_len: usize = out.len;
|
||||||
const res = c.uncompress(out.ptr, &out_len, in.ptr, in.len);
|
const res = c.zng_uncompress(out.ptr, &out_len, in.ptr, in.len);
|
||||||
return switch (res) {
|
return switch (res) {
|
||||||
c.Z_OK => out_len,
|
c.Z_OK => out_len,
|
||||||
c.Z_MEM_ERROR => error.NotEnoughMemory,
|
c.Z_MEM_ERROR => error.NotEnoughMemory,
|
||||||
@@ -53,7 +53,7 @@ fn cGzip(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const lzmaDecompress = if (config.use_c_libs) cLzma else zigLzma;
|
pub const lzmaDecompress = if (!config.use_zig_decomp) cLzma else zigLzma;
|
||||||
|
|
||||||
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
fn zigLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
var rdr: Reader = .fixed(in);
|
var rdr: Reader = .fixed(in);
|
||||||
@@ -90,7 +90,7 @@ fn cLzma(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub const lzoDecompress = if (config.use_c_libs) cLzo else zigLzo;
|
// pub const lzoDecompress = if (!config.use_zig_decomp) cLzo else zigLzo;
|
||||||
|
|
||||||
// fn zigLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
// fn zigLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
// _ = alloc;
|
// _ = alloc;
|
||||||
@@ -123,7 +123,7 @@ pub fn cLzo(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const xzDecompress = if (config.use_c_libs) cXz else zigXz;
|
pub const xzDecompress = if (!config.use_zig_decomp) cXz else zigXz;
|
||||||
|
|
||||||
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
fn zigXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
var rdr: Reader = .fixed(in);
|
var rdr: Reader = .fixed(in);
|
||||||
@@ -137,6 +137,7 @@ fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
.avail_in = in.len,
|
.avail_in = in.len,
|
||||||
.next_out = out.ptr,
|
.next_out = out.ptr,
|
||||||
.avail_out = out.len,
|
.avail_out = out.len,
|
||||||
|
// .allocator = _, TODO: create a custom allocator based on alloc,
|
||||||
};
|
};
|
||||||
var res = c.lzma_stream_decoder(&stream, in.len * 2, 0);
|
var res = c.lzma_stream_decoder(&stream, in.len * 2, 0);
|
||||||
switch (res) {
|
switch (res) {
|
||||||
@@ -160,7 +161,7 @@ fn cXz(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub const lz4Decompress = if (config.use_c_libs) cLz4 else zigLz4;
|
// pub const lz4Decompress = if (!config.use_zig_decomp) cLz4 else zigLz4;
|
||||||
|
|
||||||
// fn zigLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
// fn zigLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
// _ = alloc;
|
// _ = alloc;
|
||||||
@@ -175,7 +176,7 @@ pub fn cLz4(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
|||||||
return error.Lz4DecompressFailed;
|
return error.Lz4DecompressFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const zstdDecompress = if (config.use_c_libs) cZstd else zigZstd;
|
pub const zstdDecompress = if (!config.use_zig_decomp) cZstd else zigZstd;
|
||||||
|
|
||||||
pub fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
pub fn zigZstd(alloc: std.mem.Allocator, in: []u8, out: []u8) anyerror!usize {
|
||||||
var rdr: Reader = .fixed(in);
|
var rdr: Reader = .fixed(in);
|
||||||
|
|||||||
+4
-32
@@ -96,7 +96,7 @@ pub fn iterate(self: SfsFile) !Iterator {
|
|||||||
}
|
}
|
||||||
/// Open a sub-file/folder within a directory at the given path.
|
/// Open a sub-file/folder within a directory at the given path.
|
||||||
/// If path is "", ".", "/", or "./", this File is returned.
|
/// If path is "", ".", "/", or "./", this File is returned.
|
||||||
pub fn open(self: SfsFile, path: []const u8) !SfsFile {
|
pub fn open(self: SfsFile, alloc: std.mem.Allocator, path: []const u8) !SfsFile {
|
||||||
if (!self.isDir()) return FileError.NotDirectory;
|
if (!self.isDir()) return FileError.NotDirectory;
|
||||||
if (pathIsSelf(path)) return self;
|
if (pathIsSelf(path)) return self;
|
||||||
|
|
||||||
@@ -109,7 +109,6 @@ pub fn open(self: SfsFile, path: []const u8) !SfsFile {
|
|||||||
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]);
|
if (std.mem.eql(u8, first_element, ".")) return self.open(path[idx + 1 ..]);
|
||||||
const entries = try self.getEntries();
|
const entries = try self.getEntries();
|
||||||
defer {
|
defer {
|
||||||
var alloc = self.archive.allocator();
|
|
||||||
for (entries) |e| {
|
for (entries) |e| {
|
||||||
e.deinit(alloc);
|
e.deinit(alloc);
|
||||||
}
|
}
|
||||||
@@ -127,7 +126,7 @@ pub fn open(self: SfsFile, path: []const u8) !SfsFile {
|
|||||||
return fil;
|
return fil;
|
||||||
}
|
}
|
||||||
defer fil.deinit();
|
defer fil.deinit();
|
||||||
return fil.open(path[idx + 1 ..]);
|
return fil.open(alloc, path[idx + 1 ..]);
|
||||||
},
|
},
|
||||||
.lt => cur_slice = cur_slice[0..split],
|
.lt => cur_slice = cur_slice[0..split],
|
||||||
.gt => cur_slice = cur_slice[split + 1 ..],
|
.gt => cur_slice = cur_slice[split + 1 ..],
|
||||||
@@ -170,35 +169,8 @@ pub fn devNum(self: SfsFile) !u32 {
|
|||||||
|
|
||||||
/// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist.
|
/// Extract the given File to the path. If File is a regular file, the path must be a directory or not exist.
|
||||||
/// If the gievn path is a folder, the File's contents will be extracted within.
|
/// If the gievn path is a folder, the File's contents will be extracted within.
|
||||||
pub fn extract(self: *SfsFile, path: []const u8, options: ExtractionOptions) !void {
|
pub fn extract(self: *SfsFile, alloc: std.mem.Allocator, path: []const u8, options: ExtractionOptions) !void {
|
||||||
var alloc = self.archive.allocator();
|
return self.inode.extractToThreaded(alloc, self.archive, path, options);
|
||||||
var ext_path: []u8 = undefined;
|
|
||||||
if (std.fs.cwd().statFile(path)) |stat| {
|
|
||||||
if (stat.kind == .directory) {
|
|
||||||
if (!self.isDir()) {
|
|
||||||
const has_end_sep = path[path.len - 1] == '/';
|
|
||||||
const alloc_size = if (has_end_sep)
|
|
||||||
path.len + self.name.len
|
|
||||||
else
|
|
||||||
path.len + self.name.len + 1;
|
|
||||||
ext_path = try alloc.alloc(u8, alloc_size);
|
|
||||||
@memcpy(ext_path[0..path.len], path);
|
|
||||||
@memcpy(ext_path[ext_path.len - self.name.len ..], self.name);
|
|
||||||
if (!has_end_sep) ext_path[path.len] = '/';
|
|
||||||
} else {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
}
|
|
||||||
} else return FileError.ExtractionPathExists;
|
|
||||||
} else |err| {
|
|
||||||
if (err == error.FileNotFound) {
|
|
||||||
ext_path = @constCast(path);
|
|
||||||
} else {
|
|
||||||
std.log.err("Error stat-ing extraction path {s}: {}\n", .{ path, err });
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer if (ext_path.len > path.len) alloc.free(ext_path);
|
|
||||||
return self.inode.extractToThreaded(self.archive, path, options, self.archive.thread_count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function.
|
/// Utility function.
|
||||||
|
|||||||
+44
-341
@@ -4,6 +4,7 @@ const std = @import("std");
|
|||||||
const Reader = std.Io.Reader;
|
const Reader = std.Io.Reader;
|
||||||
const WaitGroup = std.Thread.WaitGroup;
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
const Pool = std.Thread.Pool;
|
const Pool = std.Thread.Pool;
|
||||||
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
const Archive = @import("archive.zig");
|
const Archive = @import("archive.zig");
|
||||||
const DirEntry = @import("dir_entry.zig");
|
const DirEntry = @import("dir_entry.zig");
|
||||||
@@ -11,8 +12,12 @@ const ExtractionOptions = @import("options.zig");
|
|||||||
const dir = @import("inode_data/dir.zig");
|
const dir = @import("inode_data/dir.zig");
|
||||||
const file = @import("inode_data/file.zig");
|
const file = @import("inode_data/file.zig");
|
||||||
const misc = @import("inode_data/misc.zig");
|
const misc = @import("inode_data/misc.zig");
|
||||||
|
const Tables = @import("tables.zig");
|
||||||
const DataReader = @import("util/data.zig");
|
const DataReader = @import("util/data.zig");
|
||||||
const ThreadedDataReader = @import("util/data_threaded.zig");
|
const ThreadedDataReader = @import("util/data_threaded.zig");
|
||||||
|
const InodeExtract = @import("util/extract.zig");
|
||||||
|
const InodeFinish = @import("util/inode_finish.zig");
|
||||||
|
const FinishUnion = InodeFinish.FinishUnion;
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
|
||||||
pub const Ref = packed struct {
|
pub const Ref = packed struct {
|
||||||
@@ -92,7 +97,7 @@ pub fn read(alloc: std.mem.Allocator, rdr: *Reader, block_size: u32) !Inode {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn readFromEntry(alloc: std.mem.Allocator, archive: *Archive, entry: DirEntry) !Inode {
|
pub fn readFromEntry(alloc: std.mem.Allocator, archive: Archive, entry: DirEntry) !Inode {
|
||||||
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
var rdr = try archive.fil.readerAt(archive.super.inode_start + entry.block_start, &[0]u8{});
|
||||||
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
var meta: MetadataReader = .init(alloc, &rdr.interface, archive.decomp);
|
||||||
try meta.interface.discardAll(entry.block_offset);
|
try meta.interface.discardAll(entry.block_offset);
|
||||||
@@ -110,31 +115,17 @@ pub fn deinit(self: Inode, alloc: std.mem.Allocator) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data reader for a file inode.
|
/// Get the data reader for a file inode.
|
||||||
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !DataReader {
|
pub fn dataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive, tables: *Tables) !DataReader {
|
||||||
return switch (self.hdr.inode_type) {
|
return switch (self.hdr.inode_type) {
|
||||||
.file => readerFromData(alloc, archive, self.data.file),
|
.file => readerFromData(alloc, archive, tables, self.data.file),
|
||||||
.ext_file => readerFromData(alloc, archive, self.data.ext_file),
|
.ext_file => readerFromData(alloc, archive, tables, self.data.ext_file),
|
||||||
else => error.NotRegularFile,
|
else => error.NotRegularFile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn readerFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !DataReader {
|
fn readerFromData(alloc: std.mem.Allocator, archive: Archive, tables: *Tables, data: anytype) !DataReader {
|
||||||
var out: DataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
var out: DataReader = .init(alloc, archive, data.block_sizes, data.block_start, data.size);
|
||||||
if (data.frag_idx != 0xFFFFFFFF)
|
if (data.frag_idx != 0xFFFFFFFF)
|
||||||
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
out.addFragment(try tables.frag_table.get(data.frag_idx), data.frag_block_offset);
|
||||||
return out;
|
|
||||||
}
|
|
||||||
/// Get a threaded data reader for a file inode.
|
|
||||||
pub fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: *Archive) !ThreadedDataReader {
|
|
||||||
return switch (self.hdr.inode_type) {
|
|
||||||
.file => threadedReaderFromData(alloc, archive, self.data.file),
|
|
||||||
.ext_file => threadedReaderFromData(alloc, archive, self.data.ext_file),
|
|
||||||
else => error.NotRegularFile,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: *Archive, data: anytype) !ThreadedDataReader {
|
|
||||||
var out: ThreadedDataReader = .init(alloc, archive.*, data.block_sizes, data.block_start, data.size);
|
|
||||||
if (data.frag_idx != 0xFFFFFFFF)
|
|
||||||
out.addFragment(try archive.frag(data.frag_idx), data.frag_block_offset);
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,334 +144,46 @@ fn entriesFromData(alloc: std.mem.Allocator, archive: Archive, data: anytype) ![
|
|||||||
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
return DirEntry.readDir(alloc, &meta.interface, data.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the inode to the given path. Single threaded.
|
/// Returns the xattr index for the given inode. If the inode isn't an extended variant or doesn't have any, the u32 max is returned (0xFFFFFFFF).
|
||||||
pub fn extractTo(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
pub fn xattrIdx(self: Inode) u32 {
|
||||||
switch (self.hdr.inode_type) {
|
return switch (self.data) {
|
||||||
.dir, .ext_dir => {
|
.ext_dir => |d| d.xattr_id,
|
||||||
// Removing any trailing separators since that's the easiest path forward.
|
.ext_file => |f| f.xattr_idx,
|
||||||
if (path[path.len - 1] == '/') return self.extractTo(archive, path[0 .. path.len - 1], options);
|
.ext_symlink => |s| s.xattr_idx,
|
||||||
std.fs.cwd().makeDir(path) catch |err| {
|
.ext_block_dev, .ext_char_dev => |d| d.xattr_idx,
|
||||||
if (err != std.fs.Dir.MakeError.PathAlreadyExists) return err;
|
.ext_fifo, .ext_socket => |i| i.xattr_idx,
|
||||||
|
else => 0xFFFFFFFF,
|
||||||
};
|
};
|
||||||
var alloc = archive.allocator();
|
|
||||||
const entries = try self.dirEntries(alloc, archive.*);
|
|
||||||
defer {
|
|
||||||
for (entries) |entry| entry.deinit(alloc);
|
|
||||||
alloc.free(entries);
|
|
||||||
}
|
|
||||||
for (entries) |entry| {
|
|
||||||
var new_path = try alloc.alloc(u8, path.len + 1 + entry.name.len);
|
|
||||||
@memcpy(new_path[0..path.len], path);
|
|
||||||
@memcpy(new_path[path.len + 1 ..], entry.name);
|
|
||||||
new_path[path.len] = '/';
|
|
||||||
defer alloc.free(new_path);
|
|
||||||
|
|
||||||
var inode: Inode = try readFromEntry(alloc, archive, entry);
|
|
||||||
defer inode.deinit(alloc);
|
|
||||||
try inode.extractTo(archive, new_path, options);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.file, .ext_file => try self.extractRegFile(archive.allocator(), archive, path, options),
|
|
||||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
|
||||||
else => try self.extractDevice(archive, path, options),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Perms = struct {
|
/// Applies the Inode's metadata to the given File.
|
||||||
path: []const u8,
|
/// Mod time is always set, but permissions and xattrs are set based on the given ExtractionOptions.
|
||||||
uid: u16,
|
pub fn setMetadata(self: Inode, alloc: std.mem.Allocator, tables: *Tables, fil: std.fs.File, options: ExtractionOptions) !void {
|
||||||
gid: u16,
|
|
||||||
perm: u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Extract the inode to the given path. Multi-threaded.
|
|
||||||
/// Functions identically to extractTo on all but regular files and directories.
|
|
||||||
///
|
|
||||||
/// If threads <= 1, then this just calls extractTo.
|
|
||||||
pub fn extractToThreaded(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions, threads: usize) !void {
|
|
||||||
if (threads <= 1) return self.extractTo(archive, path, options);
|
|
||||||
switch (self.hdr.inode_type) {
|
|
||||||
.dir, .ext_dir => {
|
|
||||||
// Removing any trailing separators since that's the easiest path forward.
|
|
||||||
if (path[path.len - 1] == '/') return self.extractToThreaded(archive, path[0 .. path.len - 1], options, threads);
|
|
||||||
|
|
||||||
// Fixed Allocator
|
|
||||||
// const mem_buf = archive.allocator().alloc(u8, 2 * 1024 * 1024 * 1024);
|
|
||||||
// defer archive.allocator().free(mem_buf);
|
|
||||||
// var fixed_alloc: std.heap.FixedBufferAllocator = .init(mem_buf);
|
|
||||||
// const alloc = fixed_alloc.threadSafeAllocator();
|
|
||||||
|
|
||||||
// Arena Allocator
|
|
||||||
var arena_alloc: std.heap.ArenaAllocator = .init(archive.allocator());
|
|
||||||
defer arena_alloc.deinit();
|
|
||||||
var thread_alloc: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena_alloc.allocator() };
|
|
||||||
const alloc = thread_alloc.allocator();
|
|
||||||
|
|
||||||
var wg: WaitGroup = .{};
|
|
||||||
var perms: ?std.ArrayList(Perms) = if (options.ignore_permissions) null else try .initCapacity(alloc, 100);
|
|
||||||
// defer if(!options.ignore_permissions) perms.?.deinit(alloc); We don't need to do this due to ArenaAllocator
|
|
||||||
var pool: Pool = undefined;
|
|
||||||
try pool.init(.{ .allocator = alloc, .n_jobs = threads - 1 });
|
|
||||||
defer pool.deinit();
|
|
||||||
var out_err: ?anyerror = null;
|
|
||||||
|
|
||||||
wg.start();
|
|
||||||
self.extractThread(alloc, archive, path, options, &wg, &pool, &out_err, &perms);
|
|
||||||
pool.waitAndWork(&wg);
|
|
||||||
if (out_err != null) return out_err.?;
|
|
||||||
|
|
||||||
if (perms != null) {
|
|
||||||
for (perms.?.items) |p| {
|
|
||||||
var fil = try std.fs.cwd().openFile(p.path, .{});
|
|
||||||
defer fil.close();
|
|
||||||
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
|
||||||
try fil.updateTimes(time, time);
|
|
||||||
try fil.chmod(p.perm);
|
|
||||||
try fil.chown(p.uid, p.gid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.file, .ext_file => {
|
|
||||||
const alloc = archive.allocator();
|
|
||||||
|
|
||||||
var pool: Pool = undefined;
|
|
||||||
try pool.init(.{ .allocator = alloc, .n_jobs = threads });
|
|
||||||
defer pool.deinit();
|
|
||||||
|
|
||||||
try self.extractRegFileThreaded(alloc, archive, path, options, &pool);
|
|
||||||
|
|
||||||
var fil = try std.fs.cwd().openFile(path, .{});
|
|
||||||
defer fil.close();
|
|
||||||
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
||||||
try fil.updateTimes(time, time);
|
try fil.updateTimes(time, time);
|
||||||
if (!options.ignore_permissions) {
|
if (!options.ignore_permissions) {
|
||||||
try fil.chmod(self.hdr.permissions);
|
try fil.chmod(self.hdr.permissions);
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
try fil.chown(try tables.id_table.get(self.hdr.uid_idx), try tables.id_table.get(self.hdr.gid_idx));
|
||||||
}
|
|
||||||
},
|
|
||||||
.symlink, .ext_symlink => try self.extractSymlink(path),
|
|
||||||
else => try self.extractDevice(archive, path, options),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extractThreadEntry(
|
|
||||||
entry: DirEntry,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
archive: *Archive,
|
|
||||||
path: []const u8,
|
|
||||||
options: ExtractionOptions,
|
|
||||||
wg: *WaitGroup,
|
|
||||||
pool: *Pool,
|
|
||||||
out_err: *?anyerror,
|
|
||||||
perms: *?std.ArrayList(Perms),
|
|
||||||
) void {
|
|
||||||
var new_path = alloc.alloc(u8, path.len + entry.name.len + 1) catch |err| {
|
|
||||||
wg.finish();
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
@memcpy(new_path[0..path.len], path);
|
|
||||||
@memcpy(new_path[path.len + 1 ..], entry.name);
|
|
||||||
new_path[path.len] = '/';
|
|
||||||
var inode = readFromEntry(alloc, archive, entry) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
wg.finish();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
inode.extractThread(alloc, archive, new_path, options, wg, pool, out_err, perms);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract threadedly the inode to the path.
|
|
||||||
fn extractThread(
|
|
||||||
self: Inode,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
archive: *Archive,
|
|
||||||
path: []const u8,
|
|
||||||
options: ExtractionOptions,
|
|
||||||
wg: *WaitGroup,
|
|
||||||
pool: *Pool,
|
|
||||||
out_err: *?anyerror,
|
|
||||||
perms: *?std.ArrayList(Perms),
|
|
||||||
) void {
|
|
||||||
defer wg.finish();
|
|
||||||
if (out_err.* != null) return;
|
|
||||||
switch (self.hdr.inode_type) {
|
|
||||||
.dir, .ext_dir => {
|
|
||||||
_ = std.fs.cwd().makePathStatus(path) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const entries = self.dirEntries(alloc, archive.*) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
wg.startMany(entries.len);
|
|
||||||
// defer files.deinit(alloc); We don't need to do this due to ArenaAllocator
|
|
||||||
for (entries) |entry| {
|
|
||||||
if (entry.inode_type == .dir) {
|
|
||||||
extractThreadEntry(entry, alloc, archive, path, options, wg, pool, out_err, perms);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pool.spawn(
|
|
||||||
extractThreadEntry,
|
|
||||||
.{
|
|
||||||
entry,
|
|
||||||
alloc,
|
|
||||||
archive,
|
|
||||||
path,
|
|
||||||
options,
|
|
||||||
wg,
|
|
||||||
pool,
|
|
||||||
out_err,
|
|
||||||
perms,
|
|
||||||
},
|
|
||||||
) catch |err| {
|
|
||||||
wg.finish();
|
|
||||||
out_err.* = err;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!options.ignore_permissions) {
|
|
||||||
const new_val = perms.*.?.addOne(alloc) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
new_val.* = .{
|
|
||||||
.path = path,
|
|
||||||
.uid = archive.id(self.hdr.uid_idx) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.gid = archive.id(self.hdr.gid_idx) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.perm = self.hdr.permissions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.file, .ext_file => {
|
|
||||||
self.extractRegFileThreaded(alloc, archive, path, options, pool) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
.symlink, .ext_symlink => {
|
|
||||||
self.extractSymlink(path) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
self.extractDevice(archive, path, options) catch |err| {
|
|
||||||
out_err.* = err;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Creates and writes the inode file contents to the given path.
|
|
||||||
/// Optionally set owner & permissions.
|
|
||||||
///
|
|
||||||
/// Assumes the inode is a file or ext_file type.
|
|
||||||
fn extractRegFile(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
|
||||||
var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true });
|
|
||||||
defer fil.close();
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
|
||||||
var dat_rdr = try self.dataReader(alloc, archive);
|
|
||||||
defer dat_rdr.deinit();
|
|
||||||
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
|
||||||
try wrt.interface.flush();
|
|
||||||
|
|
||||||
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
|
||||||
try fil.updateTimes(time, time);
|
|
||||||
if (!options.ignore_permissions) {
|
|
||||||
try fil.chmod(self.hdr.permissions);
|
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Extract the inode file contents to the given path threadedly.
|
|
||||||
/// pool is used to spawn threads.
|
|
||||||
///
|
|
||||||
/// Assumes the inode is a file or ext_file type.
|
|
||||||
fn extractRegFileThreaded(self: Inode, alloc: std.mem.Allocator, archive: *Archive, path: []const u8, options: ExtractionOptions, pool: *Pool) !void {
|
|
||||||
var fil = try std.fs.cwd().createFile(path, .{});
|
|
||||||
defer fil.close();
|
|
||||||
var data = try self.threadedDataReader(alloc, archive);
|
|
||||||
try data.extractThreaded(fil, pool);
|
|
||||||
|
|
||||||
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
|
||||||
try fil.updateTimes(time, time);
|
|
||||||
if (!options.ignore_permissions) {
|
|
||||||
try fil.chmod(self.hdr.permissions);
|
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Creates the symlink described by the inode.
|
|
||||||
///
|
|
||||||
/// Assumes the inode is a symlink or ext_symlink type.
|
|
||||||
fn extractSymlink(self: Inode, path: []const u8) !void {
|
|
||||||
const target = switch (self.data) {
|
|
||||||
.symlink => |s| s.target,
|
|
||||||
.ext_symlink => |s| s.target,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
try std.fs.cwd().symLink(target, path, .{});
|
|
||||||
}
|
|
||||||
/// Creates the device described by the inode.
|
|
||||||
///
|
|
||||||
/// Optionally set owner & permissions.
|
|
||||||
/// Assumes the inode is a char_dev, block_dev, fifo, socket, or their extended counterparts.
|
|
||||||
fn extractDevice(self: Inode, archive: *Archive, path: []const u8, options: ExtractionOptions) !void {
|
|
||||||
var mode: u32 = undefined;
|
|
||||||
var dev: u32 = 0;
|
|
||||||
switch (self.data) {
|
|
||||||
.char_dev => |d| {
|
|
||||||
mode = std.posix.S.IFCHR;
|
|
||||||
dev = d.dev;
|
|
||||||
},
|
|
||||||
.ext_char_dev => |d| {
|
|
||||||
mode = std.posix.S.IFCHR;
|
|
||||||
dev = d.dev;
|
|
||||||
},
|
|
||||||
.block_dev => |d| {
|
|
||||||
mode = std.posix.S.IFBLK;
|
|
||||||
dev = d.dev;
|
|
||||||
},
|
|
||||||
.ext_block_dev => |d| {
|
|
||||||
mode = std.posix.S.IFBLK;
|
|
||||||
dev = d.dev;
|
|
||||||
},
|
|
||||||
.fifo, .ext_fifo => mode = std.posix.S.IFIFO,
|
|
||||||
.socket, .ext_socket => mode = std.posix.S.IFSOCK,
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
const res: std.os.linux.E = @enumFromInt(std.os.linux.mknod(@ptrCast(path), mode, dev));
|
|
||||||
switch (res) {
|
|
||||||
.SUCCESS => {},
|
|
||||||
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
|
|
||||||
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
|
|
||||||
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
|
|
||||||
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
|
|
||||||
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
|
|
||||||
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
|
|
||||||
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
|
|
||||||
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
|
|
||||||
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
|
|
||||||
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
|
|
||||||
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
|
|
||||||
else => return blk: {
|
|
||||||
std.debug.print("unhandled mknod result: {}\n", .{res});
|
|
||||||
break :blk std.fs.Dir.MakeError.Unexpected;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var fil = try std.fs.cwd().openFile(path, .{});
|
|
||||||
defer fil.close();
|
|
||||||
const time = @as(i128, self.hdr.mod_time) * 1000000000;
|
|
||||||
try fil.updateTimes(time, time);
|
|
||||||
if (!options.ignore_permissions) {
|
|
||||||
try fil.chmod(self.hdr.permissions);
|
|
||||||
try fil.chown(try archive.id(self.hdr.uid_idx), try archive.id(self.hdr.gid_idx));
|
|
||||||
}
|
}
|
||||||
if (!options.ignore_xattr) {
|
if (!options.ignore_xattr) {
|
||||||
// TODO
|
const idx = self.xattrIdx();
|
||||||
|
if (idx == 0xFFFFFFFF) return;
|
||||||
|
const xattrs = try tables.xattr_table.get(alloc, idx);
|
||||||
|
defer alloc.free(xattrs);
|
||||||
|
for (xattrs) |kv| {
|
||||||
|
const res = std.os.linux.fsetxattr(fil.handle, kv.key, kv.value.ptr, kv.value.len, 0);
|
||||||
|
alloc.free(kv.key);
|
||||||
|
alloc.free(kv.value);
|
||||||
|
if (res != 0) {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("fsetxattr has result of: {}\n", .{res}) catch {};
|
||||||
|
return error.SetXattr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract the inode to the given path.
|
||||||
|
pub fn extractTo(self: Inode, alloc: std.mem.Allocator, archive: Archive, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
return InodeExtract.extractTo(alloc, self, archive, path, options);
|
||||||
|
}
|
||||||
|
|||||||
+10
-2
@@ -5,6 +5,8 @@ const Writer = std.Io.Writer;
|
|||||||
|
|
||||||
const ExtractionOptions = @This();
|
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
|
/// Don't set the file's owner & permissions after extraction
|
||||||
ignore_permissions: bool = false,
|
ignore_permissions: bool = false,
|
||||||
/// Don't set xattr values. Currently xattrs are never set anyway.
|
/// Don't set xattr values. Currently xattrs are never set anyway.
|
||||||
@@ -16,10 +18,16 @@ verbose: bool = false,
|
|||||||
/// Where to print verbose log.
|
/// Where to print verbose log.
|
||||||
verbose_writer: ?*Writer = null,
|
verbose_writer: ?*Writer = null,
|
||||||
|
|
||||||
pub const Default: ExtractionOptions = .{};
|
pub const SingleThreadedDefault: ExtractionOptions = .{};
|
||||||
pub fn VerboseDefault(wrt: *Writer) ExtractionOptions {
|
pub fn Default() !ExtractionOptions {
|
||||||
|
return .{
|
||||||
|
.threads = try std.Thread.getCpuCount(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn VerboseDefault(wrt: *Writer) !ExtractionOptions {
|
||||||
return .{
|
return .{
|
||||||
.verbose = true,
|
.verbose = true,
|
||||||
.verbose_writer = wrt,
|
.verbose_writer = wrt,
|
||||||
|
.threads = try std.Thread.getCpuCount(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub const Archive = @import("archive.zig");
|
pub const Archive = @import("archive.zig");
|
||||||
|
pub const ExtractionOptions = @import("options.zig");
|
||||||
|
|||||||
@@ -1,14 +1,45 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Mutex = std.Thread.Mutex;
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
|
const Archive = @import("archive.zig");
|
||||||
const DecompFn = @import("decomp.zig").DecompFn;
|
const DecompFn = @import("decomp.zig").DecompFn;
|
||||||
|
const BlockSize = @import("inode_data/file.zig").BlockSize;
|
||||||
|
const InodeRef = @import("inode.zig").Ref;
|
||||||
|
const Superblock = @import("super.zig").Superblock;
|
||||||
const MetadataReader = @import("util/metadata.zig");
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
const OffsetFile = @import("util/offset_file.zig");
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
const XattrTable = @import("xattr.zig");
|
||||||
|
|
||||||
const TableError = error{
|
/// Information about a fragment section. Multiple fragments are contained in the block described by a single FragEntry.
|
||||||
InvalidIndex,
|
/// The offset into the block and fragment size is stored in the file's inode.
|
||||||
|
pub const FragEntry = packed struct {
|
||||||
|
start: u64,
|
||||||
|
size: BlockSize,
|
||||||
|
_: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Tables = @This();
|
||||||
|
|
||||||
|
frag_table: Table(FragEntry),
|
||||||
|
id_table: Table(u16),
|
||||||
|
export_table: Table(InodeRef),
|
||||||
|
xattr_table: XattrTable,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, archive: Archive) !Tables {
|
||||||
|
return .{
|
||||||
|
.frag_table = try .init(alloc, archive.fil, archive.decomp, archive.super.frag_start, archive.super.frag_count),
|
||||||
|
.id_table = try .init(alloc, archive.fil, archive.decomp, archive.super.id_start, archive.super.id_count),
|
||||||
|
.export_table = try .init(alloc, archive.fil, archive.decomp, archive.super.export_start, archive.super.inode_count),
|
||||||
|
.xattr_table = try .init(alloc, archive.fil, archive.decomp, archive.super.xattr_start),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Tables) void {
|
||||||
|
self.frag_table.deinit();
|
||||||
|
self.id_table.deinit();
|
||||||
|
self.export_table.deinit();
|
||||||
|
self.xattr_table.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
/// A two-layer metadata table.
|
/// A two-layer metadata table.
|
||||||
pub fn Table(T: anytype) type {
|
pub fn Table(T: anytype) type {
|
||||||
return struct {
|
return struct {
|
||||||
@@ -47,7 +78,7 @@ pub fn Table(T: anytype) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: *Self, idx: u32) !T {
|
pub fn get(self: *Self, idx: u32) !T {
|
||||||
if (idx >= self.values) return TableError.InvalidIndex;
|
if (idx >= self.values) return error.InvalidIndex;
|
||||||
const block_num = idx / VALS_PER_BLOCK;
|
const block_num = idx / VALS_PER_BLOCK;
|
||||||
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
const idx_offset = idx - (block_num * VALS_PER_BLOCK);
|
||||||
if (self.tab.contains(block_num)) {
|
if (self.tab.contains(block_num)) {
|
||||||
+1
-1
@@ -6,9 +6,9 @@ const Writer = std.Io.Writer;
|
|||||||
const Limit = std.Io.Limit;
|
const Limit = std.Io.Limit;
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
const Archive = @import("../archive.zig");
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||||
|
const FragEntry = @import("../tables.zig").FragEntry;
|
||||||
const OffsetFile = @import("offset_file.zig");
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
const DataReader = @This();
|
const DataReader = @This();
|
||||||
|
|||||||
+73
-49
@@ -8,9 +8,10 @@ const WaitGroup = std.Thread.WaitGroup;
|
|||||||
const Pool = std.Thread.Pool;
|
const Pool = std.Thread.Pool;
|
||||||
|
|
||||||
const Archive = @import("../archive.zig");
|
const Archive = @import("../archive.zig");
|
||||||
const FragEntry = Archive.FragEntry;
|
|
||||||
const DecompFn = @import("../decomp.zig").DecompFn;
|
const DecompFn = @import("../decomp.zig").DecompFn;
|
||||||
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
const BlockSize = @import("../inode_data/file.zig").BlockSize;
|
||||||
|
const FragEntry = @import("../tables.zig").FragEntry;
|
||||||
|
const InodeFinish = @import("inode_finish.zig");
|
||||||
const OffsetFile = @import("offset_file.zig");
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
const ThreadedDataReader = @This();
|
const ThreadedDataReader = @This();
|
||||||
@@ -25,6 +26,7 @@ blocks: []BlockSize,
|
|||||||
frag: ?FragEntry = null, // TODO: do something better?
|
frag: ?FragEntry = null, // TODO: do something better?
|
||||||
frag_offset: u32 = 0,
|
frag_offset: u32 = 0,
|
||||||
size: u64,
|
size: u64,
|
||||||
|
num_blocks: usize,
|
||||||
|
|
||||||
start_offset: u64,
|
start_offset: u64,
|
||||||
|
|
||||||
@@ -34,8 +36,12 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, sta
|
|||||||
.fil = archive.fil,
|
.fil = archive.fil,
|
||||||
.decomp = archive.decomp,
|
.decomp = archive.decomp,
|
||||||
.block_size = archive.super.block_size,
|
.block_size = archive.super.block_size,
|
||||||
|
|
||||||
.blocks = blocks,
|
.blocks = blocks,
|
||||||
|
|
||||||
.size = size,
|
.size = size,
|
||||||
|
.num_blocks = blocks.len,
|
||||||
|
|
||||||
.start_offset = start,
|
.start_offset = start,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -43,141 +49,159 @@ pub fn init(alloc: std.mem.Allocator, archive: Archive, blocks: []BlockSize, sta
|
|||||||
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void {
|
pub fn addFragment(self: *ThreadedDataReader, entry: FragEntry, frag_offset: u32) void {
|
||||||
self.frag = entry;
|
self.frag = entry;
|
||||||
self.frag_offset = frag_offset;
|
self.frag_offset = frag_offset;
|
||||||
}
|
self.num_blocks = self.blocks.len + 1;
|
||||||
|
|
||||||
fn numBlocks(self: ThreadedDataReader) usize {
|
|
||||||
var res = self.blocks.len;
|
|
||||||
if (self.frag != null) res += 1;
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the data to the file threadedly, using pool to spawn threads.
|
/// Extract the data to the file threadedly, using pool to spawn threads.
|
||||||
/// If multiple errors occur, thread spawning errors will have, then the last decompression error that occurs;
|
/// If errors occur, they are set to finish.out_err.
|
||||||
///
|
pub fn extractThreaded(self: ThreadedDataReader, file: std.fs.File, pool: *Pool, finish: *InodeFinish) void {
|
||||||
/// The function must be called from an unused DataReader. The DataReader is still usable afterwards.
|
|
||||||
/// If only extractThreaded is used, there is no need to call deinit() afterwards.
|
|
||||||
///
|
|
||||||
/// The file will always be written to starting at 0.
|
|
||||||
pub fn extractThreaded(self: ThreadedDataReader, file: std.fs.File, pool: *Pool) !void {
|
|
||||||
var wg: WaitGroup = .{};
|
|
||||||
wg.startMany(self.numBlocks());
|
|
||||||
var out_err: ?anyerror = null;
|
|
||||||
|
|
||||||
var cur_write_offset: u64 = 0;
|
var cur_write_offset: u64 = 0;
|
||||||
var cur_read_offset: u64 = self.start_offset;
|
var cur_read_offset: u64 = self.start_offset;
|
||||||
for (0..self.blocks.len) |i| {
|
for (0..self.blocks.len) |i| {
|
||||||
const cur_block_size = if (i == self.numBlocks() - 1) self.size % self.block_size else self.block_size;
|
const cur_block_size = if (i == self.num_blocks - 1) self.size % self.block_size else self.block_size;
|
||||||
try pool.spawn(workThreadBlocks, .{ self, file, cur_write_offset, cur_read_offset, self.blocks[i], cur_block_size, &wg, &out_err });
|
pool.spawn(workThreadBlocks, .{ self, file, cur_write_offset, cur_read_offset, self.blocks[i], cur_block_size, finish }) catch |res_err| {
|
||||||
|
finish.logError("Can't spawn pool task: {}", .{res_err});
|
||||||
|
finish.out_err.* = res_err;
|
||||||
|
finish.finish();
|
||||||
|
};
|
||||||
cur_write_offset += cur_block_size;
|
cur_write_offset += cur_block_size;
|
||||||
cur_read_offset += self.blocks[i].size;
|
cur_read_offset += self.blocks[i].size;
|
||||||
}
|
}
|
||||||
if (self.frag != null) {
|
if (self.frag != null)
|
||||||
try pool.spawn(workThreadFragment, .{ self, file, cur_write_offset, &wg, &out_err });
|
pool.spawn(workThreadFragment, .{ self, file, cur_write_offset, finish }) catch |res_err| {
|
||||||
}
|
finish.logError("Can't spawn pool task: {}", .{res_err});
|
||||||
pool.waitAndWork(&wg);
|
finish.out_err.* = res_err;
|
||||||
if (out_err != null) return out_err.?;
|
finish.finish();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workThreadBlocks(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, read_offset: u64, block: BlockSize, cur_block_size: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
fn workThreadBlocks(
|
||||||
defer wg.finish();
|
self: ThreadedDataReader,
|
||||||
|
fil: std.fs.File,
|
||||||
|
write_offset: u64,
|
||||||
|
read_offset: u64,
|
||||||
|
block: BlockSize,
|
||||||
|
cur_block_size: u64,
|
||||||
|
finish: *InodeFinish,
|
||||||
|
) void {
|
||||||
|
defer finish.finish();
|
||||||
var wrt = fil.writer(&[0]u8{});
|
var wrt = fil.writer(&[0]u8{});
|
||||||
wrt.seekTo(write_offset) catch |err| {
|
wrt.seekTo(write_offset) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error seeking file writer: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer wrt.interface.flush() catch |err| {
|
defer wrt.interface.flush() catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error flushing file writer: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
};
|
};
|
||||||
if (block.size == 0) {
|
if (block.size == 0) {
|
||||||
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
|
wrt.interface.splatByteAll(0, cur_block_size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error writing zeroes: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var rdr = self.fil.readerAt(read_offset, &[0]u8{}) catch |err| {
|
var rdr = self.fil.readerAt(read_offset, &[0]u8{}) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error creating file reader: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (block.uncompressed) {
|
if (block.uncompressed) {
|
||||||
rdr.interface.streamExact(&wrt.interface, block.size) catch |err| {
|
rdr.interface.streamExact(&wrt.interface, block.size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error streaming data: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: shared buffers
|
// TODO: shared buffers
|
||||||
const read_buf = self.alloc.alloc(u8, block.size) catch |err| {
|
const read_buf = self.alloc.alloc(u8, block.size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error creating reader buffer: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer self.alloc.free(read_buf);
|
defer self.alloc.free(read_buf);
|
||||||
rdr.interface.readSliceAll(read_buf) catch |err| {
|
rdr.interface.readSliceAll(read_buf) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error reading data into reader buffer: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// TODO: shared buffers
|
// TODO: shared buffers
|
||||||
const res_buf = self.alloc.alloc(u8, cur_block_size) catch |err| {
|
const res_buf = self.alloc.alloc(u8, cur_block_size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error creating result buffer: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer self.alloc.free(res_buf);
|
defer self.alloc.free(res_buf);
|
||||||
_ = self.decomp(self.alloc, read_buf, res_buf) catch |err| {
|
_ = self.decomp(self.alloc, read_buf, res_buf) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error decompressing data block: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
wrt.interface.writeAll(res_buf) catch |err| {
|
wrt.interface.writeAll(res_buf) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error writing to file: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn workThreadFragment(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, wg: *WaitGroup, out_err: *?anyerror) void {
|
fn workThreadFragment(self: ThreadedDataReader, fil: std.fs.File, write_offset: u64, finish: *InodeFinish) void {
|
||||||
defer wg.finish();
|
defer finish.finish();
|
||||||
|
|
||||||
var wrt = fil.writer(&[0]u8{});
|
var wrt = fil.writer(&[0]u8{});
|
||||||
wrt.seekTo(write_offset) catch |err| {
|
wrt.seekTo(write_offset) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error seeking file writer for file fragment: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer wrt.interface.flush() catch |err| {
|
defer wrt.interface.flush() catch |err| {
|
||||||
out_err.* = err;
|
finish.out_err.* = err;
|
||||||
};
|
};
|
||||||
|
|
||||||
var rdr = self.fil.readerAt(self.frag.?.start, &[0]u8{}) catch |err| {
|
var rdr = self.fil.readerAt(self.frag.?.start, &[0]u8{}) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error creating file reader for file fragment: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (self.frag.?.size.uncompressed) {
|
if (self.frag.?.size.uncompressed) {
|
||||||
rdr.interface.discardAll(self.frag_offset) catch |err| {
|
rdr.interface.discardAll(self.frag_offset) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error discarding useless fragment data: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
rdr.interface.streamExact(&wrt.interface, self.size % self.block_size) catch |err| {
|
rdr.interface.streamExact(&wrt.interface, self.size % self.block_size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error streaming fragment data: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tmp_buf = self.alloc.alloc(u8, self.frag.?.size.size) catch |err| {
|
const tmp_buf = self.alloc.alloc(u8, self.frag.?.size.size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error creating a temporary buffer for a file fragment: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer self.alloc.free(tmp_buf);
|
defer self.alloc.free(tmp_buf);
|
||||||
rdr.interface.readSliceAll(tmp_buf) catch |err| {
|
rdr.interface.readSliceAll(tmp_buf) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error reading data into fragment buffer: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const needed_block = self.alloc.alloc(u8, self.block_size) catch |err| {
|
const needed_block = self.alloc.alloc(u8, self.block_size) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error allocating fragment decompression results: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer self.alloc.free(needed_block);
|
defer self.alloc.free(needed_block);
|
||||||
_ = self.decomp(self.alloc, tmp_buf, needed_block) catch |err| {
|
_ = self.decomp(self.alloc, tmp_buf, needed_block) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error decompressing fragment: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
wrt.interface.writeAll(needed_block[self.frag_offset .. self.frag_offset + (self.size % self.block_size)]) catch |err| {
|
wrt.interface.writeAll(needed_block[self.frag_offset .. self.frag_offset + (self.size % self.block_size)]) catch |err| {
|
||||||
out_err.* = err;
|
finish.logError("Error writing fragment: {}", .{err});
|
||||||
|
finish.out_err.* = err;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,353 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Pool = std.Thread.Pool;
|
||||||
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
|
||||||
|
const Archive = @import("../archive.zig");
|
||||||
|
const DirEntry = @import("../dir_entry.zig");
|
||||||
|
const Inode = @import("../inode.zig");
|
||||||
|
const ExtractionOptions = @import("../options.zig");
|
||||||
|
const Tables = @import("../tables.zig");
|
||||||
|
const InodeFinish = @import("inode_finish.zig");
|
||||||
|
const FinishUnion = InodeFinish.FinishUnion;
|
||||||
|
const ThreadedDataReader = @import("data_threaded.zig");
|
||||||
|
|
||||||
|
// 1 MB
|
||||||
|
const STACK_ALLOC_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
|
pub fn extractTo(
|
||||||
|
allocator: Allocator,
|
||||||
|
inode: Inode,
|
||||||
|
archive: Archive,
|
||||||
|
path: []const u8,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
) !void {
|
||||||
|
if (path[path.len - 1] == '/')
|
||||||
|
return extractTo(allocator, inode, archive, path[0 .. path.len - 2], options);
|
||||||
|
|
||||||
|
var stack_alloc = std.heap.stackFallback(STACK_ALLOC_SIZE, allocator);
|
||||||
|
var arena: std.heap.ArenaAllocator = .init(stack_alloc.get());
|
||||||
|
defer arena.deinit();
|
||||||
|
if (options.threads <= 1) {
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
var tables: Tables = try .init(alloc, archive);
|
||||||
|
return extractSingleThread(arena.allocator(), inode, archive, &tables, path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
var thread_alloc = std.heap.ThreadSafeAllocator{ .child_allocator = arena.allocator() };
|
||||||
|
const alloc = thread_alloc.allocator();
|
||||||
|
var tables: Tables = try .init(alloc, archive);
|
||||||
|
|
||||||
|
var pool_alloc = std.heap.stackFallback(10 * 1024, alloc);
|
||||||
|
var pool: Pool = undefined;
|
||||||
|
try pool.init(.{ .allocator = pool_alloc.get(), .n_jobs = options.threads - 1 });
|
||||||
|
|
||||||
|
var wg: WaitGroup = .{};
|
||||||
|
var err: ?anyerror = null;
|
||||||
|
wg.start();
|
||||||
|
try pool.spawn(extractMultiThread, .{
|
||||||
|
alloc,
|
||||||
|
inode,
|
||||||
|
archive,
|
||||||
|
&tables,
|
||||||
|
path,
|
||||||
|
options,
|
||||||
|
&pool,
|
||||||
|
FinishUnion{ .wg = &wg },
|
||||||
|
&err,
|
||||||
|
});
|
||||||
|
pool.waitAndWork(&wg);
|
||||||
|
if (err != null) return err.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extractSingleThread(
|
||||||
|
alloc: Allocator,
|
||||||
|
inode: Inode,
|
||||||
|
archive: Archive,
|
||||||
|
tables: *Tables,
|
||||||
|
path: []const u8,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
) !void {
|
||||||
|
switch (inode.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
_ = std.fs.cwd().makeDir(path) catch |err| switch (err) {
|
||||||
|
std.fs.Dir.MakeError.PathAlreadyExists => {},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Currently we are ignoring any deinit or free calls since we know we are under an ArenaAllocator.
|
||||||
|
// Possibly in the future, do some simple math to see if it would be safe to ONLY deinit via Arena,
|
||||||
|
// otherwise be more conscientious about freeing memory.
|
||||||
|
// For now, this is good enough.
|
||||||
|
|
||||||
|
const entries = try inode.dirEntries(alloc, archive);
|
||||||
|
for (entries) |ent| {
|
||||||
|
const sub_inode: Inode = try .readFromEntry(alloc, archive, ent);
|
||||||
|
const new_path = try std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", ent.name });
|
||||||
|
try extractSingleThread(alloc, sub_inode, archive, tables, new_path, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fil = try std.fs.cwd().openFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
try inode.setMetadata(alloc, tables, fil, options);
|
||||||
|
},
|
||||||
|
.file, .ext_file => {
|
||||||
|
var fil = try std.fs.cwd().createFile(path, .{ .exclusive = true });
|
||||||
|
defer fil.close();
|
||||||
|
var wrt = fil.writer(&[0]u8{});
|
||||||
|
var dat_rdr = try inode.dataReader(alloc, archive, tables);
|
||||||
|
defer dat_rdr.deinit();
|
||||||
|
_ = try dat_rdr.interface.streamRemaining(&wrt.interface);
|
||||||
|
try wrt.interface.flush();
|
||||||
|
|
||||||
|
try inode.setMetadata(alloc, tables, fil, options);
|
||||||
|
},
|
||||||
|
.symlink, .ext_symlink => return extractSymlink(inode, path),
|
||||||
|
else => return extractDeviceAndIPC(inode, alloc, tables, path, options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extractMultiThread(
|
||||||
|
alloc: Allocator,
|
||||||
|
inode: Inode,
|
||||||
|
archive: Archive,
|
||||||
|
tables: *Tables,
|
||||||
|
path: []const u8,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
pool: *Pool,
|
||||||
|
fin: FinishUnion,
|
||||||
|
err: *?anyerror,
|
||||||
|
) void {
|
||||||
|
if (err.* != null) {
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (inode.hdr.inode_type) {
|
||||||
|
.dir, .ext_dir => {
|
||||||
|
_ = std.fs.cwd().makeDir(path) catch |res_err| switch (res_err) {
|
||||||
|
std.fs.Dir.MakeError.PathAlreadyExists => {},
|
||||||
|
else => {
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Currently we are ignoring any deinit or free calls since we know we are under an ArenaAllocator.
|
||||||
|
// Possibly in the future, do some simple math to see if it would be safe to ONLY deinit via Arena,
|
||||||
|
// otherwise be more conscientious about freeing memory.
|
||||||
|
// For now, this is good enough.
|
||||||
|
|
||||||
|
const entries = inode.dirEntries(alloc, archive) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entries.len == 0) {
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dir_fin = InodeFinish.create(
|
||||||
|
alloc,
|
||||||
|
inode,
|
||||||
|
path,
|
||||||
|
tables,
|
||||||
|
options,
|
||||||
|
fin,
|
||||||
|
err,
|
||||||
|
null,
|
||||||
|
entries.len,
|
||||||
|
) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (entries) |ent| {
|
||||||
|
if (ent.inode_type == .dir) {
|
||||||
|
extractEntry(
|
||||||
|
alloc,
|
||||||
|
ent,
|
||||||
|
archive,
|
||||||
|
tables,
|
||||||
|
path,
|
||||||
|
options,
|
||||||
|
pool,
|
||||||
|
.{ .fin = dir_fin },
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.spawn(
|
||||||
|
extractEntry,
|
||||||
|
.{ alloc, ent, archive, tables, path, options, pool, FinishUnion{ .fin = dir_fin }, err },
|
||||||
|
) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
dir_fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.file, .ext_file => {
|
||||||
|
const fil = std.fs.cwd().createFile(path, .{ .exclusive = true }) catch |res_err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Can't create file at {s}: {}\n", .{ path, res_err }) catch {};
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
var data_rdr = threadedDataReader(inode, alloc, archive, tables) catch |res_err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Can't create data reader for inode #{} (extracting to {s}): {}\n", .{ inode.hdr.num, path, res_err }) catch {};
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (data_rdr == null) {
|
||||||
|
inode.setMetadata(alloc, tables, fil, options) catch |res_err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Can't set metadata to {s}: {}\n", .{ path, res_err }) catch {};
|
||||||
|
err.* = res_err;
|
||||||
|
};
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file_fin = InodeFinish.create(
|
||||||
|
alloc,
|
||||||
|
inode,
|
||||||
|
path,
|
||||||
|
tables,
|
||||||
|
options,
|
||||||
|
fin,
|
||||||
|
err,
|
||||||
|
fil,
|
||||||
|
data_rdr.?.num_blocks,
|
||||||
|
) catch |res_err| {
|
||||||
|
if (options.verbose)
|
||||||
|
options.verbose_writer.?.print("Can't create callback for inode #{} (extracting to {s}): {}\n", .{ inode.hdr.num, path, res_err }) catch {};
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
data_rdr.?.extractThreaded(fil, pool, file_fin);
|
||||||
|
},
|
||||||
|
.symlink, .ext_symlink => {
|
||||||
|
extractSymlink(inode, path) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
};
|
||||||
|
fin.finish();
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
extractDeviceAndIPC(inode, alloc, tables, path, options) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
};
|
||||||
|
fin.finish();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extractEntry(
|
||||||
|
alloc: Allocator,
|
||||||
|
ent: DirEntry,
|
||||||
|
archive: Archive,
|
||||||
|
tables: *Tables,
|
||||||
|
path: []const u8,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
pool: *Pool,
|
||||||
|
fin: FinishUnion,
|
||||||
|
err: *?anyerror,
|
||||||
|
) void {
|
||||||
|
const new_path = std.mem.concat(alloc, u8, &[_][]const u8{ path, "/", ent.name }) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const inode = Inode.readFromEntry(alloc, archive, ent) catch |res_err| {
|
||||||
|
err.* = res_err;
|
||||||
|
fin.finish();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
extractMultiThread(alloc, inode, archive, tables, new_path, options, pool, fin, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a threaded data reader for a file inode.
|
||||||
|
fn threadedDataReader(self: Inode, alloc: std.mem.Allocator, archive: Archive, tables: *Tables) !?ThreadedDataReader {
|
||||||
|
return switch (self.hdr.inode_type) {
|
||||||
|
.file => threadedReaderFromData(alloc, archive, tables, self.data.file),
|
||||||
|
.ext_file => threadedReaderFromData(alloc, archive, tables, self.data.ext_file),
|
||||||
|
else => error.NotRegularFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn threadedReaderFromData(alloc: std.mem.Allocator, archive: Archive, tables: *Tables, data: anytype) !?ThreadedDataReader {
|
||||||
|
if (data.block_sizes.len == 0 and data.frag_idx == 0xFFFFFFFF) return null;
|
||||||
|
var out: ThreadedDataReader = .init(alloc, archive, data.block_sizes, data.block_start, data.size);
|
||||||
|
if (data.frag_idx != 0xFFFFFFFF)
|
||||||
|
out.addFragment(try tables.frag_table.get(data.frag_idx), data.frag_block_offset);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the symlink described by the inode.
|
||||||
|
/// Sets metadata.
|
||||||
|
fn extractSymlink(self: Inode, path: []const u8) !void {
|
||||||
|
const target = switch (self.data) {
|
||||||
|
.symlink => |s| s.target,
|
||||||
|
.ext_symlink => |s| s.target,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
try std.fs.cwd().symLink(target, path, .{});
|
||||||
|
}
|
||||||
|
/// Creates the device described by the inode.
|
||||||
|
/// Sets metadata.
|
||||||
|
fn extractDeviceAndIPC(self: Inode, alloc: std.mem.Allocator, tables: *Tables, path: []const u8, options: ExtractionOptions) !void {
|
||||||
|
var mode: u32 = undefined;
|
||||||
|
var dev: u32 = 0;
|
||||||
|
switch (self.data) {
|
||||||
|
.char_dev => |d| {
|
||||||
|
mode = std.posix.S.IFCHR;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.ext_char_dev => |d| {
|
||||||
|
mode = std.posix.S.IFCHR;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.block_dev => |d| {
|
||||||
|
mode = std.posix.S.IFBLK;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.ext_block_dev => |d| {
|
||||||
|
mode = std.posix.S.IFBLK;
|
||||||
|
dev = d.dev;
|
||||||
|
},
|
||||||
|
.fifo, .ext_fifo => mode = std.posix.S.IFIFO,
|
||||||
|
.socket, .ext_socket => mode = std.posix.S.IFSOCK,
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
const res: std.os.linux.E = @enumFromInt(std.os.linux.mknod(@ptrCast(path), mode, dev));
|
||||||
|
switch (res) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
.ACCES => return std.fs.Dir.MakeError.AccessDenied,
|
||||||
|
.DQUOT => return std.fs.Dir.MakeError.DiskQuota,
|
||||||
|
.EXIST => return std.fs.Dir.MakeError.PathAlreadyExists,
|
||||||
|
.FAULT, .NOENT => return std.fs.Dir.MakeError.BadPathName,
|
||||||
|
.LOOP => return std.fs.Dir.MakeError.SymLinkLoop,
|
||||||
|
.NAMETOOLONG => return std.fs.Dir.MakeError.NameTooLong,
|
||||||
|
.NOMEM => return std.fs.Dir.MakeError.SystemResources,
|
||||||
|
.NOSPC => return std.fs.Dir.MakeError.NoSpaceLeft,
|
||||||
|
.NOTDIR => return std.fs.Dir.MakeError.NotDir,
|
||||||
|
.PERM => return std.fs.Dir.MakeError.PermissionDenied,
|
||||||
|
.ROFS => return std.fs.Dir.MakeError.ReadOnlyFileSystem,
|
||||||
|
else => return blk: {
|
||||||
|
std.debug.print("unhandled mknod result: {}\n", .{res});
|
||||||
|
break :blk std.fs.Dir.MakeError.Unexpected;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var fil = try std.fs.cwd().openFile(path, .{});
|
||||||
|
defer fil.close();
|
||||||
|
try self.setMetadata(alloc, tables, fil, options);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const WaitGroup = std.Thread.WaitGroup;
|
||||||
|
const Mutex = std.Thread.Mutex;
|
||||||
|
|
||||||
|
const Archive = @import("../archive.zig");
|
||||||
|
const Inode = @import("../inode.zig");
|
||||||
|
const ExtractionOptions = @import("../options.zig");
|
||||||
|
const Tables = @import("../tables.zig");
|
||||||
|
|
||||||
|
const InodeFinish = @This();
|
||||||
|
|
||||||
|
const FinishEnum = enum {
|
||||||
|
wg,
|
||||||
|
fin,
|
||||||
|
};
|
||||||
|
pub const FinishUnion = union(FinishEnum) {
|
||||||
|
wg: *WaitGroup,
|
||||||
|
fin: *InodeFinish,
|
||||||
|
|
||||||
|
pub fn finish(self: FinishUnion) void {
|
||||||
|
switch (self) {
|
||||||
|
.wg => |wg| wg.finish(),
|
||||||
|
.fin => |fin| fin.finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
inode: Inode,
|
||||||
|
path: []const u8,
|
||||||
|
tables: *Tables,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
parent_finish: FinishUnion,
|
||||||
|
fil: ?std.fs.File,
|
||||||
|
out_err: *?anyerror,
|
||||||
|
|
||||||
|
wg: WaitGroup = .{},
|
||||||
|
mut: Mutex = .{},
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
inode: Inode,
|
||||||
|
path: []const u8,
|
||||||
|
tables: *Tables,
|
||||||
|
options: ExtractionOptions,
|
||||||
|
parent_finish: FinishUnion,
|
||||||
|
out_err: *?anyerror,
|
||||||
|
fil: ?std.fs.File,
|
||||||
|
work_size: usize,
|
||||||
|
) !*InodeFinish {
|
||||||
|
if (work_size == 0)
|
||||||
|
return error.InvalidWorkSize;
|
||||||
|
const out = try alloc.create(InodeFinish);
|
||||||
|
errdefer alloc.destroy(out);
|
||||||
|
out.* = .{
|
||||||
|
.alloc = alloc,
|
||||||
|
|
||||||
|
.inode = inode,
|
||||||
|
.path = path,
|
||||||
|
.tables = tables,
|
||||||
|
.options = options,
|
||||||
|
.parent_finish = parent_finish,
|
||||||
|
.out_err = out_err,
|
||||||
|
.fil = fil,
|
||||||
|
};
|
||||||
|
out.wg.startMany(work_size);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logError(self: *InodeFinish, comptime fmt: []const u8, args: anytype) void {
|
||||||
|
if (self.options.verbose)
|
||||||
|
self.options.verbose_writer.?.print(fmt, args) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self: *InodeFinish) void {
|
||||||
|
self.mut.lock();
|
||||||
|
{
|
||||||
|
defer self.mut.unlock();
|
||||||
|
self.wg.finish();
|
||||||
|
if (!self.wg.isDone()) return;
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
self.parent_finish.finish();
|
||||||
|
self.alloc.destroy(self);
|
||||||
|
}
|
||||||
|
if (self.fil == null)
|
||||||
|
self.fil = std.fs.cwd().openFile(self.path, .{}) catch |err| {
|
||||||
|
if (self.options.verbose)
|
||||||
|
self.options.verbose_writer.?.print("Error opening {s} to set metadata: {}\n", .{ self.path, err }) catch {};
|
||||||
|
self.out_err.* = err;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer self.fil.?.close();
|
||||||
|
self.inode.setMetadata(self.alloc, self.tables, self.fil.?, self.options) catch |err| {
|
||||||
|
if (self.options.verbose)
|
||||||
|
self.options.verbose_writer.?.print("Error setting metadata to {s}: {}\n", .{ self.path, err }) catch {};
|
||||||
|
self.out_err.* = err;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const OffsetFile = @import("offset_file.zig");
|
||||||
|
|
||||||
|
const MetadataCache = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
buf: []u8,
|
||||||
|
fixed_alloc: std.heap.FixedBufferAllocator,
|
||||||
|
|
||||||
|
cache: std.AutoArrayHashMap(u64, [8192]u8),
|
||||||
|
|
||||||
|
mut: std.Thread.Mutex = .{},
|
||||||
|
cache_mut: std.AutoArrayHashMap(u64, std.Thread.Mutex),
|
||||||
|
|
||||||
|
fil: OffsetFile,
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, cache_size: u64) !MetadataCache {}
|
||||||
|
pub fn deinit(self: *MetadataCache) void {
|
||||||
|
self.mut.lock();
|
||||||
|
defer self.mut.unlock();
|
||||||
|
self.cache.deinit();
|
||||||
|
self.cache_mut.deinit();
|
||||||
|
self.alloc.free(self.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getChunk(self: *MetadataCache, offset: u64) ![8192]u8 {
|
||||||
|
var res = self.cache.get(offset);
|
||||||
|
if (res != null) return res.?;
|
||||||
|
var offset_mut = blk: {
|
||||||
|
self.mut.lock();
|
||||||
|
defer self.mut.unlock();
|
||||||
|
const mut = try self.cache_mut.getOrPut(offset);
|
||||||
|
if (!mut.found_existing)
|
||||||
|
mut.value_ptr.* = .{};
|
||||||
|
break :blk mut.value_ptr;
|
||||||
|
};
|
||||||
|
offset_mut.lock();
|
||||||
|
defer offset_mut.unlock();
|
||||||
|
}
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const DecompFn = @import("decomp.zig").DecompFn;
|
||||||
|
const Table = @import("tables.zig").Table;
|
||||||
|
const MetadataReader = @import("util/metadata.zig");
|
||||||
|
const OffsetFile = @import("util/offset_file.zig");
|
||||||
|
|
||||||
|
const Ref = packed struct {
|
||||||
|
block_offset: u16,
|
||||||
|
block_start: u32,
|
||||||
|
_: u16,
|
||||||
|
};
|
||||||
|
const Entry = packed struct {
|
||||||
|
ref: Ref,
|
||||||
|
count: u32,
|
||||||
|
size: u32,
|
||||||
|
};
|
||||||
|
const KeyPrefix = enum(u8) {
|
||||||
|
user,
|
||||||
|
trusted,
|
||||||
|
security,
|
||||||
|
};
|
||||||
|
const KeyRaw = packed struct {
|
||||||
|
type: packed struct {
|
||||||
|
prefix: KeyPrefix,
|
||||||
|
out_of_line: bool,
|
||||||
|
_: u7,
|
||||||
|
},
|
||||||
|
name_size: u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const KeyValue = struct {
|
||||||
|
key: [:0]u8,
|
||||||
|
value: []u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const XattrTable = @This();
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
fil: OffsetFile,
|
||||||
|
decomp: DecompFn,
|
||||||
|
|
||||||
|
count: u32,
|
||||||
|
start: u64,
|
||||||
|
|
||||||
|
table: Table(Entry),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, fil: OffsetFile, decomp: DecompFn, table_start: u64) !XattrTable {
|
||||||
|
var info = packed struct {
|
||||||
|
start: u64 = undefined,
|
||||||
|
count: u32 = undefined,
|
||||||
|
_: u32 = undefined,
|
||||||
|
}{};
|
||||||
|
var rdr = try fil.readerAt(table_start, &[0]u8{});
|
||||||
|
try rdr.interface.readSliceEndian(@TypeOf(info), @ptrCast(&info), .little);
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.fil = fil,
|
||||||
|
.decomp = decomp,
|
||||||
|
.count = info.count,
|
||||||
|
.start = info.start,
|
||||||
|
.table = try .init(alloc, fil, decomp, table_start + 16, info.count),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *XattrTable) void {
|
||||||
|
self.table.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *XattrTable, alloc: std.mem.Allocator, idx: u32) ![]KeyValue {
|
||||||
|
const entry: Entry = try self.table.get(idx);
|
||||||
|
const out = try alloc.alloc(KeyValue, entry.count);
|
||||||
|
|
||||||
|
for (out) |*kv| {
|
||||||
|
var rdr = try self.fil.readerAt(self.start + entry.ref.block_start, &[0]u8{});
|
||||||
|
var meta: MetadataReader = .init(alloc, &rdr.interface, self.decomp);
|
||||||
|
try meta.interface.discardAll(entry.ref.block_offset);
|
||||||
|
|
||||||
|
var key_raw: KeyRaw = undefined;
|
||||||
|
try meta.interface.readSliceEndian(KeyRaw, @ptrCast(&key_raw), .little);
|
||||||
|
|
||||||
|
switch (key_raw.type.prefix) {
|
||||||
|
.user => {
|
||||||
|
kv.key = @ptrCast(try alloc.alloc(u8, key_raw.name_size + 5 + 1));
|
||||||
|
@memcpy(kv.key[0..5], "user.");
|
||||||
|
try meta.interface.readSliceAll(kv.key[5 .. kv.key.len - 1]);
|
||||||
|
kv.key[kv.key.len - 1] = 0;
|
||||||
|
},
|
||||||
|
.security => {
|
||||||
|
kv.key = @ptrCast(try alloc.alloc(u8, key_raw.name_size + 9 + 1));
|
||||||
|
@memcpy(kv.key[0..9], "security.");
|
||||||
|
try meta.interface.readSliceAll(kv.key[9 .. kv.key.len - 1]);
|
||||||
|
kv.key[kv.key.len - 1] = 0;
|
||||||
|
},
|
||||||
|
.trusted => {
|
||||||
|
kv.key = @ptrCast(try alloc.alloc(u8, key_raw.name_size + 8 + 1));
|
||||||
|
@memcpy(kv.key[0..8], "trusted.");
|
||||||
|
try meta.interface.readSliceAll(kv.key[8 .. kv.key.len - 1]);
|
||||||
|
kv.key[kv.key.len - 1] = 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (key_raw.type.out_of_line) {
|
||||||
|
try meta.interface.discardAll(4);
|
||||||
|
var ref: Ref = undefined;
|
||||||
|
try meta.interface.readSliceEndian(Ref, @ptrCast(&ref), .little);
|
||||||
|
|
||||||
|
rdr = try self.fil.readerAt(self.start + ref.block_start, &[0]u8{});
|
||||||
|
meta = .init(alloc, &rdr.interface, self.decomp);
|
||||||
|
try meta.interface.discardAll(ref.block_offset);
|
||||||
|
}
|
||||||
|
var value_size: u32 = undefined;
|
||||||
|
try meta.interface.readSliceEndian(u32, @ptrCast(&value_size), .little);
|
||||||
|
kv.value = try alloc.alloc(u8, value_size);
|
||||||
|
try meta.interface.readSliceAll(kv.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user